> ## 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.

# scaffold

> Auto-generate code from Entity definitions

export const FileItem = ({name, description, children}) => {
  const isLeaf = !children;
  const ext = getFileExtension(name);
  const extStyle = ext ? getExtensionStyle(ext) : null;
  return <div style={{
    marginLeft: "30px"
  }}>
      <div style={{
    display: "flex",
    alignItems: "center",
    gap: "4px"
  }}>
        <span style={{
    position: "relative",
    display: "inline-block"
  }}>
          <span className="file-icon">{isLeaf && !name.endsWith("/") ? "📄" : "📁"}</span>
          {ext && <span style={{
    position: "absolute",
    bottom: "2px",
    right: "0px",
    fontSize: "5px",
    fontWeight: "bold",
    padding: "1px",
    borderRadius: "2px",
    lineHeight: "1",
    minWidth: "8px",
    textAlign: "center",
    ...extStyle
  }}>
              {ext}
            </span>}
        </span>
        <code>{name}</code>
        {description && <span className="description"> - {description}</span>}
      </div>
      {children}
    </div>;
};

export const FileTree = ({children}) => {
  return <div className="file-tree" style={{
    margin: "20px",
    marginLeft: "-30px"
  }}>
      {children}
    </div>;
};

The `pnpm scaffold` command **auto-generates boilerplate code** based on Entity definitions. It generates Model classes and test files in seconds, significantly reducing development time.

## Basic Concept

Scaffold is an automation tool that **converts Entities to code**:

* **Entity-based**: Understand types and structure from Entity definitions
* **Type-safe**: TypeScript types are auto-generated
* **Consistent**: All code follows the same pattern
* **Customizable**: Freely customize after generation

## Commands

### model - Generate Model Class

Generate a Model class for an Entity.

```bash theme={null}
pnpm scaffold model
```

An interactive prompt appears:

```
? Please select entity: (Use arrow keys)
❯ User
  Post
  Comment
```

**Generated files**:

<FileTree>
  <FileItem name="src/models/">
    <FileItem name="User.model.ts" description="User Model class" />
  </FileItem>
</FileTree>

**Generated code**:

```typescript title="src/models/User.model.ts" theme={null}
import { BaseModelClass } from "sonamu";
import type { InferSelectModel } from "sonamu";
import { UserEntity } from "../entities/User.entity";
import type { UserSubsetKey, UserSubsetMapping } from "../sonamu.generated";
import { userLoaderQueries, userSubsetQueries } from "../sonamu.generated.sso";

export type User = InferSelectModel<typeof UserEntity>;

class UserModelClass extends BaseModelClass<
  UserSubsetKey,
  UserSubsetMapping,
  typeof userSubsetQueries,
  typeof userLoaderQueries
> {
  constructor() {
    super("User", userSubsetQueries, userLoaderQueries);
  }

  // TODO: Add Model methods
}

export const UserModel = new UserModelClass();
```

### model\_test - Generate Test File

Generate a test file for a Model.

```bash theme={null}
pnpm scaffold model_test
```

**Generated files**:

<FileTree>
  <FileItem name="src/models/">
    <FileItem name="User.model.test.ts" description="User Model tests" />
  </FileItem>
</FileTree>

**Generated code**:

```typescript title="src/models/User.model.test.ts" theme={null}
import { beforeAll, describe, test } from "bun:test";
import { expect } from "@jest/globals";
import { FixtureManager } from "sonamu/test";
import { UserModel } from "./User.model";

describe("User Model", () => {
  beforeAll(async () => {
    await FixtureManager.sync();
  });

  test("findById", async () => {
    const user = await UserModel.findById(1);
    expect(user).toBeDefined();
    expect(user?.id).toBe(1);
  });

  test("findMany", async () => {
    const users = await UserModel.findMany({
      num: 10,
      page: 1,
    });
    expect(users.rows.length).toBeGreaterThan(0);
  });

  // TODO: Add more tests
});
```

## View Scaffolding (In Development)

<Info>
  View component auto-generation is currently in development.
  You can generate React components through **Sonamu UI**.

  ```bash theme={null}
  # Run Sonamu UI
  pnpm sonamu ui

  # Generate View in Scaffolding tab
  ```
