Resolve OQ-01 through OQ-10 with formal ADRs and update all architecture docs to reference decisions. Add two new tracked questions (OQ-11, OQ-12) surfaced during review. ADR-009: Keep FooRelations naming convention for relation entries ADR-010: One module per database, include derived schemas by default ADR-011: Support JSX/TSX as ergonomic authoring layer ADR-012: Always .returning() with graceful fallback per dialect ADR-013: Adapter generates relations from module entries (no <relation> element) ADR-014: Leverage drizzle-kit for migrations, no native migration generator Also upgrades elements.md, hosts.md, repo-adapter.md status to stable, clarifies OQ-06 as design clarification, and specifies MySQL .returning() detection mechanism in ADR-012.
4.1 KiB
status, last_updated
| status | last_updated |
|---|---|
| stable | 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.
From Element Tree to Module
The element tree (<table>, <column>) is walked to extract a Record<string, TSchema> map, then compiled into a module:
UJSX elements → extractTable() → { name, schema, columns } → defs map → Type.Module(defs)
Each <column> 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.
Relations
Relations are stored as separate entries in the module, using Type.Ref to reference other tables:
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.Refresolves within the module namespace regardless of definition order - Queryable structure: The
$defsmap 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 (<column name="userId" type="uuid" references="users" />), 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: trueanddefaultset) - Making nullable columns and columns with defaults
Type.Optional - Keeping required (
notNullwithout default) columns mandatory
Implemented by adding a computed entry to the module:
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')):
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
-
Should relation entries use a naming convention?Resolved — ADR-009. Keep theFooRelationsnaming convention. Separate module entries for relations align with Drizzle's naming, stay within the module's flat namespace, and are independently addressable. -
Derived schemas in the module or separate?Resolved — ADR-010. Include all derived schemas (insert, update, filter) in the module by default. Arepo_frominterface will generate CRUD operation specs from the module using the operations system. AnincludeDerived: falseopt-out can be added later if needed. -
Should the module support multiple databases?Resolved — ADR-010. One module per database namespace. This keeps the module's flat namespace clean, avoids table name collisions, and aligns with Drizzle's schema organization.
References
- TypeBox Module API:
@alkdev/typeboxsource —type/module/module.ts,type/ref/ref.ts - Mechanical details: module.md
- Probe validation:
scripts/probe-e2e.ts - Research:
docs/research/architecture.md