qubu 0.6.1 → 0.6.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 (114) hide show
  1. package/dist/{complete-DP7pliuY.mjs → canonical-B5_ouh-c.mjs} +466 -241
  2. package/dist/codegen.d.mts +2 -2
  3. package/dist/codegen.mjs +180 -90
  4. package/dist/column-Cyc2CMnG.mjs +116 -0
  5. package/dist/column-DDRvD7SF.mjs +721 -0
  6. package/dist/{constraints-DM_tarXc.mjs → constraints-CAmi18Uk.mjs} +8 -3
  7. package/dist/core.d.mts +3 -3
  8. package/dist/core.mjs +4 -4
  9. package/dist/diff.d.mts +9 -9
  10. package/dist/diff.mjs +152 -102
  11. package/dist/{explain-CkIK13L_.mjs → explain-BIqEmZha.mjs} +1 -1
  12. package/dist/{expressions-BCjc08zw.mjs → expressions-_6JF_J77.mjs} +2 -1
  13. package/dist/index-CaxrMD1A.d.mts +1 -0
  14. package/dist/index.d.mts +2 -2
  15. package/dist/index.mjs +368 -74
  16. package/dist/introspection/mysql.d.mts +1 -1
  17. package/dist/introspection/mysql.mjs +156 -23
  18. package/dist/introspection/postgres.d.mts +6 -3
  19. package/dist/introspection/postgres.mjs +388 -53
  20. package/dist/introspection/sqlite.d.mts +1 -1
  21. package/dist/introspection/sqlite.mjs +198 -12
  22. package/dist/introspection.d.mts +26 -12
  23. package/dist/introspection.mjs +2 -672
  24. package/dist/mysql.d.mts +3 -3
  25. package/dist/mysql.mjs +6 -5
  26. package/dist/{errors-Dxv73YJu.mjs → omit-VEr9Ydux.mjs} +5 -1
  27. package/dist/{on-conflict-CnaY5qso.mjs → on-conflict-B2rFyHGF.mjs} +6 -8
  28. package/dist/on-duplicate-key-update-Czsw1Yq-.mjs +95 -0
  29. package/dist/{postgres-Dey7QXPL.mjs → postgres-hFhd0I9n.mjs} +4 -5
  30. package/dist/postgres.d.mts +2 -2
  31. package/dist/postgres.mjs +2 -2
  32. package/dist/{registry-oWDiqD7i.mjs → registry-BXE_4M9P.mjs} +1 -1
  33. package/dist/{relational-DSAJ-l58.mjs → relational-CoPBETjI.mjs} +3 -2
  34. package/dist/schema.d.mts +2 -2
  35. package/dist/schema.mjs +8 -8
  36. package/dist/{serialize-CE-gw5_s.mjs → serialize-CyobNEx-.mjs} +174 -30
  37. package/dist/serialize-Du2UPZMt.d.mts +92 -0
  38. package/dist/snapshot/mysql.d.mts +5 -5
  39. package/dist/snapshot/mysql.mjs +8 -8
  40. package/dist/snapshot/postgres.d.mts +3 -3
  41. package/dist/snapshot/postgres.mjs +9 -9
  42. package/dist/snapshot/sqlite.d.mts +3 -3
  43. package/dist/snapshot/sqlite.mjs +7 -7
  44. package/dist/snapshot-mIb-Zzb5.mjs +1489 -0
  45. package/dist/snapshot.d.mts +4 -5
  46. package/dist/snapshot.mjs +3 -4
  47. package/dist/{source-BDuUXmAk.mjs → source-DYSUqzvb.mjs} +2 -2
  48. package/dist/sqlite.d.mts +2 -2
  49. package/dist/sqlite.mjs +6 -6
  50. package/dist/{table-C1QGNe4P.mjs → table-B8zEq0az.mjs} +4 -4
  51. package/dist/{types-BLNRatG_.mjs → types-CYHpSPwj.mjs} +10 -5
  52. package/dist/{types-BEn0N_al.d.mts → types-CiMvKi5V.d.mts} +14 -4
  53. package/dist/{types-DUe6eeI0.d.mts → types-Dqr4o2I1.d.mts} +590 -168
  54. package/dist/value-CpaUFtjw.mjs +45 -0
  55. package/dist/vite/ambient.d.ts +2 -0
  56. package/dist/vite.d.mts +1 -1
  57. package/dist/vite.mjs +2 -0
  58. package/docs/dialects-and-execution.md +105 -26
  59. package/docs/getting-started.md +10 -10
  60. package/docs/guides/better-auth.md +16 -5
  61. package/docs/guides/compose-queries.md +21 -9
  62. package/docs/guides/drizzle.md +8 -3
  63. package/docs/guides/extensions/dialects.md +1 -1
  64. package/docs/guides/extensions/overview.md +1 -1
  65. package/docs/guides/extensions/sources-and-clauses.md +7 -3
  66. package/docs/guides/extensions/typed-expressions.md +26 -12
  67. package/docs/guides/extensions/unsafe-syntax.md +10 -6
  68. package/docs/guides/json.md +126 -8
  69. package/docs/guides/mutations.md +51 -6
  70. package/docs/guides/select/conditions.md +18 -11
  71. package/docs/guides/select/grouping-and-windows.md +5 -2
  72. package/docs/guides/select/ordering-and-pagination.md +5 -3
  73. package/docs/guides/select/overview.md +6 -3
  74. package/docs/guides/sql-templates.md +11 -5
  75. package/docs/guides/valtio-sync.md +11 -5
  76. package/docs/guides/vite-plugin.md +2 -2
  77. package/docs/index.md +25 -18
  78. package/docs/migrations/adapters.md +92 -27
  79. package/docs/migrations/artifacts-and-policy.md +49 -20
  80. package/docs/migrations/index.md +15 -8
  81. package/docs/migrations/lotta-adoption.md +16 -5
  82. package/docs/migrations/operations.md +29 -15
  83. package/docs/migrations/recovery.md +40 -17
  84. package/docs/query-model/fragments.md +33 -5
  85. package/docs/query-model/result-shapes.md +2 -2
  86. package/docs/query-model/source-scope.md +5 -3
  87. package/docs/reference/introspection-support.md +42 -37
  88. package/docs/reference/mysql-snapshot.md +19 -4
  89. package/docs/reference/postgres-snapshot.md +17 -4
  90. package/docs/reference/sqlite-snapshot.md +19 -2
  91. package/docs/reference/supported-surface.md +221 -84
  92. package/docs/schema/catalog-model.md +44 -14
  93. package/docs/schema/code-generation.md +40 -21
  94. package/docs/schema/columns-and-writes.md +21 -11
  95. package/docs/schema/constraints-and-indexes.md +12 -5
  96. package/docs/schema/ddl-emission.md +16 -5
  97. package/docs/schema/diff.md +13 -5
  98. package/docs/schema/introspection.md +64 -31
  99. package/docs/schema/migration-plans.md +18 -10
  100. package/docs/schema/snapshots.md +68 -30
  101. package/docs/schema/storage-and-schema-sql.md +16 -5
  102. package/docs/schema/tables-and-names.md +1 -1
  103. package/docs/sql-semantic-types.md +11 -8
  104. package/docs/troubleshooting.md +14 -6
  105. package/package.json +2 -1
  106. package/dist/canonical-DMvR9yBe.mjs +0 -972
  107. package/dist/column-BzN8KFJa.mjs +0 -364
  108. package/dist/column-CFvSbil0.mjs +0 -309
  109. package/dist/complete-types-CNMWBWap.d.mts +0 -371
  110. package/dist/index-CGui70hi.d.mts +0 -32
  111. package/dist/json-Db7XRD91.mjs +0 -169
  112. package/dist/omit-OxV58AwX.mjs +0 -5
  113. package/dist/serialize-OvXCLzjm.d.mts +0 -66
  114. package/dist/snapshot-DgsOhf_8.mjs +0 -354
