@voltro/database 0.13.0 → 0.15.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
@@ -702,6 +702,37 @@ export declare const defaultJsonClause: (value: object, dialect: DialectId) => s
702
702
 
703
703
  export declare const describeOutcome: (outcome: BootMigrationOutcome, dialectId: string) => string;
704
704
 
705
+ /**
706
+ * Parse `VOLTRO_DESTRUCTIVE_OK`.
707
+ *
708
+ * `1` / `true` acknowledges every lossy op in the run. A comma-separated list
709
+ * acknowledges only those TABLES — which is the form somebody wants far more
710
+ * often, and its absence was a real complaint: a plan carrying one intended drop
711
+ * plus three other lossy ops had no way to say yes to the one. `1` said yes to
712
+ * all four, and the alternative was a hand-written DROP migration, which the
713
+ * framework's own upgrade note warns against.
714
+ *
715
+ * `undefined` = not set at all.
716
+ */
717
+ export declare const destructiveScope: (raw: string | undefined) => {
718
+ readonly kind: "all";
719
+ } | {
720
+ readonly kind: "tables";
721
+ readonly tables: ReadonlySet<string>;
722
+ } | undefined;
723
+
724
+ /**
725
+ * Compare declared reactivity against the triggers actually installed.
726
+ *
727
+ * Postgres only — it is the one dialect where reactivity is carried by DDL. The
728
+ * binlog and Change Tracking readers are configured at runtime from the same
729
+ * in-memory table list the schema produced, so they cannot drift from it.
730
+ */
731
+ export declare const detectReactiveTriggerDrift: (sql: SqlClient.SqlClient, tables: ReadonlyArray<{
732
+ readonly tableName: string;
733
+ readonly isReactive?: boolean;
734
+ }>) => Effect.Effect<ReactiveTriggerDrift, SqlError_2>;
735
+
705
736
  /** Dialect IDs the framework supports. */
706
737
  declare type DialectId = 'postgres' | 'mysql' | 'mariadb' | 'mssql' | 'sqlite' | 'turso';
707
738
 
