Phase 0 architecture specification following the alkdev documentation pattern from @alkdev/flowgraph. Documents the validated architecture (UJSX elements → Type.Module → Drizzle hosts) based on e2e probe results. Docs added: - README: Project overview, architecture, current state - architecture/README: Index, design decisions, relationships - architecture/schema: Type.Module as bundle, construction, serialization - architecture/hosts: HostConfig per dialect, column mapping, symbolic defaults - architecture/elements: UJSX element types, props, function components - architecture/module: Module mechanics, format registration, diffing - architecture/repo-adapter: from-dbtype operations adapter (phase 2) - architecture/build-distribution: Package structure, exports - architecture/open-questions: 10 open questions across all topics - ADRs 001-005: UJSX as IR, Type.Module, HostConfig, format, repo adapter
9.2 KiB
status, last_updated
| status | last_updated |
|---|---|
| draft | 2026-05-22 |
@alkdev/dbtype Architecture
Schema-first multi-dialect database type system. Define your database schema once as a UJSX element tree, validate it with TypeBox, and render it to any Drizzle dialect (SQLite, PostgreSQL, MySQL) — all from a single source of truth.
Why This Exists
Every project that uses Drizzle ORM across multiple backends defines schemas repeatedly: once per dialect (SQLite for local dev, PostgreSQL for production), once again for input validation (TypeBox, Zod, etc.), and once more for API contracts (GraphQL types, OpenAPI schemas). The existing drizzle-typebox only goes one direction (Drizzle → TypeBox) and requires defining schemas in Drizzle's dialect-specific API first.
The gap: there is no schema-first, dialect-agnostic way to define a database schema that simultaneously produces validation schemas, Drizzle table definitions, and (via the operations layer) CRUD operation specs — all from one source of truth.
Core Principle
The element tree is the schema. The module is the bundle. The host is the dialect.
- The UJSX element tree (
<table>,<column>) is the authoring surface — composable, reusable, and TypeBox-validatable - The
Type.Moduleis the schema bundle — all tables, relations, and derived schemas in one namespace withType.Refresolving cross-table references - The
HostConfigis the dialect adapter — render the same tree tosqliteTable,pgTable,mysqlTable, or any future target
Relationship to Sibling Packages
| Package | Relationship |
|---|---|
@alkdev/typebox |
Type system foundation. Type.Module, Type.Ref, Value.Check/Diff/Edit, FormatRegistry |
@alkdev/ujsx |
Element authoring. h(), createComponent, HostConfig, createRoot, reconciler |
@alkdev/operations |
CRUD generation. OperationSpec, OperationRegistry, from-dbtype adapter (future) |
@alkdev/pubsub |
Event transport. Used by operations call protocol |
drizzle-orm |
Peer dependency. Dialect-specific column builders consumed by hosts |
Current State
Phase 0: Exploration — Architecture probing complete. Probe scripts in scripts/probe-e2e.ts validate the core architecture. No implementation yet.
What Exists
- Fork of
drizzle-typeboxwith@alkdev/typeboxsupport (currentsrc/) - Research docs in
docs/research/(architecture, type-map, column diffs, typedef kind pattern) - E2E probe script validating: UJSX → Type.Module → Drizzle rendering pipeline
What Doesn't Exist Yet
- Core
dbtypepackage (the implementation) - Host configs for SQLite, PostgreSQL, MySQL
- Schema extraction functions (
createSelectSchema,createInsertSchema,createUpdateSchemafrom element trees) - Repo/CRUD generation adapter (
from-dbtypefor@alkdev/operations) @alkdev/ujsxas a declared dependency (currently dev-only)
Architecture Documents
| Document | Content |
|---|---|
| schema.md | Type.Module structure, element tree types, column type vocabulary, schema derivation |
| hosts.md | HostConfig implementations per dialect, symbolic defaults, Drizzle rendering |
| elements.md | UJSX element definitions, function components, props schemas, common components |
| module.md | Type.Module as the bundle, incremental construction, serialization, migration diffing |
| repo-adapter.md | from-dbtype adapter for @alkdev/operations, filter/schema generation, access control |
| build-distribution.md | Package structure, sub-path exports, dependencies, tree-shaking |
| open-questions.md | Cross-cutting unresolved questions |
Design Decisions
| ADR | Decision |
|---|---|
| 001 | UJSX element tree as the IR, not a separate DbType builder API |
| 002 | Type.Module as the schema bundle, not a custom registry |
| 003 | HostConfig for dialect rendering, not a transform registry |
| 004 | Column formats are annotations, not validators — register explicitly |
| 005 | CRUD generation as an operations adapter, not a core feature |
Open Questions
All unresolved questions tracked in open-questions.md.
Source Structure (Planned)
src/
core/
elements.ts # h() wrappers, createComponent for table/column/index/fk
schema.ts # extractTable, createSelectSchema, createInsertSchema, createUpdateSchema
module.ts # buildModule, module construction helpers
column-types.ts # DbColumnType union, colToTypeBox mapping
defaults.ts # Symbolic default resolution ('now', 'uuid', 'autoincrement')
guards.ts # Type guards for element types
hosts/
sqlite.ts # HostConfig for drizzle-orm/sqlite-core
pg.ts # HostConfig for drizzle-orm/pg-core
mysql.ts # HostConfig for drizzle-orm/mysql-core
repo/
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
Key Design Decisions
1. UJSX Element Tree as the IR, Not a Separate Builder API
The architecture docs in docs/research/ proposed a DbTypeBuilder class with methods like DbType.String(), DbType.Table(). The probe results show that UJSX elements (<table>, <column>) with props serve the same purpose — they carry all the metadata (type, notNull, primaryKey, default, references) and compose via function components (IdCol, AuditCols).
A separate builder API would be redundant: both the element tree and the builder produce the same TypeBox + metadata. The element tree is strictly more capable (function components for composition, reconciler for migration diffing in the future) and already exists as @alkdev/ujsx.
Alternative considered: DbTypeBuilder (the research docs pattern). Rejected because it duplicates what UJSX already provides and cannot compose as naturally.
2. Type.Module as the Schema Bundle
All table schemas and their relations live in a single Type.Module call. Type.Ref resolves cross-table references including mutual/circular ones. This eliminates the circular-import problem that the storage_sqlite pattern solves with a separate relations.ts file.
The module is also serializable as JSON Schema with $defs, enabling Value.Diff for schema migration and FromSchema for round-tripping.
Alternative considered: A custom registry with separate schema objects. Rejected because Type.Module already does everything needed (ref resolution, validation, serialization) and doesn't require a new abstraction.
3. HostConfig for Dialect Rendering, Not a Transform Registry
The research docs proposed a TransformRegistry with priority-sorted rules. UJSX's HostConfig serves the same purpose — each dialect is a host, createInstance maps element types to Drizzle column builders, appendChild assembles columns into tables.
This leverages existing infrastructure (createRoot, createComponent, reconciler) rather than building a parallel dispatch system. It also positions the project for future migration support via prepareUpdate/commitUpdate.
Alternative considered: TransformRegistry from docs/research/architecture.md. Rejected because HostConfig is the same pattern with more capabilities (reconciler, function components, context) and already exists in @alkdev/ujsx.
4. Column Formats as Annotations
Type.String({ format: 'uuid' }) and Type.String({ format: 'email' }) are annotations by default in TypeBox. Value.Check does not enforce format unless validators are explicitly registered via FormatRegistry.Set. This is correct JSON Schema behavior.
For dbtype, formats serve as metadata that hosts can use (e.g., the PG host maps format: 'uuid' to uuid() column type, the SQLite host maps it to text() with $defaultFn). Validation is opt-in via format registration.
5. CRUD Generation as an Operations Adapter
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.
Document Lifecycle
| From | To | Condition |
|---|---|---|
draft |
reviewed |
All open questions resolved |
reviewed |
stable |
Implementation complete and verified by tests |
stable |
deprecated |
Superseded by new architecture |
References
- Research:
docs/research/architecture.md,docs/research/typemap-architecture.md,docs/research/dizzle-column-diffs.md,docs/research/typedef-kind-pattern.md - Probe results:
scripts/probe-e2e.ts - Sibling projects:
@alkdev/typebox,@alkdev/ujsx,@alkdev/operations,@alkdev/pubsub - Reference implementation:
@alkdev/drizzle-typebox(currentsrc/),drizzle-graphql(workspace)