@voltro/database 0.81.0 → 0.82.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/sql.d.ts CHANGED
@@ -3,11 +3,15 @@ import { Effect } from 'effect';
3
3
  import { Layer } from 'effect';
4
4
  import { Schema } from 'effect';
5
5
  import { SqlClient } from '@effect/sql';
6
- import { SqlError } from '@effect/sql';
7
- import { SqlError as SqlError_2 } from '@effect/sql/SqlError';
6
+ import { SqlError } from '@effect/sql/SqlError';
7
+ import { SqlError as SqlError_2 } from '@effect/sql';
8
8
  import { Statement } from '@effect/sql';
9
9
 
10
- export declare const acquireMigrationLock: (sql: SqlClient.SqlClient, scope?: MigrationLockScope) => Effect.Effect<HeldMigrationLock, SqlError_2>;
10
+ /** Every hash this source may have been recorded under: its own, and the body
11
+ * before each framework edit in {@link FRAMEWORK_MIGRATION_EDITS}. */
12
+ export declare const acceptedBodyHashes: (source: string) => ReadonlySet<string>;
13
+
14
+ export declare const acquireMigrationLock: (sql: SqlClient.SqlClient, scope?: MigrationLockScope) => Effect.Effect<HeldMigrationLock, SqlError>;
11
15
 
