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:
@@ -1,6 +1,6 @@
|
||||
---
|
||||
status: draft
|
||||
last_updated: 2026-05-22
|
||||
last_updated: 2026-05-23
|
||||
---
|
||||
|
||||
# @alkdev/dbtype Architecture
|
||||
@@ -51,10 +51,10 @@ The gap: there is no schema-first, dialect-agnostic way to define a database sch
|
||||
|
||||
| Document | Content |
|
||||
|----------|---------|
|
||||
| [schema.md](schema.md) | Type.Module structure, element tree types, column type vocabulary, schema derivation |
|
||||
| [hosts.md](hosts.md) | HostConfig implementations per dialect, symbolic defaults, Drizzle rendering |
|
||||
| [elements.md](elements.md) | UJSX element definitions, function components, props schemas, common components |
|
||||
| [module.md](module.md) | Type.Module as the bundle, incremental construction, serialization, migration diffing |
|
||||
| [schema.md](schema.md) | Table/relation schema structure, schema derivation (insert/update/filter) |
|
||||
| [module.md](module.md) | Type.Module mechanics: construction, validation, serialization, migration diffing |
|
||||
| [elements.md](elements.md) | UJSX element definitions, column types, type definitions, defaults, function components |
|
||||
| [hosts.md](hosts.md) | HostConfig implementations, column type mapping tables, rendering pipeline |
|
||||
| [repo-adapter.md](repo-adapter.md) | `from-dbtype` adapter for `@alkdev/operations`, filter/schema generation, access control |
|
||||
| [build-distribution.md](build-distribution.md) | Package structure, sub-path exports, dependencies, tree-shaking |
|
||||
| [open-questions.md](open-questions.md) | Cross-cutting unresolved questions |
|
||||
@@ -68,6 +68,9 @@ The gap: there is no schema-first, dialect-agnostic way to define a database sch
|
||||
| [003](decisions/003-hostconfig-for-dialects.md) | HostConfig for dialect rendering, not a transform registry |
|
||||
| [004](decisions/004-format-annotation-only.md) | Column formats are annotations, not validators — register explicitly |
|
||||
| [005](decisions/005-repo-as-adapter.md) | CRUD generation as an operations adapter, not a core feature |
|
||||
| [006](decisions/006-references-vs-fk.md) | Column `references` as FK shorthand, explicit `<fk>` for complex FKs |
|
||||
| [007](decisions/007-inner-escape-hatch.md) | Flat props with `inner` escape hatch for column validation |
|
||||
| [008](decisions/008-pg-enum-predeclaration.md) | PG enum pre-declaration — return enums and tables from render context |
|
||||
|
||||
### Open Questions
|
||||
|
||||
@@ -92,9 +95,65 @@ src/
|
||||
from-dbtype.ts # FromDbType adapter for @alkdev/operations
|
||||
filters.ts # Filter schema generation per column type
|
||||
handlers.ts # CRUD handler generation (findMany, insertOne, etc.)
|
||||
index.ts
|
||||
index.ts
|
||||
```
|
||||
|
||||
## End-to-End Pipeline
|
||||
|
||||
The dbtype pipeline has two walks over the same element tree:
|
||||
|
||||
1. **Schema extraction** (`extractTable`) — walks the tree to produce TypeBox schemas and column metadata
|
||||
2. **Host rendering** (`createRoot(host).render()`) — walks the tree to produce Drizzle table definitions
|
||||
|
||||
These are separate walks because they serve different purposes: schema extraction feeds the Type.Module (for validation and serialization), while host rendering feeds Drizzle (for database operations). They consume the same element tree but produce different outputs.
|
||||
|
||||
### Complete Flow
|
||||
|
||||
```typescript
|
||||
import { Type } from '@alkdev/typebox'
|
||||
import { h, createComponent, createRoot } from '@alkdev/ujsx'
|
||||
import { Value } from '@alkdev/typebox/value'
|
||||
|
||||
// 1. Define elements
|
||||
const UsersEl = h('table', { name: 'users' },
|
||||
h('column', { name: 'id', type: 'uuid', primaryKey: true, default: 'uuid' }),
|
||||
h('column', { name: 'name', type: 'string', notNull: true }),
|
||||
)
|
||||
|
||||
// 2. Schema extraction — produces TypeBox schemas and column metadata
|
||||
const { name, schema, columns, indexes, foreignKeys } = extractTable(UsersEl)
|
||||
|
||||
// 3. Module construction — assemble into Type.Module
|
||||
const defs = { Users: schema }
|
||||
const M = Type.Module(defs)
|
||||
const Users = M.Import('Users')
|
||||
|
||||
// 4. Validation — check data against module schema
|
||||
Value.Check(Users, { id: '...', name: 'alice' }) // true
|
||||
|
||||
// 5. Host rendering — produce Drizzle tables
|
||||
const root = createRoot(sqliteHost, {})
|
||||
root.render(UsersEl)
|
||||
const drizzleUsers = root.ctx.tables.users // sqliteTable result
|
||||
|
||||
// 6. Serialization — JSON Schema with $defs (for migration diffing)
|
||||
const serialized = JSON.parse(JSON.stringify(Users))
|
||||
|
||||
// 7. Schema derivation — insert, update, filter schemas (for API validation)
|
||||
defs.InsertUsers = Type.Object({ name: Type.String() }) // omit auto-generated columns
|
||||
defs.UpdateUsers = Type.Partial(Type.Ref('Users')) // all optional
|
||||
```
|
||||
|
||||
### Key Relationships
|
||||
|
||||
| Step | Input | Output | Consumer |
|
||||
|------|-------|--------|----------|
|
||||
| Extract | UJSX elements | `TableMeta` (schema + metadata) | Module construction, Host rendering |
|
||||
| Module | `Record<string, TSchema>` | `Type.Module` | Validation, Serialization |
|
||||
| Import | Module key | `TImport` (resolved schema) | Value.Check, Value.Diff |
|
||||
| Render | UJSX elements + HostConfig | Drizzle tables (in ctx) | Database queries |
|
||||
| Derive | Module entries | Insert/Update/Partial schemas | API validation |
|
||||
|
||||
## Key Design Decisions
|
||||
|
||||
### 1. UJSX Element Tree as the IR, Not a Separate Builder API
|
||||
@@ -131,6 +190,38 @@ For dbtype, formats serve as metadata that hosts can use (e.g., the PG host maps
|
||||
|
||||
The repo pattern (auto-generated CRUD for each table) is not a core feature of dbtype. It's an adapter for `@alkdev/operations` that consumes the same element tree and module bundle. This keeps dbtype focused on schema definition and rendering, while the operations integration is a separate concern.
|
||||
|
||||
## Error Handling Strategy
|
||||
|
||||
dbtype uses a layered validation approach:
|
||||
|
||||
1. **TypeScript compile-time enforcement**: `DbColumnType`, column props, and element types are enforced by TypeScript types. Invalid `type` values, missing required props, and incorrect prop types are caught at compile time.
|
||||
|
||||
2. **Runtime validation at extraction**: `extractTable()` validates the element tree — missing `name` or `type` on columns, duplicate column names, invalid `DbColumnType` values. These throw descriptive errors.
|
||||
|
||||
3. **TypeBox validation at module compile**: `Type.Module(defs)` validates the schema map. Invalid `Type.Ref` targets, duplicate keys, and malformed schemas throw TypeBox errors.
|
||||
|
||||
4. **Host rendering validation**: `createInstance()` in the host validates dialect-specific constraints — unknown column types fall back to `text()`, invalid symbolic defaults throw errors. PG enum names must be unique within a module.
|
||||
|
||||
The general principle: **catch errors as early as possible**. Type errors at compile time, structural errors at extraction time, schema errors at module compile time, dialect errors at render time.
|
||||
|
||||
## Glossary
|
||||
|
||||
| Term | Definition |
|
||||
|------|------------|
|
||||
| **UJSX** | Universal JSX — `@alkdev/ujsx`'s element system. Uses `h()` and `createComponent` to create element trees that render to different hosts via `HostConfig`. Not an acronym, a project name. |
|
||||
| **Element tree** | A tree of UJSX elements (`<table>`, `<column>`, `<index>`, `<fk>`) representing a database schema. The tree is the IR (intermediate representation). |
|
||||
| **HostConfig** | UJSX's host configuration interface. Defines how elements map to output objects (`createInstance`, `appendChild`, `finalizeInstance`). Each dialect (SQLite, PG, MySQL) is a host. |
|
||||
| **Type.Module** | `@alkdev/typebox`'s module system. Holds all schemas in a flat namespace with `Type.Ref` for cross-references. The module is dbtype's schema bundle. |
|
||||
| **Type.Ref** | A TypeBox reference type that resolves within a `Type.Module`. Enables forward and circular references without import ordering issues. |
|
||||
| **TImport** | The type returned by `M.Import(key)`. Embeds the full `$defs` namespace and resolves `$ref` pointers for validation. |
|
||||
| **$defs** | JSON Schema keyword used by serialized Type.Modules. Contains all referenced schemas. `$ref` pointers reference entries in `$defs`. |
|
||||
| **extractTable()** | Core function that walks an element tree, resolves function components, and produces `TableMeta` (TypeBox schema + column metadata + indexes + FKs). |
|
||||
| **Function component** | A UJSX component created with `createComponent` that returns element(s). Transparent to the host — only resolved intrinsic elements (`column`, `table`) reach `createInstance`. |
|
||||
| **Intrinsic element** | A built-in element type (`table`, `column`, `index`, `fk`). Not a function component — directly dispatched to the host's `createInstance`. |
|
||||
| **Reconciler** | UJSX's diffing engine. Can compare old and new element trees to produce update instructions. Not used in phase 1, but positions dbtype for future schema migration support. |
|
||||
| **Host rendering** | The process of walking an element tree through a `HostConfig` to produce output (Drizzle tables). Done via `createRoot(host).render(element)`. |
|
||||
| **Schema derivation** | Producing insert, update, and filter schemas from the module. E.g., `Type.Partial(Type.Ref('Users'))` for update schemas. |
|
||||
|
||||
## Document Lifecycle
|
||||
|
||||
| From | To | Condition |
|
||||
|
||||
Reference in New Issue
Block a user