@@ -1,6 +1,6 @@
1
1
  # Column behavior and write types
2
2
 
3
- > Separate selected values from insert and update inputs, then record the database rules that make fields optional or generated.
3
+ > Choose the values a column returns and accepts, including defaults and generated values.
4
4
 
5
5
  ## Give each operation its own type
6
6
 
@@ -32,7 +32,7 @@ updates accept number | null.
32
32
 
33
33
  ## Describe defaults and generated columns
34
34
 
35
- The legacy hasDefault and generated flags describe the write contract. Use
35
+ The `hasDefault` and `generated` flags describe which values writes accept. Use
36
36
  complete metadata when schema tooling also needs the database fact:
37
37
 
38
38
  ```ts
@@ -60,15 +60,18 @@ Primitive values in `default` are canonical literals. Strings are never
60
60
  interpreted as SQL, and booleans remain semantic values so each dialect can
61
61
  choose its own spelling. Pass a branded deterministic schema expression
62
62
  directly when the default is SQL, and use `unsafeSchemaSql()` only for trusted
63
- syntax Qubu does not model. Generated expressions record stored or virtual
64
- mode. An identity descriptor stays separate because identity behavior is not
65
- an ordinary generated expression.
63
+ syntax Qubu does not model.
64
+
65
+ Generated expressions record stored or virtual mode. An identity descriptor
66
+ stays separate because identity behavior is not an ordinary generated expression.
66
67
 
67
68
  Complete defaults cannot be combined with generated or identity metadata.
68
69
  Contradictory flags fail with a structured `ColumnBehaviorError`. Use
69
70
  `externalDefault()` or `externalGeneratedColumn()` when another schema authority
70
71
  owns the missing detail.
71
72
 
73
+ ### Supply defaults at runtime
74
+
72
75
  Use `defaultFn` when Qubu should supply an omitted insert value at runtime:
73
76
 
74
77
  ```ts
