qubu 0.6.2 → 0.7.0

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 (73) hide show
  1. package/dist/codegen.d.mts +1 -1
  2. package/dist/codegen.mjs +1 -1
  3. package/dist/core.d.mts +1 -1
  4. package/dist/diff.d.mts +1 -1
  5. package/dist/index-DB07P2X8.d.mts +2 -0
  6. package/dist/index.d.mts +1 -1
  7. package/dist/introspection/mysql.d.mts +1 -1
  8. package/dist/introspection/postgres.d.mts +1 -1
  9. package/dist/introspection/sqlite.d.mts +1 -1
  10. package/dist/introspection.d.mts +3 -2
  11. package/dist/introspection.mjs +1 -1
  12. package/dist/mysql.d.mts +2 -2
  13. package/dist/postgres.d.mts +2 -2
  14. package/dist/schema.d.mts +1 -1
  15. package/dist/{serialize-Du2UPZMt.d.mts → serialize-Dg-Q55YQ.d.mts} +1 -1
  16. package/dist/snapshot/mysql.d.mts +2 -2
  17. package/dist/snapshot/postgres.d.mts +2 -2
  18. package/dist/snapshot/sqlite.d.mts +2 -2
  19. package/dist/{snapshot-mIb-Zzb5.mjs → snapshot-C1x2R-iK.mjs} +25 -9
  20. package/dist/snapshot.d.mts +3 -3
  21. package/dist/sqlite.d.mts +2 -2
  22. package/dist/{types-CiMvKi5V.d.mts → types-CdcYI0lU.d.mts} +1 -1
  23. package/dist/{types-Dqr4o2I1.d.mts → types-DcIiNePM.d.mts} +7 -0
  24. package/docs/dialects-and-execution.md +54 -25
  25. package/docs/getting-started.md +9 -9
  26. package/docs/guides/better-auth.md +16 -5
  27. package/docs/guides/compose-queries.md +21 -9
  28. package/docs/guides/drizzle.md +8 -3
  29. package/docs/guides/extensions/dialects.md +1 -1
  30. package/docs/guides/extensions/overview.md +1 -1
  31. package/docs/guides/extensions/sources-and-clauses.md +7 -3
  32. package/docs/guides/extensions/typed-expressions.md +25 -13
  33. package/docs/guides/extensions/unsafe-syntax.md +10 -6
  34. package/docs/guides/json.md +52 -27
  35. package/docs/guides/mutations.md +15 -6
  36. package/docs/guides/select/conditions.md +18 -11
  37. package/docs/guides/select/grouping-and-windows.md +5 -2
  38. package/docs/guides/select/ordering-and-pagination.md +5 -3
  39. package/docs/guides/select/overview.md +6 -3
  40. package/docs/guides/sql-templates.md +11 -5
  41. package/docs/guides/valtio-sync.md +11 -5
  42. package/docs/guides/vite-plugin.md +2 -2
  43. package/docs/index.md +24 -17
  44. package/docs/migrations/adapters.md +58 -22
  45. package/docs/migrations/adopt.md +331 -0
  46. package/docs/migrations/artifacts-and-policy.md +49 -20
  47. package/docs/migrations/index.md +18 -9
  48. package/docs/migrations/operations.md +60 -70
  49. package/docs/migrations/recovery.md +34 -17
  50. package/docs/query-model/fragments.md +13 -5
  51. package/docs/query-model/result-shapes.md +2 -2
  52. package/docs/query-model/source-scope.md +5 -3
  53. package/docs/reference/introspection-support.md +26 -19
  54. package/docs/reference/mysql-snapshot.md +19 -4
  55. package/docs/reference/postgres-snapshot.md +17 -4
  56. package/docs/reference/sqlite-snapshot.md +19 -2
  57. package/docs/reference/supported-surface.md +221 -85
  58. package/docs/schema/catalog-model.md +24 -7
  59. package/docs/schema/code-generation.md +40 -21
  60. package/docs/schema/columns-and-writes.md +21 -11
  61. package/docs/schema/constraints-and-indexes.md +12 -5
  62. package/docs/schema/ddl-emission.md +16 -5
  63. package/docs/schema/diff.md +12 -4
  64. package/docs/schema/introspection.md +47 -21
  65. package/docs/schema/migration-plans.md +18 -10
  66. package/docs/schema/snapshots.md +57 -29
  67. package/docs/schema/storage-and-schema-sql.md +10 -4
  68. package/docs/schema/tables-and-names.md +1 -1
  69. package/docs/sql-semantic-types.md +11 -8
  70. package/docs/troubleshooting.md +14 -6
  71. package/package.json +1 -1
  72. package/dist/index-CaxrMD1A.d.mts +0 -1
  73. package/docs/migrations/lotta-adoption.md +0 -50
