docs: resolve architecture open questions, add type definitions, consolidate docs
Architecture review session resolving all high-priority open questions and filling documentation gaps identified during review: Decisions resolved: - OQ-04: Flat props with inner escape hatch for column validation (ADR-007) - OQ-05: PG enum pre-declaration returns enums and tables (ADR-008) - OQ-06: Render results accumulate in root.ctx (resolved in hosts.md) - Column references vs fk: references is shorthand, explicit fk takes precedence (ADR-006) - ADR-001, 002, 003 promoted from Proposed to Accepted (probe-validated) Documentation improvements: - Complete DbColumnType mapping tables for all 14 types across 3 dialects - Define ColumnMeta, TableMeta, IndexMeta, FkMeta types in elements.md - Document inner prop, mode prop, and default prop semantics - Add PgRootCtx, SqliteRootCtx, MySqlRootCtx context types - Consolidate schema.md and module.md (remove duplication) - Add end-to-end pipeline walkthrough to README - Add glossary with 13 terms - Add error handling strategy - Remove duplicate content from hosts.md (cross-ref elements.md)
This commit is contained in:
@@ -34,10 +34,10 @@ A single column definition within a table.
|
||||
| `notNull` | `boolean` | no | Column is NOT NULL |
|
||||
| `primaryKey` | `boolean` | no | Column is primary key |
|
||||
| `unique` | `boolean` | no | Column has UNIQUE constraint |
|
||||
| `default` | `DbDefault \| unknown` | no | Symbolic default or literal value |
|
||||
| `references` | `string` | no | FK target table name |
|
||||
| `default` | `DbDefault \| unknown` | no | Symbolic default (`'now'`, `'uuid'`, `'autoincrement'`, `'current_timestamp'`), a literal value (string, number, boolean), or a Drizzle `sql` template expression. Symbolic defaults are resolved by the host to dialect-specific SQL. Literal values use `.default(value)`. SQL expressions use `.default(sql\`...\`)`. |
|
||||
| `references` | `string` | no | FK target table name (shorthand — references the target table's primary key). Normalized to an `<fk>` entry internally by extractTable(). For composite FKs or FKs with ON DELETE/ON UPDATE, use the `<fk>` element instead. |
|
||||
| `format` | `string` | no | TypeBox format annotation (uuid, email, etc.) |
|
||||
| `mode` | `'json' \| 'text'` | no | Storage mode for compound types |
|
||||
| `mode` | `'json' \| 'text'` | no | Storage mode for compound types. `'json'` stores the value as JSON text (SQLite/MySQL) or JSONB (PG). `'text'` stores as plain text. Only meaningful for `type: 'json'`, `type: 'array'`, and `type: 'object'`. Defaults to `'json'` for those types. |
|
||||
| `values` | `string[]` | no | Enum values (for `type: 'enum'`) |
|
||||
| `length` | `number` | no | Max length (for varchar) |
|
||||
| `precision` | `number` | no | Numeric precision |
|
||||
@@ -45,6 +45,32 @@ A single column definition within a table.
|
||||
| `postgres` | `PgColumnOpts` | no | PG-specific overrides |
|
||||
| `sqlite` | `SqliteColumnOpts` | no | SQLite-specific overrides |
|
||||
| `mysql` | `MySqlColumnOpts` | no | MySQL-specific overrides |
|
||||
| `inner` | `TSchema` | no | Override the auto-generated TypeBox schema. The host ignores this — it's purely for validation. When provided, `extractTable()` uses this schema directly instead of calling `colToTypeBox()`. |
|
||||
|
||||
### Storage Mode
|
||||
|
||||
The `mode` prop controls how compound types (json, array, object) are stored in the database:
|
||||
|
||||
- `mode: 'json'` (default for json/array/object) — stored as JSON text in SQLite/MySQL, JSONB in PG
|
||||
- `mode: 'text'` — stored as plain text, useful for columns that need JSON validation but plain-text storage
|
||||
|
||||
For scalar types, `mode` has no effect. The host maps `mode: 'json'` to the appropriate dialect storage (e.g., `text({ mode: 'json' })` in SQLite, `jsonb()` in PG).
|
||||
|
||||
### Default Values
|
||||
|
||||
Column defaults support three categories:
|
||||
|
||||
| Category | Example | Drizzle Output |
|
||||
|----------|---------|----------------|
|
||||
| Symbolic | `default: 'now'` | `.default(sql\`now()\`)` (PG) / `.default(sql\`(strftime('%s', 'now'))\`)` (SQLite) |
|
||||
| Symbolic | `default: 'uuid'` | `.defaultRandom()` (PG) / `.$defaultFn(() => crypto.randomUUID())` (SQLite) |
|
||||
| Symbolic | `default: 'autoincrement'` | Implicit on `INTEGER PRIMARY KEY` (SQLite) / `serial()` type (PG) |
|
||||
| Literal | `default: true` | `.default(true)` |
|
||||
| Literal | `default: 0` | `.default(0)` |
|
||||
| SQL expression | `default: sql\`CURRENT_TIMESTAMP\`` | `.default(sql\`CURRENT_TIMESTAMP\`)` |
|
||||
| JS function | `default: () => crypto.randomUUID()` | `.$defaultFn(() => crypto.randomUUID())` |
|
||||
|
||||
Symbolic defaults are translated by the host. Literal and SQL expression defaults pass through directly. JS function defaults use Drizzle's `.$defaultFn()`.
|
||||
|
||||
### `<index>`
|
||||
|
||||
@@ -138,13 +164,7 @@ The `extractTable()` function walks an element tree, resolving function componen
|
||||
3. **Table metadata**: `{ name, columns, indexes, foreignKeys }` for the host
|
||||
|
||||
```typescript
|
||||
function extractTable(el: UElement): {
|
||||
name: string
|
||||
schema: TObject // TypeBox schema for Type.Module
|
||||
columns: Record<string, ColumnMeta> // Props + computed metadata
|
||||
indexes: IndexMeta[]
|
||||
foreignKeys: FkMeta[]
|
||||
}
|
||||
function extractTable(el: UElement): TableMeta
|
||||
```
|
||||
|
||||
For each column, `extractTable`:
|
||||
@@ -157,11 +177,63 @@ For each column, `extractTable`:
|
||||
- **Column elements must have `name` and `type` props** — these are required for both TypeBox schema construction and Drizzle rendering
|
||||
- **Function components must return column elements** (or arrays of column elements) — returning other element types inside a table is undefined
|
||||
- **The `references` prop is metadata-only** — it's not part of the TypeBox schema. It informs the host about FK constraints and the repo adapter about relation structure
|
||||
- **`references` is a shorthand** — a `<column references="users">` prop is syntactic sugar for `{columns: [column_name], references: 'users', foreignColumns: ['id']}` (targeting the referenced table's primary key by convention). Internally, `extractTable()` normalizes all `references` props into `FkMeta` entries alongside `<fk>` elements. When both `references` on a column and an explicit `<fk>` referencing the same column exist, the explicit `<fk>` takes precedence.
|
||||
- **`default` values can be symbolic strings or literals** — symbolic defaults (`now`, `uuid`, `autoincrement`) are resolved by the host; literal values pass through directly
|
||||
- **The `inner` prop overrides the auto-generated TypeBox schema** — when `inner` is provided on a column element, `extractTable()` uses it directly without calling `colToTypeBox()`. The host ignores `inner` entirely — database rendering uses only `type` and DB metadata props. This is the escape hatch for validation constraints that don't have column prop shortcuts (e.g., `minItems`, `maximum`, `pattern`). See [ADR-007](decisions/007-inner-escape-hatch.md).
|
||||
|
||||
## Type Definitions
|
||||
|
||||
```typescript
|
||||
/** Column metadata extracted from a <column> element by extractTable() */
|
||||
interface ColumnMeta {
|
||||
name: string
|
||||
type: DbColumnType
|
||||
notNull?: boolean
|
||||
primaryKey?: boolean
|
||||
unique?: boolean
|
||||
default?: DbDefault | unknown
|
||||
references?: string // Shorthand: FK target table name
|
||||
format?: string // TypeBox format annotation
|
||||
mode?: 'json' | 'text' // Storage mode for compound types
|
||||
values?: string[] // Enum values (for type: 'enum')
|
||||
length?: number // Max length (for varchar)
|
||||
precision?: number // Numeric precision
|
||||
scale?: number // Numeric scale
|
||||
postgres?: PgColumnOpts // PG-specific overrides
|
||||
sqlite?: SqliteColumnOpts // SQLite-specific overrides
|
||||
mysql?: MySqlColumnOpts // MySQL-specific overrides
|
||||
inner?: TSchema // Override TypeBox schema (escape hatch)
|
||||
}
|
||||
|
||||
/** Index metadata extracted from an <index> element */
|
||||
interface IndexMeta {
|
||||
name: string
|
||||
columns: string[]
|
||||
unique?: boolean
|
||||
}
|
||||
|
||||
/** Foreign key metadata extracted from <fk> elements and references props */
|
||||
interface FkMeta {
|
||||
columns: string[] // Local column names
|
||||
references: string // Target table name
|
||||
foreignColumns: string[] // Target column names
|
||||
onDelete?: 'cascade' | 'set null' | 'restrict' | 'no action'
|
||||
onUpdate?: 'cascade' | 'set null' | 'restrict' | 'no action'
|
||||
}
|
||||
|
||||
/** Table metadata result from extractTable() */
|
||||
interface TableMeta {
|
||||
name: string
|
||||
schema: TObject // TypeBox schema for Type.Module entry
|
||||
columns: Record<string, ColumnMeta>
|
||||
indexes: IndexMeta[]
|
||||
foreignKeys: FkMeta[]
|
||||
}
|
||||
```
|
||||
|
||||
## Open Questions
|
||||
|
||||
1. **Should column elements support `inner` TypeBox schemas?** The research docs proposed `DbType.String({ notNull: true, inner: Type.String({ format: 'email', maxLength: 255 }) })`. With UJSX elements, this would be `<column name="email" type="string" notNull format="email" maxLength={255} />`. Is the flat props model sufficient, or do we need nested TypeBox schemas?
|
||||
1. ~~Should column elements support `inner` TypeBox schemas?~~ **Resolved — Yes. ADR-007: flat props for common cases, `inner` as escape hatch for custom validation.** The `inner` prop is now documented in the column props table.
|
||||
|
||||
2. **Should `<table>` accept `extraConfig` props?** Drizzle tables accept a third callback argument for indexes and unique constraints defined in terms of column references. How does this map to element props?
|
||||
|
||||
|
||||
Reference in New Issue
Block a user