Subsets are defined in the subsets object within Entity JSON files.
Basic Subset Definition
Entity Structure
{
"id" : "User" ,
"table" : "users" ,
"props" : [
{ "name" : "id" , "type" : "integer" },
{ "name" : "username" , "type" : "string" },
{ "name" : "email" , "type" : "string" },
{ "name" : "role" , "type" : "enum" , "id" : "UserRole" }
],
"subsets" : {
"A" : [ "id" , "username" , "email" , "role" ],
"SS" : [ "id" , "username" ]
}
}
Subset Definition Rules
Basic Fields
Relation Fields (Dot Notation)
Multiple Subsets
{
"subsets" : {
"A" : [
"id" ,
"created_at" ,
"username" ,
"email" ,
"role"
]
}
}
Selecting Relation Fields
OneToOne Relations
{
"props" : [
{ "name" : "id" , "type" : "integer" },
{ "name" : "username" , "type" : "string" },
{
"type" : "relation" ,
"name" : "employee" ,
"with" : "Employee" ,
"relationType" : "OneToOne"
}
],
"subsets" : {
"P" : [ "id" , "username" , "employee.id" , "employee.employee_number" , "employee.salary" ]
}
}
OneToOne relations are automatically LEFT JOINed . Selecting employee.id will join the
employees table.
Nested Relations (2+ Levels)
{
"subsets" : {
"P" : [
"id" ,
"username" ,
"employee.salary" ,
"employee.department.name" ,
"employee.department.company.name"
]
}
}
SQL Result :
SELECT
users . id ,
users . username ,
employees . salary AS employee__salary,
departments . name AS employee__department__name,
companies . name AS employee__department__company__name
FROM users
LEFT JOIN employees ON users . id = employees . user_id
LEFT JOIN departments ON employees . department_id = departments . id
LEFT JOIN companies ON departments . company_id = companies . id
Hydrate Result :
{
id : 1 ,
username : "john" ,
employee : {
salary : "70000" ,
department : {
name : "Engineering" ,
company : {
name : "Tech Corp"
}
}
}
}
Subset Naming Strategy
General Conventions
{
"subsets" : {
"A" : [ ... ], // All - all fields (admin detail)
"L" : [ ... ], // List - for lists (table rows)
"P" : [ ... ], // Profile - profile (with relations)
"SS" : [ ... ], // Summary - summary (dropdowns, tags)
"C" : [ ... ] // Card - Card UI
}
}
Domain-Specific Subsets
{
"subsets" : {
"AdminList" : [ "id" , "username" , "email" , "role" , "created_at" ],
"UserCard" : [ "id" , "username" , "role" ],
"ProfileView" : [ "id" , "username" , "bio" , "employee.department.name" ]
}
}
Naming Tips : - Use short, clear names (A, L, P, etc.) - Maintain consistent conventions within
your team - Can use meaningful domain-specific names
Subset Design Principles
1. Minimum Fields Principle
// ❌ Bad: Including unnecessary fields
{
"L" : [
"id" , "created_at" , "updated_at" , "deleted_at" ,
"username" , "email" , "password" , "bio" ,
"last_login_at" , "is_verified"
]
}
// ✅ Good: Only fields needed for list
{
"L" : [
"id" ,
"username" ,
"role" ,
"created_at"
]
}
2. Separate by Purpose
{
"subsets" : {
// For list views
"L" : [ "id" , "username" , "role" , "created_at" ],
// For detail views (with relations)
"P" : [ "id" , "username" , "email" , "bio" , "employee.salary" , "employee.department.name" ],
// For dropdowns
"SS" : [ "id" , "username" ]
}
}
// ✅ Good: JOIN only needed relations
{
"P" : [
"id" ,
"username" ,
"employee.department.name"
]
}
// ❌ Bad: Unnecessary JOINs
{
"P" : [
"id" ,
"username" ,
"employee.id" ,
"employee.company.id" ,
"employee.projects.id"
]
}
Performance Tips : - Minimize JOINs (only needed relations) - Use L Subset for list views
(exclude relations) - Use P Subset only for detail views (include relations)
Practical Example
User Entity
{
"id" : "User" ,
"table" : "users" ,
"props" : [
{ "name" : "id" , "type" : "integer" },
{ "name" : "created_at" , "type" : "date" },
{ "name" : "username" , "type" : "string" },
{ "name" : "email" , "type" : "string" },
{ "name" : "role" , "type" : "enum" , "id" : "UserRole" },
{ "name" : "bio" , "type" : "string" , "nullable" : true },
{
"type" : "relation" ,
"name" : "employee" ,
"with" : "Employee" ,
"relationType" : "OneToOne" ,
"nullable" : true
}
],
"subsets" : {
"A" : [ "id" , "created_at" , "username" , "email" , "role" , "bio" ],
"L" : [ "id" , "username" , "role" , "created_at" ],
"P" : [ "id" , "username" , "email" , "bio" , "employee.salary" , "employee.department.name" ],
"SS" : [ "id" , "username" ]
}
}
Code Generation
When you define Subsets, types are automatically generated:
# Regenerate code after Subset changes
pnpm sonamu generate
After changing Subsets, you must run Code Generation . This updates the TypeScript types.
Generated Types :
// sonamu.generated.ts
export type UserSubsetKey = "A" | "L" | "P" | "SS" ;
export type UserSubsetMapping = {
A : {
id : number ;
created_at : Date ;
username : string ;
email : string ;
role : UserRole ;
bio : string | null ;
};
L : {
id : number ;
username : string ;
role : UserRole ;
created_at : Date ;
};
P : {
id : number ;
username : string ;
email : string ;
bio : string | null ;
employee : {
salary : string ;
department : {
name : string ;
};
} | null ;
};
SS : {
id : number ;
username : string ;
};
};
Next Steps
Using Subsets Use Subsets in Model
Nested Relations Load 1:N with LoaderQuery
Entity Definition Understanding Entity structure
Code Generation Automatic code generation