@voltro/database 0.80.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,13 @@ 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>;
361
+ /**
362
+ * The declared store charset — the one `replan` plans with. Here only for
363
+ * the resume ledger this creates before the first statement: nothing declares
364
+ * it, so without this it is created in the database default.
365
+ */
366
+ readonly charset?: MysqlCharset | undefined;
323
367
  }
324
368
 
325
369
  export declare const applySchema: (tables: ReadonlyArray<AnyTable>, dialect?: DialectId,
@@ -330,6 +374,14 @@ export declare const applySchema: (tables: ReadonlyArray<AnyTable>, dialect?: Di
330
374
  * raised nothing. */
331
375
  cdcChannel?: string, options?: SchemaApplyOptions) => Effect.Effect<void, never, SqlClient.SqlClient>;
332
376
 
377
+ /**
378
+ * Run the work. No lock of its own — the caller holds the one that fits:
379
+ * `convergeUndeclaredCharset` the migration lock, the cluster gate its own.
380
+ * `.unprepared`, the text protocol, like every DDL statement a prepared
381
+ * protocol may refuse on one of the two engines.
382
+ */
383
+ export declare const applyUndeclaredCharsetWork: (sql: SqlClient.SqlClient, work: UndeclaredCharsetWork) => Effect.Effect<void, SqlError>;
384
+
333
385
  /**
334
386
  * The OPT-IN refuse gate over the advisory. By default (`VOLTRO_ROLLING_DEPLOY`
335
387
  * unset) this never fires — the classifier stays a `db plan` advisory, because
@@ -530,6 +582,9 @@ declare type ChangeEvent = {
530
582
  * re-read failed, or the row was already gone). Re-read or resync.
531
583
  */
532
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;
533
588
  /**
534
589
  * How the event reached this process. Absent/'inline' = emitted by this
535
590
  * process's own write path; 'injected' = delivered over a cross-instance
@@ -609,6 +664,25 @@ export declare const charsetColumnsOf: (table: TableSnapshot) => ReadonlyArray<{
609
664
  readonly charset: string;
610
665
  }>;
611
666
 
667
+ /**
668
+ * Check — and, when `mayWrite`, establish — which app owns this schema.
669
+ *
670
+ * With `mayWrite` the owner table is bootstrapped and an unclaimed schema is
671
+ * claimed for `claim.appName`. Two apps racing for an unclaimed database both
672
+ * try the INSERT; the loser's fails on the primary key and it re-reads, so
673
+ * exactly one of them owns it and the other is told so.
674
+ *
675
+ * Without `mayWrite` nothing is created or written: a missing table or row is
676
+ * `unclaimed`, and only an existing FOREIGN claim is reported.
677
+ */
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: {
682
+ readonly mayWrite: boolean;
683
+ readonly charset: MysqlCharset | undefined;
684
+ }) => Effect.Effect<SchemaOwnership, SqlError>;
685
+
612
686
  /** Partition into ≤`n`-sized chunks (disjoint, complete). Exported for the
613
687
  * batching regression test — the introspection's correctness reduces to this
614
688
  * producing a complete, non-overlapping partition of the table list. */
@@ -1504,6 +1578,28 @@ export declare type ColumnInput = {
1504
1578
  __definition(): ColumnDefinition<unknown>;
1505
1579
  } | ColumnDefinition<unknown>;
1506
1580
 
