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.
- package/dist/codegen.d.mts +1 -1
- package/dist/codegen.mjs +1 -1
- package/dist/core.d.mts +1 -1
- package/dist/diff.d.mts +1 -1
- package/dist/index-DB07P2X8.d.mts +2 -0
- package/dist/index.d.mts +1 -1
- package/dist/introspection/mysql.d.mts +1 -1
- package/dist/introspection/postgres.d.mts +1 -1
- package/dist/introspection/sqlite.d.mts +1 -1
- package/dist/introspection.d.mts +3 -2
- package/dist/introspection.mjs +1 -1
- package/dist/mysql.d.mts +2 -2
- package/dist/postgres.d.mts +2 -2
- package/dist/schema.d.mts +1 -1
- package/dist/{serialize-Du2UPZMt.d.mts → serialize-Dg-Q55YQ.d.mts} +1 -1
- package/dist/snapshot/mysql.d.mts +2 -2
- package/dist/snapshot/postgres.d.mts +2 -2
- package/dist/snapshot/sqlite.d.mts +2 -2
- package/dist/{snapshot-mIb-Zzb5.mjs → snapshot-C1x2R-iK.mjs} +25 -9
- package/dist/snapshot.d.mts +3 -3
- package/dist/sqlite.d.mts +2 -2
- package/dist/{types-CiMvKi5V.d.mts → types-CdcYI0lU.d.mts} +1 -1
- package/dist/{types-Dqr4o2I1.d.mts → types-DcIiNePM.d.mts} +7 -0
- package/docs/dialects-and-execution.md +54 -25
- package/docs/getting-started.md +9 -9
- package/docs/guides/better-auth.md +16 -5
- package/docs/guides/compose-queries.md +21 -9
- package/docs/guides/drizzle.md +8 -3
- package/docs/guides/extensions/dialects.md +1 -1
- package/docs/guides/extensions/overview.md +1 -1
- package/docs/guides/extensions/sources-and-clauses.md +7 -3
- package/docs/guides/extensions/typed-expressions.md +25 -13
- package/docs/guides/extensions/unsafe-syntax.md +10 -6
- package/docs/guides/json.md +52 -27
- package/docs/guides/mutations.md +15 -6
- package/docs/guides/select/conditions.md +18 -11
- package/docs/guides/select/grouping-and-windows.md +5 -2
- package/docs/guides/select/ordering-and-pagination.md +5 -3
- package/docs/guides/select/overview.md +6 -3
- package/docs/guides/sql-templates.md +11 -5
- package/docs/guides/valtio-sync.md +11 -5
- package/docs/guides/vite-plugin.md +2 -2
- package/docs/index.md +24 -17
- package/docs/migrations/adapters.md +58 -22
- package/docs/migrations/adopt.md +331 -0
- package/docs/migrations/artifacts-and-policy.md +49 -20
- package/docs/migrations/index.md +18 -9
- package/docs/migrations/operations.md +60 -70
- package/docs/migrations/recovery.md +34 -17
- package/docs/query-model/fragments.md +13 -5
- package/docs/query-model/result-shapes.md +2 -2
- package/docs/query-model/source-scope.md +5 -3
- package/docs/reference/introspection-support.md +26 -19
- package/docs/reference/mysql-snapshot.md +19 -4
- package/docs/reference/postgres-snapshot.md +17 -4
- package/docs/reference/sqlite-snapshot.md +19 -2
- package/docs/reference/supported-surface.md +221 -85
- package/docs/schema/catalog-model.md +24 -7
- package/docs/schema/code-generation.md +40 -21
- package/docs/schema/columns-and-writes.md +21 -11
- package/docs/schema/constraints-and-indexes.md +12 -5
- package/docs/schema/ddl-emission.md +16 -5
- package/docs/schema/diff.md +12 -4
- package/docs/schema/introspection.md +47 -21
- package/docs/schema/migration-plans.md +18 -10
- package/docs/schema/snapshots.md +57 -29
- package/docs/schema/storage-and-schema-sql.md +10 -4
- package/docs/schema/tables-and-names.md +1 -1
- package/docs/sql-semantic-types.md +11 -8
- package/docs/troubleshooting.md +14 -6
- package/package.json +1 -1
- package/dist/index-CaxrMD1A.d.mts +0 -1
- package/docs/migrations/lotta-adoption.md +0 -50
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Compose SQL templates
|
|
2
2
|
|
|
3
|
-
>
|
|
3
|
+
> Write SQL templates that bind values safely and preserve type information from Qubu expressions.
|
|
4
4
|
|
|
5
5
|
## Bind every runtime value
|
|
6
6
|
|
|
@@ -108,10 +108,16 @@ reaches `unsafeExpression()`.
|
|
|
108
108
|
|
|
109
109
|
## Preserve metadata through interpolated fragments
|
|
110
110
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
111
|
+
Qubu fragment substitutions pass these facts to the template:
|
|
112
|
+
|
|
113
|
+
- Required sources.
|
|
114
|
+
- Possible nulls from outer joins.
|
|
115
|
+
- Grouping dependencies.
|
|
116
|
+
- Aggregate and window state.
|
|
117
|
+
- Subquery state.
|
|
118
|
+
- Required dialect capabilities.
|
|
119
|
+
|
|
120
|
+
Qubu does not infer these facts from template text.
|
|
115
121
|
|
|
116
122
|
Use a built-in expression as the substitution when its semantics matter:
|
|
117
123
|
|
|
@@ -102,11 +102,17 @@ const handlers = applyOpsWithQubu<SyncContext>({
|
|
|
102
102
|
export const sync = valtioSync({ schema: { todos }, handlers })
|
|
103
103
|
```
|
|
104
104
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
105
|
+
Each mutation runs these steps in one Qubu transaction:
|
|
106
|
+
|
|
107
|
+
1. Check authorization.
|
|
108
|
+
2. Check conflicts.
|
|
109
|
+
3. Apply the application mutation.
|
|
110
|
+
4. Write the sync event with `syncEvents.write()`.
|
|
111
|
+
|
|
112
|
+
If any step fails, the adapter rolls the transaction back.
|
|
113
|
+
|
|
114
|
+
The event sequence becomes `serverVersion` unless the mutation handler returns
|
|
115
|
+
an explicit version. Read handlers pass through unchanged.
|
|
110
116
|
|
|
111
117
|
The integration does not define persistence tables, import Drizzle, or execute
|
|
112
118
|
driver APIs. The application owns table design, authorization, conflict policy,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Vite compiler hint
|
|
2
2
|
|
|
3
|
-
>
|
|
3
|
+
> Use a "use qubu" directive to add the Qubu imports a module needs.
|
|
4
4
|
|
|
5
5
|
The optional Vite plugin recognizes the `"use qubu"` directive and injects only
|
|
6
6
|
the referenced named imports from the configured module.
|
|
@@ -33,7 +33,7 @@ ambient value and type declarations for the TypeScript compiler.
|
|
|
33
33
|
|
|
34
34
|
## Mark a module explicitly
|
|
35
35
|
|
|
36
|
-
Put the directive
|
|
36
|
+
Put the directive at the start of the module, alongside any other directives:
|
|
37
37
|
|
|
38
38
|
```ts
|
|
39
39
|
"use qubu"
|
package/docs/index.md
CHANGED
|
@@ -1,11 +1,17 @@
|
|
|
1
1
|
# Qubu
|
|
2
2
|
|
|
3
|
-
> Build
|
|
3
|
+
> Build SQL queries from typed tables and reusable values.
|
|
4
4
|
|
|
5
|
-
Qubu builds SQL from values.
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
5
|
+
Qubu builds SQL from reusable values. You can combine query parts without
|
|
6
|
+
changing a shared query-builder object.
|
|
7
|
+
|
|
8
|
+
TypeScript checks:
|
|
9
|
+
|
|
10
|
+
- Which fields the query returns and their types.
|
|
11
|
+
- Whether each column belongs to a source in the query.
|
|
12
|
+
- Whether a result can be `null`.
|
|
13
|
+
|
|
14
|
+
Render a query to inspect its SQL text and ordered parameters.
|
|
9
15
|
|
|
10
16
|
The preferred source style names each projected field and writes the final
|
|
11
17
|
`select()` clauses in SQL order. Clause values remain order-independent at
|
|
@@ -17,7 +23,7 @@ and placed in that final call where it reads best.
|
|
|
17
23
|
If this is your first query, follow [Getting started](getting-started.md) to
|
|
18
24
|
define a table, build a `SELECT`, and inspect its SQL and parameters.
|
|
19
25
|
|
|
20
|
-
##
|
|
26
|
+
## Build and run queries
|
|
21
27
|
|
|
22
28
|
- [Build a `SELECT`](guides/select/overview.md) with projections, joins,
|
|
23
29
|
predicates, ordering, and grouping.
|
|
@@ -36,18 +42,18 @@ define a table, build a `SELECT`, and inspect its SQL and parameters.
|
|
|
36
42
|
- [Query nested JSON](guides/json.md) or read scalars from structured paths.
|
|
37
43
|
- [Enable the Vite compiler hint](guides/vite-plugin.md) when query modules
|
|
38
44
|
should opt into named imports through a directive.
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
45
|
+
|
|
46
|
+
## Work with schemas and migrations
|
|
47
|
+
|
|
48
|
+
- [Inspect an existing database](schema/introspection.md) using a connection you provide.
|
|
49
|
+
- [Generate a schema module](schema/code-generation.md) from an introspection result without losing schema facts.
|
|
43
50
|
- [Compare snapshots](schema/diff.md) with explicit rename hints and reviewable
|
|
44
51
|
safety diagnostics.
|
|
45
52
|
- [Build migration plans](schema/migration-plans.md) as reviewed, deterministic
|
|
46
53
|
data before DDL emission.
|
|
47
54
|
- [Emit DDL](schema/ddl-emission.md) from an approved migration plan without
|
|
48
55
|
handing Qubu a database connection.
|
|
49
|
-
- [Operate migrations](migrations/index.md) with
|
|
50
|
-
adapter profiles, baselines, a portable executor, and explicit recovery.
|
|
56
|
+
- [Operate migrations](migrations/index.md) with reviewed migration files and a verified adapter.
|
|
51
57
|
|
|
52
58
|
## The query pipeline
|
|
53
59
|
|
|
@@ -106,8 +112,9 @@ render(query)
|
|
|
106
112
|
The inferred row is `{ id: number; name: string }`. The value `7` stays out
|
|
107
113
|
of the SQL text and appears in the `parameters` array in placeholder order.
|
|
108
114
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
starts from common errors and
|
|
115
|
+
## Find support details
|
|
116
|
+
|
|
117
|
+
- [Supported features](reference/supported-surface.md) lists package imports and
|
|
118
|
+
explains which responsibilities stay with your application.
|
|
119
|
+
- [Troubleshooting](troubleshooting.md) starts from common errors and explains
|
|
120
|
+
how to fix them.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Adapter capability profiles
|
|
2
2
|
|
|
3
|
-
>
|
|
3
|
+
> Choose a migration adapter based on what its driver and environment have been tested to support.
|
|
4
4
|
|
|
5
5
|
Every executable migration adapter opens a migration session and
|
|
6
6
|
advertises the exact behavior the executor may use:
|
|
@@ -25,12 +25,12 @@ executor never treats one as proof of the other.
|
|
|
25
25
|
|
|
26
26
|
## Trusted migration SQL
|
|
27
27
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
28
|
+
Qubu validates program structure and adapter capabilities. It trusts the SQL
|
|
29
|
+
you supply, including SQL conditions, and does not parse it for safety.
|
|
30
|
+
|
|
31
|
+
Migration SQL must preserve the executor’s transactions, connection settings,
|
|
32
|
+
and journal state. For example, an explicit `COMMIT` can apply schema changes
|
|
33
|
+
without their journal record, breaking the executor’s recovery guarantees.
|
|
34
34
|
|
|
35
35
|
## Current profiles
|
|
36
36
|
|
|
@@ -44,11 +44,32 @@ The following stable profiles have live conformance coverage in this checkout:
|
|
|
44
44
|
| `@qubu/adapter-postgresjs/migration` | PostgreSQL | required, optional, forbidden | none, exclusive | checkpointed | Reserves and releases one connection |
|
|
45
45
|
| `@qubu/adapter-pglite/migration` | PostgreSQL | required, optional, forbidden | none, exclusive | checkpointed | Uses the database query queue as the pinned session |
|
|
46
46
|
|
|
47
|
-
All five support
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
47
|
+
All five support:
|
|
48
|
+
|
|
49
|
+
- Every current tagged parameter kind.
|
|
50
|
+
- A journal and migrator lease stored in the database.
|
|
51
|
+
- An atomic update of the applied record and journal head.
|
|
52
|
+
- A recovery-required result when a commit’s outcome is uncertain.
|
|
53
|
+
|
|
54
|
+
Parameter kinds are:
|
|
55
|
+
|
|
56
|
+
- `null` and `boolean`.
|
|
57
|
+
- `string` and `number`.
|
|
58
|
+
- `bigint` and `bytes`.
|
|
59
|
+
- `json`.
|
|
60
|
+
|
|
61
|
+
The artifact’s server, feature, transaction, and lock requirements must still
|
|
62
|
+
match the adapter.
|
|
63
|
+
|
|
64
|
+
### Unavailable profiles
|
|
65
|
+
|
|
66
|
+
MySQL has a [basic SQL migration runner](../../adapters/mysql2/README.md#run-sql-migrations),
|
|
67
|
+
`migrate`, which executes pending statements and records completed
|
|
68
|
+
migrations. It runs independently of the shared executor and its stronger
|
|
69
|
+
capability contract. The MySQL profile below remains unavailable; possible
|
|
70
|
+
improvements are tracked in [issue #1](https://github.com/aleclarson/qubu/issues/1).
|
|
71
|
+
The same entry point exports `baselineAdapter` for [reviewed MySQL adoption](adopt.md#mysql)
|
|
72
|
+
through the shared CLI and `@qubu/migrate/baseline` API.
|
|
52
73
|
|
|
53
74
|
These exported profiles are unavailable and must not be passed to the
|
|
54
75
|
executor:
|
|
@@ -62,16 +83,18 @@ executor:
|
|
|
62
83
|
Unavailable profiles expose `reason` and `missingCapabilities`; they do not
|
|
63
84
|
fall back to a generic executor.
|
|
64
85
|
|
|
86
|
+
### Configure libSQL inspection
|
|
87
|
+
|
|
65
88
|
For libSQL, let the migration entrypoint exclude all reserved journal objects
|
|
66
89
|
during strict inspection:
|
|
67
90
|
|
|
68
91
|
```ts
|
|
69
92
|
import { createClient } from "@libsql/client"
|
|
70
|
-
import {
|
|
93
|
+
import { migrationAdapter, readMigrationSnapshot } from "@qubu/adapter-libsql/migration"
|
|
71
94
|
|
|
72
95
|
const client = createClient({ url: process.env.DATABASE_URL! })
|
|
73
|
-
const adapter =
|
|
74
|
-
readSnapshot:
|
|
96
|
+
const adapter = migrationAdapter(client, {
|
|
97
|
+
readSnapshot: readMigrationSnapshot,
|
|
75
98
|
})
|
|
76
99
|
```
|
|
77
100
|
|
|
@@ -84,7 +107,9 @@ Each executable artifact must contain exactly one phase and an embedded before
|
|
|
84
107
|
snapshot. The adapter submits its statements, SQL assertions, applied-history
|
|
85
108
|
record, head update, and terminal attempt state in one `client.migrate()` call.
|
|
86
109
|
For example, creating a table and recording that migration either both commit
|
|
87
|
-
or both roll back.
|
|
110
|
+
or both roll back.
|
|
111
|
+
|
|
112
|
+
Multiple artifacts are separate batches; earlier successful
|
|
88
113
|
artifacts remain applied if a later one fails.
|
|
89
114
|
|
|
90
115
|
Preparation reads the schema in a read transaction. The submitted batch checks
|
|
@@ -92,13 +117,24 @@ that the catalog still matches that inspection, the lease is still owned, and
|
|
|
92
117
|
the head still equals the expected parent. Foreign-key validation runs before
|
|
93
118
|
commit because libSQL temporarily disables enforcement during `migrate()`.
|
|
94
119
|
|
|
120
|
+
### Supported conditions
|
|
121
|
+
|
|
95
122
|
Schema fingerprint and property preconditions are checked against the embedded
|
|
96
|
-
before snapshot
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
123
|
+
before snapshot. Preparation verifies its physical facts, and the batch
|
|
124
|
+
asserts that the catalog still matches.
|
|
125
|
+
|
|
126
|
+
Object-presence and scalar SQL checks run inside the batch. Postconditions
|
|
127
|
+
must use either:
|
|
128
|
+
|
|
129
|
+
- Object-presence or absence checks without fingerprints.
|
|
130
|
+
- Scalar SQL checks returning `1`.
|
|
131
|
+
|
|
132
|
+
Unsupported conditions and multiple phases are rejected.
|
|
133
|
+
|
|
134
|
+
SQL is passed to the driver without safety validation. Each program entry
|
|
135
|
+
must follow the driver’s statement contract.
|
|
136
|
+
|
|
137
|
+
### Crashes and uncertain outcomes
|
|
102
138
|
|
|
103
139
|
The database-row lease has no expiry or heartbeat. A process crash can leave
|
|
104
140
|
it held; ownership must be resolved before another runner can proceed. A lost
|
|
@@ -0,0 +1,331 @@
|
|
|
1
|
+
# Adopt an existing database
|
|
2
|
+
|
|
3
|
+
> Capture and accept the live schema as migration history's starting point, then
|
|
4
|
+
> reconcile it with the application's desired schema separately.
|
|
5
|
+
|
|
6
|
+
If the live `game` table lacks `manifest`, the captured baseline must also lack
|
|
7
|
+
it. Adoption changes no application schema/data and does not certify that the
|
|
8
|
+
desired application version can run. Keep incompatible code stopped until a
|
|
9
|
+
separate reviewed migration reconciles the schema.
|
|
10
|
+
|
|
11
|
+
## Choose an adapter
|
|
12
|
+
|
|
13
|
+
All supported adoption adapters use the same [CLI](#shared-cli) and
|
|
14
|
+
[API](#shared-api). Each checks an empty artifact repository and its own history.
|
|
15
|
+
|
|
16
|
+
| Database and adapter | History storage | Coordination |
|
|
17
|
+
| ----------------------- | -------------------------- | ---------------------------------------------------- |
|
|
18
|
+
| SQLite through `libsql` | Migration journal | Adapter lease coordinates participating Qubu runners |
|
|
19
|
+
| PostgreSQL through `pg` | Migration journal | Pinned client and advisory lease |
|
|
20
|
+
| MySQL through `mysql2` | `__qubu_mysql2_migrations` | Caller serializes runners; no database lease |
|
|
21
|
+
|
|
22
|
+
Adoption does not require migration execution capabilities. MySQL supports the
|
|
23
|
+
shared adoption commands while retaining its basic SQL runner for subsequent
|
|
24
|
+
migrations. See [adapter profiles](adapters.md) for execution contracts.
|
|
25
|
+
|
|
26
|
+
Configure the desired application Snapshot v1 as the managed `scope`
|
|
27
|
+
(`config.snapshot` for the CLI). Preserve the same database, namespace, adapter,
|
|
28
|
+
and original scope through acceptance, including tables that are currently
|
|
29
|
+
absent. A candidate does not persist that original selection or bind itself to
|
|
30
|
+
a particular database. Verify the connection independently; reported selectors
|
|
31
|
+
do not prove database identity. Keep credentials out of snapshots and metadata.
|
|
32
|
+
|
|
33
|
+
### Inspection scope
|
|
34
|
+
|
|
35
|
+
Standard readers select managed tables by their expected physical names and
|
|
36
|
+
preserve logical identities. Expected facts never fill missing tables or columns.
|
|
37
|
+
Other live tables are reported as unmanaged.
|
|
38
|
+
|
|
39
|
+
| Adapter | Connection and namespace | Other inspected objects |
|
|
40
|
+
| -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
41
|
+
| `libsql` | Configure the existing database as in [CLI configuration](operations.md#configuration) | Non-table objects retain the adapter's inspection policy; strict inspection can reject unsupported facts outside selected tables |
|
|
42
|
+
| `pg` | Connected `Client` or acquired `PoolClient`; keep `search_path` equal to the scope's existing namespace; see [PostgreSQL configuration](#postgresql-configuration) | Excluded tables take their sequences, triggers, policies, comments, and ownership with them. Namespace-level views, free sequences, enums, domains, routines, extensions, and opaque evidence remain included |
|
|
43
|
+
| `mysql2` | Dedicated connection, autocommit enabled, no active transaction; the selected database must equal the scope's database | Excluded tables take attached triggers, partitions, and comments with them. Namespace-level views, routines, used collations, and opaque evidence remain included |
|
|
44
|
+
|
|
45
|
+
Readers exclude reserved `__qubu_migration_` objects and their attached metadata;
|
|
46
|
+
MySQL also excludes `__qubu_mysql2_migrations`. Strict catalog failures remain
|
|
47
|
+
errors. A managed reference to an excluded object can require a broader scope.
|
|
48
|
+
|
|
49
|
+
The adapters expose `readMigrationSnapshot` directly. Without an expected scope,
|
|
50
|
+
the `pg` reader inspects non-journal objects in `public`; the `mysql2` reader uses
|
|
51
|
+
the selected database. Use `captureBaseline` for adoption so journal setup
|
|
52
|
+
precedes inspection, including any catalog facts introduced by that setup.
|
|
53
|
+
|
|
54
|
+
## Capture, review, and verify
|
|
55
|
+
|
|
56
|
+
1. **Capture** the actual managed schema to a new candidate file. The candidate
|
|
57
|
+
is an ordinary Snapshot v1, not a migration artifact. Keep it outside the
|
|
58
|
+
shared artifact directory and refuse to overwrite an existing candidate.
|
|
59
|
+
2. **Review** its tables, columns, defaults, constraints, indexes, comments,
|
|
60
|
+
dialect facts, included namespace objects, and reported unmanaged tables.
|
|
61
|
+
3. **Preflight** the explicitly reviewed file against a fresh inspection of the
|
|
62
|
+
original scope. Require empty history and an identical canonical snapshot,
|
|
63
|
+
including metadata. Changed facts or a newly appeared managed table block
|
|
64
|
+
acceptance. Investigate, recapture, and review; do not patch expected facts
|
|
65
|
+
into the candidate to hide differences.
|
|
66
|
+
4. **Accept** only after verifying the acknowledgments below. Acceptance repeats
|
|
67
|
+
preflight and records a non-executable baseline at sequence zero with a null
|
|
68
|
+
parent. Preserve the returned or written artifact.
|
|
69
|
+
|
|
70
|
+
Capture and preflight record no baseline and change no application schema/data.
|
|
71
|
+
Session setup may initialize Qubu history tables and perform lease bookkeeping.
|
|
72
|
+
Stop other migrators and prevent concurrent DDL throughout cutover. Leases
|
|
73
|
+
coordinate participating Qubu runners, not every possible schema writer.
|
|
74
|
+
Preflight needs no acceptance confirmations. Comparison failures include
|
|
75
|
+
diagnostics and actual snapshot evidence when available.
|
|
76
|
+
|
|
77
|
+
### Acceptance acknowledgments
|
|
78
|
+
|
|
79
|
+
All seven facts must be true. The application acknowledgment means incompatible
|
|
80
|
+
code remains stopped; it does not assert that desired code is already compatible.
|
|
81
|
+
|
|
82
|
+
| CLI confirmation | What the operator verifies |
|
|
83
|
+
| ------------------------------------ | ---------------------------------------------------------------------------------- |
|
|
84
|
+
| `database-target` | The configured connection targets the intended database and environment |
|
|
85
|
+
| `snapshot-source` | The selected candidate was captured and reviewed using the unchanged managed scope |
|
|
86
|
+
| `zero-managed-drift` | Fresh strict inspection has zero differences from the reviewed candidate |
|
|
87
|
+
| `backup-restore-ready` | Backup and restore procedures are ready |
|
|
88
|
+
| `other-migrators-stopped` | Other runners are stopped and concurrent DDL is prevented |
|
|
89
|
+
| `incompatible-application-prevented` | Incompatible code will remain stopped until reconciliation is complete |
|
|
90
|
+
| `legacy-history-cutover` | The team accepts this baseline as the new lineage start |
|
|
91
|
+
|
|
92
|
+
## Shared CLI
|
|
93
|
+
|
|
94
|
+
Install `@qubu/cli`, `@qubu/migrate`, and the selected adapter and driver.
|
|
95
|
+
Configure the [CLI](operations.md#configuration) with the existing database,
|
|
96
|
+
desired application snapshot, and an empty artifact directory. Standard `libsql`
|
|
97
|
+
and `pg` migration adapters provide strict inspection; intentional `readSnapshot`
|
|
98
|
+
overrides remain caller-owned. The CLI accepts either a migration adapter or an
|
|
99
|
+
adoption-only `baselineAdapter` in `config.adapter`. See the connection examples
|
|
100
|
+
for [PostgreSQL](#postgresql-configuration) and [MySQL](#mysql).
|
|
101
|
+
|
|
102
|
+
Capture and review:
|
|
103
|
+
|
|
104
|
+
```bash
|
|
105
|
+
qubu migrate baseline-capture --out ./baseline-candidate.json \
|
|
106
|
+
--config ./qubu.config.js --format json
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
The CLI refuses to overwrite the file and reports the config path, environment,
|
|
110
|
+
dialect, namespace, configured managed tables, included tables, and exclusions.
|
|
111
|
+
After reviewing that output and candidate, run preflight:
|
|
112
|
+
|
|
113
|
+
```bash
|
|
114
|
+
qubu migrate baseline initial --candidate ./baseline-candidate.json \
|
|
115
|
+
--config ./qubu.config.js --dry-run --format json
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
Once all [acknowledgments](#acceptance-acknowledgments) are verified, accept:
|
|
119
|
+
|
|
120
|
+
```bash
|
|
121
|
+
qubu migrate baseline initial --candidate ./baseline-candidate.json \
|
|
122
|
+
--config ./qubu.config.js \
|
|
123
|
+
--confirm database-target \
|
|
124
|
+
--confirm snapshot-source \
|
|
125
|
+
--confirm zero-managed-drift \
|
|
126
|
+
--confirm backup-restore-ready \
|
|
127
|
+
--confirm other-migrators-stopped \
|
|
128
|
+
--confirm incompatible-application-prevented \
|
|
129
|
+
--confirm legacy-history-cutover \
|
|
130
|
+
--format json --non-interactive
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
### Shared API
|
|
134
|
+
|
|
135
|
+
Import adoption operations from `@qubu/migrate/baseline` and `baselineAdapter`
|
|
136
|
+
from the selected driver's `/migration` entry point. For example, with a
|
|
137
|
+
dedicated MySQL connection:
|
|
138
|
+
|
|
139
|
+
```ts
|
|
140
|
+
import { baselineAdapter } from "@qubu/adapter-mysql2/migration"
|
|
141
|
+
import { captureBaseline } from "@qubu/migrate/baseline"
|
|
142
|
+
import { encodeSchemaSnapshot } from "qubu/snapshot"
|
|
143
|
+
import { writeFile } from "node:fs/promises"
|
|
144
|
+
|
|
145
|
+
const adapter = baselineAdapter(connection)
|
|
146
|
+
const inspection = await captureBaseline({ adapter, scope: desiredSnapshot })
|
|
147
|
+
await writeFile("baseline-candidate.json", encodeSchemaSnapshot(inspection.snapshot), {
|
|
148
|
+
flag: "wx",
|
|
149
|
+
})
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
Review the candidate before continuing. Reuse the same database and original
|
|
153
|
+
scope, and reload the reviewed file for preflight and acceptance:
|
|
154
|
+
|
|
155
|
+
```ts
|
|
156
|
+
import { readFile, writeFile } from "node:fs/promises"
|
|
157
|
+
import { preflightBaseline, createBaseline } from "@qubu/migrate/baseline"
|
|
158
|
+
import { encodeBaselineArtifact } from "@qubu/migrate/artifact"
|
|
159
|
+
import { assertSchemaSnapshot } from "qubu/snapshot"
|
|
160
|
+
|
|
161
|
+
const candidate = assertSchemaSnapshot(await readFile("baseline-candidate.json", "utf8"))
|
|
162
|
+
const input = { adapter, scope: desiredSnapshot, candidate, repository: [] }
|
|
163
|
+
await preflightBaseline(input)
|
|
164
|
+
|
|
165
|
+
const { artifact } = await createBaseline({
|
|
166
|
+
...input,
|
|
167
|
+
id: "initial",
|
|
168
|
+
provenance: { source: "my-service" },
|
|
169
|
+
confirmation: {
|
|
170
|
+
databaseTargetVerified: true,
|
|
171
|
+
snapshotSourceVerified: true,
|
|
172
|
+
zeroManagedDriftVerified: true,
|
|
173
|
+
backupRestoreReady: true,
|
|
174
|
+
otherMigratorsStopped: true,
|
|
175
|
+
incompatibleApplicationPrevented: true,
|
|
176
|
+
legacyHistoryCutoverAccepted: true,
|
|
177
|
+
},
|
|
178
|
+
})
|
|
179
|
+
await writeFile("baseline-accepted.json", encodeBaselineArtifact(artifact), { flag: "wx" })
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
Supply the real artifact repository when one exists; `[]` represents an empty
|
|
183
|
+
repository. The adapter separately verifies its database history. The caller
|
|
184
|
+
owns connection cleanup, including after failed capture or acceptance.
|
|
185
|
+
|
|
186
|
+
Existing custom migration adapters can use `fromMigrationAdapter(adapter)` from
|
|
187
|
+
`@qubu/migrate/baseline`. It reuses their strict reader, lease, and journal. A
|
|
188
|
+
custom adoption-only adapter implements `openBaselineSession`; its session
|
|
189
|
+
provides schema inspection, empty-history verification, baseline recording,
|
|
190
|
+
and cleanup. It does not need SQL execution or migration recovery methods.
|
|
191
|
+
|
|
192
|
+
### PostgreSQL configuration
|
|
193
|
+
|
|
194
|
+
The caller owns connecting, releasing, and ending the pinned `pg` client. Keep
|
|
195
|
+
it exclusively available until the migration session closes. Qubu releases its
|
|
196
|
+
locks and closes its session; it never releases or ends the supplied client.
|
|
197
|
+
|
|
198
|
+
This CLI wrapper opens a client per session and ends it after cleanup. Set
|
|
199
|
+
`search_path` to the snapshot namespace: inspection selects it explicitly, while
|
|
200
|
+
journal SQL and unqualified migration statements use `search_path`.
|
|
201
|
+
|
|
202
|
+
```ts
|
|
203
|
+
import { defineConfig } from "@qubu/cli/config"
|
|
204
|
+
import { migrationAdapter } from "@qubu/adapter-pg/migration"
|
|
205
|
+
import { Client } from "pg"
|
|
206
|
+
import snapshot from "./schema.snapshot.js"
|
|
207
|
+
|
|
208
|
+
export default defineConfig({
|
|
209
|
+
artifacts: "./migrations",
|
|
210
|
+
snapshot,
|
|
211
|
+
environment: "production",
|
|
212
|
+
provenance: { source: "my-service" },
|
|
213
|
+
adapter: () => ({
|
|
214
|
+
async openMigrationSession(signal) {
|
|
215
|
+
const connectionString = process.env.DATABASE_URL
|
|
216
|
+
if (!connectionString) throw new Error("DATABASE_URL is required")
|
|
217
|
+
const client = new Client({ connectionString })
|
|
218
|
+
try {
|
|
219
|
+
await client.connect()
|
|
220
|
+
const namespace = '"' + snapshot.namespace.name.replaceAll('"', '""') + '"'
|
|
221
|
+
await client.query("SELECT set_config('search_path', $1, false)", [namespace])
|
|
222
|
+
const session = await migrationAdapter(client).openMigrationSession(signal)
|
|
223
|
+
const close = session.close.bind(session)
|
|
224
|
+
let closed = false
|
|
225
|
+
session.close = async () => {
|
|
226
|
+
if (closed) return
|
|
227
|
+
closed = true
|
|
228
|
+
try {
|
|
229
|
+
await close()
|
|
230
|
+
} finally {
|
|
231
|
+
await client.end()
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
return session
|
|
235
|
+
} catch (error) {
|
|
236
|
+
await client.end()
|
|
237
|
+
throw error
|
|
238
|
+
}
|
|
239
|
+
},
|
|
240
|
+
}),
|
|
241
|
+
})
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
## MySQL
|
|
245
|
+
|
|
246
|
+
Use a dedicated `mysql2/promise` connection with autocommit enabled and no active
|
|
247
|
+
transaction. This configuration opens a connection per adoption session and
|
|
248
|
+
ends it after cleanup. Run the [shared CLI commands](#shared-cli) with this config:
|
|
249
|
+
|
|
250
|
+
```ts
|
|
251
|
+
import { defineConfig } from "@qubu/cli/config"
|
|
252
|
+
import { baselineAdapter } from "@qubu/adapter-mysql2/migration"
|
|
253
|
+
import mysql from "mysql2/promise"
|
|
254
|
+
import snapshot from "./schema.snapshot.js"
|
|
255
|
+
|
|
256
|
+
export default defineConfig({
|
|
257
|
+
artifacts: "./migrations",
|
|
258
|
+
snapshot,
|
|
259
|
+
environment: "production",
|
|
260
|
+
provenance: { source: "my-service" },
|
|
261
|
+
adapter: () => ({
|
|
262
|
+
async openBaselineSession(scope, signal) {
|
|
263
|
+
const connection = await mysql.createConnection(process.env.DATABASE_URL!)
|
|
264
|
+
try {
|
|
265
|
+
const session = await baselineAdapter(connection).openBaselineSession(scope, signal)
|
|
266
|
+
return {
|
|
267
|
+
...session,
|
|
268
|
+
async close() {
|
|
269
|
+
try {
|
|
270
|
+
await session.close()
|
|
271
|
+
} finally {
|
|
272
|
+
await connection.end()
|
|
273
|
+
}
|
|
274
|
+
},
|
|
275
|
+
}
|
|
276
|
+
} catch (error) {
|
|
277
|
+
await connection.end()
|
|
278
|
+
throw error
|
|
279
|
+
}
|
|
280
|
+
},
|
|
281
|
+
}),
|
|
282
|
+
})
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
Acceptance inserts one baseline record in `__qubu_mysql2_migrations`. Its
|
|
286
|
+
`baseline` column contains the full sealed artifact, including the reviewed
|
|
287
|
+
snapshot, provenance, and acknowledgments. The CLI also writes that artifact to
|
|
288
|
+
the configured repository.
|
|
289
|
+
|
|
290
|
+
This adoption-only configuration cannot run `migrate apply`, `status`, or
|
|
291
|
+
`reconcile`, which require the shared migration executor. Use the basic MySQL
|
|
292
|
+
runner for subsequent SQL migrations as shown below.
|
|
293
|
+
|
|
294
|
+
## After acceptance
|
|
295
|
+
|
|
296
|
+
If recording or artifact-file writing fails, inspect history and saved files
|
|
297
|
+
before retrying. The shared CLI records the journal before writing the artifact;
|
|
298
|
+
MySQL can lose an insert response after the record commits. Rerunning adoption
|
|
299
|
+
against nonempty history is not a recovery procedure. For MySQL, inspect:
|
|
300
|
+
|
|
301
|
+
```sql
|
|
302
|
+
SELECT id, hash, baseline FROM __qubu_mysql2_migrations WHERE id = 'initial';
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
Reconcile the desired application schema in a separate reviewed migration.
|
|
306
|
+
For SQLite/PostgreSQL, keep `config.snapshot` as the desired snapshot and plan
|
|
307
|
+
from the accepted baseline:
|
|
308
|
+
|
|
309
|
+
```bash
|
|
310
|
+
qubu migrate create add-manifest --config ./qubu.config.js --dry-run --format json
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
Review approvals or custom programs, create the artifact, and apply it through
|
|
314
|
+
the [normal workflow](operations.md). The tested path extends the captured
|
|
315
|
+
catalog snapshot with a supported change. Independently authored snapshots can
|
|
316
|
+
differ in constraint names, native types, defaults, or dialect metadata even
|
|
317
|
+
when their SQL seems equivalent. Adoption adds no general SQL equivalence rules
|
|
318
|
+
or automatic repairs.
|
|
319
|
+
|
|
320
|
+
For MySQL, review changes against the accepted snapshot and append SQL migrations
|
|
321
|
+
with distinct, permanent IDs:
|
|
322
|
+
|
|
323
|
+
```ts
|
|
324
|
+
import { migrate } from "@qubu/adapter-mysql2/migration"
|
|
325
|
+
|
|
326
|
+
await migrate(connection, [{ id: "add-manifest", sql: ["ALTER TABLE game ADD manifest JSON"] }])
|
|
327
|
+
```
|
|
328
|
+
|
|
329
|
+
The basic runner skips completed IDs. It does not check live schema drift before
|
|
330
|
+
subsequent migrations or consume sealed executable artifacts. Partial failures
|
|
331
|
+
still require inspection before retrying.
|