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
{
"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