---
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`