@voltro/database 0.22.1 → 0.24.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
@@ -135,7 +135,13 @@ export declare interface ApplyPlanCtx {
135
135
  * Caller passes the dialect explicitly. Defaults to `'postgres'` for
136
136
  * call-sites that haven't migrated yet.
137
137
  */
138
- export declare const applySchema: (tables: ReadonlyArray<AnyTable>, dialect?: DialectId) => Effect.Effect<void, never, SqlClient.SqlClient>;
138
+ export declare const applySchema: (tables: ReadonlyArray<AnyTable>, dialect?: DialectId,
139
+ /** NOTIFY channel for the reactive triggers. Pass the SAME value the store
140
+ * was built with. They are two halves of one contract and used to be able to
141
+ * disagree silently: the store listened on its `cdcChannel` while the DDL
142
+ * always emitted on the default, so a custom channel received nothing and
143
+ * raised nothing. */
144
+ cdcChannel?: string) => Effect.Effect<void, never, SqlClient.SqlClient>;
139
145
 
140
146
  /** Per-row backfill body executed by the migration runner. */
141
147
  declare type BackfillFunction<TsType = unknown> = (row: Readonly<Record<string, unknown>>) => TsType | Promise<TsType>;
@@ -184,6 +190,28 @@ export declare type BootMigrationOutcome = {
184
190
  * producing a complete, non-overlapping partition of the table list. */
185
191
  export declare const chunkTables: <T>(xs: ReadonlyArray<T>, n: number) => T[][];
186
192
 
193
+ /**
194
+ * Classify a single DDL operation for rolling-deploy safety.
195
+ *
196
+ * UNSAFE (breaks old code during the overlap window):
197
+ * - `drop-column` / `drop-table` — old code reads it → error.
198
+ * - `rename-column` / `rename-table` — to old code the old name vanished.
199
+ * - `alter-column-type` — old reads may fail to decode, old writes may be
200
+ * rejected by the new type; even a "widening" is risky to assume.
201
+ * - `alter-column-nullability` → NOT NULL — old code that inserts without the
202
+ * column (or with NULL) is now rejected.
203
+ * - `add-unique` / `add-unique-composite` / `add-check` / `add-foreign-key`
204
+ * — a constraint old writes may violate the instant it exists.
205
+ *
206
+ * SAFE (old code keeps working):
207
+ * - `create-table` / `add-column` (nullable or defaulted) / `add-index` —
208
+ * additive; old code doesn't see it.
209
+ * - `drop-index` / `drop-unique*` / `drop-check` / `drop-foreign-key` —
210
+ * removing enforcement never makes an old query ERROR.
211
+ * - `alter-column-nullability` → nullable, `alter-column-default` — relaxing.
212
+ */
213
+ export declare const classifyRollingDeploySafety: (op: MigrationOperation) => RollingDeploySafety;
214
+
187
215
  declare interface ColumnDefinition<TsType, Type extends ColumnType = ColumnType, HasDefault extends boolean = boolean> {
188
216
  readonly type: Type;
189
217
  readonly nullable: boolean;
@@ -706,6 +734,9 @@ declare type ColumnType = 'id' | 'text' | 'integer' | 'real' | 'decimal' | 'bigi
706
734
  */
707
735
  export declare const declaredSnapshot: (tables: ReadonlyArray<TableLike>, dialect?: DialectId) => SchemaSnapshot;
708
736
 
737
+ /** The NOTIFY channel a schema emits on when none is configured. */
738
+ export declare const DEFAULT_CDC_CHANNEL = "framework_changes";
739
+
709
740
  /**
710
741
  * `DEFAULT <expr>` for an array-valued default.
711
742
  *
@@ -757,7 +788,11 @@ export declare const destructiveScope: (raw: string | undefined) => {
757
788
  export declare const detectReactiveTriggerDrift: (sql: SqlClient.SqlClient, tables: ReadonlyArray<{
758
789
  readonly tableName: string;
759
790
  readonly isReactive?: boolean;
760
- }>) => Effect.Effect<ReactiveTriggerDrift, SqlError_2>;
791
+ }>,
792
+ /** The app's `cdcChannel`. Part of the trigger NAME on any non-default
793
+ * channel, so omitting it here reported every table as missing on a database
794
+ * where every trigger was present. */
795
+ channel?: string) => Effect.Effect<ReactiveTriggerDrift, SqlError_2>;
761
796
 
762
797
  /** Dialect IDs the framework supports. */
763
798
  declare type DialectId = 'postgres' | 'mysql' | 'mariadb' | 'mssql' | 'sqlite' | 'turso';
@@ -815,7 +850,9 @@ export declare const emitDropColumnDdlMysql: (op: Extract<MigrationOperation, {
815
850
  * Dialect-uniform now, because there is nothing left in it that only one dialect
816
851
  * can express.
817
852
  */
818
- export declare const emitFrameworkBootstrapSql: (tables: ReadonlyArray<AnyTable>, dialect: DialectId) => string;
853
+ export declare const emitFrameworkBootstrapSql: (tables: ReadonlyArray<AnyTable>, dialect: DialectId,
854
+ /** NOTIFY channel for the reactive triggers — see `applySchema`. */
855
+ cdcChannel?: string) => string;
819
856
 
820
857
  /**
821
858
  * Emit the full schema DDL SCOPED to a tenant namespace. The container is
@@ -856,7 +893,10 @@ export declare const emitNamespacedSchemaSql: (tables: ReadonlyArray<AnyTable>,
856
893
  */
857
894
  export declare const emitNamespaceProvisionDdl: (namespace: string, dialect: DialectId, sqliteFile?: string) => string;
858
895
 
859
- export declare const emitSchemaSql: (entities: ReadonlyArray<AnyTable | AnyView>, dialect: DialectId) => string;
896
+ export declare const emitSchemaSql: (entities: ReadonlyArray<AnyTable | AnyView>, dialect: DialectId,
897
+ /** NOTIFY channel for the reactive triggers. Must match the store's
898
+ * `cdcChannel`, or writes emit where nothing is listening. */
899
+ cdcChannel?: string) => string;
860
900
 
861
901
  declare type EmptyMerge = unknown;
862
902
 
@@ -921,6 +961,21 @@ export declare interface FileMigrationContext {
921
961
  readonly appliedAt: string;
922
962
  }
923
963
 
964
+ /**
965
+ * A migration whose `up()` SUCCEEDED and whose ledger row could not be written.
966
+ *
967
+ * Its own error type because the recovery is the opposite of the ordinary one:
968
+ * do NOT re-run. The change has landed; what is missing is the record that says
969
+ * so, and every future invocation will apply it again until that row exists.
970
+ */
971
+ export declare class FileMigrationLedgerError extends Error {
972
+ readonly migrationId: string;
973
+ readonly file: string;
974
+ readonly cause: unknown;
975
+ readonly _tag = "FileMigrationLedgerError";
976
+ constructor(migrationId: string, file: string, cause: unknown);
977
+ }
978
+
924
979
  export declare interface FileMigrationRunResult {
925
980
  readonly applied: ReadonlyArray<{
926
981
  id: string;
@@ -1548,7 +1603,7 @@ export declare interface RawSqlFragment {
1548
1603
  readonly dependsOn?: ReadonlyArray<string>;
1549
1604
  }
1550
1605
 
1551
- /** Prefix `reactiveTableTriggerSql` names its triggers with. */
1606
+ /** Prefix every reactive trigger name starts with, whatever the channel. */
1552
1607
  export declare const REACTIVE_TRIGGER_PREFIX = "framework_changes_";
1553
1608
 
1554
1609
  export declare interface ReactiveTriggerDrift {
@@ -1559,6 +1614,39 @@ export declare interface ReactiveTriggerDrift {
1559
1614
  readonly stale: ReadonlyArray<string>;
1560
1615
  }
1561
1616
 
1617
+ /**
1618
+ * The statements that bring the DATABASE's change triggers back in line with
1619
+ * what the schema declares.
1620
+ *
1621
+ * This exists because the diff could not do it and said it could. Reactive
1622
+ * triggers are emitted only by the two full-schema emitters — the CREATE-
1623
+ * everything path for a fresh database, and the framework bootstrap — so a
1624
+ * table that arrived through the PLANNER (every table an existing app has
1625
+ * added since it was created) never got one. The planner has no trigger
1626
+ * dimension at all, so `db plan` reports 0 operations and `db apply` reports
1627
+ * "schema is up to date" while the drift detector is simultaneously telling
1628
+ * you 500 tables have no trigger, and pointing at `voltro db apply` as the
1629
+ * remedy. Reported by a consumer with 525 tables and 27 triggers, all 27 on
1630
+ * framework tables.
1631
+ *
1632
+ * A single instance is unaffected — its own writes reach its own subscribers
1633
+ * through the in-process path — so this is invisible until you scale out, which
1634
+ * is the worst possible time to find it.
1635
+ *
1636
+ * Postgres only: it is the one dialect where reactivity is carried by DDL. The
1637
+ * binlog / Change Tracking readers are configured at runtime from the same
1638
+ * in-memory table list, so they cannot drift from it.
1639
+ */
1640
+ export declare const reactiveTriggerRepairSql: (input: {
1641
+ readonly tables: ReadonlyArray<AnyTable>;
1642
+ readonly dialect: DialectId;
1643
+ /** Table names the detector found reactive-but-untriggered. */
1644
+ readonly missing: ReadonlyArray<string>;
1645
+ /** Table names the detector found triggered-but-declared-nonReactive. */
1646
+ readonly stale: ReadonlyArray<string>;
1647
+ readonly channel?: string;
1648
+ }) => ReadonlyArray<string>;
1649
+
1562
1650
  export declare const releaseMigrationLock: (sql: SqlClient.SqlClient) => Effect.Effect<void, SqlError_2>;
1563
1651
 
1564
1652
  export declare const renderColumnMysql: (col: ColumnSnapshot) => string;
@@ -1583,6 +1671,27 @@ export declare const rollbackFileBasedMigration: (sql: SqlClient.SqlClient, ctx:
1583
1671
  durationMs: number;
1584
1672
  }, unknown>;
1585
1673
 
1674
+ /** Verdict for one operation under a rolling deploy. */
1675
+ export declare type RollingDeploySafety = {
1676
+ readonly safe: true;
1677
+ } | {
1678
+ readonly safe: false;
1679
+ /** Why an instance on the OLD code breaks against the new schema. */
1680
+ readonly reason: string;
1681
+ /** The expand/contract move that keeps both code versions working. */
1682
+ readonly remedy: string;
1683
+ };
1684
+
1685
+ /**
1686
+ * The rolling-deploy-unsafe operations in a plan, each paired with its verdict.
1687
+ * Empty ⇒ the whole plan is safe to apply while an old deploy is still live.
1688
+ */
1689
+ export declare const rollingDeployUnsafeOps: (operations: ReadonlyArray<PlannedOperation>) => ReadonlyArray<{
1690
+ readonly op: PlannedOperation;
1691
+ readonly reason: string;
1692
+ readonly remedy: string;
1693
+ }>;
1694
+
1586
1695
  export declare const runFileBasedMigrations: (sql: SqlClient.SqlClient, ctx: RunFileBasedMigrationsCtx) => Effect.Effect<FileMigrationRunResult, unknown>;
1587
1696
 
1588
1697
  export declare interface RunFileBasedMigrationsCtx {