Add SDD architecture docs for dbtype
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
This commit is contained in:
147
docs/architecture/README.md
Normal file
147
docs/architecture/README.md
Normal file
@@ -0,0 +1,147 @@
|
||||
---
|
||||
status: draft
|
||||
last_updated: 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.Module` is the schema bundle — all tables, relations, and derived schemas in one namespace with `Type.Ref` resolving cross-table references
|
||||
- The `HostConfig` is the dialect adapter — render the same tree to `sqliteTable`, `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-typebox` with `@alkdev/typebox` support (current `src/`)
|
||||
- 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 `dbtype` package (the implementation)
|
||||
- Host configs for SQLite, PostgreSQL, MySQL
|
||||
- Schema extraction functions (`createSelectSchema`, `createInsertSchema`, `createUpdateSchema` from element trees)
|
||||
- Repo/CRUD generation adapter (`from-dbtype` for `@alkdev/operations`)
|
||||
- `@alkdev/ujsx` as a declared dependency (currently dev-only)
|
||||
|
||||
## Architecture Documents
|
||||
|
||||
| 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 |
|
||||
| [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 |
|
||||
|
||||
### Design Decisions
|
||||
|
||||
| ADR | Decision |
|
||||
|-----|----------|
|
||||
| [001](decisions/001-ujsx-as-ir.md) | UJSX element tree as the IR, not a separate DbType builder API |
|
||||
| [002](decisions/002-type-module-as-bundle.md) | Type.Module as the schema bundle, not a custom registry |
|
||||
| [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 |
|
||||
|
||||
### Open Questions
|
||||
|
||||
All unresolved questions tracked in [open-questions.md](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` (current `src/`), `drizzle-graphql` (workspace)
|
||||
Reference in New Issue
Block a user