@@ -1,6 +1,6 @@
1
1
  # Generate a schema from introspection
2
2
 
3
- > Turn one complete, non-lossy Snapshot v1 introspection result into a deterministic, machine-owned TypeScript schema module.
3
+ > Generate a TypeScript schema module from a complete introspection result with no omitted facts.
4
4
 
5
5
  Source generation is an optional capability exported from `qubu/codegen`. It
6
6
  is a pure handoff after introspection: it opens no connection, runs no catalog
@@ -33,12 +33,20 @@ returns data. A successful result contains deterministic `source` and every
33
33
  retained diagnostic. A failed result contains diagnostics and no partial
34
34
  source.
35
35
 
36
- The module exports one declaration for every ordinary Snapshot v1 table and
37
- one schema registry. It reconstructs physical names, exact native storage,
38
- column write behavior, defaults, generated and identity metadata, constraints,
39
- indexes, opaque predicates and expressions, and dialect extensions. Checks use
40
- `catalogCheck()`. Foreign keys use lazy `catalogForeignKey()` targets so
41
- forward declarations and cycles remain valid.
36
+ ### What the module contains
37
+
38
+ The module exports each ordinary Snapshot v1 table and one schema registry. It
39
+ reconstructs:
40
+
41
+ - Physical names and exact native storage.
42
+ - Column write behavior and defaults.
43
+ - Generated-column and identity metadata.
44
+ - Constraints and indexes.
45
+ - Opaque predicates and expressions.
46
+ - Dialect extensions.
47
+
48
+ Checks use `catalogCheck()`. Foreign keys use lazy `catalogForeignKey()` targets
49
+ so forward declarations and cycles remain valid.
42
50
 
43
51
  ## Adopt the generated identity baseline
44
52
 
@@ -100,11 +108,15 @@ diagnostics and no source.
100
108
 
101
109
  ## Diagnostics and source safety
102
110
 
103
- Generation rejects failed or lossy introspection, an altered snapshot that no
104
- longer matches its catalog, omitted Snapshot v1 facts, unresolved references,
105
- unsafe names, invalid mapping tokens, and data that cannot be represented
106
- without source injection. Existing introspection diagnostics stay attached to
107
- the result.
111
+ Generation fails when the input contains:
112
+
113
+ - Failed or lossy introspection.
114
+ - An edited snapshot that no longer matches its catalog.
115
+ - Omitted Snapshot v1 facts or unresolved references.
116
+ - Unsafe names or invalid mapping tokens.
117
+ - Data that cannot be printed safely as source.
118
+
119
+ Existing introspection diagnostics stay attached to the result.
108
120
 
109
121
  > [!IMPORTANT]
110
122
  > A database can allow a foreign key to reference a nullable `UNIQUE`
@@ -126,15 +138,22 @@ result contracts.
126
138
 
127
139
  ## Snapshot v1 boundary
128
140
 
129
- Generation covers ordinary Snapshot v1 tables in one namespace. Complete
130
- catalog families outside that model—views, materialized views, sequences,
131
- enums, domains, routines, triggers, partitions, policies, collations,
132
- extensions, comments, ownership, and retained opaque or deferred objects—are
133
- not emitted. Non-empty excluded families produce diagnostics so the generated
134
- module does not look complete by omission.
141
+ Generation covers ordinary Snapshot v1 tables in one namespace. It does not emit:
142
+
143
+ - Views and materialized views.
144
+ - Sequences, enums, and domains.
145
+ - Routines and triggers.
146
+ - Partitions and policies.
147
+ - Collations and extensions.
148
+ - Comments and ownership.
149
+ - Retained opaque or deferred objects.
150
+
151
+ If any excluded family is non-empty, generation reports diagnostics rather than
152
+ presenting the module as complete.
153
+
154
+ The caller handles file writes and driver integration. Generation does not
155
+ merge hand edits or run migrations.
135
156
 