1581
+ /**
1582
+ * A table's COLUMN-level CHECKs on MariaDB, by column — what a `MODIFY COLUMN`
1583
+ * has to restate so that it keeps them.
1584
+ *
1585
+ * MariaDB records a CHECK written inline on a column (the bring-up path writes
1586
+ * every enum CHECK that way) as part of the column definition
1587
+ * (`check_constraints.level = 'Column'`, named after the column), and `MODIFY
1588
+ * COLUMN` REDEFINES the column: a definition without the CHECK removes it.
1589
+ * Measured on 11.8 — plain MODIFY: gone; MODIFY with the CHECK inline: kept,
1590
+ * still column-level. A table-level CHECK (what `add-check` writes) survives
1591
+ * either way, and MySQL has no column-level constraints in its catalog, so this
1592
+ * is empty there.
1593
+ *
1594
+ * Without it every operation that renders MODIFY — a character-set conversion,
1595
+ * a nullability or type change — removed the CHECK as a side effect, the re-plan
1596
+ * proposed `add-check`, and the apply refused as "did not converge". A declared
1597
+ * charset over a real schema produced 67 of them at once. The planner's own
1598
+ * `drop-check` still removes a CHECK the declaration no longer has: it runs
1599
+ * after the column changes and reads the catalog again.
1600
+ */
1601
+ export declare const columnLevelChecksMysql: (sql: SqlClient.SqlClient, table: string) => Effect.Effect<ReadonlyMap<string, string>, SqlError>;
1602
+
1507
1603
  /**
1508
1604
  * A column as seen by the planner: either declared in code (extracted
1509
1605
  * from `ColumnDefinition`) or live in the DB (extracted via introspection).
@@ -1764,6 +1860,17 @@ declare type ColumnType = 'id' | 'text' | 'integer' | 'real' | 'decimal' | 'bigi
1764
1860
 
1765
1861
  declare type ColumnType_2 = 'id' | 'text' | 'integer' | 'real' | 'decimal' | 'bigint' | 'boolean' | 'timestamp' | 'date' | 'json' | 'bytes' | 'reference' | 'vector' | 'enum' | 'array' | 'interval' | 'raw';
1766
1862
 
1863
+ /**
1864
+ * Carry a `utf8mb4` declaration to the undeclared framework tables that
1865
+ * already exist, under the migration lock. Idempotent: a second run reads a
1866
+ * converged catalog and does nothing.
1867
+ */
1868
+ export declare const convergeUndeclaredCharset: (sql: SqlClient.SqlClient, options: {
1869
+ readonly declaredNames: ReadonlySet<string>;
1870
+ readonly charset: MysqlCharset | undefined;
1871
+ readonly lockSchema?: string | undefined;
1872
+ }) => Effect.Effect<UndeclaredCharsetWork, SqlError>;
1873
+
1767
1874
  /** A live foreign key a charset conversion has to carry across the rewrite. */
1768
1875
  export declare interface ConvertCharsetForeignKey {
1769
1876
  /** Catalog name of the constraint. */
@@ -2369,8 +2476,8 @@ export declare const defaultJsonClause: (value: object, dialect: DialectId) => s
2369
2476
  export declare interface DeferredData {
2370
2477
  readonly table: string;
2371
2478
  readonly op: MigrationOperation;
2372
- readonly data: Effect.Effect<void, SqlError_2>;
2373
- readonly after: Effect.Effect<void, SqlError_2>;
2479
+ readonly data: Effect.Effect<void, SqlError>;
2480
+ readonly after: Effect.Effect<void, SqlError>;
2374
2481
  }
2375
2482
 
2376
2483
  export declare const DEPLOY_LEDGER_TABLE = "_voltro_deploys";
@@ -2393,6 +2500,9 @@ export declare const describeProbe: (probe: SchemaProbe) => string;
2393
2500
  /** One line per finding, for the CLI and the doctor. */
2394
2501
  export declare const describeStoreCharsetFinding: (f: StoreCharsetFinding) => string;
2395
2502
 
2503
+ /** The lines `db plan` prints and `db apply` logs — one per table. */
2504
+ export declare const describeUndeclaredCharsetWork: (w: UndeclaredCharsetWork) => ReadonlyArray<string>;
2505
+
2396
2506
  /**
2397
2507
  * Parse `VOLTRO_DESTRUCTIVE_OK`.
2398
2508
  *
@@ -2426,11 +2536,33 @@ export declare const detectReactiveTriggerDrift: (sql: SqlClient.SqlClient, tabl
2426
2536
  /** The app's `cdcChannel`. Part of the trigger NAME on any non-default
2427
2537
  * channel, so omitting it here reported every table as missing on a database
2428
2538
  * where every trigger was present. */
2429
- channel?: string) => Effect.Effect<ReactiveTriggerDrift, SqlError_2>;
2539
+ channel?: string) => Effect.Effect<ReactiveTriggerDrift, SqlError>;
2430
2540
 
2431
2541
  /** Dialect IDs the framework supports. */
2432
2542
  declare type DialectId = 'postgres' | 'mysql' | 'mariadb' | 'mssql' | 'sqlite' | 'turso';
