Skip to main content
Sonamu automatically generates various types from Entity definitions. Not only base types, but also API parameters, Subset types, Enums, and more are auto-generated to provide complete type safety.

Generated Types Overview

Base Types

Entity’s basic types: User, Post, Comment

Subset Types

Partial query types: UserA, UserP, UserSS

Params Types

API parameter types: ListParams, SaveParams

Enum Types

Enums and helper functions: UserRole, userRoleLabel

Base Types

Basic types including all fields of the Entity.
{
  "id": "User",
  "props": [
    { "name": "id", "type": "integer" },
    { "name": "email", "type": "string" },
    { "name": "username", "type": "string" },
    { "name": "created_at", "type": "date" }
  ]
}
Characteristics:
  • HasMany, ManyToMany Relations are excluded
  • BelongsToOne, OneToOne(hasJoinColumn) are included as {name}_id format
  • Virtual fields only have type definition, values are computed in Enhancer

Subset Types

Types that include only partial fields of the Entity.
{
  "subsets": {
    "A": ["id", "email", "username", "created_at"],
    "P": ["id", "email", "employee.id", "employee.department.name"],
    "SS": ["id", "email"]
  }
}
Usage example:
import type { UserSubsetKey, UserSubsetMapping } from "./sonamu.generated";

// Select Subset type with generics
async function findMany<T extends UserSubsetKey>(
  subset: T
): Promise<UserSubsetMapping[T][]> {
  // ...
}

const users = await findMany("A"); // UserA[] type
const profiles = await findMany("P"); // UserP[] type

ListParams Types

Parameter types for list query APIs.
{
  "props": [
    { "name": "email", "type": "string", "toFilter": true },
    { "name": "username", "type": "string", "toFilter": true }
  ]
}
Usage example:
// API call
const result = await findManyUsers({
  num: 20,
  page: 1,
  search: "email",
  keyword: "john",
  orderBy: "created_at-desc",
  email: ["john@test.com", "jane@test.com"], // Arrays also possible
});
zArrayable: A helper that allows both single values and arrays.
// Both possible
{
  email: "john@test.com";
}
{
  email: ["john@test.com", "jane@test.com"];
}

SaveParams Types

Parameter types for save APIs.
// SaveParams = Base type with id as optional
export const UserSaveParams = User.partial({ id: true });
export type UserSaveParams = z.infer<typeof UserSaveParams>;

// Equivalent to:
// type UserSaveParams = {
//   id?: number;
//   email: string;
//   username: string;
//   created_at: Date;
// }
Characteristics:
  • Only id is optional, other fields are required
  • With id → UPDATE, without → INSERT
  • Can save multiple records simultaneously with array

Enum Types

Entity Enums are auto-generated.
{
  "enums": {
    "UserRole": {
      "admin": "Administrator",
      "moderator": "Moderator",
      "normal": "Normal User"
    }
  }
}
Usage example:
import { UserRole, userRoleLabel } from "./sonamu.generated";

// Type-safe Enum usage
const role: UserRole = "admin";

// Display label
console.log(userRoleLabel(role)); // "Administrator"

// Generate selection options in UI
const options = UserRole.options.map((value) => ({
  value,
  label: userRoleLabel(value),
}));
// [
//   { value: "admin", label: "Administrator" },
//   { value: "moderator", label: "Moderator" },
//   { value: "normal", label: "Normal User" }
// ]

Custom Params Types

Define custom parameter types when creating additional APIs in Model.
// Add custom parameters
export const UserLoginParams = z.object({
  email: z.string().email(),
  password: z.string().min(8),
  rememberMe: z.boolean().optional(),
});
export type UserLoginParams = z.infer<typeof UserLoginParams>;

export const UserRegisterParams = User.pick({
  email: true,
  username: true,
}).extend({
  password: z.string().min(8),
  passwordConfirm: z.string().min(8),
}).refine(
  (data) => data.password === data.passwordConfirm,
  { message: "Passwords do not match", path: ["passwordConfirm"] }
);
export type UserRegisterParams = z.infer<typeof UserRegisterParams>;
Auto-generated:
// services.generated.ts
export async function loginUser(
  params: UserLoginParams
): Promise<{ user: User; token: string }> {
  const { data } = await axios.post("/user/login", { params });
  return data;
}

export function useLoginUser() {
  return useMutation({
    mutationFn: (params: UserLoginParams) => loginUser(params),
  });
}

Action Params Types

