> ## Documentation Index
> Fetch the complete documentation index at: https://sonamu.cartanova.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Entity and Model

> Questions about Entity and Model definitions

## Entity Basics

<AccordionGroup>
  <Accordion title="What names are automatically generated when I name an Entity 'product'?">
    Entity ID is enforced as CamelCase, and various names are auto-generated based on it.

    **Input: Product**

    **Auto-generated names:**

    1. **Entity ID (id)**
       * `Product` (same as input)
       * Validation: `/^[A-Z][a-zA-Z0-9]*$/` (CamelCase required)

    2. **Table name (table)**
       * `products` (auto-pluralized)
       * Customizable (direct input in Sonamu UI)

    3. **File/directory name (`names.fs`)**
       * `product` (lowercase + dash)
       * Used for: directory names, file names

    4. **Module name (`names.module`)**
       * `Product` (same as input)
       * Used for: class names, type names

    **Generated files:**

    ```
    api/src/application/product/product.entity.json
    api/src/application/product/product.types.ts
    api/src/application/product/product.model.ts (via Scaffolding)
    ```

    **Generated type/class names:**

    ```typescript theme={null}
    ProductBaseSchema
    ProductBaseListParams
    ProductSubsetMapping
    ProductModel (Model class)
    ProductService (frontend)
    ```

    **Conversion rules:**

    * ID → `table`: `inflection.underscore(inflection.pluralize(id))`
      * `Product` → `products`
      * `OrderItem` → `order_items`

    * ID → `fs`: `inflection.dasherize(inflection.underscore(id)).toLowerCase()`
      * `Product` → `product`
      * `OrderItem` → `order-item`

    **Note:**

    * Entity ID must start with uppercase (CamelCase)
    * Table name is auto-generated but customizable
  </Accordion>

  <Accordion title="What are the conditions for auto-generating migrations when Entity is modified?">
    When Entity is modified, migrations are **not auto-generated**. You must **manually Generate** in the Sonamu UI's DB Migration tab.

    **Migration generation targets (shown in Prepared Migration Codes):**

    **Props changes:**

    * Prop add/delete
    * Prop type change (`string` → `numeric`, etc.)
    * `StringProp` length change (`length`)
    * `nullable` change
    * `dbDefault` change
    * `precision`, `scale` change when `NumberProp` has `numberType: "numeric"`

    **Relation changes:**

    * BelongsToOne add/delete (FK column create/delete)
    * OneToOne add/delete (when `hasJoinColumn: true`)
    * ManyToMany add/delete (join table create/delete)
    * onUpdate, onDelete changes

    **Index changes:**

    * Index add/delete
    * unique, index, fulltext type changes
    * Index column changes

    **Table changes:**

    * Table name change

    **Changes that don't generate migrations:**

    * Subset add/modify/delete
    * Enum labels change
    * Virtual prop add/modify/delete
    * HasMany relation add/modify (FK is on the other side)
    * Title, Description changes

    **Note:**

    * Migration files are not auto-generated after Entity modification
    * Check DB Migration tab → Prepared Migration Codes
    * Manual generation required by clicking Generate button
  </Accordion>

  <Accordion title="What order are files created when I create a new Entity in Sonamu UI?">
    **1. Entity JSON file created**

    * `{entity}.entity.json`
    * Location: `api/src/application/{entity}/{entity}.entity.json`

    **2. Type file auto-generated**

    * `{entity}.types.ts`
    * Location: `api/src/application/{entity}/{entity}.types.ts`

    **3. Generated files updated**

    * `sonamu.generated.sso.ts`
    * `sonamu.generated.ts`
    * Location: `api/src/application/`

    **4. Frontend files copied**

    * `user.types.ts` → `web/src/services/user/user.types.ts`
    * `sonamu.generated.ts` → `web/src/services/sonamu.generated.ts`

    **5. `sonamu.lock` updated**

    * Stores checksums of newly created files

    **Note:**

    * Migration files are not auto-generated
    * Separate Generate click required in DB Migration tab
    * `model`, `model_test`, `view` are generated separately in Scaffolding tab
  </Accordion>
</AccordionGroup>

***

## Entity Properties

