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
6.1 KiB
status, last_updated
| status | last_updated |
|---|---|
| draft | 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
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 typesTInstance: The union of table and column instance types for this dialectTRootCtx: 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:
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:
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
- Walk elements: The host's
createInstancemethod receives each<table>,<column>,<index>,<fk>element - Build columns:
createInstance('column', props, ctx)mapsprops.typeto the dialect-specific Drizzle builder, applyingnotNull,primaryKey,unique,defaultconstraints - Assemble tables:
appendChild(tableInst, columnInst, ctx)attaches column builders to the table's columns record - Finalize:
finalizeInstance(tableInst, ctx)callssqliteTable(name, columns)(orpgTable,mysqlTable) and stores the result
const root = createRoot(sqliteHost, {})
root.render(UsersEl)
// root's context now contains: { tables: { users: drizzleSqliteTable } }
Constraints
createTextInstanceis not supported — DB schemas are purely structural, no text nodes- Column type props must be from the
DbColumnTypevocabulary — hosts map these to dialect-specific builders; unknown types fall back totext() - Symbolic defaults are resolved by the host —
default="now"becomessql\(strftime('%s', 'now'))`on SQLite andsql`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
-
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. -
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. -
prepareUpdate/commitUpdatefor 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