2433
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
+
2434
2566
  /**
2435
2567
  * Output of `voltro db drift`: compares the live DB's introspected
2436
2568
  * schema against the last applied fingerprint in `_voltro_migrations`.
@@ -2746,7 +2878,19 @@ declare type EmptyMerge = unknown;
2746
2878
  export declare const engineFromVersion: (version: string) => MysqlEngine;
2747
2879
 
2748
2880
  /** The deploy ledger (`_voltro_deploys`) — same bootstrap, same reason: it has to exist before the first plan. */
2749
- export declare const ensureDeployLedgerTable: (sql: SqlClient.SqlClient, dialect: DialectId) => 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>;
2750
2894
 
2751
2895
  /**
2752
2896
  * Create `_voltro_migration_ops` if it isn't there yet.
@@ -2761,7 +2905,7 @@ export declare const ensureDeployLedgerTable: (sql: SqlClient.SqlClient, dialect
2761
2905
  * framework-table set because it has to exist before the FIRST plan — and the
2762
2906
  * first plan is the one that creates the framework tables.
2763
2907
  */
2764
- export declare const ensureLedgerTable: (sql: SqlClient.SqlClient, dialect: DialectId) => Effect.Effect<void, SqlError_2>;
2908
+ export declare const ensureLedgerTable: (sql: SqlClient.SqlClient, dialect: DialectId, charset: MysqlCharset | undefined) => Effect.Effect<void, SqlError>;
2765
2909
 
2766
2910
  /**
2767
2911
  * The PLAN ledger (`_voltro_migration_plans`) — same bootstrap, one caller.
@@ -2776,7 +2920,11 @@ export declare const ensureLedgerTable: (sql: SqlClient.SqlClient, dialect: Dial
2776
2920
  * Index-free like the others, so the planner still owns the table's shape and a
2777
2921
  * re-plan converges by adding the indexes it declares.
2778
2922
  */
2779
- 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>;
2780
2928
 
2781
2929
  /**
2782
2930
  * The in-progress marker (`_voltro_replace_in_progress`) — same bootstrap,
@@ -2792,7 +2940,11 @@ export declare const ensurePlanLedgerTable: (sql: SqlClient.SqlClient, dialect:
2792
2940
  * exits 1 over a database that is otherwise fine. The planner emits no trigger
2793
2941
  * for this table either, so the shape converges on the next apply.
2794
2942
  */
2795
- 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>;
2944
+
2945
+ /** The schema-owner row's table (`_voltro_schema_owner`) — bootstrapped so the
2946
+ * ownership check can run BEFORE the first plan, which is the plan it guards. */
2947
+ export declare const ensureSchemaOwnerTable: (sql: SqlClient.SqlClient, dialect: DialectId, charset: MysqlCharset | undefined) => Effect.Effect<void, SqlError>;
2796
2948
 
2797
2949
  /** Exported for the same reason as {@link enumTypeBlocks}, and it MUST run
2798
2950
  * before it — see that function's note on ordering. */
@@ -2849,6 +3001,21 @@ export declare const failedExpectations: (expectations: ReadonlyArray<{
2849
3001
  readonly check: () => Promise<boolean> | Effect.Effect<boolean, unknown, never>;
2850
3002
  }>) => Effect.Effect<ReadonlyArray<string>, unknown>;
2851
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
+
2852
3019
  /**
2853
3020
  * Filename pattern the discovery walker matches: timestamp-prefixed
2854
3021
  * `.ts` files in any depth under the project's `migrations/` dir.
@@ -3144,6 +3311,9 @@ export declare const fingerprintSchema: (snapshot: SchemaSnapshot) => string;
3144
3311
 
3145
3312
  export declare const fnv1a64: (input: string) => bigint;
3146
3313
 
3314
+ /** The refusal a foreign claim produces — one sentence for both boot paths. */
3315
+ export declare const foreignOwnerMessage: (self: string, owner: string, claimedAt: string | undefined) => string;
3316
+
3147
3317
  /** Human/PR-comment rendering. Markdown-free on purpose — the CI job that posts
3148
3318
  * it to a PR wraps it, and a terminal reader gets the same text. */
3149
3319
  export declare const formatBranchRehearsal: (report: BranchRehearsalReport) => string;
@@ -3151,6 +3321,49 @@ export declare const formatBranchRehearsal: (report: BranchRehearsalReport) => s
3151
3321
  /** Human report, or `undefined` when the database and the schema agree. */
