qubu 0.0.0 → 0.3.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (109) hide show
  1. package/dist/canonical-BbnqavJm.mjs +972 -0
  2. package/dist/codegen.d.mts +107 -0
  3. package/dist/codegen.mjs +1199 -0
  4. package/dist/column-CXMxx8Hq.mjs +118 -0
  5. package/dist/complete-D5Djh-zo.mjs +1788 -0
  6. package/dist/complete-types-BdFqUfbb.d.mts +371 -0
  7. package/dist/core.d.mts +33 -0
  8. package/dist/core.mjs +36 -0
  9. package/dist/ddl.d.mts +118 -0
  10. package/dist/ddl.mjs +1120 -0
  11. package/dist/dialect-b2-Z6uBF.mjs +13 -0
  12. package/dist/diff.d.mts +2 -0
  13. package/dist/diff.mjs +883 -0
  14. package/dist/drizzle-mysql.d.mts +24 -0
  15. package/dist/drizzle-mysql.mjs +72 -0
  16. package/dist/drizzle-postgres.d.mts +24 -0
  17. package/dist/drizzle-postgres.mjs +73 -0
  18. package/dist/drizzle-sqlite.d.mts +24 -0
  19. package/dist/drizzle-sqlite.mjs +82 -0
  20. package/dist/drizzle.d.mts +13 -0
  21. package/dist/drizzle.mjs +2 -0
  22. package/dist/errors-BGCoLe_r.mjs +14 -0
  23. package/dist/errors-Dxv73YJu.mjs +26 -0
  24. package/dist/explain-CkIK13L_.mjs +98 -0
  25. package/dist/index-Ds7-mhJi.d.mts +146 -0
  26. package/dist/index-Dug5HnLB.d.mts +204 -0
  27. package/dist/index.d.mts +2 -0
  28. package/dist/index.mjs +1276 -0
  29. package/dist/introspection.d.mts +117 -0
  30. package/dist/introspection.mjs +4341 -0
  31. package/dist/json-CUZlv4HT.mjs +169 -0
  32. package/dist/migration.d.mts +215 -0
  33. package/dist/migration.mjs +1161 -0
  34. package/dist/mysql-DqkqXB6A.mjs +355 -0
  35. package/dist/mysql.d.mts +5 -0
  36. package/dist/mysql.mjs +50 -0
  37. package/dist/naming-QVCOnSj2.mjs +20 -0
  38. package/dist/omit-OxV58AwX.mjs +5 -0
  39. package/dist/on-conflict-BxnxubMb.mjs +143 -0
  40. package/dist/postgres-DEBBeh52.mjs +235 -0
  41. package/dist/postgres.d.mts +123 -0
  42. package/dist/postgres.mjs +52 -0
  43. package/dist/registry-BufIskVN.mjs +569 -0
  44. package/dist/relational-DCZrrNia.mjs +77 -0
  45. package/dist/runtime-BTr-MTlo.mjs +193 -0
  46. package/dist/schema.d.mts +2 -0
  47. package/dist/schema.mjs +5 -0
  48. package/dist/serialize-PF1cfH2P.mjs +556 -0
  49. package/dist/snapshot-CWPgzxNx.mjs +392 -0
  50. package/dist/snapshot.d.mts +4 -0
  51. package/dist/snapshot.mjs +8 -0
  52. package/dist/source-DUoJVXmL.mjs +68 -0
  53. package/dist/sqlite-BU6DBxef.mjs +320 -0
  54. package/dist/sqlite.d.mts +5 -0
  55. package/dist/sqlite.mjs +48 -0
  56. package/dist/standard-BTVYKh_F.mjs +12 -0
  57. package/dist/table-llv9tsZ8.mjs +384 -0
  58. package/dist/types-4Q076HKo.d.mts +3636 -0
  59. package/dist/types-BX0mckiU.d.mts +644 -0
  60. package/dist/types-CO1KaRNc.d.mts +46 -0
  61. package/dist/types-Cec0xzo4.mjs +116 -0
  62. package/dist/value-BvilP0oz.mjs +29 -0
  63. package/dist/vite/ambient.d.ts +449 -0
  64. package/dist/vite.d.mts +35 -0
  65. package/dist/vite.mjs +498 -0
  66. package/docs/config.json +21 -0
  67. package/docs/dialects-and-execution.md +427 -0
  68. package/docs/getting-started.md +96 -0
  69. package/docs/guides/compose-queries.md +225 -0
  70. package/docs/guides/drizzle.md +129 -0
  71. package/docs/guides/extensions/dialects.md +40 -0
  72. package/docs/guides/extensions/overview.md +36 -0
  73. package/docs/guides/extensions/sources-and-clauses.md +82 -0
  74. package/docs/guides/extensions/typed-expressions.md +94 -0
  75. package/docs/guides/extensions/unsafe-syntax.md +27 -0
  76. package/docs/guides/json.md +65 -0
  77. package/docs/guides/mutations.md +138 -0
  78. package/docs/guides/select/conditions.md +110 -0
  79. package/docs/guides/select/grouping-and-windows.md +82 -0
  80. package/docs/guides/select/ordering-and-pagination.md +78 -0
  81. package/docs/guides/select/overview.md +139 -0
  82. package/docs/guides/sql-templates.md +157 -0
  83. package/docs/guides/vite-plugin.md +92 -0
  84. package/docs/index.md +111 -0
  85. package/docs/query-model/fragments.md +109 -0
  86. package/docs/query-model/result-shapes.md +125 -0
  87. package/docs/query-model/source-scope.md +182 -0
  88. package/docs/reference/introspection-support.md +196 -0
  89. package/docs/reference/mysql-snapshot.md +55 -0
  90. package/docs/reference/postgres-snapshot.md +61 -0
  91. package/docs/reference/sqlite-snapshot.md +53 -0
  92. package/docs/reference/supported-surface.md +104 -0
  93. package/docs/schema/catalog-model.md +58 -0
  94. package/docs/schema/code-generation.md +139 -0
  95. package/docs/schema/columns-and-writes.md +130 -0
  96. package/docs/schema/constraints-and-indexes.md +180 -0
  97. package/docs/schema/ddl-emission.md +75 -0
  98. package/docs/schema/diff.md +75 -0
  99. package/docs/schema/introspection.md +214 -0
  100. package/docs/schema/migration-plans.md +76 -0
  101. package/docs/schema/snapshots.md +93 -0
  102. package/docs/schema/storage-and-schema-sql.md +104 -0
  103. package/docs/schema/tables-and-names.md +106 -0
  104. package/docs/sql-semantic-types.md +141 -0
  105. package/docs/troubleshooting.md +151 -0
  106. package/package.json +132 -10
  107. package/skills/qubu/SKILL.md +42 -0
  108. package/skills/qubu/agents/openai.yaml +6 -0
  109. package/readme.md +0 -1
