--- status: stable last_updated: 2026-05-23 --- # Schema: Domain Data Inside the Module What goes into a dbtype `Type.Module` — table schemas, relation entries, derived schemas (insert, update, filter), and the schema derivation semantics. For the mechanical details of how `Type.Module` works (construction, validation, serialization, migration diffing, constraints), see [module.md](module.md). ## From Element Tree to Module The element tree (``, ``) is walked to extract a `Record` map, then compiled into a module: ``` UJSX elements → extractTable() → { name, schema, columns } → defs map → Type.Module(defs) ``` Each `` element produces a TypeBox type based on its `type` prop: | Column Type Prop | TypeBox Schema | |-----------------|---------------| | `uuid` | `Type.String({ format: 'uuid' })` | | `string` | `Type.String()` | | `integer` | `Type.Integer()` | | `boolean` | `Type.Boolean()` | | `timestamp` | `Type.Number()` | | `enum` | `Type.Union(values.map(v => Type.Literal(v)))` | For incremental construction patterns and compilation mechanics, see [module.md](module.md). ## Relations Relations are stored as separate entries in the module, using `Type.Ref` to reference other tables: ```typescript defs.UsersRelations = Type.Object({ tasks: Type.Array(Type.Ref('Tasks')) }) defs.TasksRelations = Type.Object({ user: Type.Ref('Users') }) ``` This gives: - **Type-safe validation**: `Value.Check(M.Import('UsersRelations'), { tasks: [...] })` validates the full nested structure - **No circular import issues**: `Type.Ref` resolves within the module namespace regardless of definition order - **Queryable structure**: The `$defs` map is enumerable — you can find all relations for a table by naming convention - **Drizzle integration**: The repo adapter reads relation entries to generate `relations()` calls for drizzle's relational query builder Foreign key metadata lives on the column element's `references` prop (``), not in the relation entry. Relations describe the "from this side, I see many of those" semantics. ## Schema Derivation ### Select Schema The module entry as-is is the select schema. Every column is present, nullable columns become `Type.Union([innerType, Type.Null()])`. ### Insert Schema Derive from the table entry by: - Removing auto-generated primary keys (columns with `primaryKey: true` and `default` set) - Making nullable columns and columns with defaults `Type.Optional` - Keeping required (`notNull` without default) columns mandatory Implemented by adding a computed entry to the module: ```typescript defs.InsertUsers = Type.Object({ name: Type.String(), email: Type.String(), // id, createdAt, updatedAt omitted (auto-generated) }) ``` ### Update Schema All columns optional. Use `Type.Partial(Type.Ref('TableName'))`: ```typescript defs.UpdateUsers = Type.Partial(Type.Ref('Users')) ``` ### Filter Schema Per-column comparison operators derived from the column type. Generated by the repo adapter, not the core module. ## Open Questions 1. **Should relation entries use a naming convention?** Currently `UsersRelations` / `TasksRelations`. Is this sufficient, or should relations be structured differently (e.g., a `relations` field on the table entry)? 2. **Derived schemas in the module or separate?** Insert/update schemas can be added as module entries (`InsertUsers`, `UpdateUsers`) or extracted by walking the module schema. Which is cleaner for the repo adapter? 3. **Should the module support multiple databases?** One module per database, or one module with all tables across all databases? Probably one per database namespace. ## References - TypeBox Module API: `@alkdev/typebox` source — `type/module/module.ts`, `type/ref/ref.ts` - Mechanical details: [module.md](module.md) - Probe validation: `scripts/probe-e2e.ts` - Research: `docs/research/architecture.md`