@@ -79,15 +82,22 @@ const sessions = table("sessions", {
79
82
 
80
83
  Runtime defaults make the insert key optional and run once for each omitted
81
84
  row value. They remain live column behavior: snapshots and emitted DDL do not
82
- record a database default. A column may declare both `default` and `defaultFn`;
85
+ record a database default.
86
+
87
+ A column may declare both `default` and `defaultFn`;
83
88
  Qubu writes use the runtime value while the database default remains available
84
89
  to other clients.
85
90
 
86
- Dialect-owned identity details stay on the identity descriptor. SQLite's
87
- autoIncrement requires an exact INTEGER rowid alias that is the sole column of
88
- a primary key. MySQL's AUTO_INCREMENT is a column-level identity extension, and
89
- MySQL's ON UPDATE clause accepts a branded deterministic expression. The
90
- database-specific restrictions are listed in the
91
+ ### Check database-specific rules
92
+
93
+ Identity details stay on the identity descriptor:
94
+
95
+ - SQLite `autoIncrement` requires an exact `INTEGER` rowid alias that is the
96
+ sole column of a primary key.
97
+ - MySQL `AUTO_INCREMENT` is a column-level identity extension.
98
+ - MySQL `ON UPDATE` accepts a branded deterministic expression.
99
+
100
+ The database-specific restrictions are listed in the
91
101
  [snapshot overview](snapshots.md) and its dialect matrices.
92
102
 
93
103
  ## Narrow an application type
@@ -60,9 +60,11 @@ each item a stable application name in the constraints or indexes record. The
60
60
  record key becomes `constraint.id` or `index.id`. Qubu resolves `physicalName` from
61
61
  the explicit option or the version-one snake_case policy.
62
62
 
63
- Index terms may use `asc()` or `desc()`. Set `unique: true` for a unique index,
64
- `where` for a partial index, and `include` for columns stored in the index payload but not
65
- used as key terms.
63
+ Index terms may use `asc()` or `desc()`. Set these options as needed:
64
+
65
+ - `unique: true` creates a unique index.
66
+ - `where` supplies a partial-index predicate.
67
+ - `include` lists columns stored in the index payload but not used as key terms.
66
68
 
67
69
  ## Distinguish candidate keys from unique constraints
68
70
 
@@ -100,8 +102,11 @@ same length and matching known `SqlSemanticType` identities. `SqlUnknown` cannot
100
102
  prove a foreign-key match.
101
103
 
102
104
  The target tuple must exactly match a primary key, unique() constraint, or
103
- eligible unique index. Options such as onUpdate, onDelete, match, deferrable,
104
- and initially remain metadata:
105
+ eligible unique index. These options record database behavior as metadata:
106
+
107
+ - `onUpdate` and `onDelete`.
108
+ - `match`.
109
+ - `deferrable` and `initially`.
105
110
 
106
111
  ```ts
107
112
  const memberships = table("memberships", { accountId: integer() }, (memberships) => ({
@@ -118,6 +123,8 @@ const memberships = table("memberships", { accountId: integer() }, (memberships)
118
123
  Use the preliminary callback table for direct self-references. Wrap the target
119
124
  in a function when two modules import each other's tables.
120
125
 
126
+ ### Restrictions on schema expressions
127
+
121
128
  Checks, index expressions, and partial-index predicates may read only columns
122
129
  from their callback table. They cannot contain aggregates, window functions, or
123
130
  subqueries. Check expressions and partial predicates must have the boolean SQL
@@ -1,6 +1,6 @@
1
1
  # DDL emission
2
2
 
3
- > Preview deterministic SQL from a migration plan without confusing preview policy with a sealed executable program.
3
+ > Preview the SQL for a migration plan before preparing it for execution.
4
4
 
5
5
  The `@qubu/migrate/ddl` entrypoint accepts only a `MigrationPlan` and a `SchemaDialect`.
6
6
  It does not read a catalog, open a connection, start a transaction, or write a
@@ -22,8 +22,13 @@ for (const statement of result.statements) {
22
22
  }
23
23
  ```
24
24
 
25
- `statements` is a deterministic preview surface. Each statement carries its
26
- operation ID, topological position, SQL text, and an ordered parameter list.
25
+ `statements` contains the SQL preview in dependency order. Each statement has:
26
+
27
+ - Its operation ID.
28
+ - Its position in that order.
29
+ - SQL text.
30
+ - An ordered parameter list.
31
+
27
32
  Schema literals and expressions are parameter-free by contract. `sql` joins
28
33
  the statements with a newline and adds a semicolon for migration-file writers.
29
34
 
@@ -31,12 +36,16 @@ the statements with a newline and adds a semicolon for migration-file writers.
31
36
 
32
37
  The emitter rejects a plan with `ready: false`, `decision-required` operations,
33
38
  unknown or lossy facts, unsupported safety, or destructive changes unless the
34
- caller supplies the matching explicit option. `allowUnsafe` is available for
39
+ caller supplies the matching explicit option.
40
+
41
+ `allowUnsafe` is available for
35
42
  preview integrations, but it is not accepted as an artifact approval and does
36
43
  not make an opaque object renderable. Opaque and deferred catalog records need
37
44
  an explicit tagged `custom-sql` operation for preview and an operation-scoped
38
45
  custom program for sealed execution.
39
46
 
47
+ ### Check execution requirements
48
+
40
49
  Lock and transaction requirements describe what a later executor must provide.
41
50
  Pass `lock` or `transaction` to preflight those requirements against the
42
51
  executor's context. A required transaction with `transaction: 'autocommit'`
@@ -74,7 +83,9 @@ an inline constraint declaration or an explicit rebuild/custom-SQL operation.
74
83
  Custom SQL stays opaque and appears at its plan position. The emitter does not
75
84
  inspect it for object names or infer SQL from an opaque catalog record.
76
85
 
77
- For execution, lower the plan with `compileMigrationProgram()` from
86
+ ## Prepare for execution
87
+
88
+ For execution, compile the plan with `compileMigrationProgram()` from
78
89
  `@qubu/migrate/artifact`. The versioned program—not the aggregate `sql`
79
90
  string—is authoritative. See [Artifacts and approval
80
91
  policy](../migrations/artifacts-and-policy.md).
@@ -1,11 +1,15 @@
1
1
  # Snapshot diffing
2
2
 
3
- > Compare canonical Snapshot v1 or v2 values and review identity changes before a later planning step.
3
+ > Compare two snapshots and review changes before planning a migration.
4
4
 
5
5
  The optional `qubu/diff` entrypoint compares immutable snapshot data. It does
6
- not open a connection, render SQL, execute a change, or infer migration history.
7
- It keeps the object kind, namespace, logical ID, physical name, dialect, path,
8
- and catalog evidence on every result object.
6
+ not access a database or execute changes. Each result keeps the information
7
+ needed to review it:
8
+
9
+ - Object kind and namespace.
10
+ - Logical ID and physical name.
11
+ - Dialect and path.
12
+ - Catalog evidence.
9
13
 
10
14
  ```ts
11
15
  import { diffSnapshots } from "qubu/diff"
@@ -17,6 +21,8 @@ for (const change of result.changes) {
17
21
  }
18
22
  ```
19
23
 
24
+ ## How objects are matched
25
+
20
26
  The matcher first uses an explicit rename hint, then a stable logical ID. A
21
27
  physical-name change for a stable match is a `physical-rename` operation. Other
22
28
  matched fields become `property-change` operations. Unmatched records remain
@@ -57,10 +63,12 @@ an `ambiguous` diagnostic and leaves both operations visible for review.
57
63
 
58
64
  Removing an object is marked `destructive`. Narrowing nullability, changing
59
65
  storage, removing a value, or changing a constraint can also receive that
60
- classification. Opaque and deferred Snapshot v2 records remain visible as
66
+ classification. Opaque and deferred Snapshot v1 records remain visible as
61
67
  `add` or `remove` data and produce `lossy` or `unsupported` diagnostics. They
62
68
  cannot be silently promoted to a rename.
63
69
 
70
+ ### Equality and ordering
71
+
64
72
  `result.equal` means that no diff operation was emitted. A result can therefore
65
73
  be equal while still carrying a warning about an unchanged opaque record. The
66
74
  diagnostics remain part of the review boundary.
@@ -1,12 +1,16 @@
1
1
  # Database introspection
2
2
 
3
- > Read one existing database namespace into explainable catalog data and an optional canonical Snapshot v1 or complete Snapshot v2 without giving Qubu ownership of the connection.
3
+ > Read an existing database schema and turn it into a Qubu snapshot.
4
4
 
5
5
  Database introspection is an optional capability exported from
6
6
  `qubu/introspection`. It discovers database facts; it does not recreate the
7
- original TypeScript declarations. Planning and DDL emission use the separate
7
+ original TypeScript declarations.
8
+
9
+ Planning and DDL emission use the separate
8
10
  `@qubu/migrate/plan` and `@qubu/migrate/ddl` entrypoints, while migration
9
- execution remains application-owned. The separate
11
+ execution remains application-owned.
12
+
13
+ The separate
10
14
  `qubu/codegen` entrypoint can create a new machine-owned schema module from a
11
15
  complete Snapshot v1 result.
12
16
 
@@ -32,9 +36,12 @@ continuity, and strict versus lossy output.
32
36
 
33
37
  ## Supply a connection
34
38
 
35
- Qubu does not open or close a connection, select a driver, manage a pool, retry
36
- queries, authenticate, or start a transaction. Adapt the driver you already
37
- use to `CatalogConnection`:
39
+ Adapt your existing driver to `CatalogConnection`. Your application handles:
40
+
41
+ - Opening and closing connections.
42
+ - Driver selection and connection pools.
43
+ - Authentication.
44
+ - Retries and transactions.
38
45
 
39
46
  ```ts
40
47
  import type { CatalogConnection } from "qubu/introspection"
@@ -85,8 +92,8 @@ catalog. Use `createCompleteIntrospectionCatalog()` to materialize and freeze
85
92
  all optional collections, then `mapCatalogToCompleteSnapshot()` when an
86
93
  adapter-supported family such as views, routines, triggers, partitions,
87
94
  collations, comments, or retained opaque and deferred objects must cross the
88
- strict Snapshot v2 boundary. Snapshot v1 is still selected explicitly by
89
- `mapCatalogToSnapshot()` and remains table-shaped.
95
+ strict Snapshot v1 boundary. `mapCatalogToSnapshot()` delegates to the complete
96
+ mapper, so the canonical result is always Snapshot v1.
90
97
 
91
98
  The result is successful only when Snapshot v1 validation succeeds. A failed
92
99
  result may retain the partial catalog and structured diagnostics, but it has no
@@ -108,9 +115,9 @@ rename. Pass the previous snapshot or an identity hint when a later diff must
108
115
  preserve identity across a rename. See [snapshot diffing](diff.md) for the
109
116
  comparison and hint boundary.
110
117
 
111
- The first version selects one namespace: a PostgreSQL schema, MySQL database,
112
- or SQLite database such as `main`. It does not combine attached databases or
113
- multiple PostgreSQL schemas into one Snapshot v1 value.
118
+ Each Snapshot v1 result selects one namespace: a PostgreSQL schema, MySQL
119
+ database, or SQLite database such as `main`. It does not combine attached
120
+ databases or multiple PostgreSQL schemas into one value.
114
121
 
115
122
  ## Strict and lossy output
116
123
 
@@ -127,21 +134,44 @@ generated expressions, checks, predicates, and expression index terms remain
127
134
  dialect-tagged SQL. Falsy values such as `0`, `false`, `NULL`, and empty
128
135
  strings are preserved.
129
136
 
130
- PostgreSQL readers expose views, materialized views, sequences, enums, domains,
131
- collations, routines, triggers, policies, partitions, extensions, comments,
132
- and ownership as typed complete catalog records. `mapCatalogToCompleteSnapshot`
133
- retains those records in Snapshot v2. The existing `mapCatalogToSnapshot`
134
- mapper still emits the table-only Snapshot v1 and does not fabricate these
135
- objects into tables. If a PostgreSQL catalog row lacks the evidence needed for
136
- safe normalization, the reader retains a deferred or opaque record and emits a
137
- diagnostic.
137
+ ### References and dialect metadata
138
+
139
+ References to nested columns, constraints, and indexes include their owning
140
+ table, view, or domain in the complete Snapshot v1 output. References to
141
+ top-level objects remain unscoped, and table-local backing relationships must
142
+ resolve within the table that contains them. Dialect provenance, extensions,
143
+ native storage, and typed expressions are checked against the selected
144
+ snapshot dialect; arbitrary catalog `data` and `configuration` JSON remains
145
+ opaque.
146
+
147
+ ## PostgreSQL catalog support
148
+
149
+ PostgreSQL readers expose typed catalog records for:
150
+
151
+ - Views and materialized views.
152
+ - Sequences, enums, and domains.
153
+ - Collations.
154
+ - Routines and triggers.
155
+ - Policies and partitions.
156
+ - Extensions.
157
+ - Comments and ownership.
158
+
159
+ `mapCatalogToCompleteSnapshot()` retains those records in Snapshot v1.
160
+ `mapCatalogToSnapshot()` uses the same complete mapping and keeps each object
161
+ as its own kind.
162
+
163
+ If a PostgreSQL catalog row lacks the evidence needed for safe normalization,
164
+ the reader retains a deferred or opaque record and emits a diagnostic.
165
+
166
+ ## SQLite catalog support
138
167
 
139
168
  SQLite readers expose recoverable views and triggers as typed complete records.
140
169
  They retain virtual and shadow tables as deferred objects, and keep attached
141
170
  databases outside the selected namespace as opaque boundary records. SQLite
142
171
  declared types, derived affinity, generated expressions, rowid identity, and
143
- `AUTOINCREMENT` stay tagged with SQLite dialect metadata. When an attached
144
- database is selected, table PRAGMAs may be visible but CREATE SQL remains
172
+ `AUTOINCREMENT` stay tagged with SQLite dialect metadata.
173
+
174
+ When an attached database is selected, table PRAGMAs may be visible but CREATE SQL remains
145
175
  limited to the fixed `main` and `temp` catalog statements, so the reader marks
146
176
  the catalog visibility as limited instead of combining namespaces.
147
177
 
@@ -155,13 +185,13 @@ collations used by the selected tables or columns, and comments.
155
185
 
156
186
  View definitions come from `INFORMATION_SCHEMA.VIEWS`. The reader cross-
157
187
  references each view with its `INFORMATION_SCHEMA.COLUMNS` rows by physical
158
- table name, so view columns remain attached to the view and Snapshot v2 can
188
+ table name, so view columns remain attached to the view and Snapshot v1 can
159
189
  validate their own column IDs. A missing definition or an unresolved
160
190
  cross-object reference becomes a deferred record with a diagnostic.
161
191
 
162
192
  MySQL scheduled events are kept as `CatalogOpaqueObject` records with their
163
193
  metadata and definition tagged as opaque SQL. The reader emits an
164
- `unmodeled-object` warning, and Snapshot v2 retains the record in
194
+ `unmodeled-object` warning, and Snapshot v1 retains the record in
165
195
  `opaqueObjects` without treating it as a typed routine, trigger, or migration
166
196
  operation.
167
197
 
@@ -178,17 +208,20 @@ The reader keeps that metadata as normalized typed data and never evaluates
178
208
  database-provided SQL. Optional source generation remains a later, pure step
179
209
  with a controlled literal printer.
180
210
 
181
- Use `mapCatalogToCompleteSnapshot()` for the typed MySQL families and its
182
- opaque or deferred boundaries. Use `mapCatalogToSnapshot()` when the caller
183
- needs the table-only Snapshot v1.
211
+ Use `mapCatalogToSnapshot()` or `mapCatalogToCompleteSnapshot()` for the typed
212
+ MySQL families and their opaque or deferred boundaries.
184
213
 
185
214
  ## Diagnostics and safety
186
215
 
187
- Diagnostics include a severity, stable code, catalog path, physical reference
188
- when available, and a remediation hint. They distinguish connection/query
189
- failures, permission limits, unsupported products or versions, unresolved
190
- references, expression recovery failures, unmodeled objects, and lossy
191
- mappings.
216
+ Each diagnostic includes:
217
+
218
+ - Severity and a stable code.
219
+ - A catalog path.
220
+ - A physical reference, when available.
221
+ - A hint for fixing the problem.
222
+
223
+ Codes distinguish connection failures and permission limits from unsupported
224
+ features or incomplete mappings.
192
225
 
193
226
  Introspection is read-only from Qubu's perspective. Do not pass credentials or
194
227
  DSNs through diagnostic fields. Keep driver-specific error text in the
@@ -1,11 +1,16 @@
1
1
  # Migration plans
2
2
 
3
- > Describe reviewed snapshot changes as deterministic data before selecting a DDL emitter.
3
+ > Turn a reviewed schema diff into an ordered migration plan.
4
4
 
5
- The `@qubu/migrate/plan` entrypoint consumes a resolved `SnapshotDiff` and
6
- returns an immutable migration-plan IR. It contains operation IDs, paths,
7
- logical and physical identity evidence, dependency edges, preconditions, safety,
8
- lock and transaction requirements, and reversibility markers.
5
+ The `@qubu/migrate/plan` entrypoint takes a resolved `SnapshotDiff` and returns
6
+ an immutable plan. Each operation records:
7
+
8
+ - Its ID and path.
9
+ - Evidence for logical and physical identities.
10
+ - Dependencies and preconditions.
11
+ - Safety classification.
12
+ - Lock and transaction requirements.
13
+ - Whether the change can be reversed.
9
14
 
10
15
  ```ts
11
16
  import { createMigrationPlan } from "@qubu/migrate/plan"
@@ -16,8 +21,9 @@ if (!result.ok) {
16
21
  }
17
22
  ```
18
23
 
19
- Creation is pure. The planner does not open a connection, execute a transaction,
20
- render SQL, or create migration history. A physical rename remains a
24
+ The planner only returns data. It does not access the database or render SQL.
25
+
26
+ A physical rename remains a
21
27
  `physical-rename` operation; it is never represented as custom SQL or silently
22
28
  changed into a drop and add.
23
29
 
@@ -64,9 +70,11 @@ extracts SQL from opaque catalog payloads.
64
70
 
65
71
  ## Ordering and validation
66
72
 
67
- Parent creation precedes child creation, while child removal precedes parent
68
- removal. Reference edges and explicit custom-SQL dependencies are included in
69
- the stable topological ordering. `encodeMigrationPlan()` emits canonical JSON;
73
+ The planner creates parents before children and removes children before
74
+ parents. It also orders operations by their references and explicit custom-SQL
75
+ dependencies. The same inputs produce the same order.
76
+
77
+ `encodeMigrationPlan()` emits canonical JSON;
70
78
  `decodeMigrationPlan()` and `validateMigrationPlan()` reject unknown fields,
71
79
  future versions, malformed operations, missing edges, and dependency cycles.
72
80
 
@@ -1,6 +1,6 @@
1
1
  # Canonical schema snapshots
2
2
 
3
- > Serialize schema metadata into strict, deterministic data and keep serialization separate from diffing, planning, and DDL emission.
3
+ > Save a schema as versioned data that you can compare and check into source control.
4
4
 
5
5
  Qubu's schema tooling lives behind the `qubu/snapshot` entrypoint. It converts
6
6
  an immutable `schema()` registry into versioned data that can be inspected,
@@ -14,20 +14,33 @@ const snapshot = createSchemaSnapshot(appSchema)
14
14
  const json = encodeSchemaSnapshot(snapshot)
15
15
  ```
16
16
 
17
- The v1 envelope contains a format version, an independently versioned dialect
18
- extension, a versioned naming-policy description, an optional namespace, and
19
- arrays of tables. Tables, columns, constraints, and indexes are sorted by
20
- stable logical ID. Physical names are values in the snapshot, not identities:
17
+ ## What a snapshot contains
18
+
19
+ The Snapshot v1 envelope contains:
20
+
21
+ - A format version.
22
+ - An independently versioned dialect extension.
23
+ - A versioned naming-policy description.
24
+ - A namespace.
25
+ - Supported-capability facts.
26
+ - Arrays for each supported object family.
27
+
28
+ Tables, columns, constraints, and indexes are sorted by stable logical ID. Physical
29
+ names are values in the snapshot, not identities:
21
30
  changing a physical name does not change the TypeScript field or metadata key.
22
31
 
23
- Snapshot data is deliberately not executable Qubu state. Expressions are
24
- parameter-free data records, and decoding never creates tables, column
25
- references, or render closures. The neutral fallback renders branded built-in
26
- expressions through the standard schema context; a dialect adapter may replace
32
+ Snapshot expressions are data records without parameters. Decoding does not
33
+ create executable table or column objects, or rendering functions.
34
+
35
+ The neutral fallback renders branded built-in expressions through the standard schema context; a dialect adapter may replace
27
36
  that hook with its own literal and expression policy. An explicitly unsafe
28
37
  expression retains its dialect tag and is rejected when it does not match the
29
38
  selected snapshot dialect.
30
39
 
40
+ ## Decode and validate a snapshot
41
+
42
+ Use `decodeSchemaSnapshot()` to read saved JSON and inspect validation failures:
43
+
31
44
  ```ts
32
45
  import { decodeSchemaSnapshot } from "qubu/snapshot"
33
46
 
@@ -45,6 +58,19 @@ dialect metadata, and broken foreign-key or column references as structured
45
58
  diagnostics. It does not call `process.exit()` and has no runtime validation
46
59
  library dependency.
47
60
 
61
+ ### References and ownership
62
+
63
+ References to nested columns, constraints, and indexes carry an explicit
64
+ `owner: { kind, id }` scope. Table columns, constraints, and indexes are owned
65
+ by their table; view columns are owned by their view; and domain constraints
66
+ are owned by their domain. References to top-level objects remain ownerless,
67
+ and the decoder validates each nested scope independently. Dialect metadata is
68
+ checked only in typed snapshot fields. Extension `data`, `configuration`, and
69
+ other opaque JSON payloads are retained as data and are not interpreted as
70
+ typed metadata.
71
+
72
+ ### Content fingerprints
73
+
48
74
  `schemaSnapshotFingerprint()` computes a deterministic content fingerprint from canonical
49
75
  JSON. The fingerprint is useful for cache keys and fixture assertions only. It is not
50
76
  an entity identity, a rename marker, or migration lineage.
@@ -59,15 +85,23 @@ and advertised capabilities while adding schema encoders and validation under
59
85
  second query dialect. The schema snapshot format version remains independent
60
86
  from the dialect identity.
61
87
 
62
- The common traversal owns logical IDs, fixed property order, canonical sorting,
63
- portable constraints, cross-reference checks, and the immutable snapshot
64
- envelope. A dialect adapter owns physical storage mapping, SQL literal and
65
- expression encoding, dialect extensions, capability checks, and any dialect
66
- naming policy. PostgreSQL, SQLite, and MySQL adapters can implement
67
- `SchemaSnapshotAdapter` without duplicating traversal or decoder rules.
68
- The neutral API stays at `qubu/snapshot`; built-in dialect adapters have
69
- dedicated subpaths so importing neutral snapshot utilities does not widen that
70
- API:
88
+ The shared serializer handles:
89
+
90
+ - Logical IDs and fixed property order.
91
+ - Canonical sorting and portable constraints.
92
+ - Cross-reference checks.
93
+ - The immutable snapshot envelope.
94
+
95
+ A dialect adapter handles:
96
+
97
+ - Physical storage mapping.
98
+ - SQL literal and expression encoding.
99
+ - Dialect extensions and capability checks.
100
+ - Any dialect-specific naming policy.
101
+
102
+ PostgreSQL, SQLite, and MySQL adapters can implement `SchemaSnapshotAdapter`
103
+ without duplicating traversal or decoder rules. Import built-in adapters from
104
+ their dedicated subpaths:
71
105
 
72
106
  ```ts
73
107
  import { createSchemaSnapshot } from "qubu/snapshot"
@@ -87,19 +121,23 @@ matrix](../reference/mysql-snapshot.md). Its query and snapshot dialects both
87
121
  use `mysql`, while MySQL-only `ON UPDATE` and `AUTO_INCREMENT` details remain
88
122
  inside the column and identity metadata they describe.
89
123
 
90
- Snapshot serialization remains separate from database introspection,
91
- comparison, rename resolution, migration planning, and DDL emission. The
92
- optional `qubu/introspection` entrypoint can produce the same canonical
93
- Snapshot v1 data from a user-owned catalog connection. The complete normalized
94
- catalog can also be encoded as strict Snapshot v2 with the dedicated
95
- complete-snapshot APIs described in [the catalog model](catalog-model.md).
96
- Readers and connection lifecycle do not belong to this pure serialization
97
- layer. Diffing can compare either snapshot version. Resolved diffs feed
98
- migration plans, and approved plans feed DDL emission. The package-wide
99
- [ownership map](../reference/supported-surface.md#ownership-boundary) keeps
100
- those pure steps separate from application-owned database execution.
124
+ ## Use a snapshot in later steps
125
+
126
+ The optional `qubu/introspection` entrypoint can produce Snapshot v1 data from
127
+ a catalog connection you provide. The [catalog model](catalog-model.md)
128
+ describes the complete-snapshot APIs.
129
+
130
+ A snapshot then passes through separate steps:
131
+
132
+ 1. Diffing compares Snapshot v1 values.
133
+ 2. Migration planning uses the resolved diff.
134
+ 3. DDL emission renders an approved plan.
135
+
136
+ These steps return data without accessing the database. The
137
+ [ownership map](../reference/supported-surface.md#ownership-boundary) explains
138
+ how they connect to application-owned execution.
101
139
 
102
140
  The optional [schema source generator](code-generation.md) consumes a complete,
103
141
  non-lossy introspection result and makes its generated schema the next identity
104
- baseline. It does not replace snapshot serialization or generate Snapshot v2
142
+ baseline. It does not replace snapshot serialization or populate non-table
105
143
  object families.
@@ -1,11 +1,15 @@
1
1
  # Storage and schema SQL
2
2
 
3
- > Keep application types, SQL domains, physical storage, and schema expressions separate so each adapter can make its own rendering decision.
3
+ > Describe database storage types and write expressions for schema definitions.
4
4
 
5
5
  ## Keep application and SQL types separate
6
6
 
7
- A column can have an application value type, a SQL semantic domain, a physical
8
- storage descriptor, and a cast target. These facts answer different questions.
7
+ A column can describe four different things:
8
+
9
+ - Its application value type.
10
+ - Its SQL semantic domain, which controls compatible operations.
11
+ - Its physical database storage type.
12
+ - Its target type when used in a cast.
9
13
 
10
14
  For example, `numeric()` decodes to a TypeScript number, carries `SqlDecimal`,
11
15
  uses portable numeric storage, and has a logical decimal cast target. Read
@@ -36,14 +40,21 @@ declaration:
36
40
  import { nativeColumn, nativeStorage, table } from "qubu"
37
41
 
38
42
  const accounts = table("accounts", {
39
- handle: nativeColumn(nativeStorage("postgresql", 'citext COLLATE "C"')),
43
+ handle: nativeColumn(nativeStorage("postgresql", 'citext COLLATE "C"'), {
44
+ sqlType: "postgres.citext",
45
+ }),
40
46
  })
41
47
  ```
42
48
 
43
49
  `nativeStorage()` preserves the declaration text and freezes the descriptor. The
44
50
  `ColumnStorageOf`, `ColumnStorageTypeOf`, `ColumnStorageDialectOf`, and
45
51
  `ColumnStorageDeclarationOf` helpers read its metadata. Native storage is
46
- descriptive. It does not change selection, mutation, or query rendering.
52
+ descriptive.
53
+
54
+ The optional `sqlType` field is the runtime semantic domain passed
55
+ to adapters; provide it for custom domains because the compile-time SQL type is
56
+ not available at runtime. It does not change selection, mutation, or query
57
+ rendering.
47
58
 
48
59
  ## Render deterministic schema expressions
49
60
 
@@ -1,6 +1,6 @@
1
1
  # Tables and names
2
2
 
3
- > Define query-facing tables, keep their TypeScript identities stable, and control how fields become SQL names.
3
+ > Define tables and control how TypeScript field names map to database names.
4
4
 
5
5
  `table()` definitions describe the columns Qubu can select and write. They are
6
6
  not database introspection and they do not create or migrate a database.
@@ -1,6 +1,6 @@
1
1
  # SQL semantic types
2
2
 
3
- > Use SQL domains to constrain valid query composition without conflating database semantics with driver-decoded application values.
3
+ > Understand which SQL operations a column supports, even when its JavaScript type looks the same as another column’s.
4
4
 
5
5
  Qubu tracks four independent facts for a field or result expression:
6
6
 
@@ -11,7 +11,7 @@ Qubu tracks four independent facts for a field or result expression:
11
11
  | Nullability | Can the selected value be `null`? | `false` |
12
12
  | SQL domain | Which portable SQL operations may consume the expression? | `SqlText` or `SqlUuid` |
13
13
 
14
- The axes are deliberately separate. Both `text()` and `uuid()` decode to a
14
+ These facts are separate. Both `text()` and `uuid()` decode to a
15
15
  JavaScript `string`, but their SQL behavior differs. Likewise, two
16
16
  `timestamp()` definitions may share `SqlTimestamp` while a custom column uses
17
17
  different JavaScript output and write types for its driver.
@@ -111,12 +111,15 @@ target is vendor-specific.
111
111
 
112
112
  ## Known incompatibility is rejected
113
113
 
114
- Qubu checks capabilities and compatibility when it knows both SQL domains.
115
- Arithmetic and `SUM`/`AVG` require numeric-like expressions; text functions,
116
- concatenation, `LIKE`, and PostgreSQL `ILIKE` require text-like expressions;
117
- ordering and range comparisons require compatible ordering groups; and
118
- equality, `IN`, `CASE`, `COALESCE`, and set-operation fields require compatible
119
- equality groups. Boolean clauses require a boolean SQL domain.
114
+ When Qubu knows both SQL domains, it checks these rules:
115
+
116
+ - Arithmetic and `SUM`/`AVG` require numeric-like expressions.
117
+ - Text functions, concatenation, and pattern matching (`LIKE` or `ILIKE`)
118
+ require text-like expressions.
119
+ - Ordering and range comparisons require compatible ordering groups.
120
+ - Equality comparisons and `IN` require compatible equality groups. The same
121
+ rule applies to `CASE`, `COALESCE`, and set-operation fields.
122
+ - Boolean clauses require a boolean SQL domain.
120
123
 
121
124
  These checks model portable capability and group relationships, not every
122
125
  database's implicit casts. An expression accepted by one database after an