@@ -0,0 +1,104 @@
1
+ # Supported features
2
+
3
+ > Choose a public package entrypoint, check what Qubu handles, and keep database work on the application side of each adapter.
4
+
5
+ ## Package entrypoints
6
+
7
+ | Import | Kind | Use it for |
8
+ | ----------------------- | ---------------- | ---------------------------------------------------------------------------------------------------------------- |
9
+ | `qubu` | Runtime | Ordinary query and schema definitions, reads, writes, SQL templates, rendering, EXPLAIN, and execution contracts |
10
+ | `qubu/core` | Runtime | Fragment and rendering primitives, dialect construction, SQL types, and extension constructors |
11
+ | `qubu/codegen` | Runtime | Deterministic machine-owned TypeScript schemas from complete, non-lossy introspection |
12
+ | `qubu/ddl` | Runtime | DDL preflight and deterministic PostgreSQL, SQLite, or MySQL emission from a migration plan |
13
+ | `qubu/diff` | Runtime | Canonical Snapshot v1 or v2 comparison, rename hints, suggestions, and safety diagnostics |
14
+ | `qubu/drizzle` | Runtime | Shared Drizzle conversion errors and dialect types |
15
+ | `qubu/introspection` | Runtime | Catalog readers, normalized catalogs, and mapping to Snapshot v1 or v2 |
16
+ | `qubu/migration` | Runtime | Pure migration planning with dependencies, decisions, preconditions, and explicit custom SQL |
17
+ | `qubu/mysql` | Runtime | The MySQL query dialect policy |
18
+ | `qubu/postgres` | Runtime | PostgreSQL query dialect helpers such as `postgresDialect()` and `ilike()` |
19
+ | `qubu/schema` | Runtime | Advanced schema metadata, storage and constraint models, source models, and schema-expression extensions |
20
+ | `qubu/snapshot` | Runtime | Canonical Snapshot v1 and v2 traversal, encoding, decoding, diagnostics, and digests |
21
+ | `qubu/sqlite` | Runtime | The SQLite query dialect policy |
22
+ | `qubu/vite` | Runtime | The optional `qubu()` Vite compiler hint |
23
+ | `qubu/package.json` | JSON | The published package manifest |
24
+ | `qubu/drizzle/mysql` | Runtime | Runtime conversion from Qubu schemas to MySQL Drizzle tables |
25
+ | `qubu/drizzle/postgres` | Runtime | Runtime conversion from Qubu schemas to PostgreSQL Drizzle tables |
26
+ | `qubu/drizzle/sqlite` | Runtime | Runtime conversion from Qubu schemas to SQLite Drizzle tables |
27
+ | `qubu/globals` | TypeScript types | Opt-in ambient declarations for directive-bearing modules |
28
+
29
+ The package validator confirms 17 runtime entrypoints, 18 type entrypoints,
30
+ `qubu/package.json` as JSON, and `qubu/globals` as type-only. Concrete dialect
31
+ constructors live on their database subpaths. The root renderer uses Qubu's
32
+ standard SQL policy by default.
33
+
34
+ Snapshot dialect behavior is documented in the [PostgreSQL](postgres-snapshot.md),
35
+ [SQLite](sqlite-snapshot.md), and [MySQL](mysql-snapshot.md) support matrices.
36
+
37
+ ## Canonical query vocabulary
38
+
39
+ Use the root names in new query code: `eq`, `ne`, `lt`, `lte`, `gt`, `gte`,
40
+ `avg`, `min`, `max`, `fetchFirst`, `alias`, `render`, `qubu`, `execute`,
41
+ `deleteFrom`, and `allowAll`. The package does not document competing aliases
42
+ for these operations. Keep advanced fragment, dialect-construction, and
43
+ schema-extension imports on `qubu/core` or `qubu/schema` as shown in the
44
+ entrypoint table.
45
+
46
+ ## Capability map
47
+
48
+ | Area | Supported building blocks |
49
+ | ------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
50
+ | Schema values | `table`, immutable `schema` registries with namespaces, portable and dialect-native column storage descriptors, canonical default and generated-column metadata, identity descriptors, named primary, candidate-key, nullable unique, foreign-key, and check constraints, physical object names, included-column indexes, typed dialect extensions, and typed column helpers |
51
+ | Read queries | Named projections, spreadable source columns, aliases, joins, typed custom and LATERAL `FROM` sources, correlated subqueries, `WHERE`, grouping with declared-key proofs, `HAVING`, ordering, window expressions, distinctness, pagination, row locking, ordinary and recursive CTEs, subqueries, and set operations |
52
+ | Expressions | Comparison, boolean, arithmetic, null, range, membership, aggregate, window, string, JSON scalar reads, definition-backed and raw casts, cases, parameterized SQL templates, custom expressions, and branded deterministic schema expressions |
53
+ | SQL type metadata | Portable domains and capabilities, physical column storage descriptors, `SqlTypeOf`, projected SQL type maps, `SourceLike` and `TableLike` field constraints, contextual literals, typed extension values, calls, and casts, plus a permissive `SqlUnknown` fallback |
54
+ | Write queries | `INSERT` values, defaults, and selects; `UPDATE`; `DELETE`; typed assignments; `RETURNING`; and explicit unrestricted-write opt-in |
55
+ | Rendering | Standard, PostgreSQL, SQLite, MySQL, and user-created policies for identifiers, placeholders, pagination, row locking, JSON, logical cast targets, schema literals, and EXPLAIN options |
56
+ | Execution boundary | `QueryAdapter`, opt-in `ExplainableQueryAdapter`, `StreamingQueryAdapter`, and `TransactionalQueryAdapter` capabilities, bound clients from `qubu()`, structured results from `execute()` or `db.execute()`, row-only results from `executeRows()` or `db.rows()`, typed read streams from `stream()` or `db.stream()`, and adapter-decoded plan rows from `explain()` or `db.explain()` |
57
+ | Snapshots | Pure Snapshot v1 and v2 creation, canonical encoding and strict decoding, immutable data, diagnostics, and content digests |
58
+ | Introspection | PostgreSQL, SQLite, and MySQL catalog readers for one selected namespace, normalized catalog data, structured diagnostics, and strict or explicit lossy snapshot mapping |
59
+ | Snapshot diffing | Pure Snapshot v1 and v2 comparison, explicit rename evidence, non-authoritative suggestions, and safety diagnostics |
60
+ | Migration planning | Pure, dialect-neutral plans with stable ordering, dependency edges, preconditions, explicit review decisions, and tagged custom SQL |
61
+ | DDL emission | Preflight plus deterministic PostgreSQL, SQLite, and MySQL statements from an approved `MigrationPlan` and matching `SchemaDialect` |
62
+ | Build tooling | The optional Vite directive transform and its matching TypeScript ambient declarations |
63
+ | Drizzle conversion | Optional, dialect-specific runtime conversion from Qubu schema registries to Drizzle tables |
64
+ | Source generation | Pure Snapshot v1 table source printing, deterministic camelCase IDs, exact physical metadata, controlled type mappings, and structured failure diagnostics |
65
+
66
+ ## Ownership boundary
67
+
68
+ Snapshot creation, diffing, migration planning, and DDL emission are pure.
69
+ `execute()`, clients, and catalog readers can reach a driver only through
70
+ interfaces the application provides. Qubu can emit DDL, but it never applies
71
+ that DDL to a database.
72
+
73
+ | Boundary | Qubu side | Application side |
74
+ | ------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
75
+ | Query rendering | Builds a typed query and renders SQL text with ordered raw parameter values | Keeps the runtime database schema aligned with query definitions and validates any dynamic syntax passed to an unsafe helper |
76
+ | Query execution | Binds an adapter with `qubu()` when requested; renders and passes statements to `QueryAdapter`, `ExplainableQueryAdapter`, or a read-only `StreamingQueryAdapter`; scopes `db.transaction()` callbacks through `TransactionalQueryAdapter`; returns `ExecutionResult`, `ExplainResult`, rows, or an adapter-owned `AsyncIterable` | Owns the adapter, driver, connections, pools, cursors, stream cleanup, transaction begin/commit/rollback, savepoints, retries, parameter encoding, application-row and plan-row decoding, backpressure, cancellation behavior, driver error translation, and database lifecycle |
77
+ | Catalog introspection | Selects fixed parameterized catalog queries, normalizes rows, and maps catalog data to snapshots | Supplies `CatalogConnection`, credentials, already-decoded catalog rows, logging, and connection lifecycle |
78
+ | Schema source generation | Prints deterministic TypeScript from complete, non-lossy Snapshot v1 introspection without writing files | Owns generated-file writes, replacement policy, hand-edit merging, and CLI integration |
79
+ | Schema changes | Creates snapshots, compares them, builds deterministic migration plans, and emits DDL from approved plans with preflight diagnostics | Reviews decisions, executes or rolls back statements, acquires locks, manages transactions and migration journals, and owns database lifecycle |
80
+
81
+ Start with [Dialects and execution](../dialects-and-execution.md) for the query
82
+ adapter contract. The schema path is documented in [Canonical schema
83
+ snapshots](../schema/snapshots.md), [Snapshot diffing](../schema/diff.md),
84
+ [Migration plans](../schema/migration-plans.md), and [DDL
85
+ emission](../schema/ddl-emission.md).
86
+
87
+ ## SQL safety boundaries
88
+
89
+ Qubu binds values through the render context and quotes identifiers through the
90
+ active dialect. `UPDATE` and `DELETE` require a `WHERE` clause unless the caller
91
+ passes `allowAll()`.
92
+
93
+ The `sql` tag treats static template text as trusted SQL syntax. Ordinary
94
+ substitutions become parameters, and fragment substitutions compose through the
95
+ active renderer. Dynamic identifiers and syntax remain explicit through
96
+ `identifier()` and unsafe helpers. Those helpers are not sanitizers. Validate
97
+ dynamic syntax against an application-owned allowlist before it reaches an
98
+ unsafe helper.
99
+
100
+ SQL semantic types provide compile-time portable capability and compatibility
101
+ checks. They do not prove the runtime schema state or model every dialect's
102
+ implicit coercions. Custom and untyped extensions default to permissive
103
+ `SqlUnknown`; declare a domain when an extension should participate in stricter
104
+ checks.
@@ -0,0 +1,58 @@
1
+ # Complete catalog model
2
+
3
+ Qubu keeps database discovery in a normalized catalog before producing a
4
+ snapshot. The catalog is a read-only record of observed facts; it does not
5
+ contain a connection, execute catalog SQL, or assign database catalog keys as
6
+ persisted logical IDs.
7
+
8
+ The optional `qubu/introspection` entry point exposes the complete object
9
+ families and an immutable materializer:
10
+
11
+ ```ts
12
+ import {
13
+ createCompleteIntrospectionCatalog,
14
+ mapCatalogToCompleteSnapshot,
15
+ } from 'qubu/introspection'
16
+
17
+ const completeCatalog = createCompleteIntrospectionCatalog(catalog)
18
+ const result = mapCatalogToCompleteSnapshot(completeCatalog)
19
+ ```
20
+
21
+ Tables, columns, views, materialized views, sequences, enums, domains,
22
+ collations, triggers, routines, partitions, row-level policies, extension
23
+ objects, comments, and ownership metadata have typed records. A reader may
24
+ also retain a deferred or opaque object when it observes a family that Qubu
25
+ cannot yet normalize. Such an object remains visible and can carry opaque
26
+ catalog data, SQL text, provenance, and a dialect extension; it is never
27
+ silently dropped.
28
+
29
+ Physical names and references describe the current database. Stable logical
30
+ IDs are evidence selected by the adapter's identity policy. PostgreSQL OIDs,
31
+ SQLite implementation names, and similar catalog keys stay in current-run
32
+ references and are not used as logical IDs.
33
+
34
+ ## Snapshot v2
35
+
36
+ `qubu/snapshot` provides the strict complete format as a separate API:
37
+
38
+ ```ts
39
+ import {
40
+ decodeCompleteSchemaSnapshot,
41
+ encodeCompleteSchemaSnapshot,
42
+ } from 'qubu/snapshot'
43
+
44
+ const encoded = encodeCompleteSchemaSnapshot(snapshotV2)
45
+ const decoded = decodeCompleteSchemaSnapshot(encoded)
46
+ ```
47
+
48
+ Snapshot v2 uses the same `qubu-schema` envelope with `version: 2`. Its
49
+ namespace, capability facts, object-family arrays, cross-object references,
50
+ provenance, typed dialect extensions, and deferred/opaque boundaries are
51
+ strictly validated. Arrays are ordered by logical ID (with ordinal sequences
52
+ and index terms ordered by their semantic position), and the digest is computed
53
+ from the deterministic encoding.
54
+
55
+ Snapshot v1 remains a separate strict format. `decodeSchemaSnapshot` still
56
+ accepts only v1 and continues to reject unknown fields and future versions;
57
+ v2 callers must select `decodeCompleteSchemaSnapshot` explicitly. Neither
58
+ snapshot format evaluates database-provided SQL.
@@ -0,0 +1,139 @@
1
+ # Generate a schema from introspection
2
+
3
+ > Turn one complete, non-lossy Snapshot v1 introspection result into a deterministic, machine-owned TypeScript schema module.
4
+
5
+ Source generation is an optional capability exported from `qubu/codegen`. It
6
+ is a pure handoff after introspection: it opens no connection, runs no catalog
7
+ query, and writes no file. The caller owns those boundaries.
8
+
9
+ ## Generate a module
10
+
11
+ Read and map one namespace in strict mode, then pass that exact result to the
12
+ generator:
13
+
14
+ ```ts
15
+ import { writeFile } from 'node:fs/promises'
16
+ import { generateSchemaSource } from 'qubu/codegen'
17
+ import { mapCatalogToSnapshot, readSqliteCatalog } from 'qubu/introspection'
18
+
19
+ const catalog = await readSqliteCatalog(connection, { namespace: 'main' })
20
+ const introspection = mapCatalogToSnapshot(catalog, { namespace: 'main' })
21
+ const generated = generateSchemaSource(introspection)
22
+
23
+ if (!generated.ok) {
24
+ throw new Error(generated.diagnostics.map(issue => issue.message).join('\n'))
25
+ }
26
+
27
+ await writeFile('src/schema.generated.ts', generated.source, 'utf8')
28
+ ```
29
+
30
+ `writeFile()` belongs to the application; `generateSchemaSource()` only
31
+ returns data. A successful result contains deterministic `source` and every
32
+ retained diagnostic. A failed result contains diagnostics and no partial
33
+ source.
34
+
35
+ The module exports one declaration for every ordinary Snapshot v1 table and
36
+ one schema registry. It reconstructs physical names, exact native storage,
37
+ column write behavior, defaults, generated and identity metadata, constraints,
38
+ indexes, opaque predicates and expressions, and dialect extensions. Checks use
39
+ `catalogCheck()`. Foreign keys use lazy `catalogForeignKey()` targets so
40
+ forward declarations and cycles remain valid.
41
+
42
+ ## Adopt the generated identity baseline
43
+
44
+ The first introspection snapshot commonly uses physical names as logical IDs.
45
+ Generated declarations use deterministic camelCase registry, table, column,
46
+ constraint, and index IDs while retaining every physical database name. Once
47
+ the generated module is accepted, its serialized snapshot becomes the identity
48
+ baseline for the next catalog read:
49
+
50
+ ```ts
51
+ import { mapCatalogToSnapshot } from 'qubu/introspection'
52
+ import { createSqliteSchemaSnapshot } from 'qubu/snapshot'
53
+ import { mainSchema } from './schema.generated.ts'
54
+
55
+ const previousSnapshot = createSqliteSchemaSnapshot(mainSchema)
56
+ const next = mapCatalogToSnapshot(nextCatalog, {
57
+ namespace: 'main',
58
+ previousSnapshot,
59
+ })
60
+ ```
61
+
62
+ This handoff is deliberate. Do not keep using the pre-generation snapshot as
63
+ the long-term identity source, or later diffs will compare physical IDs with
64
+ the new generated IDs.
65
+
66
+ ## Control names and column types
67
+
68
+ Application output, insert, and update types default independently to
69
+ `unknown`. Qubu may attach a SQL semantic domain only when catalog evidence is
70
+ exact. Native storage always keeps the catalog declaration, even when the
71
+ semantic domain remains `SqlUnknown`.
72
+
73
+ Use the controlled callbacks to adopt trusted names or application mappings:
74
+
75
+ ```ts
76
+ const generated = generateSchemaSource(introspection, {
77
+ naming(context) {
78
+ if (context.kind === 'table' && context.physicalName === 'user_records') {
79
+ return 'users'
80
+ }
81
+ },
82
+ mapColumn(context) {
83
+ if (context.columnPhysicalName === 'account_id') {
84
+ return {
85
+ output: 'string',
86
+ insert: 'string',
87
+ update: 'string',
88
+ sqlDomain: 'uuid',
89
+ }
90
+ }
91
+ },
92
+ })
93
+ ```
94
+
95
+ Callbacks select names and fixed type tokens; they never take over printing and
96
+ cannot return imports, expressions, comments, or arbitrary source. A returned
97
+ name must still be a safe camelCase ID. Collisions and unsafe names fail with
98
+ diagnostics and no source.
99
+
100
+ ## Diagnostics and source safety
101
+
102
+ Generation rejects failed or lossy introspection, an altered snapshot that no
103
+ longer matches its catalog, omitted Snapshot v1 facts, unresolved references,
104
+ unsafe names, invalid mapping tokens, and data that cannot be represented
105
+ without source injection. Existing introspection diagnostics stay attached to
106
+ the result.
107
+
108
+ > [!IMPORTANT]
109
+ > A database can allow a foreign key to reference a nullable `UNIQUE`
110
+ > constraint. Snapshot v1 retains that constraint as nullable uniqueness, not
111
+ > as a Qubu candidate key. Source generation returns an
112
+ > `unrepresentable-fact` diagnostic instead of weakening the generated
113
+ > `references()` proof. Use a non-null primary key, strict unique key, or
114
+ > candidate index as the foreign-key target before adopting generated source.
115
+
116
+ Catalog names, native declarations, SQL, and extension metadata are untrusted
117
+ input. The printer serializes them only as controlled literals beneath a
118
+ static header. It does not interpolate catalog text as code or comments, parse
119
+ opaque SQL, or merge a previous generated file with hand edits. Treat the file
120
+ as replaceable output and keep application customizations in separate modules.
121
+
122
+ The public types and TSDoc on `generateSchemaSource()`,
123
+ `SchemaCodegenOptions`, and `CodegenDiagnostic` define the exact callback and
124
+ result contracts.
125
+
126
+ ## Snapshot v1 boundary
127
+
128
+ Generation covers ordinary Snapshot v1 tables in one namespace. Complete
129
+ catalog families outside that model—views, materialized views, sequences,
130
+ enums, domains, routines, triggers, partitions, policies, collations,
131
+ extensions, comments, ownership, and retained opaque or deferred objects—are
132
+ not emitted. Non-empty excluded families produce diagnostics so the generated
133
+ module does not look complete by omission.
134
+
135
+ The entrypoint does not provide a CLI, filesystem ownership, live driver
136
+ integration, multiple namespaces, runtime schema materialization, migrations,
137
+ DDL, Snapshot v2 object generation, or hand-edit merging. Use
138
+ [Database introspection](introspection.md) for the catalog boundary and
139
+ [Canonical schema snapshots](snapshots.md) for the identity artifact.
@@ -0,0 +1,130 @@
1
+ # Column behavior and write types
2
+
3
+ > Separate selected values from insert and update inputs, then record the database rules that make fields optional or generated.
4
+
5
+ ## Give each operation its own type
6
+
7
+ Every column has a selected output type. It can also describe what inserts and
8
+ updates accept:
9
+
10
+ | Option | Selected output | Insert input | Update input |
11
+ | ---------------- | --------------- | ----------------- | ----------------- |
12
+ | nullable: true | T or null | accepts T or null | accepts T or null |
13
+ | hasDefault: true | unchanged | key is optional | unchanged |
14
+ | generated: true | unchanged | key is omitted | key is omitted |
15
+
16
+ Use `column<Output, Insert, Update>()` when the driver returns a different type
17
+ from the type the application writes:
18
+
19
+ ```ts
20
+ import { column, integer, table, text } from 'qubu'
21
+
22
+ const accounts = table('accounts', {
23
+ id: integer({ generated: true }),
24
+ email: text(),
25
+ nickname: text({ nullable: true, hasDefault: true }),
26
+ externalScore: column<number, string, number>({ nullable: true }),
27
+ })
28
+ ```
29
+
30
+ The selected `externalScore` is number | null. Inserts accept string | null, and
31
+ updates accept number | null.
32
+
33
+ ## Describe defaults and generated columns
34
+
35
+ The legacy hasDefault and generated flags describe the write contract. Use
36
+ complete metadata when schema tooling also needs the database fact:
37
+
38
+ ```ts
39
+ import {
40
+ boolean,
41
+ generatedColumn,
42
+ identityColumn,
43
+ integer,
44
+ table,
45
+ text,
46
+ value,
47
+ } from 'qubu'
48
+ import { defineSchemaExpression } from 'qubu/schema'
49
+
50
+ const currentTimestamp = defineSchemaExpression('function', context => {
51
+ context.append('CURRENT_TIMESTAMP')
52
+ })
53
+
54
+ const accounts = table('accounts', {
55
+ id: integer({ identity: identityColumn('always') }),
56
+ status: text({ default: 'pending' }),
57
+ active: boolean({ default: true }),
58
+ score: integer({
59
+ generatedColumn: generatedColumn(value(1), 'stored'),
60
+ }),
61
+ createdAt: text({
62
+ default: currentTimestamp,
63
+ }),
64
+ })
65
+ ```
66
+
67
+ Primitive values in `default` are canonical literals. Strings are never
68
+ interpreted as SQL, and booleans remain semantic values so each dialect can
69
+ choose its own spelling. Pass a branded deterministic schema expression
70
+ directly when the default is SQL, and use `unsafeSchemaSql()` only for trusted
71
+ syntax Qubu does not model. Generated expressions record stored or virtual
72
+ mode. An identity descriptor stays separate because identity behavior is not
73
+ an ordinary generated expression.
74
+
75
+ Complete defaults cannot be combined with generated or identity metadata.
76
+ Contradictory flags fail with a structured `ColumnBehaviorError`. Use
77
+ `externalDefault()` or `externalGeneratedColumn()` when another schema authority
78
+ owns the missing detail.
79
+
80
+ Dialect-owned identity details stay on the identity descriptor. SQLite's
81
+ autoIncrement requires an exact INTEGER rowid alias that is the sole column of
82
+ a primary key. MySQL's AUTO_INCREMENT is a column-level identity extension, and
83
+ MySQL's ON UPDATE clause accepts a branded deterministic expression. The
84
+ database-specific restrictions are listed in the
85
+ [snapshot overview](snapshots.md) and its dialect matrices.
86
+
87
+ ## Narrow an application type
88
+
89
+ Use `$type<T>()` to narrow a helper's TypeScript type without changing its
90
+ runtime column definition:
91
+
92
+ ```ts
93
+ import { table, text } from 'qubu'
94
+
95
+ const users = table('users', {
96
+ status: text().$type<'active' | 'disabled'>(),
97
+ })
98
+ ```
99
+
100
+ The narrowed type applies to selected values and insert and update inputs. It
101
+ does not validate values at runtime or add a database constraint.
102
+
103
+ ## Derive write input types
104
+
105
+ `TableInsertInput` and `TableUpdateInput` expose the same rules to application
106
+ code. The following example uses the accounts table from the earlier example:
107
+
108
+ ```ts
109
+ import type { TableInsertInput, TableUpdateInput } from 'qubu'
110
+
111
+ type AccountInsert = TableInsertInput<typeof accounts.definitions>
112
+ type AccountUpdate = TableUpdateInput<typeof accounts.definitions>
113
+
114
+ const insert: AccountInsert = {
115
+ email: 'ada@example.com',
116
+ externalScore: '10',
117
+ }
118
+
119
+ const update: AccountUpdate = {
120
+ nickname: null,
121
+ externalScore: 10,
122
+ }
123
+ ```
124
+
125
+ `id` is not accepted because it is generated. `nickname` is optional on insert
126
+ because the database supplies a default, but it remains a valid nullable update
127
+ field.
128
+
129
+ Continue with [Write mutations](../guides/mutations.md) for typed INSERT,
130
+ UPDATE, and DELETE statements.
@@ -0,0 +1,180 @@
1
+ # Constraints, keys, and indexes
2
+
3
+ > Record database constraints and indexes when Qubu needs them to check foreign keys or grouped query projections.
4
+
5
+ ## Declare constraints and indexes
6
+
7
+ Pass a metadata callback to `table()` when the application schema knows which
8
+ rules the database enforces:
9
+
10
+ ```ts
11
+ import {
12
+ check,
13
+ eq,
14
+ foreignKey,
15
+ index,
16
+ integer,
17
+ primaryKey,
18
+ references,
19
+ table,
20
+ text,
21
+ unique,
22
+ value,
23
+ } from 'qubu'
24
+
25
+ const accounts = table(
26
+ 'accounts',
27
+ { id: integer(), slug: text() },
28
+ accounts => ({
29
+ constraints: {
30
+ accountsPrimary: primaryKey(accounts.id),
31
+ },
32
+ indexes: {
33
+ accountsSlug: index([accounts.slug], { unique: true }),
34
+ },
35
+ })
36
+ )
37
+
38
+ const memberships = table(
39
+ 'memberships',
40
+ {
41
+ id: integer(),
42
+ accountId: integer(),
43
+ slug: text(),
44
+ displayName: text(),
45
+ },
46
+ memberships => ({
47
+ constraints: {
48
+ membershipsPrimary: primaryKey(memberships.id),
49
+ membershipsUnique: unique(memberships.accountId, memberships.slug),
50
+ membershipsAccount: foreignKey(
51
+ [memberships.accountId],
52
+ references(accounts, accounts.id)
53
+ ),
54
+ membershipsCheck: check(eq(memberships.slug, value('public'))),
55
+ },
56
+ indexes: {
57
+ membershipsAccountSlug: index([memberships.accountId, memberships.slug], {
58
+ unique: true,
59
+ }),
60
+ },
61
+ })
62
+ )
63
+ ```
64
+
65
+ The callback receives the preliminary table, including its typed columns. Give
66
+ each item a stable application name in the constraints or indexes record. The
67
+ record key becomes `constraint.id` or `index.id`. Qubu resolves `physicalName` from
68
+ the explicit option or the version-one snake_case policy.
69
+
70
+ Index terms may use `asc()` or `desc()`. Set `unique: true` for a unique index,
71
+ `where` for a partial index, and `include` for columns stored in the index payload but not
72
+ used as key terms.
73
+
74
+ ## Distinguish candidate keys from unique constraints
75
+
76
+ `unique()` describes a non-null candidate key. Qubu can use it to prove that a
77
+ grouped key determines other columns. Every key column must belong to the
78
+ callback table and be non-nullable.
79
+
80
+ Use `uniqueConstraint()` when the database enforces uniqueness but the rule should
81
+ not prove a functional dependency:
82
+
83
+ ```ts
84
+ import { table, text, uniqueConstraint } from 'qubu'
85
+
86
+ const accounts = table(
87
+ 'accounts',
88
+ { email: text({ nullable: true }) },
89
+ accounts => ({
90
+ constraints: {
91
+ emailUnique: uniqueConstraint(accounts.email, {
92
+ nulls: 'distinct',
93
+ physicalName: 'accounts_email_key',
94
+ }),
95
+ },
96
+ indexes: {},
97
+ })
98
+ )
99
+ ```
100
+
101
+ `nulls: 'distinct'` describes the common rule where multiple NULLs do not
102
+ conflict. `'not-distinct'` describes a rule where NULL participates in the
103
+ comparison. Neither form proves that a grouped column determines the rest of a
104
+ row.
105
+
106
+ ## Add foreign keys
107
+
108
+ `foreignKey(localColumns, target, options)` accepts single or composite tuples.
109
+ Build the target with `references(table, ...columns)`. The tuples must have the
110
+ same length and matching known `SqlSemanticType` identities. `SqlUnknown` cannot
111
+ prove a foreign-key match.
112
+
113
+ The target tuple must exactly match a primary key, unique() constraint, or
114
+ eligible unique index. Options such as onUpdate, onDelete, match, deferrable,
115
+ and initially remain metadata:
116
+
117
+ ```ts
118
+ const memberships = table(
119
+ 'memberships',
120
+ { accountId: integer() },
121
+ memberships => ({
122
+ constraints: {
123
+ accountForeign: foreignKey(
124
+ [memberships.accountId],
125
+ references(accounts, accounts.id),
126
+ { onDelete: 'cascade', onUpdate: 'cascade' }
127
+ ),
128
+ },
129
+ indexes: {},
130
+ })
131
+ )
132
+ ```
133
+
134
+ Use the preliminary callback table for direct self-references. Wrap the target
135
+ in a function when two modules import each other's tables.
136
+
137
+ Checks, index expressions, and partial-index predicates may read only columns
138
+ from their callback table. They cannot contain aggregates, window functions, or
139
+ subqueries. Check expressions and partial predicates must have the boolean SQL
140
+ domain.
141
+
142
+ ## Use key metadata for grouped queries
143
+
144
+ Grouping every column in a declared candidate key lets Qubu select other
145
+ columns from that source:
146
+
147
+ ```ts
148
+ import { count, from, groupBy, select } from 'qubu'
149
+
150
+ const summary = select(
151
+ { displayName: memberships.displayName, total: count() },
152
+ from(memberships),
153
+ groupBy(memberships.accountId, memberships.slug)
154
+ )
155
+ ```
156
+
157
+ A primary key, `unique()` constraint, or eligible unique index supplies the
158
+ proof. A partial index, expression index, nullable key, or included column does
159
+ not silently become candidate-key evidence.
160
+
161
+ The proof follows a table alias and remains source-local through a join,
162
+ including a source made nullable by `leftJoin()`. It does not cross a derived
163
+ query, CTE, LATERAL query, or custom-source boundary. Those relations need an
164
+ explicit proof on their own source model.
165
+
166
+ ## Store dialect-specific metadata
167
+
168
+ Index methods, operator classes, concurrency, storage parameters, and
169
+ MySQL-specific algorithm or locking settings belong in the typed dialect
170
+ extension on an index. Constraint extensions follow the same pattern. A
171
+ dialect validator reports an error when the selected engine does not support an
172
+ extension.
173
+
174
+ ## Read next
175
+
176
+ - [Column behavior and write types](columns-and-writes.md) covers metadata
177
+ attached to individual columns.
178
+ - [Storage and schema SQL](storage-and-schema-sql.md) covers physical storage
179
+ and deterministic expressions.
180
+ - [SQL semantic types](../sql-semantic-types.md) explains type compatibility.