Parameter types for specific actions.
// ID array parameters
export const UserBulkDeleteParams = z.object({
  ids: z.number().int().array().min(1),
});
export type UserBulkDeleteParams = z.infer<typeof UserBulkDeleteParams>;

// Status change parameters
export const UserStatusUpdateParams = z.object({
  ids: z.number().int().array().min(1),
  status: z.enum(["active", "suspended", "deleted"]),
  reason: z.string().optional(),
});
export type UserStatusUpdateParams = z.infer<typeof UserStatusUpdateParams>;

ListResult Type

Type for list query results.
sonamu.shared.ts
export type ListResult<
  LP extends { num?: number; page?: number; queryMode?: string },
  T
> = LP["queryMode"] extends "list"
  ? { rows: T[] }
  : LP["queryMode"] extends "count"
  ? { total: number }
  : { rows: T[]; total: number };
Usage example:
// Type is automatically determined based on queryMode

// queryMode: "both" (default)
const result = await findManyUsers({ num: 10, page: 1 });
// ListResult<UserListParams, User> = { rows: User[]; total: number }

// queryMode: "list"
const result = await findManyUsers({ num: 10, page: 1, queryMode: "list" });
// ListResult<UserListParams, User> = { rows: User[] }

// queryMode: "count"
const result = await findManyUsers({ queryMode: "count" });
// ListResult<UserListParams, User> = { total: number }
Conditional types: ListResult uses TypeScript conditional types to return different types based on queryMode.

Type Exports

Generated types are automatically exported.
sonamu.generated.ts
// Re-export all types per Entity
export * from "./user/user.model";
export * from "./post/post.model";
export * from "./comment/comment.model";

// Model instances are also exported
export { UserModel } from "./user/user.model";
export { PostModel } from "./post/post.model";
Usage:
// Import from one place
import {
  User,
  UserListParams,
  UserSaveParams,
  UserRole,
} from "./sonamu.generated";

// Or individual imports
import type { User } from "./user/user.types";
import { UserModel } from "./user/user.model";

Type Utilities

Useful type utilities provided by Sonamu.

zArrayable

Type that allows both single values and arrays.
import { zArrayable } from "sonamu";

const UserListParams = z.object({
  id: zArrayable(z.number().int()).optional(),
  email: zArrayable(z.string()).optional(),
});

// Both possible
{
  id: 1;
}
{
  id: [1, 2, 3];
}

DistributiveOmit

Safer than regular Omit for Union types.
import type { DistributiveOmit } from "sonamu";

type UserOrAdmin =
  | { type: "user"; userId: number }
  | { type: "admin"; adminId: number };

// DistributiveOmit is applied to each type individually
type WithoutId = DistributiveOmit<UserOrAdmin, "userId" | "adminId">;
// = { type: "user" } | { type: "admin" }

Customizing Type Generation

You can extend or modify auto-generated types.

Extending Params

user.types.ts
// Extend basic ListParams
export const ExtendedUserListParams = UserListParams.extend({
  includeDeleted: z.boolean().optional(),
  dateRange: z
    .object({
      from: z.date(),
      to: z.date(),
    })
    .optional(),
});
export type ExtendedUserListParams = z.infer<typeof ExtendedUserListParams>;

Extending Subset Types

user.types.ts
// Add computed fields to Subset type
export type UserAWithStats = UserA & {
  post_count: number;
  follower_count: number;
};

Creating Conditional Types

user.types.ts
// Different types based on role
export type UserByRole<T extends UserRole> = T extends "admin"
  ? UserA & { permissions: string[] }
  : T extends "normal"
  ? UserSS
  : User;

Practical Example

Example of using generated types in a real project.
import type {
  UserSubsetKey,
  UserSubsetMapping,
  UserListParams,
  UserSaveParams,
  UserLoginParams,
} from "./user.types";

class UserModelClass extends BaseModelClass<
  UserSubsetKey,
  UserSubsetMapping,
  typeof userSubsetQueries,
  typeof userLoaderQueries
> {
  @api({ httpMethod: "GET" })
  async findMany<T extends UserSubsetKey>(
    subset: T,
    params?: UserListParams
  ): Promise<ListResult<UserListParams, UserSubsetMapping[T]>> {
    // Type-safe implementation
  }

  @api({ httpMethod: "POST" })
  async save(params: UserSaveParams[]): Promise<number[]> {
    // Type-safe implementation
  }

  @api({ httpMethod: "POST" })
  async login(params: UserLoginParams): Promise<{ user: User; token: string }> {
    // Type-safe implementation
  }
}

Next Steps