--- 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: Dialect tables: Record } interface DbtypeTableInst { name: string columns: Record // Drizzle column builders constraints: any[] result?: any // The final drizzle table } interface DbtypeColumnInst { name: string columnType: string builder: any // Drizzle column builder dbMeta: Record } ``` 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 ``, ``, ``, `` 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`