</Info>

## Generated Code Structure

### Model Class

Generated Model classes have the following structure:

```typescript theme={null}
class UserModelClass extends BaseModelClass<
  UserSubsetKey,
  UserSubsetMapping,
  typeof userSubsetQueries,
  typeof userLoaderQueries
> {
  constructor() {
    super("User", userSubsetQueries, userLoaderQueries);
  }

  // Entity type
  entity = UserEntity;

  // Add methods here
  async findByEmail(email: string): Promise<User | null> {
    return this.getPuri("r").table("users").where("email", email).first();
  }
}
```

**Auto-generated features**:

* ✅ TypeScript types
* ✅ Entity connection
* ✅ BaseModel inheritance
* ✅ Basic CRUD methods

**You need to add**:

* Business logic methods
* Complex queries
* Data validation
* Relation loading

### Test File

Generated test files include basic tests:

```typescript theme={null}
describe("User Model", () => {
  beforeAll(async () => {
    await FixtureManager.sync(); // Load fixtures
  });

  test("findById", async () => {
    // Basic CRUD test
  });

  test("findMany", async () => {
    // List query test
  });
});
```

**Extensible**:

* Add business logic tests
* Edge case tests
* Performance tests
* Integration tests

## Development Workflow

### 1. Define Entity

```typescript title="src/entities/Product.entity.ts" theme={null}
export const ProductEntity = {
  properties: [
    { name: "id", type: "int", primaryKey: true, autoIncrement: true },
    { name: "title", type: "string", length: 255 },
    { name: "price", type: "decimal", precision: 10, scale: 2 },
    { name: "category_id", type: "int" },
    { name: "created_at", type: "datetime" },
  ],
  belongsTo: [{ entityId: "Category", as: "category" }],
} satisfies EntityType;
```

### 2. Migration

```bash theme={null}
# Create and apply migration
pnpm migrate run
```

### 3. Generate Model

```bash theme={null}
# Generate Model class
pnpm scaffold model

# Select Entity: Product
```

### 4. Add Business Logic

```typescript title="src/models/Product.model.ts" theme={null}
class ProductModelClass extends BaseModelClass<
  ProductSubsetKey,
  ProductSubsetMapping,
  typeof productSubsetQueries,
  typeof productLoaderQueries
> {
  constructor() {
    super("Product", productSubsetQueries, productLoaderQueries);
  }

  // Find products by category
  async findByCategory(categoryId: number) {
    return this.getPuri("r").table("products").where("category_id", categoryId).where("deleted_at", null);
  }

  // Find products by price range
  async findByPriceRange(min: number, max: number) {
    return this.getPuri("r").table("products").whereBetween("price", [min, max]);
  }
}
```

### 5. Generate Tests

```bash theme={null}
# Generate test file
pnpm scaffold model_test

# Select Entity: Product
```

### 6. Write Tests

```typescript title="src/models/Product.model.test.ts" theme={null}
describe("Product Model", () => {
  beforeAll(async () => {
    await FixtureManager.sync();
  });

  test("findByCategory", async () => {
    const products = await ProductModel.findByCategory(1);
    expect(products.length).toBeGreaterThan(0);
    expect(products[0].category_id).toBe(1);
  });

  test("findByPriceRange", async () => {
    const products = await ProductModel.findByPriceRange(10000, 50000);
    products.forEach((product) => {
      expect(product.price).toBeGreaterThanOrEqual(10000);
      expect(product.price).toBeLessThanOrEqual(50000);
    });
  });
});
```

### 7. Add API

```typescript title="src/models/Product.model.ts" theme={null}
class ProductModelClass extends BaseModelClass {
  @api({ httpMethod: "GET" })
  async findByCategory(categoryId: number) {
    const { qb } = this.getSubsetQueries("A");
    qb.where("category_id", categoryId);

    return this.executeSubsetQuery({
      subset: "A",
      qb,
      params: { num: 20, page: 1 },
    });
  }
}
```

## Customization

### Adding Model Methods

Add business logic to generated Models:

```typescript theme={null}
class UserModelClass extends BaseModelClass<
  UserSubsetKey,
  UserSubsetMapping,
  typeof userSubsetQueries,
  typeof userLoaderQueries
> {
  constructor() {
    super("User", userSubsetQueries, userLoaderQueries);
  }

  // Find user by email
  async findByEmail(email: string): Promise<User | null> {
    return this.getPuri("r").table("users").where("email", email).first();
  }

  // Find only active users
  async findActiveUsers() {
    return this.getPuri("r").table("users").where("is_active", true).where("deleted_at", null);
  }

  // User statistics
  async getUserStats(userId: number) {
    const [stats] = await this.getPuri("r")
      .table("users")
      .select({
        postCount: this.raw("COUNT(posts.id)"),
        commentCount: this.raw("COUNT(comments.id)"),
      })
      .leftJoin("posts", "posts.user_id", "users.id")
      .leftJoin("comments", "comments.user_id", "users.id")
      .where("users.id", userId)
      .groupBy("users.id");

    return stats;
  }
}
```

### Extending Tests

Add more test cases:

```typescript theme={null}
describe("User Model - Extended", () => {
  test("findByEmail - existing email", async () => {
    const user = await UserModel.findByEmail("user1@example.com");
    expect(user).toBeDefined();
  });

  test("findByEmail - non-existing email", async () => {
    const user = await UserModel.findByEmail("notexist@example.com");
    expect(user).toBeNull();
  });

  test("findActiveUsers", async () => {
    const users = await UserModel.findActiveUsers();
    users.forEach((user) => {
      expect(user.is_active).toBe(true);
      expect(user.deleted_at).toBeNull();
    });
  });

  test("getUserStats", async () => {
    const stats = await UserModel.getUserStats(1);
    expect(stats.postCount).toBeGreaterThanOrEqual(0);
    expect(stats.commentCount).toBeGreaterThanOrEqual(0);
  });
});
```

## Batch Generation for Multiple Entities

### Using Scripts

```bash theme={null}
# Generate Model for all Entities
for entity in User Post Comment Category; do
  echo $entity | pnpm scaffold model
done
```

### Batch Test Generation

```bash theme={null}
# Generate tests for all Models
for entity in User Post Comment Category; do
  echo $entity | pnpm scaffold model_test
done
```

## Practical Tips

### 1. Complete Entity First

```typescript theme={null}
// ✅ Scaffold after Entity is fully defined
export const UserEntity = {
  properties: [
    // Define all fields
  ],
  indexes: [
    // Define indexes
  ],
  belongsTo: [
    // Define relations
  ],
} satisfies EntityType;
```

### 2. Incremental Extension

```typescript theme={null}
// 1. Generate basic Model
pnpm scaffold model

// 2. Add simple methods first
async findById(id: number) { ... }

// 3. Add complex logic
async complexQuery() { ... }

// 4. Write tests
pnpm scaffold model_test
```

### 3. Follow Conventions

```typescript theme={null}
// ✅ Consistent naming
async findByEmail(email: string) { }     // find + By + Field
async findActiveUsers() { }               // find + Adjective + Entities
async getUserStats(userId: number) { }   // get + Noun + Type

// ✅ Consistent return types
Promise<User | null>      // Single record (nullable)
Promise<User[]>           // Multiple records
Promise<ListResult<User>> // Pagination
```

### 4. Git Management

```bash theme={null}
# Commit generated files
git add src/models/User.model.ts
git add src/models/User.model.test.ts
git commit -m "Add User model and tests"
```

## Troubleshooting

### Regenerating Model

**Problem**: Want to regenerate Model

**Solution**:

```bash theme={null}
# Backup existing file
cp src/models/User.model.ts src/models/User.model.ts.bak

# Regenerate
pnpm scaffold model

# Restore necessary code
```

### Syncing After Entity Change

**Problem**: Update Model after Entity change

**Solution**:

```bash theme={null}
# 1. Apply migration
pnpm migrate run

# 2. Update Model manually
# (Types sync automatically)
```

## Next Steps

<CardGroup cols={2}>
  <Card title="Model" icon="database" href="/en/core-concepts/model">
    Learn more about Model classes
  </Card>

  <Card title="Testing" icon="flask" href="/en/testing/naite">
    Write tests
  </Card>
</CardGroup>
