# Database Models

## Factory Function Pattern

Each model is a `createXType<const F>(params)` factory:

- Params interface generic: `F extends Record<string, TailorAnyDBField>`
- `fields?: NoReservedFields<F, ReservedFields>` — custom fields from parent modules, guarded so a built-in field name can't be redefined (see below)
- Spread as `...(params.fields ?? {}) as F` to preserve type information
- Include `...db.fields.timestamps()`
- Use `.description()` for field docs
- Apply permissions at model level
- Export the codegen instance: `export const x = createXType({})` — cross-module FK params stay optional and unset here (see [cross-module-dependency.md](cross-module-dependency.md))

## Reserved Field Guard

`fields` only *adds* fields, so redefining a built-in must fail to compile. Hold the built-in fields in a `builtins` binding and derive the reserved set from it — no hand-maintained list:

```typescript
import { type CommonReservedFields, type NoReservedFields } from "@tailor-platform/erp-kit/core";

const builtins = {
  code: db.string().unique().description("..."),
  // ...
};
type ReservedFields = keyof typeof builtins | CommonReservedFields;
// then: fields?: NoReservedFields<F, ReservedFields>
```

- `CommonReservedFields` covers `id` / `createdAt` / `updatedAt`.
- If `builtins` references `params` (cross-module relation), make it `(params) => ({ ... })` and use `keyof ReturnType<typeof builtins>`.
- Spread `...builtins` (or `...builtins(params)`) before the custom-fields spread.

## Stateful Model Enums

When a model has a State Transitions table in its doc, `erp-kit module generate code` produces a `db/<model>.lifecycle.generated.ts` file. Use its `.states` for the enum definition instead of hardcoding status arrays.

```typescript
import { productLifecycle } from "./product.lifecycle.generated";

// Good: derive enum from lifecycle — single source of truth
status: db.enum(productLifecycle.states).description("Lifecycle status"),

// Bad: hardcoded status array that can drift from the doc
const STATUSES = ["DRAFT", "ACTIVE", "ARCHIVED"] as const;
status: db.enum(STATUSES).description("Lifecycle status"),
```

- The lifecycle file is regenerated on every `generate code` run — never edit it
- The `<Model>Status` type exported from the lifecycle file gives the union type of all states