136
- The entrypoint does not provide a CLI, filesystem ownership, live driver
137
- integration, multiple namespaces, runtime schema materialization, migrations,
138
- DDL, non-table object generation, or hand-edit merging. Use
157
+ Use
139
158
  [Database introspection](introspection.md) for the catalog boundary and
140
159
  [Canonical schema snapshots](snapshots.md) for the identity artifact.
@@ -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 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
@@ -61,6 +67,8 @@ 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 a canonical Snapshot v1 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"
@@ -127,6 +134,8 @@ 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
 
137
+ ### References and dialect metadata
138
+
130
139
  References to nested columns, constraints, and indexes include their owning
131
140
  table, view, or domain in the complete Snapshot v1 output. References to
132
141
  top-level objects remain unscoped, and table-local backing relationships must
@@ -135,21 +144,34 @@ native storage, and typed expressions are checked against the selected
135
144
  snapshot dialect; arbitrary catalog `data` and `configuration` JSON remains
136
145
  opaque.
137
146
 
138
- PostgreSQL readers expose views, materialized views, sequences, enums, domains,
139
- collations, routines, triggers, policies, partitions, extensions, comments,
140
- and ownership as typed complete catalog records. `mapCatalogToCompleteSnapshot`
141
- retains those records in Snapshot v1. `mapCatalogToSnapshot()` uses the same
142
- complete mapping and does not fabricate these objects into tables. If a
143
- PostgreSQL catalog row lacks the evidence needed for
144
- safe normalization, the reader retains a deferred or opaque record and emits a
145
- diagnostic.
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
146
167
 
147
168
  SQLite readers expose recoverable views and triggers as typed complete records.
148
169
  They retain virtual and shadow tables as deferred objects, and keep attached
149
170
  databases outside the selected namespace as opaque boundary records. SQLite
150
171
  declared types, derived affinity, generated expressions, rowid identity, and
151
- `AUTOINCREMENT` stay tagged with SQLite dialect metadata. When an attached
152
- 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
153
175
  limited to the fixed `main` and `temp` catalog statements, so the reader marks
154
176
  the catalog visibility as limited instead of combining namespaces.
155
177
 
@@ -191,11 +213,15 @@ MySQL families and their opaque or deferred boundaries.
191
213
 
192
214
  ## Diagnostics and safety
193
215
 
194
- Diagnostics include a severity, stable code, catalog path, physical reference
195
- when available, and a remediation hint. They distinguish connection/query
196
- failures, permission limits, unsupported products or versions, unresolved
197
- references, expression recovery failures, unmodeled objects, and lossy
198
- 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.
199
225
 
200
226
  Introspection is read-only from Qubu's perspective. Do not pass credentials or
201
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,21 +14,33 @@ const snapshot = createSchemaSnapshot(appSchema)
14
14
  const json = encodeSchemaSnapshot(snapshot)
15
15
  ```
16
16
 
17
- The Snapshot v1 envelope contains a format version, an independently versioned
18
- dialect extension, a versioned naming-policy description, a namespace,
19
- capability facts, and arrays for every supported object family. Tables,
20
- columns, constraints, and indexes are sorted by stable logical ID. Physical
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
21
29
  names are values in the snapshot, not identities:
22
30
  changing a physical name does not change the TypeScript field or metadata key.
23
31
 
24
- Snapshot data is deliberately not executable Qubu state. Expressions are
25
- parameter-free data records, and decoding never creates tables, column
26
- references, or render closures. The neutral fallback renders branded built-in
27
- 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
28
36
  that hook with its own literal and expression policy. An explicitly unsafe
29
37
  expression retains its dialect tag and is rejected when it does not match the
30
38
  selected snapshot dialect.
31
39
 
40
+ ## Decode and validate a snapshot
41
+
42
+ Use `decodeSchemaSnapshot()` to read saved JSON and inspect validation failures:
43
+
32
44
  ```ts
