@voltro/database 0.23.0 → 0.25.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +537 -0
- package/dist/{frameworkLiveTables-BvPIm2cD.js → frameworkLiveTables-BQwB8K6R.js} +9 -0
- package/dist/index.d.ts +178 -0
- package/dist/index.js +457 -426
- package/dist/sql.d.ts +143 -6
- package/dist/sql.js +600 -497
- package/package.json +2 -2
package/dist/sql.d.ts
CHANGED
|
@@ -143,6 +143,25 @@ export declare const applySchema: (tables: ReadonlyArray<AnyTable>, dialect?: Di
|
|
|
143
143
|
* raised nothing. */
|
|
144
144
|
cdcChannel?: string) => Effect.Effect<void, never, SqlClient.SqlClient>;
|
|
145
145
|
|
|
146
|
+
/**
|
|
147
|
+
* The OPT-IN refuse gate over the advisory. By default (`VOLTRO_ROLLING_DEPLOY`
|
|
148
|
+
* unset) this never fires — the classifier stays a `db plan` advisory, because
|
|
149
|
+
* the framework can't know the deploy strategy and a maintenance-window /
|
|
150
|
+
* scale-to-zero deploy has no overlap window. An operator who DOES rolling
|
|
151
|
+
* deploys opts in with `VOLTRO_ROLLING_DEPLOY=1`, turning an unsafe `db apply`
|
|
152
|
+
* into a hard refusal they must split (expand/contract) or explicitly `--force`.
|
|
153
|
+
*
|
|
154
|
+
* Pure — the caller supplies the resolved flags + the unsafe-op count (the ⚠
|
|
155
|
+
* detail is already printed by the plan renderer), so the decision is testable
|
|
156
|
+
* without a CLI or a DB, and the cloud managed-hosting migration wall can reuse
|
|
157
|
+
* exactly this verdict.
|
|
158
|
+
*/
|
|
159
|
+
export declare const assessRollingDeployGate: (input: {
|
|
160
|
+
readonly enabled: boolean;
|
|
161
|
+
readonly forced: boolean;
|
|
162
|
+
readonly unsafeCount: number;
|
|
163
|
+
}) => RollingDeployGateVerdict;
|
|
164
|
+
|
|
146
165
|
/** Per-row backfill body executed by the migration runner. */
|
|
147
166
|
declare type BackfillFunction<TsType = unknown> = (row: Readonly<Record<string, unknown>>) => TsType | Promise<TsType>;
|
|
148
167
|
|
|
@@ -190,6 +209,28 @@ export declare type BootMigrationOutcome = {
|
|
|
190
209
|
* producing a complete, non-overlapping partition of the table list. */
|
|
191
210
|
export declare const chunkTables: <T>(xs: ReadonlyArray<T>, n: number) => T[][];
|
|
192
211
|
|
|
212
|
+
/**
|
|
213
|
+
* Classify a single DDL operation for rolling-deploy safety.
|
|
214
|
+
*
|
|
215
|
+
* UNSAFE (breaks old code during the overlap window):
|
|
216
|
+
* - `drop-column` / `drop-table` — old code reads it → error.
|
|
217
|
+
* - `rename-column` / `rename-table` — to old code the old name vanished.
|
|
218
|
+
* - `alter-column-type` — old reads may fail to decode, old writes may be
|
|
219
|
+
* rejected by the new type; even a "widening" is risky to assume.
|
|
220
|
+
* - `alter-column-nullability` → NOT NULL — old code that inserts without the
|
|
221
|
+
* column (or with NULL) is now rejected.
|
|
222
|
+
* - `add-unique` / `add-unique-composite` / `add-check` / `add-foreign-key`
|
|
223
|
+
* — a constraint old writes may violate the instant it exists.
|
|
224
|
+
*
|
|
225
|
+
* SAFE (old code keeps working):
|
|
226
|
+
* - `create-table` / `add-column` (nullable or defaulted) / `add-index` —
|
|
227
|
+
* additive; old code doesn't see it.
|
|
228
|
+
* - `drop-index` / `drop-unique*` / `drop-check` / `drop-foreign-key` —
|
|
229
|
+
* removing enforcement never makes an old query ERROR.
|
|
230
|
+
* - `alter-column-nullability` → nullable, `alter-column-default` — relaxing.
|
|
231
|
+
*/
|
|
232
|
+
export declare const classifyRollingDeploySafety: (op: MigrationOperation) => RollingDeploySafety;
|
|
233
|
+
|
|
193
234
|
declare interface ColumnDefinition<TsType, Type extends ColumnType = ColumnType, HasDefault extends boolean = boolean> {
|
|
194
235
|
readonly type: Type;
|
|
195
236
|
readonly nullable: boolean;
|
|
@@ -358,6 +399,23 @@ declare interface ColumnDefinition<TsType, Type extends ColumnType = ColumnType,
|
|
|
358
399
|
* COSTS is per command — see `serverOnly()` below for the matrix.
|
|
359
400
|
*/
|
|
360
401
|
readonly serverOnly?: boolean;
|
|
402
|
+
/**
|
|
403
|
+
* Optimistic-concurrency marker, set by `.version()`.
|
|
404
|
+
*
|
|
405
|
+
* The column the store INCREMENTS on every update, and the one an update may
|
|
406
|
+
* carry an expectation of. Two clients that read the same row and both write
|
|
407
|
+
* it are the normal case, not an exotic one — and without this the second
|
|
408
|
+
* write silently wins, which is the shape of every "my change disappeared"
|
|
409
|
+
* report ever filed.
|
|
410
|
+
*
|
|
411
|
+
* A TIMESTAMP cannot do this job. Two writes in the same millisecond are
|
|
412
|
+
* indistinguishable, and across replicas the clocks disagree; this repo has
|
|
413
|
+
* already lost rows to exactly that (an analytics sink dropped 7 of 40 events
|
|
414
|
+
* written in the same millisecond as the query bounding them). A monotonic
|
|
415
|
+
* integer has neither problem, and it also answers the other half of the
|
|
416
|
+
* question — which of two copies is NEWER — without consulting a clock.
|
|
417
|
+
*/
|
|
418
|
+
readonly versionColumn?: boolean;
|
|
361
419
|
/**
|
|
362
420
|
* Data-sensitivity classification, set by `.sensitive(class)`. Declares that
|
|
363
421
|
* this column holds personal / sensitive data of a given CLASS (`'email'`,
|
|
@@ -712,10 +770,7 @@ declare type ColumnType = 'id' | 'text' | 'integer' | 'real' | 'decimal' | 'bigi
|
|
|
712
770
|
*/
|
|
713
771
|
export declare const declaredSnapshot: (tables: ReadonlyArray<TableLike>, dialect?: DialectId) => SchemaSnapshot;
|
|
714
772
|
|
|
715
|
-
/** The NOTIFY channel a schema emits on when none is configured.
|
|
716
|
-
* the store and the DDL cannot drift: they must agree, and before this they
|
|
717
|
-
* did not — the store read `options.cdcChannel` while the trigger hardcoded
|
|
718
|
-
* the default, so any custom channel listened to silence. */
|
|
773
|
+
/** The NOTIFY channel a schema emits on when none is configured. */
|
|
719
774
|
export declare const DEFAULT_CDC_CHANNEL = "framework_changes";
|
|
720
775
|
|
|
721
776
|
/**
|
|
@@ -769,7 +824,11 @@ export declare const destructiveScope: (raw: string | undefined) => {
|
|
|
769
824
|
export declare const detectReactiveTriggerDrift: (sql: SqlClient.SqlClient, tables: ReadonlyArray<{
|
|
770
825
|
readonly tableName: string;
|
|
771
826
|
readonly isReactive?: boolean;
|
|
772
|
-
}
|
|
827
|
+
}>,
|
|
828
|
+
/** The app's `cdcChannel`. Part of the trigger NAME on any non-default
|
|
829
|
+
* channel, so omitting it here reported every table as missing on a database
|
|
830
|
+
* where every trigger was present. */
|
|
831
|
+
channel?: string) => Effect.Effect<ReactiveTriggerDrift, SqlError_2>;
|
|
773
832
|
|
|
774
833
|
/** Dialect IDs the framework supports. */
|
|
775
834
|
declare type DialectId = 'postgres' | 'mysql' | 'mariadb' | 'mssql' | 'sqlite' | 'turso';
|
|
@@ -938,6 +997,21 @@ export declare interface FileMigrationContext {
|
|
|
938
997
|
readonly appliedAt: string;
|
|
939
998
|
}
|
|
940
999
|
|
|
1000
|
+
/**
|
|
1001
|
+
* A migration whose `up()` SUCCEEDED and whose ledger row could not be written.
|
|
1002
|
+
*
|
|
1003
|
+
* Its own error type because the recovery is the opposite of the ordinary one:
|
|
1004
|
+
* do NOT re-run. The change has landed; what is missing is the record that says
|
|
1005
|
+
* so, and every future invocation will apply it again until that row exists.
|
|
1006
|
+
*/
|
|
1007
|
+
export declare class FileMigrationLedgerError extends Error {
|
|
1008
|
+
readonly migrationId: string;
|
|
1009
|
+
readonly file: string;
|
|
1010
|
+
readonly cause: unknown;
|
|
1011
|
+
readonly _tag = "FileMigrationLedgerError";
|
|
1012
|
+
constructor(migrationId: string, file: string, cause: unknown);
|
|
1013
|
+
}
|
|
1014
|
+
|
|
941
1015
|
export declare interface FileMigrationRunResult {
|
|
942
1016
|
readonly applied: ReadonlyArray<{
|
|
943
1017
|
id: string;
|
|
@@ -1565,7 +1639,7 @@ export declare interface RawSqlFragment {
|
|
|
1565
1639
|
readonly dependsOn?: ReadonlyArray<string>;
|
|
1566
1640
|
}
|
|
1567
1641
|
|
|
1568
|
-
/** Prefix
|
|
1642
|
+
/** Prefix every reactive trigger name starts with, whatever the channel. */
|
|
1569
1643
|
export declare const REACTIVE_TRIGGER_PREFIX = "framework_changes_";
|
|
1570
1644
|
|
|
1571
1645
|
export declare interface ReactiveTriggerDrift {
|
|
@@ -1576,6 +1650,39 @@ export declare interface ReactiveTriggerDrift {
|
|
|
1576
1650
|
readonly stale: ReadonlyArray<string>;
|
|
1577
1651
|
}
|
|
1578
1652
|
|
|
1653
|
+
/**
|
|
1654
|
+
* The statements that bring the DATABASE's change triggers back in line with
|
|
1655
|
+
* what the schema declares.
|
|
1656
|
+
*
|
|
1657
|
+
* This exists because the diff could not do it and said it could. Reactive
|
|
1658
|
+
* triggers are emitted only by the two full-schema emitters — the CREATE-
|
|
1659
|
+
* everything path for a fresh database, and the framework bootstrap — so a
|
|
1660
|
+
* table that arrived through the PLANNER (every table an existing app has
|
|
1661
|
+
* added since it was created) never got one. The planner has no trigger
|
|
1662
|
+
* dimension at all, so `db plan` reports 0 operations and `db apply` reports
|
|
1663
|
+
* "schema is up to date" while the drift detector is simultaneously telling
|
|
1664
|
+
* you 500 tables have no trigger, and pointing at `voltro db apply` as the
|
|
1665
|
+
* remedy. Reported by a consumer with 525 tables and 27 triggers, all 27 on
|
|
1666
|
+
* framework tables.
|
|
1667
|
+
*
|
|
1668
|
+
* A single instance is unaffected — its own writes reach its own subscribers
|
|
1669
|
+
* through the in-process path — so this is invisible until you scale out, which
|
|
1670
|
+
* is the worst possible time to find it.
|
|
1671
|
+
*
|
|
1672
|
+
* Postgres only: it is the one dialect where reactivity is carried by DDL. The
|
|
1673
|
+
* binlog / Change Tracking readers are configured at runtime from the same
|
|
1674
|
+
* in-memory table list, so they cannot drift from it.
|
|
1675
|
+
*/
|
|
1676
|
+
export declare const reactiveTriggerRepairSql: (input: {
|
|
1677
|
+
readonly tables: ReadonlyArray<AnyTable>;
|
|
1678
|
+
readonly dialect: DialectId;
|
|
1679
|
+
/** Table names the detector found reactive-but-untriggered. */
|
|
1680
|
+
readonly missing: ReadonlyArray<string>;
|
|
1681
|
+
/** Table names the detector found triggered-but-declared-nonReactive. */
|
|
1682
|
+
readonly stale: ReadonlyArray<string>;
|
|
1683
|
+
readonly channel?: string;
|
|
1684
|
+
}) => ReadonlyArray<string>;
|
|
1685
|
+
|
|
1579
1686
|
export declare const releaseMigrationLock: (sql: SqlClient.SqlClient) => Effect.Effect<void, SqlError_2>;
|
|
1580
1687
|
|
|
1581
1688
|
export declare const renderColumnMysql: (col: ColumnSnapshot) => string;
|
|
@@ -1600,6 +1707,36 @@ export declare const rollbackFileBasedMigration: (sql: SqlClient.SqlClient, ctx:
|
|
|
1600
1707
|
durationMs: number;
|
|
1601
1708
|
}, unknown>;
|
|
1602
1709
|
|
|
1710
|
+
export declare interface RollingDeployGateVerdict {
|
|
1711
|
+
/** The apply MUST NOT proceed (opt-in gate tripped, not forced). */
|
|
1712
|
+
readonly refuse: boolean;
|
|
1713
|
+
/** The gate tripped but `--force` waved it through — surface a warning. */
|
|
1714
|
+
readonly warnForced: boolean;
|
|
1715
|
+
/** Empty unless refuse or warnForced. */
|
|
1716
|
+
readonly message: string;
|
|
1717
|
+
}
|
|
1718
|
+
|
|
1719
|
+
/** Verdict for one operation under a rolling deploy. */
|
|
1720
|
+
export declare type RollingDeploySafety = {
|
|
1721
|
+
readonly safe: true;
|
|
1722
|
+
} | {
|
|
1723
|
+
readonly safe: false;
|
|
1724
|
+
/** Why an instance on the OLD code breaks against the new schema. */
|
|
1725
|
+
readonly reason: string;
|
|
1726
|
+
/** The expand/contract move that keeps both code versions working. */
|
|
1727
|
+
readonly remedy: string;
|
|
1728
|
+
};
|
|
1729
|
+
|
|
1730
|
+
/**
|
|
1731
|
+
* The rolling-deploy-unsafe operations in a plan, each paired with its verdict.
|
|
1732
|
+
* Empty ⇒ the whole plan is safe to apply while an old deploy is still live.
|
|
1733
|
+
*/
|
|
1734
|
+
export declare const rollingDeployUnsafeOps: (operations: ReadonlyArray<PlannedOperation>) => ReadonlyArray<{
|
|
1735
|
+
readonly op: PlannedOperation;
|
|
1736
|
+
readonly reason: string;
|
|
1737
|
+
readonly remedy: string;
|
|
1738
|
+
}>;
|
|
1739
|
+
|
|
1603
1740
|
export declare const runFileBasedMigrations: (sql: SqlClient.SqlClient, ctx: RunFileBasedMigrationsCtx) => Effect.Effect<FileMigrationRunResult, unknown>;
|
|
1604
1741
|
|
|
1605
1742
|
export declare interface RunFileBasedMigrationsCtx {
|