3152
3322
  export declare const formatReactiveTriggerDrift: (drift: ReactiveTriggerDrift) => string | undefined;
3153
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
+
3154
3367
  /**
3155
3368
  * A lock that is being HELD, and the only way to let it go.
3156
3369
  *
@@ -3164,7 +3377,7 @@ export declare const formatReactiveTriggerDrift: (drift: ReactiveTriggerDrift) =
3164
3377
  export declare interface HeldMigrationLock {
3165
3378
  /** Release the lock and return its connection to the pool. Idempotent only
3166
3379
  * in the sense that the dialect statements are; call it once. */
3167
- readonly release: Effect.Effect<void, SqlError_2>;
3380
+ readonly release: Effect.Effect<void, SqlError>;
3168
3381
  }
3169
3382
 
3170
3383
  /**
@@ -3388,7 +3601,7 @@ declare interface InsertIgnoreResult {
3388
3601
  readonly outcome: 'inserted' | 'ignored';
3389
3602
  }
3390
3603
 
3391
- export declare const introspectSchema: (sql: SqlClient.SqlClient) => Effect.Effect<SchemaSnapshot, SqlError_2>;
3604
+ export declare const introspectSchema: (sql: SqlClient.SqlClient) => Effect.Effect<SchemaSnapshot, SqlError>;
3392
3605
 
3393
3606
  /**
3394
3607
  * A `down` that says there is no inverse — with the reason, which is what a
@@ -3433,6 +3646,16 @@ export declare const isRawSqlFragment: (value: unknown) => value is RawSqlFragme
3433
3646
  */
3434
3647
  export declare const isReRunSafeDdl: (statement: string, dialect: DialectId) => boolean;
3435
3648
 
3649
+ /**
3650
+ * The tables this step owns: created by the framework or its engines, not
3651
+ * declared by the app. A soft-drop snapshot and a `--mode replace` staging copy
3652
+ * are data the operator is handling, and `_cloud_*` / `__turso_internal_*`
3653
+ * belong to another product and another engine.
3654
+ */
3655
+ export declare const isUndeclaredCharsetTable: (name: string, declaredNames: ReadonlySet<string>) => boolean;
3656
+
3657
+ export declare const isUndeclaredCharsetWorkEmpty: (w: UndeclaredCharsetWork) => boolean;
3658
+
3436
3659
  /**
3437
3660
  * A json-path index entry, produced by {@link jsonIndex}. Unlike a plain
3438
3661
  * `{ expr }` (verbatim, dialect-specific) this carries the column + path
@@ -3453,6 +3676,22 @@ declare type LeafOperator = 'eq' | 'neq' | 'gt' | 'gte' | 'lt' | 'lte' | 'in' |
3453
3676
 
3454
3677
  declare type LeafOperator_2 = 'eq' | 'neq' | 'gt' | 'gte' | 'lt' | 'lte' | 'in' | 'notIn' | 'contains' | 'startsWith' | 'arrayContains' | 'arrayOverlaps' | 'arrayHas' | 'isNull' | 'isNotNull' | 'spatial';
3455
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
+
3456
3695
  /**
3457
3696
  * Build the context for one migration body. Returns the context and the
3458
3697
  * post-conditions it collects, so the runner can evaluate them after `up`.
@@ -3957,6 +4196,20 @@ export declare class MigrationSkipped {
3957
4196
  constructor(reason: string);
3958
4197
  }
3959
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
+
3960
4213
  declare interface MixinDefinition<F extends Record<string, ColumnDefinition<unknown>>> {
3961
4214
  /**
3962
4215
  * Stable dedup key. Two mixins with the same `id` reaching a table via
@@ -4341,6 +4594,9 @@ export declare const plannedCreations: (plan: MigrationPlan) => ReadonlyArray<Sc
4341
4594
  */
