Skip to main content
Sonamu automatically generates TypeScript types, Zod schemas, API clients, React components, and more from Entity definitions. This document describes the types and purposes of all generated files.

Generated Files Overview

Types & Schemas

TypeScript types and Zod schemas*.types.ts, sonamu.generated.ts

API Clients

HTTP client functionsservices.generated.ts

Query Helpers

Subset query functionssonamu.generated.sso.ts

React Components

Form, List, Select componentsview_*.tsx

Core Generated Files

1. Entity Types ({entity}.types.ts)

TypeScript types and Zod schemas are generated per Entity.
api/src/application/user/user.types.ts (auto-generated)
import { z } from "zod";

// Base type
export type User = {
  id: number;
  email: string;
  username: string;
  role: "admin" | "normal";
  created_at: Date;
};

// Zod schema
export const User = z.object({
  id: z.number(),
  email: z.string(),
  username: z.string(),
  role: z.enum(["admin", "normal"]),
  created_at: z.date(),
});

// List parameters
export const UserListParams = z.object({
  num: z.number().optional(),
  page: z.number().optional(),
  search: UserSearchField.optional(),
  keyword: z.string().optional(),
  orderBy: UserOrderBy.optional(),
});

// Save parameters
export const UserSaveParams = User.partial({ id: true });
Generated when: Entity save or pnpm sonamu sync Editable: ❌ Auto-regenerated (custom types in separate file)

2. Generated Base (sonamu.generated.ts)

Base types and Enums for the entire project.
api/src/application/sonamu.generated.ts (auto-generated)
import { z } from "zod";

// All Entity Enums
export const UserRole = z.enum(["admin", "normal"]);
export type UserRole = z.infer<typeof UserRole>;

export const UserSearchField = z.enum(["id", "email", "username"]);
export const UserOrderBy = z.enum(["id-desc", "id-asc", "created_at-desc"]);

// Subset types
export type UserSubsetKey = "A" | "P" | "SS";
export type UserSubsetMapping = {
  A: UserA;
  P: UserP;
  SS: UserSS;
};

// Enum label helpers
export function userRoleLabel(role: UserRole): string {
  return {
    admin: "Administrator",
    normal: "Normal User",
  }[role];
}

// Export all models
export * from "./user/user.model";
export * from "./post/post.model";
Generated when: Entity save or pnpm sonamu sync Editable: ❌ Auto-regenerated

3. Subset Queries (sonamu.generated.sso.ts)

Query functions per Subset.
api/src/application/sonamu.generated.sso.ts (auto-generated)
import type { PuriWrapper } from "sonamu";

// Subset query functions
export const userSubsetQueries = {
  A: (puri: PuriWrapper) =>
    puri
      .table("users")
      .select([
        "users.id",
        "users.email",
        "users.username",
        "users.role",
        "users.created_at",
      ]),
      
  P: (puri: PuriWrapper) =>
    puri
      .table("users")
      .select([
        "users.id",
        "users.email",
        "users.username",
      ])
      .leftJoin("employees", "employees.user_id", "users.id")
      .select([
        "employees.id as employee__id",
        "employees.department_id as employee__department_id",
      ]),
      
  SS: (puri: PuriWrapper) =>
    puri
      .table("users")
      .select(["users.id", "users.email"]),
};

// Loader query functions (HasMany, ManyToMany)
export const userLoaderQueries = {
  P: [
    {
      as: "posts",
      refId: "id",
      qb: (puri: PuriWrapper, ids: number[]) =>
        puri
          .table("posts")
          .whereIn("posts.user_id", ids)
          .select([
            "posts.id",
            "posts.user_id as refId",
            "posts.title",
          ]),
    },
  ],
};
Generated when: Entity Subset changes Editable: ❌ Auto-regenerated

4. API Services (services.generated.ts)

API client functions.
web/src/services/services.generated.ts (auto-generated)
import axios from "axios";
import type { ListResult } from "./sonamu.shared";
import type { User, UserListParams, UserSaveParams } from "./user/user.types";

// Axios clients
export async function findUserById(id: number): Promise<User> {
  const { data } = await axios.get("/user/findById", { params: { id } });
  return data;
}

export async function findManyUsers(
  params?: UserListParams
): Promise<ListResult<UserListParams, User>> {
  const { data } = await axios.get("/user/findMany", { params });
  return data;
}

export async function saveUser(params: UserSaveParams[]): Promise<number[]> {
  const { data } = await axios.post("/user/save", { params });
  return data;
}

// TanStack Query Hooks
export function useUserById(id: number) {
  return useQuery({
    queryKey: ["User", "findById", id],
    queryFn: () => findUserById(id),
  });
}

export function useUsers(params?: UserListParams) {
  return useQuery({
    queryKey: ["Users", "findMany", params],
    queryFn: () => findManyUsers(params),
  });
}

// TanStack Mutation Hooks
export function useSaveUser() {
  return useMutation({
    mutationFn: (params: UserSaveParams[]) => saveUser(params),
  });
}
Generated when: Model’s @api decorator changes Editable: ❌ Auto-regenerated
Target-specific generation: Copied to each target specified in sync.targets in sonamu.config.ts (web, app, etc.).

5. HTTP Test File (sonamu.generated.http)

HTTP test file for REST Client.
api/sonamu.generated.http (auto-generated)
@baseUrl = http://localhost:3000

### User.findById
GET {{baseUrl}}/user/findById?id=1

### User.findMany
GET {{baseUrl}}/user/findMany?num=10&page=1

### User.save
POST {{baseUrl}}/user/save
Content-Type: application/json

