Files
dbtype/docs/architecture/hosts.md
glm-5.1 dd2ec9df3c 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
2026-05-22 11:34:58 +00:00

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 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:

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

  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
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 hostdefault="now" becomes sql\(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

  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