@voltro/database 0.77.1 → 0.78.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/CHANGELOG.md +38 -0
- package/dist/index.d.ts +94 -4
- package/dist/index.js +642 -642
- package/dist/{replaceInProgress-DdjHyYFT.js → replaceInProgress-B1ZgTEI5.js} +445 -444
- package/dist/sql.d.ts +199 -9
- package/dist/sql.js +1391 -1238
- package/package.json +2 -2
package/dist/sql.d.ts
CHANGED
|
@@ -2222,6 +2222,42 @@ declare interface DataStore {
|
|
|
2222
2222
|
* a way to hand a store somebody else's connection.
|
|
2223
2223
|
*/
|
|
2224
2224
|
run?<A, E>(effect: Effect.Effect<A, E, SqlClient.SqlClient>): Promise<A>;
|
|
2225
|
+
/**
|
|
2226
|
+
* What this store can say about its OWN shape without a catalog — the
|
|
2227
|
+
* tables it holds and the columns their rows carry.
|
|
2228
|
+
*
|
|
2229
|
+
* The inverse capability of {@link DataStore.run}. A SQL store answers a
|
|
2230
|
+
* schema question by asking the server's catalog, which is authoritative
|
|
2231
|
+
* about far more than this (types, indexes, constraints) and is therefore
|
|
2232
|
+
* the right source whenever there is a connection. A store that IS its data
|
|
2233
|
+
* has no catalog — and is not thereby ignorant: it knows exactly which
|
|
2234
|
+
* tables it holds, because holding them is all a table is there. That much
|
|
2235
|
+
* is answerable honestly, and nothing beyond it is.
|
|
2236
|
+
*
|
|
2237
|
+
* So the contract is deliberately narrow, and the narrowness is the point:
|
|
2238
|
+
*
|
|
2239
|
+
* - `table` is unqualified by whatever namespace the view is bound to, so
|
|
2240
|
+
* a tenant view reports the tenant's tables under their bare names.
|
|
2241
|
+
* - `columns` is the UNION of the keys the held rows carry. An empty
|
|
2242
|
+
* table therefore reports NO columns, and that is not a claim that it
|
|
2243
|
+
* has none — a caller must read `rowCount` before treating an absent
|
|
2244
|
+
* column as absent.
|
|
2245
|
+
* - `rowCount` is how many rows are held, so "the table is empty and says
|
|
2246
|
+
* nothing about its columns" is distinguishable from "the table has
|
|
2247
|
+
* rows and none of them carries that column".
|
|
2248
|
+
*
|
|
2249
|
+
* OPTIONAL on the interface: the in-memory store implements it (it is the
|
|
2250
|
+
* only store whose data IS its schema); SQL stores omit it, because the
|
|
2251
|
+
* catalog answers the same questions better and a second, weaker source of
|
|
2252
|
+
* truth beside it is how the two drift. Callers guard with
|
|
2253
|
+
* `if (store.storedTables)`.
|
|
2254
|
+
*
|
|
2255
|
+
* Read by the file-migration context built WITHOUT a `sql` client
|
|
2256
|
+
* (`makeFileMigrationContext`), so a migration body that opens with
|
|
2257
|
+
* `ctx.skipUnless(ctx.schema.tableExists('…'), …)` — the guard the file
|
|
2258
|
+
* migration docs ask for — runs unchanged in a database-free test.
|
|
2259
|
+
*/
|
|
2260
|
+
storedTables?(): ReadonlyArray<StoredTableShape>;
|
|
2225
2261
|
}
|
|
2226
2262
|
|
|
2227
2263
|
/**
|
|
@@ -2286,6 +2322,9 @@ export declare interface DeployLedgerRow {
|
|
|
2286
2322
|
|
|
2287
2323
|
export declare const describeOutcome: (outcome: BootMigrationOutcome, dialectId: string) => string;
|
|
2288
2324
|
|
|
2325
|
+
/** `bf_links`, `bf_links.ticket_key` — the spelling a message uses. */
|
|
2326
|
+
export declare const describeProbe: (probe: SchemaProbe) => string;
|
|
2327
|
+
|
|
2289
2328
|
/**
|
|
2290
2329
|
* Parse `VOLTRO_DESTRUCTIVE_OK`.
|
|
2291
2330
|
*
|
|
@@ -2757,6 +2796,31 @@ export declare interface FileMigration {
|
|
|
2757
2796
|
* schema change through `ctx.schema` in a migration of its own).
|
|
2758
2797
|
*/
|
|
2759
2798
|
readonly transactional?: boolean;
|
|
2799
|
+
/**
|
|
2800
|
+
* WHERE in a migrate run this body belongs, relative to the declarative
|
|
2801
|
+
* schema apply. Required, and there is no default, because the answer cannot
|
|
2802
|
+
* be inferred from the body and getting it wrong is silent.
|
|
2803
|
+
*
|
|
2804
|
+
* - **`'before-schema'`** — runs FIRST, before the planner diffs declared
|
|
2805
|
+
* against live. This is the phase for everything a state diff cannot infer
|
|
2806
|
+
* and must not be computed against: a table split, a merge, a foreign key
|
|
2807
|
+
* dropped so the planner does not trip over it, a type change with a USING
|
|
2808
|
+
* expression. The plan is computed AFTER this body, against the shape it
|
|
2809
|
+
* left behind.
|
|
2810
|
+
* - **`'after-schema'`** — runs LAST, after the plan has been applied and the
|
|
2811
|
+
* plugin steps and change triggers have converged. This is the phase for a
|
|
2812
|
+
* body that NEEDS the new shape: a back-fill into a table the same deploy
|
|
2813
|
+
* creates, a column filled from its neighbours. It runs in the same job,
|
|
2814
|
+
* under the same failure gate, and — on a dialect that holds a deploy in
|
|
2815
|
+
* one transaction — inside the same unit as the schema it depends on.
|
|
2816
|
+
*
|
|
2817
|
+
* A back-fill declared `'before-schema'` is the mistake this field exists to
|
|
2818
|
+
* make unavailable: on the deploy that creates its table the body can only
|
|
2819
|
+
* take its own `skipUnless` guard, and the skip is recorded, so it never gets
|
|
2820
|
+
* a second chance. The runner names that case when it can see it, but a
|
|
2821
|
+
* warning after the fact is the weaker half — the phase is the answer.
|
|
2822
|
+
*/
|
|
2823
|
+
readonly phase: MigrationPhase;
|
|
2760
2824
|
/** The forward body. */
|
|
2761
2825
|
readonly up: (ctx: FileMigrationContext) => Promise<void> | Effect.Effect<void, unknown, never>;
|
|
2762
2826
|
/**
|
|
@@ -2878,9 +2942,12 @@ export declare interface FileMigrationContext {
|
|
|
2878
2942
|
/**
|
|
2879
2943
|
* A body driven over a store that has no SQL underneath — the in-memory
|
|
2880
2944
|
* store a unit test uses. No client: `each`, `rewrite`, `expect`,
|
|
2881
|
-
* `skipUnless`, `store`, `log
|
|
2882
|
-
*
|
|
2883
|
-
* under `transactional`
|
|
2945
|
+
* `skipUnless`, `store`, `log`, the two read-only probes the store can answer
|
|
2946
|
+
* about itself (`schema.tableExists`, `schema.columnExists`) and a Promise
|
|
2947
|
+
* body under `transactional` run against the store; `raw()`, an Effect body
|
|
2948
|
+
* under `transactional` and every `schema` member that needs a CATALOG —
|
|
2949
|
+
* `snapshot`, `indexExists`, `foreignKeyExists` and all the DDL — fail with
|
|
2950
|
+
* `MigrationNeedsSqlClient`, each naming what to do instead. A store that
|
|
2884
2951
|
* CAN run SQL (`run` present) is not accepted here: its migration context
|
|
2885
2952
|
* needs the client, and the type says so before the runtime check does.
|
|
2886
2953
|
*/
|
|
@@ -2928,7 +2995,21 @@ export declare interface FileMigrationRunResult {
|
|
|
2928
2995
|
id: string;
|
|
2929
2996
|
durationMs: number;
|
|
2930
2997
|
}>;
|
|
2998
|
+
/** Ids this pass did NOT run because they are already recorded. */
|
|
2931
2999
|
readonly skipped: ReadonlyArray<string>;
|
|
3000
|
+
/**
|
|
3001
|
+
* The migrations whose body stopped at its own `skipUnless`, with the schema
|
|
3002
|
+
* objects it found absent. A caller holding the plan that follows can say
|
|
3003
|
+
* whether the skip was permanent; nobody else can.
|
|
3004
|
+
*/
|
|
3005
|
+
readonly skips: ReadonlyArray<FileMigrationSkip>;
|
|
3006
|
+
}
|
|
3007
|
+
|
|
3008
|
+
/** One migration that skipped, and what its body found missing. */
|
|
3009
|
+
export declare interface FileMigrationSkip {
|
|
3010
|
+
readonly id: string;
|
|
3011
|
+
readonly reason: string;
|
|
3012
|
+
readonly absent: ReadonlyArray<SchemaProbe>;
|
|
2932
3013
|
}
|
|
2933
3014
|
|
|
2934
3015
|
/**
|
|
@@ -2946,6 +3027,8 @@ export declare const fileMigrationStatus: (sql: SqlClient.SqlClient, projectRoot
|
|
|
2946
3027
|
/** One row of `voltro db status` for a `migrations/<timestamp>_<slug>.ts` file. */
|
|
2947
3028
|
export declare interface FileMigrationStatusRow {
|
|
2948
3029
|
readonly id: string;
|
|
3030
|
+
/** Where in a run this body executes — `voltro db status` says which. */
|
|
3031
|
+
readonly phase: MigrationPhase;
|
|
2949
3032
|
readonly applied: boolean;
|
|
2950
3033
|
/** The file no longer hashes to what was recorded when it ran. Always false
|
|
2951
3034
|
* for a row applied before hashes were recorded — see `appliedFileMigrations`. */
|
|
@@ -3305,6 +3388,7 @@ declare type MergeMixinFields<Mixins extends ReadonlyArray<AnyMixin>> = Mixins e
|
|
|
3305
3388
|
* export default migration({
|
|
3306
3389
|
* id: '20260415_120000_split_address_out',
|
|
3307
3390
|
* description: 'Move users.address* into a separate addresses table.',
|
|
3391
|
+
* phase: 'before-schema',
|
|
3308
3392
|
* up: ({ sql, store, log }) => Effect.gen(function* () {
|
|
3309
3393
|
* yield* sql.unsafe(`CREATE TABLE addresses ( ... )`)
|
|
3310
3394
|
* yield* sql.unsafe(`INSERT INTO addresses (...) SELECT (...) FROM users`)
|
|
@@ -3326,6 +3410,9 @@ export declare const MIGRATION_ATOMICITY: Readonly<Record<DialectId, MigrationAt
|
|
|
3326
3410
|
*/
|
|
3327
3411
|
export declare const MIGRATION_NEEDS_TWO_CONNECTIONS: string;
|
|
3328
3412
|
|
|
3413
|
+
/** Both phases, in the order a run executes them. The one definition; readers derive. */
|
|
3414
|
+
export declare const MIGRATION_PHASES: readonly [MigrationPhase, MigrationPhase];
|
|
3415
|
+
|
|
3329
3416
|
/**
|
|
3330
3417
|
* How much of a plan a dialect can apply atomically.
|
|
3331
3418
|
*
|
|
@@ -3403,10 +3490,13 @@ export declare interface MigrationLockScope {
|
|
|
3403
3490
|
/**
|
|
3404
3491
|
* A migration context was built without a SQL client — over the in-memory
|
|
3405
3492
|
* store, the shape a unit test uses — and the body reached a surface that
|
|
3406
|
-
* needs a live connection: `raw()`,
|
|
3407
|
-
*
|
|
3408
|
-
*
|
|
3409
|
-
*
|
|
3493
|
+
* needs a live connection: `raw()`, an Effect body under `ctx.transactional`
|
|
3494
|
+
* (which runs ON the connection), or a `ctx.schema` member that can only be
|
|
3495
|
+
* answered from a CATALOG — `snapshot`, `indexExists`, `foreignKeyExists` and
|
|
3496
|
+
* every schema CHANGE. `each`, `rewrite`, `expect`, `skipUnless`, `store`,
|
|
3497
|
+
* `log` and the two probes the store can answer about its own shape
|
|
3498
|
+
* (`tableExists`, `columnExists`) work without one; each refusal names what to
|
|
3499
|
+
* do instead. Also raised at construction when what was handed in as `sql` is not
|
|
3410
3500
|
* an `@effect/sql` client, or when a SQL-backed store arrives without the
|
|
3411
3501
|
* client it executes on — both are refused before a body can run rather
|
|
3412
3502
|
* than surfacing as a TypeError from inside the first helper that touches
|
|
@@ -3555,6 +3645,18 @@ export declare type MigrationOperation = {
|
|
|
3555
3645
|
readonly dropConstraints: ReadonlyArray<string>;
|
|
3556
3646
|
};
|
|
3557
3647
|
|
|
3648
|
+
/**
|
|
3649
|
+
* The two points in a migrate run at which a file-based body can execute.
|
|
3650
|
+
*
|
|
3651
|
+
* The ORDER of `'before-schema'` is what every file migration has always had
|
|
3652
|
+
* and is not negotiable: a body that drops a constraint the planner would trip
|
|
3653
|
+
* over has to run before the plan is computed, and `voltro db apply --plan`
|
|
3654
|
+
* refuses a pending one for exactly that reason. `'after-schema'` is the point
|
|
3655
|
+
* that did not exist — everything the declarative apply produces is in place,
|
|
3656
|
+
* so a body may depend on it.
|
|
3657
|
+
*/
|
|
3658
|
+
export declare type MigrationPhase = 'before-schema' | 'after-schema';
|
|
3659
|
+
|
|
3558
3660
|
export declare interface MigrationPlan {
|
|
3559
3661
|
readonly operations: ReadonlyArray<PlannedOperation>;
|
|
3560
3662
|
/** Pre-state fingerprint (live DB at plan time). */
|
|
@@ -3962,7 +4064,9 @@ export declare const parseEnumCheck: (clause: string) => {
|
|
|
3962
4064
|
* decision needs the list (to name it in the refusal) and must not have run
|
|
3963
4065
|
* anything to get it.
|
|
3964
4066
|
*/
|
|
3965
|
-
export declare const pendingFileMigrationIds: (sql: SqlClient.SqlClient, projectRoot: string
|
|
4067
|
+
export declare const pendingFileMigrationIds: (sql: SqlClient.SqlClient, projectRoot: string,
|
|
4068
|
+
/** Absent: BOTH phases — a gate that asks "is anything pending at all". */
|
|
4069
|
+
phase?: MigrationPhase) => Effect.Effect<ReadonlyArray<string>, unknown>;
|
|
3966
4070
|
|
|
3967
4071
|
/**
|
|
3968
4072
|
* What `runFileBasedMigrations` would execute next, with the one fact a deploy
|
|
@@ -3971,11 +4075,33 @@ export declare const pendingFileMigrationIds: (sql: SqlClient.SqlClient, project
|
|
|
3971
4075
|
* every pending migration except those, so they are named in the question
|
|
3972
4076
|
* the operator answers, not discovered in the report afterwards.
|
|
3973
4077
|
*/
|
|
3974
|
-
export declare const pendingFileMigrationSummary: (sql: SqlClient.SqlClient, projectRoot: string
|
|
4078
|
+
export declare const pendingFileMigrationSummary: (sql: SqlClient.SqlClient, projectRoot: string,
|
|
4079
|
+
/** Absent: BOTH phases. */
|
|
4080
|
+
phase?: MigrationPhase) => Effect.Effect<ReadonlyArray<{
|
|
3975
4081
|
readonly id: string;
|
|
4082
|
+
readonly phase: MigrationPhase;
|
|
3976
4083
|
readonly irreversible: string | undefined;
|
|
3977
4084
|
}>, unknown>;
|
|
3978
4085
|
|
|
4086
|
+
/** A skip the plan that follows makes permanent, with the objects that prove it. */
|
|
4087
|
+
export declare interface PermanentSkip {
|
|
4088
|
+
readonly id: string;
|
|
4089
|
+
readonly reason: string;
|
|
4090
|
+
/** The probed objects this plan creates — what makes the skip permanent. */
|
|
4091
|
+
readonly created: ReadonlyArray<SchemaProbe>;
|
|
4092
|
+
}
|
|
4093
|
+
|
|
4094
|
+
/**
|
|
4095
|
+
* The skips this plan turns into permanent ones.
|
|
4096
|
+
*
|
|
4097
|
+
* A `before-schema` body that probed for an object and found it absent, where
|
|
4098
|
+
* the plan computed immediately afterwards CREATES that object, could not have
|
|
4099
|
+
* taken any other outcome on this deploy — and the skip was recorded, so it
|
|
4100
|
+
* will not be offered again. Both facts are in hand at that moment; neither is
|
|
4101
|
+
* on its own.
|
|
4102
|
+
*/
|
|
4103
|
+
export declare const permanentSkips: (skips: ReadonlyArray<FileMigrationSkip>, plan: MigrationPlan) => ReadonlyArray<PermanentSkip>;
|
|
4104
|
+
|
|
3979
4105
|
export declare interface PlanInput {
|
|
3980
4106
|
readonly declared: ReadonlyArray<TableLike>;
|
|
3981
4107
|
readonly live: SchemaSnapshot;
|
|
@@ -4028,6 +4154,16 @@ export declare const plannableLiveTables: <T extends {
|
|
|
4028
4154
|
readonly name: string;
|
|
4029
4155
|
}>(tables: ReadonlyArray<T>, declaredNames: ReadonlySet<string>) => ReadonlyArray<T>;
|
|
4030
4156
|
|
|
4157
|
+
/**
|
|
4158
|
+
* What a planned operation CREATES — the objects that do not exist before the
|
|
4159
|
+
* plan runs and do after it.
|
|
4160
|
+
*
|
|
4161
|
+
* Only creations. An operation that renames or drops is not what a body probing
|
|
4162
|
+
* for absence is waiting for, and reporting one would send an operator looking
|
|
4163
|
+
* for a relationship that is not there.
|
|
4164
|
+
*/
|
|
4165
|
+
export declare const plannedCreations: (plan: MigrationPlan) => ReadonlyArray<SchemaProbe>;
|
|
4166
|
+
|
|
4031
4167
|
/**
|
|
4032
4168
|
* One row in a `MigrationPlan` — a concrete operation paired with the
|
|
4033
4169
|
* planner's classification + any blocking reason.
|
|
@@ -5086,6 +5222,13 @@ export declare interface RunFileBasedMigrationsCtx {
|
|
|
5086
5222
|
/** Schema scope for the migration advisory lock (see `migrations/lock.ts`).
|
|
5087
5223
|
* Absent → resolved from configuration (`DB_SCHEMA`). */
|
|
5088
5224
|
readonly lockSchema?: string;
|
|
5225
|
+
/**
|
|
5226
|
+
* WHICH phase this pass runs. Required, not defaulted: a run applies the two
|
|
5227
|
+
* phases as two passes around the declarative apply, and a caller that could
|
|
5228
|
+
* leave this out would run one of them at whichever point it happened to be
|
|
5229
|
+
* called — which is the defect the phase exists to remove, moved up a layer.
|
|
5230
|
+
*/
|
|
5231
|
+
readonly phase: MigrationPhase;
|
|
5089
5232
|
}
|
|
5090
5233
|
|
|
5091
5234
|
/**
|
|
@@ -5185,6 +5328,31 @@ export declare interface SchemaApplyOptions {
|
|
|
5185
5328
|
/** What a schema operation on the migration context found to do. */
|
|
5186
5329
|
export declare type SchemaOpOutcome = 'applied' | 'already-absent' | 'already-present';
|
|
5187
5330
|
|
|
5331
|
+
/** A schema object a body asked about, in the vocabulary a user writes. */
|
|
5332
|
+
export declare type SchemaProbe = {
|
|
5333
|
+
readonly kind: 'table';
|
|
5334
|
+
readonly table: string;
|
|
5335
|
+
} | {
|
|
5336
|
+
readonly kind: 'column';
|
|
5337
|
+
readonly table: string;
|
|
5338
|
+
readonly name: string;
|
|
5339
|
+
} | {
|
|
5340
|
+
readonly kind: 'index';
|
|
5341
|
+
readonly table: string;
|
|
5342
|
+
readonly name: string;
|
|
5343
|
+
} | {
|
|
5344
|
+
readonly kind: 'foreign-key';
|
|
5345
|
+
readonly table: string;
|
|
5346
|
+
readonly name: string;
|
|
5347
|
+
};
|
|
5348
|
+
|
|
5349
|
+
export declare interface SchemaProbeRecorder {
|
|
5350
|
+
/** The context to hand the body — `ctx` with a recording `schema`. */
|
|
5351
|
+
readonly ctx: FileMigrationContext;
|
|
5352
|
+
/** Every existence question that answered ABSENT, in the order it was asked. */
|
|
5353
|
+
readonly absent: () => ReadonlyArray<SchemaProbe>;
|
|
5354
|
+
}
|
|
5355
|
+
|
|
5188
5356
|
/**
|
|
5189
5357
|
* The full schema as the planner sees it on one side of the diff.
|
|
5190
5358
|
* Both the declared side and the introspected side conform to this
|
|
@@ -5350,6 +5518,16 @@ export declare interface SquashResult {
|
|
|
5350
5518
|
readonly snapshotId: string;
|
|
5351
5519
|
}
|
|
5352
5520
|
|
|
5521
|
+
/** One table as a store that has no catalog can describe it. See {@link DataStore.storedTables}. */
|
|
5522
|
+
declare interface StoredTableShape {
|
|
5523
|
+
/** The table's name, unqualified by any namespace the reporting view is bound to. */
|
|
5524
|
+
readonly table: string;
|
|
5525
|
+
/** The union of the column names the held rows carry. Empty for a table with no rows. */
|
|
5526
|
+
readonly columns: ReadonlyArray<string>;
|
|
5527
|
+
/** How many rows the store holds for this table. `0` means `columns` carries no evidence. */
|
|
5528
|
+
readonly rowCount: number;
|
|
5529
|
+
}
|
|
5530
|
+
|
|
5353
5531
|
/**
|
|
5354
5532
|
* `col IN (SELECT col FROM ...)` / `col NOT IN (SELECT ...)` — the
|
|
5355
5533
|
* RHS is a sub-query descriptor. The sub-query MUST project a
|
|
@@ -6164,6 +6342,18 @@ declare interface WindowSpec_2 {
|
|
|
6164
6342
|
|
|
6165
6343
|
export declare const withMigrationLock: <A, E, R = never>(sql: SqlClient.SqlClient, work: Effect.Effect<A, E, R>, scope?: MigrationLockScope) => Effect.Effect<A, E | SqlError_2, R>;
|
|
6166
6344
|
|
|
6345
|
+
/**
|
|
6346
|
+
* Wrap a migration context so its four existence questions are recorded when —
|
|
6347
|
+
* and only when — they answer absent.
|
|
6348
|
+
*
|
|
6349
|
+
* The four are exactly `MigrationSchemaOps`'s `*Exists` members, and the map
|
|
6350
|
+
* below is typed over them, so a fifth existence question added there fails to
|
|
6351
|
+
* compile until it has been decided whether an absent answer is evidence of the
|
|
6352
|
+
* same thing. An existence probe nobody recorded reads exactly like a body that
|
|
6353
|
+
* never asked.
|
|
6354
|
+
*/
|
|
6355
|
+
export declare const withSchemaProbeRecorder: (ctx: FileMigrationContext) => SchemaProbeRecorder;
|
|
6356
|
+
|
|
6167
6357
|
/** Tree of relations to eager-load. */
|
|
6168
6358
|
declare type WithSpec = Readonly<Record<string, true | EagerLoadSpec>>;
|
|
6169
6359
|
|