4342
4595
  export declare interface PlannedOperation {
4343
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;
4344
4600
  readonly classification: OperationClass;
4345
4601
  /** Why the op was classified this way (also surfaces in the CLI). */
4346
4602
  readonly reason?: string;
@@ -4412,6 +4668,10 @@ declare interface PredicateLeaf_2 {
4412
4668
  readonly path?: ReadonlyArray<string | number>;
4413
4669
  }
4414
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
+
4415
4675
  /**
4416
4676
  * Imperative `applyNamespacedSchema` against a fresh SqlClient — the
4417
4677
  * entry point the CLI / runtime use to provision a tenant's namespace
@@ -4435,7 +4695,7 @@ declare interface PredicateLeaf_2 {
4435
4695
  * Eager provisioning is the APP's move (call this from a seed/startup over its
4436
4696
  * own tenant table); the framework has no tenant registry to enumerate.
4437
4697
  */
4438
- 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>;
4439
4699
 
4440
4700
  /**
4441
4701
  * The runtime-facing materialized query — a plain bag of strings the store and
@@ -4577,6 +4837,16 @@ declare interface QueryDescriptor<R = Row> {
4577
4837
  readonly alias: string;
4578
4838
  readonly order: boolean;
4579
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';
4580
4850
  };
4581
4851
  /**
4582
4852
  * Common-Table-Expression definitions (Q4) — `WITH <name> AS (...)`.
@@ -4855,6 +5125,16 @@ declare interface QueryDescriptor_2<R = Row_2> {
4855
5125
  readonly alias: string;
4856
5126
  readonly order: boolean;
4857
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';
4858
5138
  };
4859
5139
  /**
4860
5140
  * Common-Table-Expression definitions (Q4) — `WITH <name> AS (...)`.
@@ -5114,6 +5394,11 @@ export declare const reactiveTriggerRepairSql: (input: {
5114
5394
  readonly channel?: string;
5115
5395
  }) => ReadonlyArray<string>;
5116
5396
 
5397
+ export declare const readMigrationSnapshotCandidates: (sql: SqlClient.SqlClient, live: SchemaSnapshot) => Effect.Effect<ReadonlyArray<MigrationSnapshotCandidate>, SqlError>;
5398
+
5399
+ /** Every string column of a base table in the connected database. */
5400
+ export declare const readUndeclaredCharsetColumns: (sql: SqlClient.SqlClient) => Effect.Effect<ReadonlyArray<UndeclaredCharsetColumn>, SqlError>;
5401
+
5117
5402
  /**
5118
5403
  * Record that a schema was applied even though there was nothing to DO.
5119
5404
  *
@@ -5213,7 +5498,12 @@ export declare const renderColumnPg: (col: ColumnSnapshot, hasCompositePk?: bool
5213
5498
  */
5214
5499
  export declare const resolveMigrationLockSchema: (scope?: MigrationLockScope) => string | undefined;
5215
5500
 
5216
- 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>;
5217
5507
 
5218
5508
  declare type RetryDecision = 'retry' | 'noRetry';
5219
5509
 
@@ -5267,6 +5557,9 @@ export declare const rollbackFileBasedMigration: (sql: SqlClient.SqlClient, ctx:
5267
5557
  id: string;
5268
5558
  lockSchema?: string;
5269
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>;
5270
5563
  }) => Effect.Effect<{
5271
5564
  id: string;
5272
5565
  durationMs: number;
@@ -5365,6 +5658,10 @@ declare interface RuleViolationDetail {
5365
5658
  export declare const runFileBasedMigrations: (sql: SqlClient.SqlClient, ctx: RunFileBasedMigrationsCtx) => Effect.Effect<FileMigrationRunResult, unknown>;
5366
5659
 
5367
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>;
5368
5665
  readonly projectRoot: string;
5369
5666
  readonly env: 'dev' | 'staging' | 'prod';
5370
5667
  readonly appliedBy: string;
@@ -5391,6 +5688,12 @@ export declare interface RunFileBasedMigrationsCtx {
5391
5688
  * called — which is the defect the phase exists to remove, moved up a layer.
5392
5689
  */
5393
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;
5394
5697
  }
5395
5698
 
5396
5699
  /**
@@ -5401,7 +5704,7 @@ export declare interface RunFileBasedMigrationsCtx {
5401
5704
  * `voltro_isr_cache` so framework releases that grow new columns
5402
5705
  * don't crash old DBs at boot.
5403
5706
  */
5404
- 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>;
5405
5708
 
5406
5709
  /**
5407
5710
  * Run `applySchema` against a fresh SqlClient from the provided sqlLayer.
@@ -5411,7 +5714,7 @@ export declare const runFrameworkBootstrap: (tables: ReadonlyArray<AnyTable>, sq
5411
5714
  * `dialect` is passed through to the DDL emitter so SQL types + reactive
5412
5715
  * trigger emission match the underlying engine.
5413
5716
  */
5414
- 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>;
5415
5718
 
5416
5719
  /**
5417
5720
  * Execute ONE planned operation with the applier's own per-dialect
@@ -5422,7 +5725,7 @@ export declare const runMigrate: (tables: ReadonlyArray<AnyTable>, sqlLayer: Lay
5422
5725
  * shape from: the live table with the change applied, materialised as a
5423
5726
  * declaration by the caller.
5424
5727
  */
5425
- 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>;
5426
5729
 
5427
5730
  /**
5428
5731
  * Top-level entry point — called once per boot. Returns a
@@ -5431,9 +5734,11 @@ export declare const runMigrationOp: (sql: SqlClient.SqlClient, planned: Planned
5431
5734
  *
5432
5735
  * Wires the dev/prod dispatch + the VOLTRO_AUTO_MIGRATE escape hatch.
5433
5736
  */
5434
- 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>;
5435
5738
 
5436
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>;
5437
5742
  /** What `voltro logs` should attribute this run to. */
5438
5743
  readonly appliedBy: string;
5439
5744
  readonly environment: BootEnvironment;
@@ -5458,10 +5763,12 @@ export declare interface RunPlannedMigrationsCtx {
5458
5763
  * answers `already-absent` before reaching here). This function assumes the
5459
5764
  * object is there.
5460
5765
  */
5461
- 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>;
5462
5767
 
5463
5768
  /** Runs one Effect over the connection — the shape `ManagedRuntime.runPromise` and a store's `run` share. */
5464
- 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>;
5770
+
5771
+ export declare const SCHEMA_OWNER_TABLE = "_voltro_schema_owner";
5465
5772
 
5466
5773
  /**
5467
5774
  * Effect that applies the emitted DDL to the provided SqlClient. Splits
@@ -5507,6 +5814,34 @@ export declare interface SchemaEmitOptions {
5507
5814
  /** What a schema operation on the migration context found to do. */
5508
5815
  export declare type SchemaOpOutcome = 'applied' | 'already-absent' | 'already-present';
5509
5816
 
5817
+ /** Who is claiming the schema. */
5818
+ export declare interface SchemaOwnerClaim {
5819
+ /** The app's `name` — the identity compared on every boot. */
5820
+ readonly appName: string;
5821
+ readonly projectName?: string;
5822
+ readonly frameworkVersion?: string;
5823
+ }
5824
+
5825
+ export declare type SchemaOwnership =
5826
+ /** This app already owned the schema. */
5827
+ {
5828
+ readonly kind: 'owned';
5829
+ }
5830
+ /** Unclaimed until now; this app's claim was written. */
5831
+ | {
5832
+ readonly kind: 'claimed';
5833
+ }
5834
+ /** ANOTHER app owns it — the boot must refuse. */
5835
+ | {
5836
+ readonly kind: 'foreign';
5837
+ readonly owner: string;
5838
+ readonly claimedAt: string | undefined;
5839
+ }
5840
+ /** Read-only mode found no claim (or no table) to compare against. */
5841
+ | {
5842
+ readonly kind: 'unclaimed';
5843
+ };
5844
+
5510
5845
  /** A schema object a body asked about, in the vocabulary a user writes. */
5511
5846
  export declare type SchemaProbe = {
5512
5847
  readonly kind: 'table';
@@ -5558,6 +5893,19 @@ export declare const snapshotColumn: (name: string, def: ColumnDefinition<unknow
5558
5893
  /** Needed only for `storageType` — a caller that renders DDL does not want it. */
5559
5894
  dialect?: DialectId) => ColumnSnapshot;
5560
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
+
5561
5909
  /**
5562
5910
  * PostGIS spatial-distance clause carried on a {@link QueryDescriptor}.
5563
5911
  * Drives a `ST_Distance` projected column (distance-as-a-value) and/or a
@@ -5669,7 +6017,7 @@ export declare const sqlType: (column: ColumnDefinition<unknown>, dialect: Diale
5669
6017
  * Effect that performs the squash + returns a summary. Fails if:
5670
6018
  * - no eligible rows match (`before` cut everything off)
5671
6019
  * - the latest pre-squash fingerprint differs from `fingerprint`
5672
- * (signals uncommitted drift — refuse to lock in)
6020
+ * (the cut-off may select an older schema, or declarations may be unapplied)
5673
6021
  */
5674
6022
  export declare const squashMigrationPlans: (options: SquashOptions) => Effect.Effect<SquashResult, Error, SqlClient.SqlClient>;
5675
6023
 
@@ -5688,6 +6036,9 @@ export declare interface SquashOptions {
5688
6036
  readonly appliedBy: string;
5689
6037
  /** Environment tag for the snapshot row. */
5690
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;
5691
6042
  }
5692
6043
 
5693
6044
  export declare interface SquashResult {
@@ -6420,8 +6771,52 @@ export declare class TransactionBrokenByBodyError extends Error {
6420
6771
  constructor(migrationId: string, dialect: DialectId);
6421
6772
  }
6422
6773
 
6774
+ /**
6775
+ * Hand the schema to a different app name — `voltro db claim`. The one
6776
+ * deliberate way to change the owner: an app was RENAMED, not replaced.
6777
+ */
6778
+ export declare const transferSchemaOwner: (sql: SqlClient.SqlClient, claim: SchemaOwnerClaim, charset: MysqlCharset | undefined) => Effect.Effect<{
6779
+ readonly previous: string | undefined;
6780
+ }, SqlError>;
6781
+
6423
6782
  export declare const unblockLossy: (plan: MigrationPlan, scope?: ReturnType<typeof destructiveScope>) => MigrationPlan;
6424
6783
 
6784
+ /** One string column of an undeclared framework table, as the catalog reports it. */
6785
+ export declare interface UndeclaredCharsetColumn {
6786
+ readonly table: string;
6787
+ readonly column: string;
6788
+ readonly charset: string;
6789
+ /** `information_schema.columns.column_type`, e.g. `varchar(255)`, `text`. */
6790
+ readonly columnType: string;
6791
+ readonly nullable: boolean;
6792
+ /** Raw `column_default`; `null` (or the text `NULL` on MariaDB) when there is none. */
6793
+ readonly columnDefault: string | null;
6794
+ readonly generated: boolean;
6795
+ }
6796
+
6797
+ /**
6798
+ * One `ALTER TABLE` per table of the work. `checks` is each table's
6799
+ * column-level CHECKs (`columnLevelChecksMysql`): MariaDB stores such a CHECK
6800
+ * in the column definition, and a MODIFY that does not restate it deletes it.
6801
+ */
6802
+ export declare const undeclaredCharsetStatements: (w: UndeclaredCharsetWork, checks?: ReadonlyMap<string, ReadonlyMap<string, string>>) => ReadonlyArray<string>;
6803
+
6804
+ export declare interface UndeclaredCharsetWork {
6805
+ readonly tables: ReadonlyArray<{
6806
+ readonly table: string;
6807
+ readonly columns: ReadonlyArray<UndeclaredCharsetColumn>;
6808
+ }>;
6809
+ /** Columns that are off but carry a default or a generation expression — named, not rewritten. */
6810
+ readonly left: ReadonlyArray<UndeclaredCharsetColumn>;
6811
+ }
6812
+
6813
+ /**
6814
+ * What a declaration asks of the undeclared framework tables. Pure, so `db
6815
+ * plan` can print it and `db apply` can do it from the same reading. `only`
6816
+ * narrows it to named tables — the ones the cluster gate has just created.
6817
+ */
6818
+ export declare const undeclaredCharsetWork: (columns: ReadonlyArray<UndeclaredCharsetColumn>, declaredNames: ReadonlySet<string>, charset: MysqlCharset | undefined, only?: ReadonlySet<string>) => UndeclaredCharsetWork;
6819
+
6425
6820
  /** Every deploy that did not finish — `running` (the process died) or `failed` (its undo did not complete). An absent table is an empty ledger. */
6426
6821
  export declare const unfinishedDeploys: (sql: SqlClient.SqlClient) => Effect.Effect<ReadonlyArray<DeployLedgerRow>, never>;
6427
6822
 
@@ -6597,7 +6992,7 @@ declare interface WindowSpec_2 {
6597
6992
  }>;
6598
6993
  }
6599
6994
 
6600
- 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>;
6601
6996
 
6602
6997
  /**
6603
6998
  * Wrap a migration context so its four existence questions are recorded when —