Files
dbtype/docs/architecture/schema.md
glm-5.1 d4fd67f4d2 docs: resolve architecture open questions, add type definitions, consolidate docs
Architecture review session resolving all high-priority open questions and
filling documentation gaps identified during review:

Decisions resolved:
- OQ-04: Flat props with inner escape hatch for column validation (ADR-007)
- OQ-05: PG enum pre-declaration returns enums and tables (ADR-008)
- OQ-06: Render results accumulate in root.ctx (resolved in hosts.md)
- Column references vs fk: references is shorthand, explicit fk takes
  precedence (ADR-006)
- ADR-001, 002, 003 promoted from Proposed to Accepted (probe-validated)

Documentation improvements:
- Complete DbColumnType mapping tables for all 14 types across 3 dialects
- Define ColumnMeta, TableMeta, IndexMeta, FkMeta types in elements.md
- Document inner prop, mode prop, and default prop semantics
- Add PgRootCtx, SqliteRootCtx, MySqlRootCtx context types
- Consolidate schema.md and module.md (remove duplication)
- Add end-to-end pipeline walkthrough to README
- Add glossary with 13 terms
- Add error handling strategy
- Remove duplicate content from hosts.md (cross-ref elements.md)
2026-05-23 12:06:51 +00:00

3.9 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.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 (<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: 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:

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

  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
  • Probe validation: scripts/probe-e2e.ts
  • Research: docs/research/architecture.md