@voltro/database 0.12.0 → 0.14.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 +378 -0
- package/dist/{fileBased-DEW2BvnP.js → fileBased-Dfuv6JWP.js} +67 -67
- package/dist/index.d.ts +85 -7
- package/dist/index.js +516 -515
- package/dist/sql.d.ts +158 -15
- package/dist/sql.js +698 -602
- package/package.json +2 -2
package/dist/sql.d.ts
CHANGED
|
@@ -29,6 +29,14 @@ export declare interface AppliedMigration {
|
|
|
29
29
|
readonly environment: 'dev' | 'staging' | 'prod';
|
|
30
30
|
readonly rollbackPlan?: ReadonlyArray<MigrationOperation>;
|
|
31
31
|
readonly durationMs: number;
|
|
32
|
+
/**
|
|
33
|
+
* How many operations the applier actually EXECUTED, counted from the per-op
|
|
34
|
+
* results — not `operations.length`, which is what the plan asked for. The
|
|
35
|
+
* two can only differ if an op reports `skipped`, but the log lines quote
|
|
36
|
+
* this one on principle: "applied N op(s)" derived from the plan length is a
|
|
37
|
+
* claim about intent dressed up as a claim about the database.
|
|
38
|
+
*/
|
|
39
|
+
readonly appliedOps: number;
|
|
32
40
|
readonly onlineStrategy?: 'inline' | 'concurrent' | 'batched' | 'shadow-column';
|
|
33
41
|
readonly source: 'auto-diff' | 'file';
|
|
34
42
|
readonly notes?: string;
|
|
@@ -90,6 +98,31 @@ export declare interface ApplyPlanCtx {
|
|
|
90
98
|
readonly source: 'auto-diff' | 'file';
|
|
91
99
|
/** Optional human note via `voltro db apply --note "..."`. */
|
|
92
100
|
readonly notes?: string;
|
|
101
|
+
/**
|
|
102
|
+
* Re-plan against the LIVE schema — the convergence proof.
|
|
103
|
+
*
|
|
104
|
+
* `applyPlan` calls this once, after the DDL and BEFORE it records the
|
|
105
|
+
* fingerprint. A plan that converged re-plans to zero operations; anything
|
|
106
|
+
* left is DDL that ran without error and did not take effect, and the apply
|
|
107
|
+
* fails rather than recording a fingerprint that describes a database state
|
|
108
|
+
* that does not exist.
|
|
109
|
+
*
|
|
110
|
+
* This exists because "reports success, applies nothing" is not hypothetical.
|
|
111
|
+
* A schema shipped 31 operations that logged `applied 31 op(s)` on every boot
|
|
112
|
+
* for two releases: the statements were real, postgres accepted all of them,
|
|
113
|
+
* and none of them changed anything (`ALTER COLUMN … TYPE text` on a text
|
|
114
|
+
* column; `SET DEFAULT NULL` for a default the renderer couldn't express).
|
|
115
|
+
* The recorded fingerprint then made the next boot short-circuit on
|
|
116
|
+
* "schema up to date". Both underlying defects are fixed — this is the guard
|
|
117
|
+
* that makes the NEXT one loud instead of permanent.
|
|
118
|
+
*
|
|
119
|
+
* It is REQUIRED, not optional, because the caller is the only place that
|
|
120
|
+
* knows the exact planner inputs (which tables were filtered, which were
|
|
121
|
+
* ignored); an applier-side re-plan would compare against a different set and
|
|
122
|
+
* report drift that isn't there. A caller that cannot re-plan cannot prove it
|
|
123
|
+
* applied anything.
|
|
124
|
+
*/
|
|
125
|
+
readonly replan: (sql: SqlClient.SqlClient) => Effect.Effect<MigrationPlan, SqlError_2, SqlClient.SqlClient>;
|
|
93
126
|
}
|
|
94
127
|
|
|
95
128
|
/**
|
|
@@ -598,6 +631,43 @@ export declare interface ColumnSnapshot {
|
|
|
598
631
|
* blocks a masking export until classified.
|
|
599
632
|
*/
|
|
600
633
|
readonly safe?: boolean;
|
|
634
|
+
/**
|
|
635
|
+
* Type PARAMETERS for the three `ColumnType`s whose DDL is not determined by
|
|
636
|
+
* the type tag alone — `vector(n)`, `array(of)`, `enum(name, values)`.
|
|
637
|
+
* DECLARED-side only (introspection reports a concrete SQL type, not the
|
|
638
|
+
* declaration that produced it).
|
|
639
|
+
*
|
|
640
|
+
* These exist because the applier renders its DDL from a ColumnSnapshot, not
|
|
641
|
+
* from the `ColumnDefinition` that `migrate.ts`'s canonical `sqlType` reads.
|
|
642
|
+
* Without them the snapshot renderers had nothing to render and collapsed all
|
|
643
|
+
* three to `text` — which is not a smaller mistake than it looks. A declared
|
|
644
|
+
* `vector(1536)` against a live `text` column planned an `alter-column-type`
|
|
645
|
+
* that emitted `ALTER COLUMN … TYPE text`: valid SQL, applied successfully,
|
|
646
|
+
* changed nothing. The plan re-emitted it on every boot, the applier reported
|
|
647
|
+
* success every time, and the schema never converged. (Reported from a live
|
|
648
|
+
* pod: "applied 31 op(s)" with none of the 31 present in the database.)
|
|
649
|
+
*
|
|
650
|
+
* The rule for anything added later: if the applier has to RENDER it, the
|
|
651
|
+
* snapshot has to CARRY it — a snapshot renderer must never invent a type it
|
|
652
|
+
* wasn't given.
|
|
653
|
+
*/
|
|
654
|
+
readonly vectorDim?: number;
|
|
655
|
+
readonly vectorPrecision?: 'float32' | 'half';
|
|
656
|
+
readonly arrayElement?: ColumnType;
|
|
657
|
+
readonly enumName?: string;
|
|
658
|
+
readonly enumValues?: ReadonlyArray<string>;
|
|
659
|
+
/**
|
|
660
|
+
* PostGIS `geography(kind, srid)` / `geometry(kind, srid)` parameters. Same
|
|
661
|
+
* reason as the three above: a spatial column declares `type: 'text'`, so
|
|
662
|
+
* WITHOUT this the applier's `add-column` created a plain `text` column and
|
|
663
|
+
* introspection (`USER-DEFINED` → `text`) agreed with it — no churn, no
|
|
664
|
+
* error, and no spatial column. DECLARED-side only.
|
|
665
|
+
*/
|
|
666
|
+
readonly spatial?: {
|
|
667
|
+
readonly kind: 'geography' | 'geometry';
|
|
668
|
+
readonly geomKind: string;
|
|
669
|
+
readonly srid: number;
|
|
670
|
+
};
|
|
601
671
|
}
|
|
602
672
|
|
|
603
673
|
declare type ColumnType = 'id' | 'text' | 'integer' | 'real' | 'decimal' | 'bigint' | 'boolean' | 'timestamp' | 'date' | 'json' | 'bytes' | 'reference' | 'vector' | 'enum' | 'array' | 'interval' | 'raw';
|
|
@@ -610,6 +680,16 @@ declare type ColumnType = 'id' | 'text' | 'integer' | 'real' | 'decimal' | 'bigi
|
|
|
610
680
|
*/
|
|
611
681
|
export declare const declaredSnapshot: (tables: ReadonlyArray<TableLike>, dialect?: DialectId) => SchemaSnapshot;
|
|
612
682
|
|
|
683
|
+
/**
|
|
684
|
+
* `DEFAULT <expr>` for an array-valued default.
|
|
685
|
+
*
|
|
686
|
+
* Postgres is the only dialect with native arrays, so a `array()` column there
|
|
687
|
+
* takes an array literal (`'{a,b}'::text[]`); every other dialect stores the
|
|
688
|
+
* value as JSON/TEXT and takes the json form. A default on a `json()` column
|
|
689
|
+
* always takes the json form — the array is the VALUE, not the storage.
|
|
690
|
+
*/
|
|
691
|
+
export declare const defaultArrayClause: (value: ReadonlyArray<unknown>, column: ColumnDefinition<unknown>, dialect: DialectId) => string;
|
|
692
|
+
|
|
613
693
|
export declare const defaultClause: (column: ColumnDefinition<unknown>, dialect: DialectId) => string | null;
|
|
614
694
|
|
|
615
695
|
/**
|
|
@@ -622,6 +702,18 @@ export declare const defaultJsonClause: (value: object, dialect: DialectId) => s
|
|
|
622
702
|
|
|
623
703
|
export declare const describeOutcome: (outcome: BootMigrationOutcome, dialectId: string) => string;
|
|
624
704
|
|
|
705
|
+
/**
|
|
706
|
+
* Compare declared reactivity against the triggers actually installed.
|
|
707
|
+
*
|
|
708
|
+
* Postgres only — it is the one dialect where reactivity is carried by DDL. The
|
|
709
|
+
* binlog and Change Tracking readers are configured at runtime from the same
|
|
710
|
+
* in-memory table list the schema produced, so they cannot drift from it.
|
|
711
|
+
*/
|
|
712
|
+
export declare const detectReactiveTriggerDrift: (sql: SqlClient.SqlClient, tables: ReadonlyArray<{
|
|
713
|
+
readonly tableName: string;
|
|
714
|
+
readonly isReactive?: boolean;
|
|
715
|
+
}>) => Effect.Effect<ReactiveTriggerDrift, SqlError_2>;
|
|
716
|
+
|
|
625
717
|
/** Dialect IDs the framework supports. */
|
|
626
718
|
declare type DialectId = 'postgres' | 'mysql' | 'mariadb' | 'mssql' | 'sqlite' | 'turso';
|
|
627
719
|
|
|
@@ -799,6 +891,9 @@ export declare interface FileMigrationRunResult {
|
|
|
799
891
|
*/
|
|
800
892
|
export declare const fingerprintSchema: (snapshot: SchemaSnapshot) => string;
|
|
801
893
|
|
|
894
|
+
/** Human report, or `undefined` when the database and the schema agree. */
|
|
895
|
+
export declare const formatReactiveTriggerDrift: (drift: ReactiveTriggerDrift) => string | undefined;
|
|
896
|
+
|
|
802
897
|
/**
|
|
803
898
|
* Fully-resolved scheme stored on the `id` column definition AFTER
|
|
804
899
|
* `table()` walks the fields. For `typeid`, the prefix is always
|
|
@@ -1289,6 +1384,17 @@ export declare const parseEnumCheck: (clause: string) => {
|
|
|
1289
1384
|
readonly values: ReadonlyArray<string>;
|
|
1290
1385
|
} | null;
|
|
1291
1386
|
|
|
1387
|
+
/**
|
|
1388
|
+
* The ids of migration files present on disk but NOT yet recorded against THIS
|
|
1389
|
+
* database — i.e. exactly what `runFileBasedMigrations` would execute next.
|
|
1390
|
+
*
|
|
1391
|
+
* Exists so a caller can decide WHETHER to apply before applying. `voltro dev`
|
|
1392
|
+
* uses it to refuse unattended file migrations against a remote database: that
|
|
1393
|
+
* decision needs the list (to name it in the refusal) and must not have run
|
|
1394
|
+
* anything to get it.
|
|
1395
|
+
*/
|
|
1396
|
+
export declare const pendingFileMigrationIds: (sql: SqlClient.SqlClient, projectRoot: string) => Effect.Effect<ReadonlyArray<string>, unknown>;
|
|
1397
|
+
|
|
1292
1398
|
export declare interface PlanInput {
|
|
1293
1399
|
readonly declared: ReadonlyArray<TableLike>;
|
|
1294
1400
|
readonly live: SchemaSnapshot;
|
|
@@ -1403,6 +1509,17 @@ export declare interface RawSqlFragment {
|
|
|
1403
1509
|
readonly dependsOn?: ReadonlyArray<string>;
|
|
1404
1510
|
}
|
|
1405
1511
|
|
|
1512
|
+
/** Prefix `reactiveTableTriggerSql` names its triggers with. */
|
|
1513
|
+
export declare const REACTIVE_TRIGGER_PREFIX = "framework_changes_";
|
|
1514
|
+
|
|
1515
|
+
export declare interface ReactiveTriggerDrift {
|
|
1516
|
+
/** Tables the schema says are reactive that carry no trigger in the DB. */
|
|
1517
|
+
readonly missing: ReadonlyArray<string>;
|
|
1518
|
+
/** Tables that carry a trigger but are declared `.nonReactive()` — their
|
|
1519
|
+
* writes still hit the NOTIFY channel and cost WAL for nothing. */
|
|
1520
|
+
readonly stale: ReadonlyArray<string>;
|
|
1521
|
+
}
|
|
1522
|
+
|
|
1406
1523
|
export declare const releaseMigrationLock: (sql: SqlClient.SqlClient) => Effect.Effect<void, SqlError_2>;
|
|
1407
1524
|
|
|
1408
1525
|
export declare const renderColumnMysql: (col: ColumnSnapshot) => string;
|
|
@@ -1427,16 +1544,6 @@ export declare const rollbackFileBasedMigration: (sql: SqlClient.SqlClient, ctx:
|
|
|
1427
1544
|
durationMs: number;
|
|
1428
1545
|
}, unknown>;
|
|
1429
1546
|
|
|
1430
|
-
/**
|
|
1431
|
-
* Discover + apply every pending file-based migration under
|
|
1432
|
-
* `projectRoot/migrations/`. Runs under the same advisory lock as
|
|
1433
|
-
* the planner-based applier, so concurrent invocations serialise.
|
|
1434
|
-
*
|
|
1435
|
-
* - Returns `applied` (newly-run ids + durations) + `skipped`
|
|
1436
|
-
* (ids that were already applied) for the caller's log line.
|
|
1437
|
-
* - Failures abort the run + propagate as the Effect's error
|
|
1438
|
-
* channel; the caller (dev.ts boot) is expected to refuse-to-boot.
|
|
1439
|
-
*/
|
|
1440
1547
|
export declare const runFileBasedMigrations: (sql: SqlClient.SqlClient, ctx: RunFileBasedMigrationsCtx) => Effect.Effect<FileMigrationRunResult, unknown>;
|
|
1441
1548
|
|
|
1442
1549
|
export declare interface RunFileBasedMigrationsCtx {
|
|
@@ -1498,6 +1605,14 @@ export declare interface SchemaSnapshot {
|
|
|
1498
1605
|
*/
|
|
1499
1606
|
export declare const shortFingerprint: (fp: string) => string;
|
|
1500
1607
|
|
|
1608
|
+
/**
|
|
1609
|
+
* Snapshot a single `TableLike` declaration. Drops the runtime-only
|
|
1610
|
+
* fields (`computed`, `defaultFactory`, `__tsType`) that don't round-
|
|
1611
|
+
* trip to/from `information_schema`. `idScheme.kind` IS carried — the
|
|
1612
|
+
* applier needs it to emit auto-increment DDL for numeric ids.
|
|
1613
|
+
*/
|
|
1614
|
+
export declare const snapshotColumn: (name: string, def: ColumnDefinition<unknown>) => ColumnSnapshot;
|
|
1615
|
+
|
|
1501
1616
|
/**
|
|
1502
1617
|
* The `sql` template tag — captures a tagged-template literal into a
|
|
1503
1618
|
* {@link RawSqlFragment} descriptor without binding it to any client.
|
|
@@ -1567,7 +1682,7 @@ export declare interface SquashResult {
|
|
|
1567
1682
|
readonly snapshotId: string;
|
|
1568
1683
|
}
|
|
1569
1684
|
|
|
1570
|
-
declare interface Table<Name extends string, Fields extends Record<string, ColumnDefinition<unknown>>, Reactive extends boolean =
|
|
1685
|
+
declare interface Table<Name extends string, Fields extends Record<string, ColumnDefinition<unknown>>, Reactive extends boolean = true, IxNames extends string = never> extends TableLike {
|
|
1571
1686
|
readonly tableName: Name;
|
|
1572
1687
|
readonly fields: Fields;
|
|
1573
1688
|
readonly isReactive: Reactive;
|
|
@@ -1879,11 +1994,39 @@ declare interface Table<Name extends string, Fields extends Record<string, Colum
|
|
|
1879
1994
|
*/
|
|
1880
1995
|
primaryKey: <const F extends readonly [keyof Fields & string, keyof Fields & string, ...Array<keyof Fields & string>]>(fields: F) => Table<Name, Fields, Reactive, IxNames>;
|
|
1881
1996
|
/**
|
|
1882
|
-
*
|
|
1883
|
-
*
|
|
1884
|
-
*
|
|
1997
|
+
* Turn reactivity OFF for this table.
|
|
1998
|
+
*
|
|
1999
|
+
* Not "off across instances" — off. The table emits no change events at all:
|
|
2000
|
+
* no local subscriber fires, no cross-instance transport carries it. That is
|
|
2001
|
+
* what the name says, and a version of this that only silenced the
|
|
2002
|
+
* cross-instance half would leave every in-process subscription live on a
|
|
2003
|
+
* table declared non-reactive.
|
|
2004
|
+
*
|
|
2005
|
+
* Reactivity is the default because that is what this framework is for. The
|
|
2006
|
+
* inverse (`.reactive()`) was the API for a long time and failed in both
|
|
2007
|
+
* directions silently: written on mariadb it did nothing at all, omitted on
|
|
2008
|
+
* postgres+cdc it meant a write on one instance never reached another
|
|
2009
|
+
* instance's subscribers.
|
|
2010
|
+
*
|
|
2011
|
+
* Implemented in EVERY dialect, at the store's emit:
|
|
2012
|
+
*
|
|
2013
|
+
* memory the store's single emit point drops the event
|
|
2014
|
+
* sqlite / same, and turso reuses that store
|
|
2015
|
+
* turso
|
|
2016
|
+
* postgres also: no `REPLICA IDENTITY FULL`, no `pg_notify` trigger
|
|
2017
|
+
* mysql / also: excluded from the binlog reader's table filter
|
|
2018
|
+
* mariadb
|
|
2019
|
+
* mssql also: excluded from the Change Tracking table set
|
|
2020
|
+
*
|
|
2021
|
+
* The WRITE is unaffected — this is about notification, never persistence.
|
|
2022
|
+
*
|
|
2023
|
+
* Worth using for a genuinely hot table nobody subscribes to: an append-only
|
|
2024
|
+
* event log, a metrics sink. On postgres it also drops `REPLICA IDENTITY
|
|
2025
|
+
* FULL`, which widens every UPDATE/DELETE in the WAL. Do NOT use it on a
|
|
2026
|
+
* table a query reads — that subscription simply never fires. `voltro dev`
|
|
2027
|
+
* says so at boot.
|
|
1885
2028
|
*/
|
|
1886
|
-
|
|
2029
|
+
nonReactive: () => Table<Name, Fields, false, IxNames>;
|
|
1887
2030
|
}
|
|
1888
2031
|
|
|
1889
2032
|
/**
|