33
45
  import { decodeSchemaSnapshot } from "qubu/snapshot"
34
46
 
@@ -46,6 +58,8 @@ dialect metadata, and broken foreign-key or column references as structured
46
58
  diagnostics. It does not call `process.exit()` and has no runtime validation
47
59
  library dependency.
48
60
 
61
+ ### References and ownership
62
+
49
63
  References to nested columns, constraints, and indexes carry an explicit
50
64
  `owner: { kind, id }` scope. Table columns, constraints, and indexes are owned
51
65
  by their table; view columns are owned by their view; and domain constraints
@@ -55,6 +69,8 @@ checked only in typed snapshot fields. Extension `data`, `configuration`, and
55
69
  other opaque JSON payloads are retained as data and are not interpreted as
56
70
  typed metadata.
57
71
 
72
+ ### Content fingerprints
73
+
58
74
  `schemaSnapshotFingerprint()` computes a deterministic content fingerprint from canonical
59
75
  JSON. The fingerprint is useful for cache keys and fixture assertions only. It is not
60
76
  an entity identity, a rename marker, or migration lineage.
@@ -69,15 +85,23 @@ and advertised capabilities while adding schema encoders and validation under
69
85
  second query dialect. The schema snapshot format version remains independent
70
86
  from the dialect identity.
71
87
 
72
- The common traversal owns logical IDs, fixed property order, canonical sorting,
73
- portable constraints, cross-reference checks, and the immutable snapshot
74
- envelope. A dialect adapter owns physical storage mapping, SQL literal and
75
- expression encoding, dialect extensions, capability checks, and any dialect
76
- naming policy. PostgreSQL, SQLite, and MySQL adapters can implement
77
- `SchemaSnapshotAdapter` without duplicating traversal or decoder rules.
78
- The neutral API stays at `qubu/snapshot`; built-in dialect adapters have
79
- dedicated subpaths so importing neutral snapshot utilities does not widen that
80
- 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:
81
105
 
82
106
  ```ts
83
107
  import { createSchemaSnapshot } from "qubu/snapshot"
@@ -97,17 +121,21 @@ matrix](../reference/mysql-snapshot.md). Its query and snapshot dialects both
97
121
  use `mysql`, while MySQL-only `ON UPDATE` and `AUTO_INCREMENT` details remain
98
122
  inside the column and identity metadata they describe.
99
123
 
100
- Snapshot serialization remains separate from database introspection,
101
- comparison, rename resolution, migration planning, and DDL emission. The
102
- optional `qubu/introspection` entrypoint can produce the same canonical
103
- Snapshot v1 data from a user-owned catalog connection. The complete normalized
104
- catalog can also be encoded with the explicit complete-snapshot APIs described
105
- in [the catalog model](catalog-model.md).
106
- Readers and connection lifecycle do not belong to this pure serialization
107
- layer. Diffing consumes Snapshot v1. Resolved diffs feed
108
- migration plans, and approved plans feed DDL emission. The package-wide
109
- [ownership map](../reference/supported-surface.md#ownership-boundary) keeps
110
- 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.
111
139
 
112
140
  The optional [schema source generator](code-generation.md) consumes a complete,
113
141
  non-lossy introspection result and makes its generated schema the next identity
@@ -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
@@ -45,7 +49,9 @@ const accounts = table("accounts", {
45
49
  `nativeStorage()` preserves the declaration text and freezes the descriptor. The
46
50
  `ColumnStorageOf`, `ColumnStorageTypeOf`, `ColumnStorageDialectOf`, and
47
51
  `ColumnStorageDeclarationOf` helpers read its metadata. Native storage is
48
- descriptive. The optional `sqlType` field is the runtime semantic domain passed
52
+ descriptive.
53
+
54
+ The optional `sqlType` field is the runtime semantic domain passed
49
55
  to adapters; provide it for custom domains because the compile-time SQL type is
50
56
  not available at runtime. It does not change selection, mutation, or query
51
57
  rendering.
@@ -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.