12
16
  export declare const addColumnDdl: (op: Extract<MigrationOperation, {
13
17
  kind: "add-column";
@@ -161,7 +165,36 @@ declare type AnyView = View<string, Record<string, ColumnDefinition<unknown>>>;
161
165
  */
162
166
  export declare const appliedAtValue: (sql: SqlClient.SqlClient, iso: string) => Effect.Effect<string>;
163
167
 
164
- export declare const appliedFileMigrationIds: (sql: SqlClient.SqlClient) => Effect.Effect<ReadonlyArray<string>, SqlError_2>;
168
+ /**
169
+ * Does the plan ledger exist on THIS database? Asked of the CATALOG, which
170
+ * ANSWERS on a virgin database rather than failing.
171
+ *
172
+ * The read below used to be a plain `SELECT` with `Effect.catchAll(() => [])`
173
+ * beside it, and that is correct only OUTSIDE a transaction — the rule
174
+ * `objectReadable` states in `applier.ts`, in those words, about its own query.
175
+ * `voltro db apply` opens ONE transaction on the plan-transactional dialects,
176
+ * so against a database without the ledger the failed SELECT aborted that
177
+ * transaction and every later statement of the deploy died — starting with the
178
+ * first migration's own `SAVEPOINT`:
179
+ *
180
+ * ERROR: relation "_voltro_migration_plans" does not exist
181
+ * ERROR: current transaction is aborted, commands ignored until end of
182
+ * transaction block
183
+ *
184
+ * The catch turned an error postgres does not forget into one only Effect had
185
+ * forgotten. So a project with even ONE file under `migrations/` could not
186
+ * `db apply` against an empty database at all — every new environment, every
187
+ * reset local database, every hermetic e2e stack — and the schema was never
188
+ * created.
189
+ *
190
+ * The fix is NOT to create the ledger first. Both apply paths used to open with
191
+ * `applySchema(voltroFrameworkTables)`, and it was removed for a reason the
192
+ * header of `runApplyFromPlanFile` records: on a virgin database it created
193
+ * tables and thereby moved the very fingerprint the saved plan was measured
194
+ * against. The ledger is planned like any other declared table; what had to
195
+ * change is that ASKING for it stops being a statement that can fail.
196
+ */
197
+ export declare const appliedFileMigrationIds: (sql: SqlClient.SqlClient) => Effect.Effect<ReadonlyArray<string>, SqlError>;
165
198
 
166
199
  /**
167
200
  * The applied file migrations WITH the body hash recorded at the time.
@@ -175,7 +208,7 @@ export declare const appliedFileMigrationIds: (sql: SqlClient.SqlClient) => Effe
175
208
  export declare const appliedFileMigrations: (sql: SqlClient.SqlClient) => Effect.Effect<ReadonlyArray<{
176
209
  readonly id: string;
177
210
  readonly hash: string;
178
- }>, SqlError_2>;
211
+ }>, SqlError>;
179
212
 
180
213
  /**
181
214
  * The operations the resume ledger says RAN — `started`, `added` or `applied` —
@@ -245,9 +278,14 @@ export declare const applyNamespacedSchema: (tables: ReadonlyArray<AnyTable>, na
245
278
  * interruption, which is exactly the set a JS `finally` inside `Effect.gen`
246
279
  * does not cover.
247
280
  */
248
- export declare const applyPlan: (sql: SqlClient.SqlClient, plan: MigrationPlan, ctx: ApplyPlanCtx) => Effect.Effect<AppliedMigration, SqlError_2, SqlClient.SqlClient>;
281
+ export declare const applyPlan: (sql: SqlClient.SqlClient, plan: MigrationPlan, ctx: ApplyPlanCtx) => Effect.Effect<AppliedMigration, SqlError, SqlClient.SqlClient>;
249
282
 
250
283
  export declare interface ApplyPlanCtx {
284
+ /** Prepare schema-owned resources under the migration lock after replan,
285
+ * refusal checks and interrupted-run reconciliation, before planned work.
286
+ * Not called when no operations remain. Ledger/extension bootstrap may
287
+ * already have run; this is not a dry-run boundary or its own transaction. */
288
+ readonly beforeExecute?: Effect.Effect<void, SqlError, SqlClient.SqlClient>;
251
289
  /** Tables declared by user code — needed to look up
252
290
  * `.backfill()` specs for add-column ops, since the snapshot
253
291
  * doesn't carry them. */
@@ -319,7 +357,7 @@ export declare interface ApplyPlanCtx {
319
357
  * migration was a framework bug. `voltro db apply` did exactly that for two
320
358
  * releases while `voltro dev`'s boot diff, which uses this parameter, did not.
321
359
  */
322
- readonly replan: (sql: SqlClient.SqlClient) => Effect.Effect<MigrationPlan, SqlError_2, SqlClient.SqlClient>;
360
+ readonly replan: (sql: SqlClient.SqlClient) => Effect.Effect<MigrationPlan, SqlError, SqlClient.SqlClient>;
323
361
  /**
324
362
  * The declared store charset — the one `replan` plans with. Here only for
325
363
  * the resume ledger this creates before the first statement: nothing declares
@@ -342,7 +380,7 @@ cdcChannel?: string, options?: SchemaApplyOptions) => Effect.Effect<void, never,
342
380
  * `.unprepared`, the text protocol, like every DDL statement a prepared
343
381
  * protocol may refuse on one of the two engines.
344
382
  */
345
- export declare const applyUndeclaredCharsetWork: (sql: SqlClient.SqlClient, work: UndeclaredCharsetWork) => Effect.Effect<void, SqlError_2>;
383
+ export declare const applyUndeclaredCharsetWork: (sql: SqlClient.SqlClient, work: UndeclaredCharsetWork) => Effect.Effect<void, SqlError>;
346
384
 
347
385
  /**
348
386
  * The OPT-IN refuse gate over the advisory. By default (`VOLTRO_ROLLING_DEPLOY`
@@ -544,6 +582,9 @@ declare type ChangeEvent = {
544
582
  * re-read failed, or the row was already gone). Re-read or resync.
545
583
  */
546
584
  readonly oversized?: 'rehydrated' | 'tombstone' | 'unrecovered';
585
+ /** A net-change feed may coalesce intermediate writes and supply current
586
+ * images or delete keys only. It cannot support exact historical replay. */
587
+ readonly netChange?: true;
547
588
  /**
548
589
  * How the event reached this process. Absent/'inline' = emitted by this
549
590
  * process's own write path; 'injected' = delivered over a cross-instance
@@ -634,9 +675,13 @@ export declare const charsetColumnsOf: (table: TableSnapshot) => ReadonlyArray<{
634
675
  * Without `mayWrite` nothing is created or written: a missing table or row is
635
676
  * `unclaimed`, and only an existing FOREIGN claim is reported.
636
677
  */
637
- export declare const checkSchemaOwner: (sql: SqlClient.SqlClient, claim: SchemaOwnerClaim, options: {
678
+ export declare const checkSchemaOwner: (sql: SqlClient.SqlClient, claim: SchemaOwnerClaim,
679
+ /** `charset`: the declared store charset (MySQL family) the owner table is
680
+ * created in — the same one every other framework table gets. */
681
+ options: {
638
682
  readonly mayWrite: boolean;
639
- }) => Effect.Effect<SchemaOwnership, SqlError_2>;
683
+ readonly charset: MysqlCharset | undefined;
684
+ }) => Effect.Effect<SchemaOwnership, SqlError>;
640
685
 
641
686
  /** Partition into ≤`n`-sized chunks (disjoint, complete). Exported for the
642
687
  * batching regression test — the introspection's correctness reduces to this
@@ -1553,7 +1598,7 @@ export declare type ColumnInput = {
1553
1598
  * `drop-check` still removes a CHECK the declaration no longer has: it runs
1554
1599
  * after the column changes and reads the catalog again.
1555
1600
  */
1556
- export declare const columnLevelChecksMysql: (sql: SqlClient.SqlClient, table: string) => Effect.Effect<ReadonlyMap<string, string>, SqlError_2>;
1601
+ export declare const columnLevelChecksMysql: (sql: SqlClient.SqlClient, table: string) => Effect.Effect<ReadonlyMap<string, string>, SqlError>;
1557
1602
 
1558
1603
  /**
1559
1604
  * A column as seen by the planner: either declared in code (extracted
@@ -1824,7 +1869,7 @@ export declare const convergeUndeclaredCharset: (sql: SqlClient.SqlClient, optio
1824
1869
  readonly declaredNames: ReadonlySet<string>;
1825
1870
  readonly charset: MysqlCharset | undefined;
1826
1871
  readonly lockSchema?: string | undefined;
1827
- }) => Effect.Effect<UndeclaredCharsetWork, SqlError_2>;
1872
+ }) => Effect.Effect<UndeclaredCharsetWork, SqlError>;
1828
1873
 
1829
1874
  /** A live foreign key a charset conversion has to carry across the rewrite. */
1830
1875
  export declare interface ConvertCharsetForeignKey {
@@ -2431,8 +2476,8 @@ export declare const defaultJsonClause: (value: object, dialect: DialectId) => s
2431
2476
  export declare interface DeferredData {
2432
2477
  readonly table: string;
2433
2478
  readonly op: MigrationOperation;
2434
- readonly data: Effect.Effect<void, SqlError_2>;
2435
- readonly after: Effect.Effect<void, SqlError_2>;
2479
+ readonly data: Effect.Effect<void, SqlError>;
2480
+ readonly after: Effect.Effect<void, SqlError>;
2436
2481
  }
2437
2482
 
2438
2483
  export declare const DEPLOY_LEDGER_TABLE = "_voltro_deploys";
@@ -2491,11 +2536,33 @@ export declare const detectReactiveTriggerDrift: (sql: SqlClient.SqlClient, tabl
2491
2536
  /** The app's `cdcChannel`. Part of the trigger NAME on any non-default
2492
2537
  * channel, so omitting it here reported every table as missing on a database
2493
2538
  * where every trigger was present. */
2494
- channel?: string) => Effect.Effect<ReactiveTriggerDrift, SqlError_2>;
2539
+ channel?: string) => Effect.Effect<ReactiveTriggerDrift, SqlError>;
2495
2540
 
2496
2541
  /** Dialect IDs the framework supports. */
2497
2542
  declare type DialectId = 'postgres' | 'mysql' | 'mariadb' | 'mssql' | 'sqlite' | 'turso';
2498
2543
 
2544
+ /**
2545
+ * Resolve the live dialect from the `SqlClient`.
2546
+ *
2547
+ * `sql.onDialectOrElse` collapses the six voltro dialects onto four driver
2548
+ * tokens: mysql+mariadb share `mysql` (one `@effect/sql-mysql2` client) and
2549
+ * sqlite+turso share `sqlite`. That collapse is fine for every consumer here —
2550
+ * the paired dialects behave identically for everything this module decides.
2551
+ *
2552
+ * Single copy on purpose: a second, subtly different dialect switch is how a
2553
+ * per-dialect divergence gets shipped in this codebase.
2554
+ */
2555
+ export declare const dialectOf: (sql: {
2556
+ readonly onDialectOrElse: <A, B, C, D, E, F>(options: {
2557
+ readonly orElse: () => A;
2558
+ readonly sqlite?: () => B;
2559
+ readonly pg?: () => C;
2560
+ readonly mysql?: () => D;
2561
+ readonly mssql?: () => E;
2562
+ readonly clickhouse?: () => F;
2563
+ }) => A | B | C | D | E | F;
2564
+ }) => DialectId;
2565
+
2499
2566
  /**
2500
2567
  * Output of `voltro db drift`: compares the live DB's introspected
2501
2568
  * schema against the last applied fingerprint in `_voltro_migrations`.
@@ -2811,7 +2878,19 @@ declare type EmptyMerge = unknown;
2811
2878
  export declare const engineFromVersion: (version: string) => MysqlEngine;
2812
2879
 
2813
2880
  /** The deploy ledger (`_voltro_deploys`) — same bootstrap, same reason: it has to exist before the first plan. */
2814
- export declare const ensureDeployLedgerTable: (sql: SqlClient.SqlClient, dialect: DialectId, charset?: MysqlCharset) => Effect.Effect<void, SqlError_2>;
2881
+ export declare const ensureDeployLedgerTable: (sql: SqlClient.SqlClient, dialect: DialectId, charset: MysqlCharset | undefined) => Effect.Effect<void, SqlError>;
2882
+
2883
+ /**
2884
+ * Make every declared full-text index exist, on a database the PLANNER
2885
+ * migrates. The planner diffs what it introspects — columns, indexes — and
2886
+ * deliberately skips the FTS artifacts (a generated tsvector column and its GIN
2887
+ * index, a FULLTEXT index, an FTS5 shadow table): they are framework plumbing
2888
+ * for a declaration, carry no data of their own, and their DDL is idempotent.
2889
+ * So they CONVERGE after the plan instead of being planned — without this, a
2890
+ * table the planner created never got its index, and `.matching()` failed on
2891
+ * the missing column. Returns how many statements ran.
2892
+ */
2893
+ export declare const ensureFullTextIndexes: (tables: ReadonlyArray<AnyTable>, dialect: DialectId) => Effect.Effect<number, SqlError_2.SqlError, SqlClient.SqlClient>;
2815
2894
 
2816
2895
  /**
2817
2896
  * Create `_voltro_migration_ops` if it isn't there yet.
@@ -2826,7 +2905,7 @@ export declare const ensureDeployLedgerTable: (sql: SqlClient.SqlClient, dialect
2826
2905
  * framework-table set because it has to exist before the FIRST plan — and the
2827
2906
  * first plan is the one that creates the framework tables.
2828
2907
  */
2829
- export declare const ensureLedgerTable: (sql: SqlClient.SqlClient, dialect: DialectId, charset?: MysqlCharset) => Effect.Effect<void, SqlError_2>;
2908
+ export declare const ensureLedgerTable: (sql: SqlClient.SqlClient, dialect: DialectId, charset: MysqlCharset | undefined) => Effect.Effect<void, SqlError>;
2830
2909
 
2831
2910
  /**
2832
2911
  * The PLAN ledger (`_voltro_migration_plans`) — same bootstrap, one caller.
@@ -2841,7 +2920,11 @@ export declare const ensureLedgerTable: (sql: SqlClient.SqlClient, dialect: Dial
2841
2920
  * Index-free like the others, so the planner still owns the table's shape and a
2842
2921
  * re-plan converges by adding the indexes it declares.
2843
2922
  */
2844
- export declare const ensurePlanLedgerTable: (sql: SqlClient.SqlClient, dialect: DialectId) => Effect.Effect<void, SqlError_2>;
2923
+ export declare const ensurePlanLedgerTable: (sql: SqlClient.SqlClient, dialect: DialectId, charset: MysqlCharset | undefined) => Effect.Effect<void, SqlError>;
2924
+
2925
+ /** The plugin-migration ledger (`_voltro_plugin_migrations`) — same bootstrap:
2926
+ * a plugin's steps run after the schema apply, outside any plan. */
2927
+ export declare const ensurePluginMigrationsLedgerTable: (sql: SqlClient.SqlClient, dialect: DialectId, charset: MysqlCharset | undefined) => Effect.Effect<void, SqlError>;
2845
2928
 
2846
2929
  /**
2847
2930
  * The in-progress marker (`_voltro_replace_in_progress`) — same bootstrap,
@@ -2857,11 +2940,11 @@ export declare const ensurePlanLedgerTable: (sql: SqlClient.SqlClient, dialect:
2857
2940
  * exits 1 over a database that is otherwise fine. The planner emits no trigger
2858
2941
  * for this table either, so the shape converges on the next apply.
2859
2942
  */
2860
- export declare const ensureReplaceMarkerTable: (sql: SqlClient.SqlClient, dialect: DialectId) => Effect.Effect<void, SqlError_2>;
2943
+ export declare const ensureReplaceMarkerTable: (sql: SqlClient.SqlClient, dialect: DialectId, charset: MysqlCharset | undefined) => Effect.Effect<void, SqlError>;
2861
2944
 
2862
2945
  /** The schema-owner row's table (`_voltro_schema_owner`) — bootstrapped so the
2863
2946
  * ownership check can run BEFORE the first plan, which is the plan it guards. */
2864
- export declare const ensureSchemaOwnerTable: (sql: SqlClient.SqlClient, dialect: DialectId) => Effect.Effect<void, SqlError_2>;
2947
+ export declare const ensureSchemaOwnerTable: (sql: SqlClient.SqlClient, dialect: DialectId, charset: MysqlCharset | undefined) => Effect.Effect<void, SqlError>;
2865
2948
 
2866
2949
  /** Exported for the same reason as {@link enumTypeBlocks}, and it MUST run
2867
2950
  * before it — see that function's note on ordering. */
@@ -2918,6 +3001,21 @@ export declare const failedExpectations: (expectations: ReadonlyArray<{
2918
3001
  readonly check: () => Promise<boolean> | Effect.Effect<boolean, unknown, never>;
2919
3002
  }>) => Effect.Effect<ReadonlyArray<string>, unknown>;
2920
3003
 
3004
+ /**
3005
+ * Look up the most recently applied plan's fingerprint. Returns
3006
+ * `undefined` if the table doesn't exist yet or no rows are present —
3007
+ * both meaning "the framework hasn't applied a planner-style migration
3008
+ * in this DB yet" (typical for fresh installs).
3009
+ *
3010
+ * A failed read is NOT one of those two unless the table is really absent.
3011
+ * This read used to end in `catchAll(() => undefined)`, and on SQL Server its
3012
+ * `LIMIT 1` was a syntax error: every production boot there reported
3013
+ * `live=<no _voltro_migration_plans row>` over a table holding the row, and
3014
+ * refused. So a failure is followed by a probe that is valid on every dialect;
3015
+ * if the table answers it, the original failure is the answer, loudly.
3016
+ */
3017
+ export declare const fetchLastFingerprint: (sql: SqlClient.SqlClient) => Effect.Effect<string | undefined, never>;
3018
+
2921
3019
  /**
2922
3020
  * Filename pattern the discovery walker matches: timestamp-prefixed
2923
3021
  * `.ts` files in any depth under the project's `migrations/` dir.
@@ -3223,6 +3321,49 @@ export declare const formatBranchRehearsal: (report: BranchRehearsalReport) => s
3223
3321
  /** Human report, or `undefined` when the database and the schema agree. */
3224
3322
  export declare const formatReactiveTriggerDrift: (drift: ReactiveTriggerDrift) => string | undefined;
3225
3323
 
3324
+ /**
3325
+ * Edits the FRAMEWORK made to migration files — through a codemod, after they
3326
+ * may already have run — each with the inverse that recovers the body as it was
3327
+ * when it ran.
3328
+ *
3329
+ * `0.78.0/01_file-migration-phase` inserted `phase: 'before-schema',` into every
3330
+ * migration without one, and 0.78.0 made the field required. Every migration
3331
+ * applied before that then warned on every boot that its body had changed,
3332
+ * forever: the file cannot be put back, and the recorded hash is the operator's
3333
+ * to keep, not to hand-edit. A warning that is always there is the one nobody
3334
+ * reads when a real edit lands, so an edit we made is not drift.
3335
+ *
3336
+ * A codemod that rewrites files under `migrations/` adds its inverse here in the
3337
+ * same change — `fileMigrationCodemodInverses.test.ts` fails otherwise, and runs
3338
+ * the real codemod over every file shape it has measured. An inverse returns
3339
+ * the CANDIDATE originals, because the rewrite is not always one line: ts-morph
3340
+ * replaces the whitespace around the property it inserts with its own (LF, two
3341
+ * spaces), so a one-line spec, a CRLF file and a tab-indented one each lose
3342
+ * bytes that only the file's own style can put back. It undoes exactly what the
3343
+ * codemod wrote: a hand-written `after-schema` on an applied migration is still
3344
+ * an edit, and still said.
3345
+ */
3346
+ export declare const FRAMEWORK_MIGRATION_EDITS: ReadonlyArray<{
3347
+ readonly codemod: string;
3348
+ readonly inverse: (source: string) => ReadonlyArray<string>;
3349
+ }>;
3350
+
3351
+ /**
3352
+ * The DDL that makes a table's `.fullTextIndex(...)` declarations exist —
3353
+ * THE one builder, used by the full-schema emit and by
3354
+ * `ensureFullTextIndexes` (the planner path's convergence). Idempotent per
3355
+ * dialect:
3356
+ * postgres: STORED tsvector column + GIN index (`IF NOT EXISTS`)
3357
+ * mysql/mariadb: native FULLTEXT INDEX (a re-run's duplicate is swallowed)
3358
+ * sqlite: native FTS5 virtual table + sync triggers (`IF NOT EXISTS`)
3359
+ * mssql: nothing — a ranked LIKE fallback at query time
3360
+ * Turso has no FTS5 and throws.
3361
+ */
3362
+ export declare const fullTextIndexSql: (table: AnyTable, dialect: DialectId) => ReadonlyArray<string>;
3363
+
3364
+ /** A converged schema can still have an unfinished history write. Boot must not skip it. */
3365
+ export declare const hasInterruptedMigration: (sql: SqlClient.SqlClient) => Effect.Effect<boolean, SqlError>;
3366
+
3226
3367
  /**
3227
3368
  * A lock that is being HELD, and the only way to let it go.
3228
3369
  *
@@ -3236,7 +3377,7 @@ export declare const formatReactiveTriggerDrift: (drift: ReactiveTriggerDrift) =
3236
3377
  export declare interface HeldMigrationLock {
3237
3378
  /** Release the lock and return its connection to the pool. Idempotent only
3238
3379
  * in the sense that the dialect statements are; call it once. */
3239
- readonly release: Effect.Effect<void, SqlError_2>;
3380
+ readonly release: Effect.Effect<void, SqlError>;
3240
3381
  }
3241
3382
 
3242
3383
  /**
@@ -3460,7 +3601,7 @@ declare interface InsertIgnoreResult {
3460
3601
  readonly outcome: 'inserted' | 'ignored';
3461
3602
  }
3462
3603
 
3463
- export declare const introspectSchema: (sql: SqlClient.SqlClient) => Effect.Effect<SchemaSnapshot, SqlError_2>;
3604
+ export declare const introspectSchema: (sql: SqlClient.SqlClient) => Effect.Effect<SchemaSnapshot, SqlError>;
3464
3605
 
3465
3606
  /**
3466
3607
  * A `down` that says there is no inverse — with the reason, which is what a
@@ -3535,6 +3676,22 @@ declare type LeafOperator = 'eq' | 'neq' | 'gt' | 'gte' | 'lt' | 'lte' | 'in' |
3535
3676
 
3536
3677
  declare type LeafOperator_2 = 'eq' | 'neq' | 'gt' | 'gte' | 'lt' | 'lte' | 'in' | 'notIn' | 'contains' | 'startsWith' | 'arrayContains' | 'arrayOverlaps' | 'arrayHas' | 'isNull' | 'isNotNull' | 'spatial';
3537
3678
 
3679
+ /**
3680
+ * `LIMIT n`, spelled for the dialect — after an `ORDER BY`, which every caller
3681
+ * has. SQL Server has no LIMIT; `OFFSET 0 ROWS FETCH NEXT n ROWS ONLY` is its
3682
+ * spelling, valid in a derived table too.
3683
+ *
3684
+ * One helper because the same mistake was written six times: a batched
3685
+ * backfill failed on its first page, and `voltro serve`'s schema check read the
3686
+ * newest applied plan with `LIMIT 1`, got a syntax error back, and reported it
3687
+ * as "no plan row" — so a production serve on SQL Server refused every boot,
3688
+ * including the one right after `voltro db apply`.
3689
+ *
3690
+ * Splicing a number into SQL text needs a reason: this one is framework-chosen
3691
+ * and floored to a positive integer here, so it cannot carry anything but digits.
3692
+ */
3693
+ export declare const limitClause: (sql: SqlClient.SqlClient, n: number) => Statement.Fragment;
3694
+
3538
3695
  /**
3539
3696
  * Build the context for one migration body. Returns the context and the
3540
3697
  * post-conditions it collects, so the runner can evaluate them after `up`.
@@ -4039,6 +4196,20 @@ export declare class MigrationSkipped {
4039
4196
  constructor(reason: string);
4040
4197
  }
4041
4198
 
4199
+ export declare interface MigrationSnapshotCandidate {
4200
+ readonly planId: string;
4201
+ readonly objects: ReadonlyArray<SnapshotRestorationObject>;
4202
+ }
4203
+
4204
+ export declare interface MigrationSquashPreview {
4205
+ readonly before: string;
4206
+ readonly fingerprint: string;
4207
+ readonly planIds: ReadonlyArray<string>;
4208
+ readonly count: number;
4209
+ /** Opaque selection identity, not an authorization credential. */
4210
+ readonly token: string;
4211
+ }
4212
+
4042
4213
  declare interface MixinDefinition<F extends Record<string, ColumnDefinition<unknown>>> {
4043
4214
  /**
4044
4215
  * Stable dedup key. Two mixins with the same `id` reaching a table via
@@ -4423,6 +4594,9 @@ export declare const plannedCreations: (plan: MigrationPlan) => ReadonlyArray<Sc
4423
4594
  */
4424
4595
  export declare interface PlannedOperation {
4425
4596
  readonly op: MigrationOperation;
4597
+ /** Exact retained object name assigned by the applier before soft-drop DDL.
4598
+ * Persisted in operation/resume history; absent when the history has no recorded identity or for hard drops. */
4599
+ readonly softDropSnapshot?: string;
4426
4600
  readonly classification: OperationClass;
4427
4601
  /** Why the op was classified this way (also surfaces in the CLI). */
4428
4602
  readonly reason?: string;
@@ -4494,6 +4668,10 @@ declare interface PredicateLeaf_2 {
4494
4668
  readonly path?: ReadonlyArray<string | number>;
4495
4669
  }
4496
4670
 
4671
+ /** Read the complete eligible history, independent of the dashboard's recent
4672
+ * history limit. The token must be rechecked by squashMigrationPlans on write. */
4673
+ export declare const previewMigrationSquash: (options: Pick<SquashOptions, "before" | "fingerprint">) => Effect.Effect<MigrationSquashPreview, Error, SqlClient.SqlClient>;
4674
+
4497
4675
  /**
4498
4676
  * Imperative `applyNamespacedSchema` against a fresh SqlClient — the
4499
4677
  * entry point the CLI / runtime use to provision a tenant's namespace
@@ -4517,7 +4695,7 @@ declare interface PredicateLeaf_2 {
4517
4695
  * Eager provisioning is the APP's move (call this from a seed/startup over its
4518
4696
  * own tenant table); the framework has no tenant registry to enumerate.
4519
4697
  */
4520
- export declare const provisionTenantNamespace: (tables: ReadonlyArray<AnyTable>, namespace: string, sqlLayer: Layer.Layer<SqlClient.SqlClient, ConfigError.ConfigError | SqlError.SqlError, never>, dialect?: DialectId, options?: SchemaApplyOptions) => Promise<void>;
4698
+ export declare const provisionTenantNamespace: (tables: ReadonlyArray<AnyTable>, namespace: string, sqlLayer: Layer.Layer<SqlClient.SqlClient, ConfigError.ConfigError | SqlError_2.SqlError, never>, dialect?: DialectId, options?: SchemaApplyOptions) => Promise<void>;
4521
4699
 
4522
4700
  /**
4523
4701
  * The runtime-facing materialized query — a plain bag of strings the store and
@@ -4659,6 +4837,16 @@ declare interface QueryDescriptor<R = Row> {
4659
4837
  readonly alias: string;
4660
4838
  readonly order: boolean;
4661
4839
  };
4840
+ /** Each query word matches as a word BEGINNING (`tomato` finds
4841
+ * `tomatoes`), every word required — `.matching(…, { prefix: true })`. */
4842
+ readonly prefix?: boolean;
4843
+ /**
4844
+ * Set by a store whose engine keeps no full-text index (Turso's MVCC mode
4845
+ * has no FTS5): the match compiles to a case-insensitive LIKE over the
4846
+ * covered columns, ranked by how many of them hit — the same fallback as
4847
+ * mssql. Never set by the query builder.
4848
+ */
4849
+ readonly fallback?: 'like';
4662
4850
  };
4663
4851
  /**
4664
4852
  * Common-Table-Expression definitions (Q4) — `WITH <name> AS (...)`.
@@ -4937,6 +5125,16 @@ declare interface QueryDescriptor_2<R = Row_2> {
4937
5125
  readonly alias: string;
4938
5126
  readonly order: boolean;
4939
5127
  };
5128
+ /** Each query word matches as a word BEGINNING (`tomato` finds
5129
+ * `tomatoes`), every word required — `.matching(…, { prefix: true })`. */
5130
+ readonly prefix?: boolean;
5131
+ /**
5132
+ * Set by a store whose engine keeps no full-text index (Turso's MVCC mode
5133
+ * has no FTS5): the match compiles to a case-insensitive LIKE over the
5134
+ * covered columns, ranked by how many of them hit — the same fallback as
5135
+ * mssql. Never set by the query builder.
5136
+ */
5137
+ readonly fallback?: 'like';
4940
5138
  };
4941
5139
  /**
4942
5140
  * Common-Table-Expression definitions (Q4) — `WITH <name> AS (...)`.
@@ -5196,8 +5394,10 @@ export declare const reactiveTriggerRepairSql: (input: {
5196
5394
  readonly channel?: string;
5197
5395
  }) => ReadonlyArray<string>;
5198
5396
 
5397
+ export declare const readMigrationSnapshotCandidates: (sql: SqlClient.SqlClient, live: SchemaSnapshot) => Effect.Effect<ReadonlyArray<MigrationSnapshotCandidate>, SqlError>;
5398
+
5199
5399
  /** Every string column of a base table in the connected database. */
5200
- export declare const readUndeclaredCharsetColumns: (sql: SqlClient.SqlClient) => Effect.Effect<ReadonlyArray<UndeclaredCharsetColumn>, SqlError_2>;
5400
+ export declare const readUndeclaredCharsetColumns: (sql: SqlClient.SqlClient) => Effect.Effect<ReadonlyArray<UndeclaredCharsetColumn>, SqlError>;
5201
5401
 
5202
5402
  /**
5203
5403
  * Record that a schema was applied even though there was nothing to DO.
@@ -5298,7 +5498,12 @@ export declare const renderColumnPg: (col: ColumnSnapshot, hasCompositePk?: bool
5298
5498
  */
5299
5499
  export declare const resolveMigrationLockSchema: (scope?: MigrationLockScope) => string | undefined;
5300
5500
 
5301
- export declare const resolveMysqlEngine: (sql: SqlClient.SqlClient) => Effect.Effect<MysqlEngine, SqlError_2>;
5501
+ export declare const resolveMysqlEngine: (sql: SqlClient.SqlClient) => Effect.Effect<MysqlEngine, SqlError>;
5502
+
5503
+ /** Restore only recorded objects, under the same lock as schema migrations. */
5504
+ export declare const restoreMigrationSnapshots: (sql: SqlClient.SqlClient, planId: string, options?: {
5505
+ readonly beforeExecute?: (sql: SqlClient.SqlClient) => Effect.Effect<void, SqlError, SqlClient.SqlClient>;
5506
+ }) => Effect.Effect<SnapshotRestorationResult, SqlError | Error, SqlClient.SqlClient>;
5302
5507
 
5303
5508
  declare type RetryDecision = 'retry' | 'noRetry';
5304
5509
 
@@ -5352,6 +5557,9 @@ export declare const rollbackFileBasedMigration: (sql: SqlClient.SqlClient, ctx:
5352
5557
  id: string;
5353
5558
  lockSchema?: string;
5354
5559
  store: DataStore;
5560
+ /** Runs under the migration lock after all refusal checks, immediately
5561
+ * before down. Failure leaves the body and ledger untouched. */
5562
+ beforeExecute?: Effect.Effect<void, unknown>;
5355
5563
  }) => Effect.Effect<{
5356
5564
  id: string;
5357
5565
  durationMs: number;
@@ -5450,6 +5658,10 @@ declare interface RuleViolationDetail {
5450
5658
  export declare const runFileBasedMigrations: (sql: SqlClient.SqlClient, ctx: RunFileBasedMigrationsCtx) => Effect.Effect<FileMigrationRunResult, unknown>;
5451
5659
 
5452
5660
  export declare interface RunFileBasedMigrationsCtx {
5661
+ /** Runs once under the migration lock, after pending work is rechecked and
5662
+ * before its bodies start. Not called for an empty/already-applied pass.
5663
+ * Failure aborts the pass. Preparation is outside each body's transaction. */
5664
+ readonly beforeExecute?: Effect.Effect<void, unknown>;
5453
5665
  readonly projectRoot: string;
5454
5666
  readonly env: 'dev' | 'staging' | 'prod';
5455
5667
  readonly appliedBy: string;
@@ -5476,6 +5688,12 @@ export declare interface RunFileBasedMigrationsCtx {
5476
5688
  * called — which is the defect the phase exists to remove, moved up a layer.
5477
5689
  */
5478
5690
  readonly phase: MigrationPhase;
5691
+ /**
5692
+ * The declared store charset (MySQL family), for the ledgers this run
5693
+ * creates before any plan has. Required, `undefined` spelled out — see
5694
+ * `ensureBootstrapTable`.
5695
+ */
5696
+ readonly charset: MysqlCharset | undefined;
5479
5697
  }
5480
5698
 
5481
5699
  /**
@@ -5486,7 +5704,7 @@ export declare interface RunFileBasedMigrationsCtx {
5486
5704
  * `voltro_isr_cache` so framework releases that grow new columns
5487
5705
  * don't crash old DBs at boot.
5488
5706
  */
5489
- export declare const runFrameworkBootstrap: (tables: ReadonlyArray<AnyTable>, sqlLayer: Layer.Layer<SqlClient.SqlClient, ConfigError.ConfigError | SqlError.SqlError, never>, dialect?: DialectId, options?: SchemaApplyOptions) => Promise<void>;
5707
+ export declare const runFrameworkBootstrap: (tables: ReadonlyArray<AnyTable>, sqlLayer: Layer.Layer<SqlClient.SqlClient, ConfigError.ConfigError | SqlError_2.SqlError, never>, dialect?: DialectId, options?: SchemaApplyOptions) => Promise<void>;
5490
5708
 
5491
5709
  /**
5492
5710
  * Run `applySchema` against a fresh SqlClient from the provided sqlLayer.
@@ -5496,7 +5714,7 @@ export declare const runFrameworkBootstrap: (tables: ReadonlyArray<AnyTable>, sq
5496
5714
  * `dialect` is passed through to the DDL emitter so SQL types + reactive
5497
5715
  * trigger emission match the underlying engine.
5498
5716
  */
5499
- export declare const runMigrate: (tables: ReadonlyArray<AnyTable>, sqlLayer: Layer.Layer<SqlClient.SqlClient, ConfigError.ConfigError | SqlError.SqlError, never>, dialect?: DialectId, options?: SchemaApplyOptions) => Promise<void>;
5717
+ export declare const runMigrate: (tables: ReadonlyArray<AnyTable>, sqlLayer: Layer.Layer<SqlClient.SqlClient, ConfigError.ConfigError | SqlError_2.SqlError, never>, dialect?: DialectId, options?: SchemaApplyOptions) => Promise<void>;
5500
5718
 
5501
5719
  /**
5502
5720
  * Execute ONE planned operation with the applier's own per-dialect
@@ -5507,7 +5725,7 @@ export declare const runMigrate: (tables: ReadonlyArray<AnyTable>, sqlLayer: Lay
5507
5725
  * shape from: the live table with the change applied, materialised as a
5508
5726
  * declaration by the caller.
5509
5727
  */
5510
- export declare const runMigrationOp: (sql: SqlClient.SqlClient, planned: PlannedOperation, declared: ReadonlyArray<TableLike>) => Effect.Effect<AppliedOp, SqlError_2>;
5728
+ export declare const runMigrationOp: (sql: SqlClient.SqlClient, planned: PlannedOperation, declared: ReadonlyArray<TableLike>) => Effect.Effect<AppliedOp, SqlError>;
5511
5729
 
5512
5730
  /**
5513
5731
  * Top-level entry point — called once per boot. Returns a
@@ -5516,9 +5734,11 @@ export declare const runMigrationOp: (sql: SqlClient.SqlClient, planned: Planned
5516
5734
  *
5517
5735
  * Wires the dev/prod dispatch + the VOLTRO_AUTO_MIGRATE escape hatch.
5518
5736
  */
5519
- export declare const runPlannedMigrations: (sql: SqlClient.SqlClient, declared: ReadonlyArray<TableLike>, ctx: RunPlannedMigrationsCtx) => Effect.Effect<BootMigrationOutcome, SqlError_2, SqlClient.SqlClient>;
5737
+ export declare const runPlannedMigrations: (sql: SqlClient.SqlClient, declared: ReadonlyArray<TableLike>, ctx: RunPlannedMigrationsCtx) => Effect.Effect<BootMigrationOutcome, SqlError, SqlClient.SqlClient>;
5520
5738
 
5521
5739
  export declare interface RunPlannedMigrationsCtx {
5740
+ /** Forwarded to the applier only when an approved schema plan executes. */
5741
+ readonly beforeExecute?: Effect.Effect<void, SqlError, SqlClient.SqlClient>;
5522
5742
  /** What `voltro logs` should attribute this run to. */
5523
5743
  readonly appliedBy: string;
5524
5744
  readonly environment: BootEnvironment;
@@ -5543,10 +5763,10 @@ export declare interface RunPlannedMigrationsCtx {
5543
5763
  * answers `already-absent` before reaching here). This function assumes the
5544
5764
  * object is there.
5545
5765
  */
5546
- export declare const runSchemaOp: (sql: SqlClient.SqlClient, op: MigrationSchemaOp, live: SchemaSnapshot) => Effect.Effect<void, SqlError_2>;
5766
+ export declare const runSchemaOp: (sql: SqlClient.SqlClient, op: MigrationSchemaOp, live: SchemaSnapshot) => Effect.Effect<void, SqlError>;
5547
5767
 
5548
5768
  /** Runs one Effect over the connection — the shape `ManagedRuntime.runPromise` and a store's `run` share. */
5549
- export declare type RunSql = <A>(effect: Effect.Effect<A, SqlError_2, SqlClient.SqlClient>) => Promise<A>;
5769
+ export declare type RunSql = <A>(effect: Effect.Effect<A, SqlError, SqlClient.SqlClient>) => Promise<A>;
5550
5770
 
5551
5771
  export declare const SCHEMA_OWNER_TABLE = "_voltro_schema_owner";
5552
5772
 
@@ -5673,6 +5893,19 @@ export declare const snapshotColumn: (name: string, def: ColumnDefinition<unknow
5673
5893
  /** Needed only for `storageType` — a caller that renders DDL does not want it. */
5674
5894
  dialect?: DialectId) => ColumnSnapshot;
5675
5895
 
5896
+ export declare interface SnapshotRestorationObject {
5897
+ readonly kind: 'column' | 'table';
5898
+ readonly table: string;
5899
+ readonly originalName: string;
5900
+ readonly snapshotName: string;
5901
+ }
5902
+
5903
+ export declare interface SnapshotRestorationResult {
5904
+ readonly restoredColumns: number;
5905
+ readonly restoredTables: number;
5906
+ readonly objects: ReadonlyArray<SnapshotRestorationObject>;
5907
+ }
5908
+
5676
5909
  /**
5677
5910
  * PostGIS spatial-distance clause carried on a {@link QueryDescriptor}.
5678
5911
  * Drives a `ST_Distance` projected column (distance-as-a-value) and/or a
@@ -5784,7 +6017,7 @@ export declare const sqlType: (column: ColumnDefinition<unknown>, dialect: Diale
5784
6017
  * Effect that performs the squash + returns a summary. Fails if:
5785
6018
  * - no eligible rows match (`before` cut everything off)
5786
6019
  * - the latest pre-squash fingerprint differs from `fingerprint`
5787
- * (signals uncommitted drift — refuse to lock in)
6020
+ * (the cut-off may select an older schema, or declarations may be unapplied)
5788
6021
  */
5789
6022
  export declare const squashMigrationPlans: (options: SquashOptions) => Effect.Effect<SquashResult, Error, SqlClient.SqlClient>;
5790
6023
 
@@ -5803,6 +6036,9 @@ export declare interface SquashOptions {
5803
6036
  readonly appliedBy: string;
5804
6037
  /** Environment tag for the snapshot row. */
5805
6038
  readonly environment: string;
6039
+ /** Confirmation token from previewMigrationSquash. Rechecked under the lock
6040
+ * before writing; changed eligibility refuses without changing history. */
6041
+ readonly expectedPreview?: string;
5806
6042
  }
5807
6043
 
5808
6044
  export declare interface SquashResult {
@@ -6539,9 +6775,9 @@ export declare class TransactionBrokenByBodyError extends Error {
6539
6775
  * Hand the schema to a different app name — `voltro db claim`. The one
6540
6776
  * deliberate way to change the owner: an app was RENAMED, not replaced.
6541
6777
  */
6542
- export declare const transferSchemaOwner: (sql: SqlClient.SqlClient, claim: SchemaOwnerClaim) => Effect.Effect<{
6778
+ export declare const transferSchemaOwner: (sql: SqlClient.SqlClient, claim: SchemaOwnerClaim, charset: MysqlCharset | undefined) => Effect.Effect<{
6543
6779
  readonly previous: string | undefined;
6544
- }, SqlError_2>;
6780
+ }, SqlError>;
6545
6781
 
6546
6782
  export declare const unblockLossy: (plan: MigrationPlan, scope?: ReturnType<typeof destructiveScope>) => MigrationPlan;
6547
6783
 
@@ -6756,7 +6992,7 @@ declare interface WindowSpec_2 {
6756
6992
  }>;
6757
6993
  }
6758
6994
 
6759
- 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>;
6995
+ export declare const withMigrationLock: <A, E, R = never>(sql: SqlClient.SqlClient, work: Effect.Effect<A, E, R>, scope?: MigrationLockScope) => Effect.Effect<A, E | SqlError, R>;
6760
6996
 
6761
6997
  /**
6762
6998
  * Wrap a migration context so its four existence questions are recorded when —