<AccordionGroup>
  <Accordion title="How do I define Entity enums?">
    Define in the `enums` object with enum ID as key and value-label pairs as object:

    ```json theme={null}
    {
      "enums": {
        "UserRole": {
          "normal": "Normal",
          "admin": "Admin"
        },
        "UserOrderBy": {
          "id-desc": "ID Newest"
        }
      }
    }
    ```

    * **Key** (`normal`, `admin`): Actual value stored in DB
    * **Value** (Normal, Admin): Label displayed in UI
    * **Enum ID** (`UserRole`): Used as TypeScript type name

    **Best Practices:**

    * DB values in English (for internationalization and migration ease)
    * UI labels in user's language
  </Accordion>

  <Accordion title="What is a virtual field and when do I use it?">
    A calculated field that is included in query results but has no DB column.

    **How to create (Sonamu UI):**

    1. Entity detail → Click Add a prop
    2. Type: Select `virtual`
    3. Name: Enter field name (e.g., `company_stats`)
    4. Check `Nullable: true`
    5. CustomType ID: Select type (e.g., `CompanyStatsType`)

    **Type definition (`{entity}.types.ts`):**

    ```typescript theme={null}
    export const CompanyStatsType = z.object({
      totalDepartments: z.number(),
      totalEmployees: z.number(),
      avgSalary: z.number(),
      departments: z.array(
        z.object({
          id: z.number(),
          name: z.string(),
          employeeCount: z.number(),
        }),
      ),
    });

    export type CompanyStatsType = z.infer<typeof CompanyStatsType>;
    ```

    **Implementation (Enhancer approach):**

    ```typescript theme={null}
    // company.model.ts
    const enhancers = this.createEnhancers({
      WithStats: async (row) => {
        const departments = await DepartmentModel.findMany("P", {
          company_id: row.id,
        });

        return {
          ...row,
          company_stats: {
            totalDepartments: departments.rows.length,
            totalEmployees: departments.rows.reduce((sum, d) => sum + d.employee_count, 0),
            avgSalary: calculateAvgSalary(departments.rows),
            departments: departments.rows.map((d) => ({
              id: d.id,
              name: d.name,
              employeeCount: d.employee_count,
            })),
          },
        };
      },
    });
    ```

    **Main use cases:**

    * Relation data aggregation (employee count, comment count)
    * Complex statistics objects (department stats, company dashboard)
    * User-specific status (like status, permission info)
    * Nested JSON structures (metadata, config objects)

    **Note:**

    * `.omit({ virtual_field: true })` required in `SaveParams`
    * Implement calculation logic in Enhancer
    * Consider performance for complex calculations
  </Accordion>
</AccordionGroup>

***

## Entity Relations

<AccordionGroup>
  <Accordion title="How do I define relations between Entities?">
    Define with `relation` type fields:

    **OneToOne:** 1:1 relation

    * Specify FK owner with `hasJoinColumn`

    **BelongsToOne:** N:1 relation (Many-to-One)

    * Defined in Entity that has the FK

    **HasMany:** 1:N relation (One-to-Many)

    * virtual relation (FK is on the other side)

    **ManyToMany:** N:M relation

    * Requires `joinTable`

    Each relation can set referential integrity with `onUpdate`, `onDelete` options in the Entity that has the FK.
  </Accordion>

  <Accordion title="How do I define a BelongsToOne relation?">
    Represents an N:1 (Many-to-One) relation.

    **How to configure (Sonamu UI):**

    1. Entity detail → Click Add a prop
    2. Type: Select `relation`
    3. Relation Type: Select `BelongsToOne`
    4. Name: Relation field name (e.g., `department`)
    5. With: Entity ID to connect (e.g., `Department`)
    6. Nullable: `true` for optional relations
    7. ON UPDATE/ON DELETE: Select `CASCADE`, `SET NULL`, `RESTRICT`, `NO ACTION`, etc.

    **Result:**

    * Foreign key column auto-created (e.g., `department_id`)
    * Type includes `department?: Department`

    ```json theme={null}
    {
      "type": "relation",
      "name": "department",
      "with": "Department",
      "nullable": true,
      "relationType": "BelongsToOne",
      "onUpdate": "CASCADE",
      "onDelete": "SET NULL"
    }
    ```
  </Accordion>
