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)
This commit is contained in:
2026-05-23 12:06:51 +00:00
parent 4644e1b362
commit d4fd67f4d2
12 changed files with 476 additions and 221 deletions

View File

@@ -1,21 +1,13 @@
---
status: draft
last_updated: 2026-05-22
status: stable
last_updated: 2026-05-23
---
# Schema: Type.Module as the Schema Bundle
# Schema: Domain Data Inside the Module
How dbtype uses `Type.Module` to store all table schemas, relations, and derived schemas in a single namespace with automatic `Type.Ref` resolution.
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).
## Overview
The `Type.Module` is the central data structure in dbtype. It holds every table's TypeBox schema, all cross-table relations, and derived schemas (insert, update, select variants) in one flat namespace. `Type.Ref` resolves forward and circular references naturally, eliminating the need for separate relation files or import-order management.
The module is also the serialization boundary: `JSON.stringify(module.Import('Users'))` produces valid JSON Schema with `$defs`, enabling migration diffing via `Value.Diff`.
## Construction
### From Element Tree to Module
## From Element Tree to Module
The element tree (`<table>`, `<column>`) is walked to extract a `Record<string, TSchema>` map, then compiled into a module:
@@ -34,29 +26,24 @@ Each `<column>` element produces a TypeBox type based on its `type` prop:
| `timestamp` | `Type.Number()` |
| `enum` | `Type.Union(values.map(v => Type.Literal(v)))` |
### Incremental Construction
For incremental construction patterns and compilation mechanics, see [module.md](module.md).
The defs map is a plain `Record<string, TSchema>` — it can be built incrementally, mutated, and extended before compilation:
## Relations
Relations are stored as separate entries in the module, using `Type.Ref` to reference other tables:
```typescript
const defs: Record<string, any> = {}
// Add tables one at a time
defs.Users = Type.Object({ id: Type.String({ format: 'uuid' }), name: Type.String() })
defs.Tasks = Type.Object({ id: Type.String({ format: 'uuid' }), userId: Type.String({ format: 'uuid' }), title: Type.String() })
// Add columns to an existing table
defs.Users = Type.Object({ ...defs.Users.properties, role: Type.String() })
// Add relations
defs.UsersRelations = Type.Object({ tasks: Type.Array(Type.Ref('Tasks')) })
defs.TasksRelations = Type.Object({ user: Type.Ref('Users') })
// Compile
const M = Type.Module(defs)
```
Once compiled, `M.Import(key)` returns a `TImport` schema with the full `$defs` namespace embedded.
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
@@ -93,64 +80,6 @@ defs.UpdateUsers = Type.Partial(Type.Ref('Users'))
Per-column comparison operators derived from the column type. Generated by the repo adapter, not the core module.
## 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 (`<column name="userId" type="uuid" references="users" />`), not in the relation entry. Relations describe the "from this side, I see many of those" semantics.
## Serialization
`JSON.stringify(M.Import('TableName'))` produces JSON Schema with `$defs`:
```json
{
"$defs": {
"Users": { "$id": "Users", "type": "object", "properties": { ... } },
"Tasks": { "$id": "Tasks", "type": "object", "properties": { ... } },
"UsersRelations": { "$id": "UsersRelations", "type": "object", "properties": { "tasks": { "items": { "$ref": "Tasks" }, "type": "array" } } }
},
"$ref": "Users"
}
```
Key properties:
- Each `$defs` entry gets an `$id` matching its key name
- `Type.Ref` leaves `$ref` pointers (not inlined) — consumers must resolve them
- The serialized form is valid JSON Schema
- `Value.Diff` produces structural edits between two serialized schemas (useful for migration diffing)
## Migration Diffing
```typescript
const v1 = JSON.parse(JSON.stringify(M.Import('Users')))
// ... add a column to defs.Users ...
const v2 = JSON.parse(JSON.stringify(M2.Import('Users')))
const edits = Value.Diff(v1, v2)
// edits: [{ type: 'insert', path: '/$defs/Users/properties/role', value: { type: 'string' } }, ...]
```
The edits use JSON Pointer paths, which can be translated to `ALTER TABLE ADD COLUMN` statements.
## Constraints
- **Module keys must be unique** — two tables cannot have the same name in the same module
- **`Type.Ref` resolves within the module only** — no cross-module references without `Module.Import`
- **`Type.Ref` outside a module has `static: unknown`** — always access via `M.Import(key)` for proper type inference
- **Defs map is mutable until compiled** — once passed to `Type.Module`, mutations to the original map don't affect the compiled module
- **Format validation requires `FormatRegistry.Set`** — `uuid`, `email`, and other custom formats must be registered before `Value.Check` will enforce them
## 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)?
@@ -162,5 +91,6 @@ The edits use JSON Pointer paths, which can be translated to `ALTER TABLE ADD CO
## 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`