Skip to main content
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 example in Entity

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
  }
}
Subset naming conventions

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"]
  }
}

3. Performance Considerations

// ✅ 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)
Subset performance comparison

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;
  };
};
Generated TypeScript types

Next Steps