</AccordionGroup>

***

## Model Classes

<AccordionGroup>
  <Accordion title="How do I create an API route without an Entity connected to DB?">
    There are two ways to create independent APIs without an Entity.

    **Method 1: Add @api decorator to existing Model**

    ```typescript theme={null}
    // api/src/application/user/user.model.ts
    class UserModelClass extends BaseModelClass {
      // Existing CRUD methods...

      @api({ httpMethod: "GET" })
      async getMyIP(): Promise<{ ip: string }> {
        const context = Sonamu.getContext();
        return { ip: context.request.ip };
      }

      @api({ httpMethod: "POST" })
      async sendEmail(params: { to: string; subject: string }): Promise<boolean> {
        // External API call (no DB query)
        return true;
      }
    }
    ```

    **Method 2: Use Frame**

    Frame is a class for creating independent APIs without an Entity.

    ```typescript theme={null}
    // api/src/application/util/util.frame.ts
    import { BaseFrameClass, api } from "sonamu";

    class UtilFrameClass extends BaseFrameClass {
      @api({ httpMethod: "GET" })
      async healthCheck(): Promise<{ status: string; timestamp: Date }> {
        return { status: "ok", timestamp: new Date() };
      }
    }

    export const UtilFrame = new UtilFrameClass();
    ```

    **Frame characteristics:**

    * Extends `BaseFrameClass`
    * Provides `getDB(preset)`, `getUpsertBuilder()` methods
    * No Entity, Subset, Model methods
    * Filename: `{name}.frame.ts`

    **Selection criteria:**

    * Related to existing Entity: Add method to that Model
    * Completely independent: Create Frame class
  </Accordion>

  <Accordion title="How should I refactor when Model class becomes too large?">
    When Model class grows, you can **separate independent logic into Frame classes**.

    **Problem: user.model.ts becomes large**

    ```typescript theme={null}
    class UserModelClass extends BaseModelClass {
      async findById() { ... }
      async save() { ... }
      // Logic unrelated to Entity
      async sendEmail() { ... }
      async uploadAvatar() { ... }
      async generateReport() { ... }
    }
    ```

    **Improvement: Separate into user-util.frame.ts**

    ```typescript theme={null}
    // user-util.frame.ts
    class UserUtilFrameClass extends BaseFrameClass {
      @api({ httpMethod: "POST" })
      async sendEmail(params: EmailParams): Promise<boolean> {
        // Email sending logic
        return true;
      }

      @api({ httpMethod: "POST" })
      async uploadAvatar(file: File): Promise<string> {
        // File upload logic
        return "url";
      }
    }
    ```

    **Separation criteria:**

    * Directly related to User Entity: Keep in `user.model.ts`
    * Utility-type features: Separate to `user-util.frame.ts`
    * Completely independent: Create separate Frame class
  </Accordion>

  <Accordion title="What does the EntityManager class do?">
    A singleton class that centrally manages all Entities.

    **Main features:**

    * Entity lookup
    * Auto-generate names (table, fs, module)
    * Schema validation
    * Subset lookup
    * Subset field expansion

    **Used in:**

    * Model classes
    * API decorators
    * Syncer
    * Migration generator
  </Accordion>

  <Accordion title="How do I validate Entity schemas?">
    **Sonamu UI:**

    * Real-time error display when editing Entity

    **EntityManager:**

    ```typescript theme={null}
    const errors = EntityManager.schemaValidate(entity);
    ```

    **Syncer:**

    * Auto-validates and outputs errors when running `pnpm dev`

    **Validation items:**

    * Required fields exist
    * Type validity
    * Subset references
    * Enum validity
  </Accordion>
</AccordionGroup>

***

## Related Documentation

* [Defining Entities](/en/core-concepts/entities/defining-entities)
* [Property Types](/en/core-concepts/entities/property-types)
* [Entity Relations](/en/core-concepts/entities/entity-relationships)
* [Model Classes](/en/core-concepts/model-classes/basemodel)
* [Frame Classes](/en/core-concepts/model-classes/frame-classes)