@@ -879,6 +910,9 @@ export declare interface FileMigrationRunResult {
879
910
  */
880
911
  export declare const fingerprintSchema: (snapshot: SchemaSnapshot) => string;
881
912
 
913
+ /** Human report, or `undefined` when the database and the schema agree. */
914
+ export declare const formatReactiveTriggerDrift: (drift: ReactiveTriggerDrift) => string | undefined;
915
+
882
916
  /**
883
917
  * Fully-resolved scheme stored on the `id` column definition AFTER
884
918
  * `table()` walks the fields. For `typeid`, the prefix is always
@@ -1369,6 +1403,17 @@ export declare const parseEnumCheck: (clause: string) => {
1369
1403
  readonly values: ReadonlyArray<string>;
1370
1404
  } | null;
1371
1405
 
1406
+ /**
1407
+ * The ids of migration files present on disk but NOT yet recorded against THIS
1408
+ * database — i.e. exactly what `runFileBasedMigrations` would execute next.
1409
+ *
1410
+ * Exists so a caller can decide WHETHER to apply before applying. `voltro dev`
1411
+ * uses it to refuse unattended file migrations against a remote database: that
1412
+ * decision needs the list (to name it in the refusal) and must not have run
1413
+ * anything to get it.
1414
+ */
1415
+ export declare const pendingFileMigrationIds: (sql: SqlClient.SqlClient, projectRoot: string) => Effect.Effect<ReadonlyArray<string>, unknown>;
1416
+
1372
1417
  export declare interface PlanInput {
1373
1418
  readonly declared: ReadonlyArray<TableLike>;
1374
1419
  readonly live: SchemaSnapshot;
@@ -1483,6 +1528,17 @@ export declare interface RawSqlFragment {
1483
1528
  readonly dependsOn?: ReadonlyArray<string>;
1484
1529
  }
1485
1530
 
1531
+ /** Prefix `reactiveTableTriggerSql` names its triggers with. */
1532
+ export declare const REACTIVE_TRIGGER_PREFIX = "framework_changes_";
1533
+
1534
+ export declare interface ReactiveTriggerDrift {
1535
+ /** Tables the schema says are reactive that carry no trigger in the DB. */
1536
+ readonly missing: ReadonlyArray<string>;
1537
+ /** Tables that carry a trigger but are declared `.nonReactive()` — their
1538
+ * writes still hit the NOTIFY channel and cost WAL for nothing. */
1539
+ readonly stale: ReadonlyArray<string>;
1540
+ }
1541
+
1486
1542
  export declare const releaseMigrationLock: (sql: SqlClient.SqlClient) => Effect.Effect<void, SqlError_2>;
1487
1543
 
1488
1544
  export declare const renderColumnMysql: (col: ColumnSnapshot) => string;
@@ -1507,16 +1563,6 @@ export declare const rollbackFileBasedMigration: (sql: SqlClient.SqlClient, ctx:
1507
1563
  durationMs: number;
1508
1564
  }, unknown>;
1509
1565
 
1510
- /**
1511
- * Discover + apply every pending file-based migration under
1512
- * `projectRoot/migrations/`. Runs under the same advisory lock as
1513
- * the planner-based applier, so concurrent invocations serialise.
1514
- *
1515
- * - Returns `applied` (newly-run ids + durations) + `skipped`
1516
- * (ids that were already applied) for the caller's log line.
1517
- * - Failures abort the run + propagate as the Effect's error
1518
- * channel; the caller (dev.ts boot) is expected to refuse-to-boot.
1519
- */
1520
1566
  export declare const runFileBasedMigrations: (sql: SqlClient.SqlClient, ctx: RunFileBasedMigrationsCtx) => Effect.Effect<FileMigrationRunResult, unknown>;
1521
1567
 
1522
1568
  export declare interface RunFileBasedMigrationsCtx {
@@ -1655,7 +1701,7 @@ export declare interface SquashResult {
1655
1701
  readonly snapshotId: string;
1656
1702
  }
1657
1703
 
1658
- declare interface Table<Name extends string, Fields extends Record<string, ColumnDefinition<unknown>>, Reactive extends boolean = false, IxNames extends string = never> extends TableLike {
1704
+ declare interface Table<Name extends string, Fields extends Record<string, ColumnDefinition<unknown>>, Reactive extends boolean = true, IxNames extends string = never> extends TableLike {
1659
1705
  readonly tableName: Name;
1660
1706
  readonly fields: Fields;
1661
1707
  readonly isReactive: Reactive;
@@ -1967,11 +2013,39 @@ declare interface Table<Name extends string, Fields extends Record<string, Colum
1967
2013
  */
1968
2014
  primaryKey: <const F extends readonly [keyof Fields & string, keyof Fields & string, ...Array<keyof Fields & string>]>(fields: F) => Table<Name, Fields, Reactive, IxNames>;
1969
2015
  /**
1970
- * Opt the table into the reactive engine. Applies
1971
- * `alter table <name> replica identity full` at migration time so the WAL
1972
- * stream carries full pre-images on UPDATE/DELETE. See the schema-DSL plan.
2016
+ * Turn reactivity OFF for this table.
2017
+ *
2018
+ * Not "off across instances" off. The table emits no change events at all:
2019
+ * no local subscriber fires, no cross-instance transport carries it. That is
2020
+ * what the name says, and a version of this that only silenced the
2021
+ * cross-instance half would leave every in-process subscription live on a
2022
+ * table declared non-reactive.
2023
+ *
2024
+ * Reactivity is the default because that is what this framework is for. The
2025
+ * inverse (`.reactive()`) was the API for a long time and failed in both
2026
+ * directions silently: written on mariadb it did nothing at all, omitted on
2027
+ * postgres+cdc it meant a write on one instance never reached another
2028
+ * instance's subscribers.
2029
+ *
2030
+ * Implemented in EVERY dialect, at the store's emit:
2031
+ *
2032
+ * memory the store's single emit point drops the event
2033
+ * sqlite / same, and turso reuses that store
2034
+ * turso
2035
+ * postgres also: no `REPLICA IDENTITY FULL`, no `pg_notify` trigger
2036
+ * mysql / also: excluded from the binlog reader's table filter
2037
+ * mariadb
2038
+ * mssql also: excluded from the Change Tracking table set
2039
+ *
2040
+ * The WRITE is unaffected — this is about notification, never persistence.
2041
+ *
2042
+ * Worth using for a genuinely hot table nobody subscribes to: an append-only
2043
+ * event log, a metrics sink. On postgres it also drops `REPLICA IDENTITY
2044
+ * FULL`, which widens every UPDATE/DELETE in the WAL. Do NOT use it on a
2045
+ * table a query reads — that subscription simply never fires. `voltro dev`
2046
+ * says so at boot.
1973
2047
  */
1974
- reactive: () => Table<Name, Fields, true, IxNames>;
2048
+ nonReactive: () => Table<Name, Fields, false, IxNames>;
1975
2049
  }
1976
2050
 
1977
2051
  /**
@@ -2105,23 +2179,7 @@ declare interface TableUnique {
2105
2179
  };
2106
2180
  }
2107
2181
 
2108
- /**
2109
- * Clear the `blocked` flag on every LOSSY operation in a plan.
2110
- *
2111
- * Called only after the destructive opt-in has been established, and it is not
2112
- * cosmetic. `applyPlan` carries its OWN unconditional refusal on any blocked
2113
- * operation, with no override parameter. So a plan that the boot gate has
2114
- * decided may proceed, but which still arrives marked blocked, is refused a
2115
- * second time by the applier — which meant `VOLTRO_DESTRUCTIVE_OK=1` could
2116
- * never actually drop a table. The planner's fix hint names that exact
2117
- * variable, so the documented escape hatch pointed at a wall.
2118
- *
2119
- * Only `lossy` ops are unblocked, and callers must have already established
2120
- * that no other class is blocked. A rename-without-marker or a NOT NULL
2121
- * without backfill stays blocked even in destructive-OK mode: those lose data
2122
- * regardless of intent, so intent is not the question being asked.
2123
- */
2124
- export declare const unblockLossy: (plan: MigrationPlan) => MigrationPlan;
2182
+ export declare const unblockLossy: (plan: MigrationPlan, scope?: ReturnType<typeof destructiveScope>) => MigrationPlan;
2125
2183
 
2126
2184
  /**
2127
2185
  * Unique-constraint metadata. Set by `.unique()` (or `.unique({ dedup })`).