{
  "params": [
    {
      "email": "test@test.com",
      "username": "Test User"
    }
  ]
}

### User.del
DELETE {{baseUrl}}/user/del
Content-Type: application/json

{
  "ids": [1, 2, 3]
}
Generated when: Model file changes Editable: ❌ Auto-regenerated Usage: Install VS Code’s REST Client extension and execute requests

React Components

React UI components can be generated via Scaffold.

6. List Component

import { useUsers } from "@/services/services.generated";

export function UserList() {
  const [params, setParams] = useState({ num: 20, page: 1 });
  const { data, isLoading } = useUsers(params);

  return (
    <div>
      <table>
        <thead>
          <tr>
            <th>ID</th>
            <th>Email</th>
            <th>Username</th>
          </tr>
        </thead>
        <tbody>
          {data?.rows.map((user) => (
            <tr key={user.id}>
              <td>{user.id}</td>
              <td>{user.email}</td>
              <td>{user.username}</td>
            </tr>
          ))}
        </tbody>
      </table>
      
      <Pagination
        total={data?.total ?? 0}
        page={params.page}
        onChange={(page) => setParams({ ...params, page })}
      />
    </div>
  );
}
Generated when: pnpm sonamu generate view_list --entity User Editable: ✅ Editable after initial generation

7. Form Component

web/src/pages/user/UserForm.tsx
import { useSaveUser } from "@/services/services.generated";
import { UserSaveParams } from "@/services/user/user.types";

export function UserForm({ userId }: { userId?: number }) {
  const { data: user } = useUserById(userId);
  const { mutate: save } = useSaveUser();
  
  const handleSubmit = (values: UserSaveParams) => {
    save([values], {
      onSuccess: () => {
        alert("Saved");
      },
    });
  };

  return (
    <form onSubmit={handleSubmit}>
      <input
        name="email"
        type="email"
        defaultValue={user?.email}
        required
      />
      <input
        name="username"
        defaultValue={user?.username}
        required
      />
      <select name="role" defaultValue={user?.role ?? "normal"}>
        <option value="admin">Administrator</option>
        <option value="normal">Normal User</option>
      </select>
      <button type="submit">Save</button>
    </form>
  );
}
Generated when: pnpm sonamu generate view_form --entity User Editable: ✅ Editable after initial generation

8. Select Components

// Async Select (searchable)
export function UserIdAsyncSelect({
  value,
  onChange,
}: {
  value?: number;
  onChange: (value: number) => void;
}) {
  const [keyword, setKeyword] = useState("");
  const { data } = useUsers({ keyword, num: 10 });

  return (
    <AsyncSelect
      value={value}
      onChange={onChange}
      onSearch={setKeyword}
      options={data?.rows.map((u) => ({
        value: u.id,
        label: u.email,
      }))}
    />
  );
}
Files for Server-Side Rendering are auto-generated.

9. Queries (queries.generated.ts)

web/src/queries.generated.ts (auto-generated)
import { queryOptions } from "@tanstack/react-query";
import { findUserById, findManyUsers } from "./services/services.generated";

export const userQueries = {
  findById: (id: number) =>
    queryOptions({
      queryKey: ["User", "findById", id],
      queryFn: () => findUserById(id),
    }),
    
  findMany: (params?: UserListParams) =>
    queryOptions({
      queryKey: ["Users", "findMany", params],
      queryFn: () => findManyUsers(params),
    }),
};
Generated when: Model file changes Editable: ❌ Auto-regenerated

10. Entry Server (entry-server.generated.tsx)

web/src/entry-server.generated.tsx (auto-generated)
import { dehydrate, QueryClient } from "@tanstack/react-query";
import { userQueries } from "./queries.generated";

export async function loader({ params }) {
  const queryClient = new QueryClient();

  // SSR data prefetching
  if (params.userId) {
    await queryClient.prefetchQuery(
      userQueries.findById(Number(params.userId))
    );
  }

  return {
    dehydratedState: dehydrate(queryClient),
  };
}
Generated when: Model file changes Editable: ❌ Auto-regenerated

Internationalization Files

Generated when i18n configuration exists.

11. Sonamu Dictionary (sd.generated.ts)

api/src/sd.generated.ts (auto-generated)
export const SD = {
  User: "User",
  user: {
    id: "ID",
    email: "Email",
    username: "Username",
    role: "Role",
    created_at: "Created At",
  },
  UserRole: {
    admin: "Administrator",
    normal: "Normal User",
  },
} as const;
Generated when: Entity or i18n file changes Editable: ❌ Auto-regenerated Targets: Generated for api, web, app each

Generated Files Summary Table

FileLocationGenerated WhenEditable
{entity}.types.tsapi/src/application/{entity}/Entity save
sonamu.generated.tsapi/src/application/Entity save
sonamu.generated.sso.tsapi/src/application/Entity save
services.generated.tsweb/src/services/Model change
sonamu.generated.httpapi/Model change
queries.generated.tsweb/src/Model change
entry-server.generated.tsxweb/src/Model change
sd.generated.tsapi/src/, web/src/, app/src/Entity/i18n change
{Entity}List.tsxweb/src/pages/{entity}/Scaffold
{Entity}Form.tsxweb/src/pages/{entity}/Scaffold
{Entity}SearchInput.tsxweb/src/pages/{entity}/Scaffold
{Entity}IdAsyncSelect.tsxweb/src/components/{entity}/Scaffold
{EnumId}Select.tsxweb/src/components/{entity}/Scaffold
Auto-regenerated files: Never modify *.generated.* files. Changes will be automatically overwritten.

Next Steps