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:
151
docs/architecture/hosts.md
Normal file
151
docs/architecture/hosts.md
Normal file
@@ -0,0 +1,151 @@
|
||||
---
|
||||
status: draft
|
||||
last_updated: 2026-05-22
|
||||
---
|
||||
|
||||
# Hosts: Dialect Rendering via HostConfig
|
||||
|
||||
How dbtype renders UJSX element trees to Drizzle table definitions for each supported database dialect.
|
||||
|
||||
## Overview
|
||||
|
||||
Each database dialect (SQLite, PostgreSQL, MySQL) is a `HostConfig` implementation. The same UJSX element tree renders to `sqliteTable`, `pgTable`, or `mysqlTable` depending on which host is chosen. The host translates column type props, symbolic defaults, and constraints to the appropriate Drizzle column builder methods.
|
||||
|
||||
## HostConfig Interface
|
||||
|
||||
```typescript
|
||||
interface DbtypeRootCtx<Dialect extends string> {
|
||||
dialect: Dialect
|
||||
tables: Record<string, DbtypeTableInst>
|
||||
}
|
||||
|
||||
interface DbtypeTableInst {
|
||||
name: string
|
||||
columns: Record<string, any> // Drizzle column builders
|
||||
constraints: any[]
|
||||
result?: any // The final drizzle table
|
||||
}
|
||||
|
||||
interface DbtypeColumnInst {
|
||||
name: string
|
||||
columnType: string
|
||||
builder: any // Drizzle column builder
|
||||
dbMeta: Record<string, any>
|
||||
}
|
||||
```
|
||||
|
||||
The `HostConfig` generic parameters:
|
||||
- `TTag`: `"table" | "column" | "index" | "fk"` — the allowed element types
|
||||
- `TInstance`: The union of table and column instance types for this dialect
|
||||
- `TRootCtx`: The dialect-specific root context
|
||||
|
||||
## Column Type Mapping
|
||||
|
||||
### SQLite
|
||||
|
||||
| Column Type Prop | Drizzle Builder | Notes |
|
||||
|-----------------|----------------|-------|
|
||||
| `uuid` | `text(name).primaryKey().$defaultFn(crypto.randomUUID)` | No native UUID type |
|
||||
| `string` | `text(name)` | |
|
||||
| `integer` | `integer(name)` | |
|
||||
| `boolean` | `integer(name, { mode: 'boolean' })` | |
|
||||
| `timestamp` | `integer(name, { mode: 'timestamp' })` | |
|
||||
| `enum` | `text(name, { enum: values })` | SQLite has no native enum |
|
||||
| `json` | `text(name, { mode: 'json' })` | Stored as JSON text |
|
||||
|
||||
### PostgreSQL
|
||||
|
||||
| Column Type Prop | Drizzle Builder | Notes |
|
||||
|-----------------|----------------|-------|
|
||||
| `uuid` | `uuid(name).defaultRandom()` or `.primaryKey()` | Native UUID |
|
||||
| `string` | `text(name)` or `varchar(name, n)` | |
|
||||
| `integer` | `integer(name)` | |
|
||||
| `boolean` | `boolean(name)` | |
|
||||
| `timestamp` | `timestamptz(name, { withTimezone: true })` | |
|
||||
| `enum` | `pgEnum(name, values)` | Requires pre-declaration |
|
||||
| `json` | `jsonb(name)` | |
|
||||
|
||||
### MySQL
|
||||
|
||||
| Column Type Prop | Drizzle Builder | Notes |
|
||||
|-----------------|----------------|-------|
|
||||
| `uuid` | `varchar(name, { length: 36 })` | No native UUID |
|
||||
| `string` | `text(name)` or `varchar(name, n)` | |
|
||||
| `integer` | `int(name)` | |
|
||||
| `boolean` | `boolean(name)` or `tinyint(name)` | |
|
||||
| `timestamp` | `timestamp(name)` | |
|
||||
| `enum` | `mysqlEnum(name, values)` | |
|
||||
| `json` | `json(name)` | |
|
||||
|
||||
## Symbolic Defaults
|
||||
|
||||
Column props like `default="now"` and `default="uuid"` are translated to dialect-specific SQL or JS functions by the host:
|
||||
|
||||
| Symbol | SQLite | PostgreSQL | MySQL |
|
||||
|--------|--------|------------|-------|
|
||||
| `now` | `sql\`(strftime('%s', 'now'))\`` | `sql\`now()\`` | `sql\`NOW()\`` |
|
||||
| `uuid` | `.$defaultFn(() => crypto.randomUUID())` | `.defaultRandom()` | `.$defaultFn(() => crypto.randomUUID())` |
|
||||
| `autoincrement` | Implicit on `INTEGER PRIMARY KEY` | `serial()` type | `.autoincrement()` |
|
||||
|
||||
## Common Column Components
|
||||
|
||||
Function components that compose into any table:
|
||||
|
||||
```typescript
|
||||
const IdColumn = createComponent('IdColumn', () =>
|
||||
h('column', { name: 'id', type: 'uuid', primaryKey: true, default: 'uuid' })
|
||||
)
|
||||
|
||||
const AuditColumns = createComponent('AuditColumns', () => [
|
||||
h('column', { name: 'createdAt', type: 'timestamp', notNull: true, default: 'now' }),
|
||||
h('column', { name: 'updatedAt', type: 'timestamp', notNull: true, default: 'now' }),
|
||||
])
|
||||
```
|
||||
|
||||
Used by spreading into a table:
|
||||
|
||||
```typescript
|
||||
const UsersEl = h('table', { name: 'users' },
|
||||
h(IdColumn, {}),
|
||||
h('column', { name: 'name', type: 'string', notNull: true }),
|
||||
h(AuditColumns, {}),
|
||||
)
|
||||
```
|
||||
|
||||
This replaces the storage_sqlite pattern of `const commonCols = { id: text('id').primaryKey(), ... }` with reusable, dialect-agnostic components.
|
||||
|
||||
## Rendering Pipeline
|
||||
|
||||
1. **Walk elements**: The host's `createInstance` method receives each `<table>`, `<column>`, `<index>`, `<fk>` element
|
||||
2. **Build columns**: `createInstance('column', props, ctx)` maps `props.type` to the dialect-specific Drizzle builder, applying `notNull`, `primaryKey`, `unique`, `default` constraints
|
||||
3. **Assemble tables**: `appendChild(tableInst, columnInst, ctx)` attaches column builders to the table's columns record
|
||||
4. **Finalize**: `finalizeInstance(tableInst, ctx)` calls `sqliteTable(name, columns)` (or `pgTable`, `mysqlTable`) and stores the result
|
||||
|
||||
```typescript
|
||||
const root = createRoot(sqliteHost, {})
|
||||
root.render(UsersEl)
|
||||
// root's context now contains: { tables: { users: drizzleSqliteTable } }
|
||||
```
|
||||
|
||||
## Constraints
|
||||
|
||||
- **`createTextInstance` is not supported** — DB schemas are purely structural, no text nodes
|
||||
- **Column type props must be from the `DbColumnType` vocabulary** — hosts map these to dialect-specific builders; unknown types fall back to `text()`
|
||||
- **Symbolic defaults are resolved by the host** — `default="now"` becomes `sql\`(strftime('%s', 'now'))\`` on SQLite and `sql\`now()\`` on PG
|
||||
- **PG enums require pre-declaration** — the host must track enum types and emit `pgEnum()` calls before tables that reference them
|
||||
- **A host renders one dialect at a time** — to generate schemas for multiple dialects, render multiple times with different hosts
|
||||
|
||||
## Open Questions
|
||||
|
||||
1. **How to handle PG enum pre-declaration?** PG requires `pgEnum()` at module scope before tables. Options: (A) return both enums and tables from render, (B) start with text for all enums, (C) per-column opt-in. Leaning toward A.
|
||||
|
||||
2. **Should hosts return the rendered table or store it in context?** The probe scripts use context (`ctx.tables`), but returning from render would be more functional. Need to resolve this.
|
||||
|
||||
3. **`prepareUpdate`/`commitUpdate` for migrations?** The UJSX reconciler could diff old and new element trees to produce ALTER TABLE statements. This is a future feature, not phase 1.
|
||||
|
||||
## References
|
||||
|
||||
- UJSX HostConfig: `@alkdev/ujsx/src/host/config.ts`
|
||||
- Drizzle column diffs: `docs/research/dizzle-column-diffs.md`
|
||||
- Storage pattern: `@alkdev/storage_sqlite/`
|
||||
- Probe: `scripts/probe-e2e.ts`
|
||||
Reference in New Issue
Block a user