@voltro/database 0.85.0 → 0.86.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.
Files changed (169) hide show
  1. package/CHANGELOG.md +666 -0
  2. package/THIRD-PARTY-NOTICES.md +5 -33
  3. package/dist/actorSnapshot.js +24 -0
  4. package/dist/arrayCodec.js +32 -0
  5. package/dist/branchExecute.js +92 -0
  6. package/dist/branching.js +106 -0
  7. package/dist/bulkInsertLimits.js +25 -0
  8. package/dist/cdcOffsetsTables.js +19 -0
  9. package/dist/changeEvent.js +41 -0
  10. package/dist/changeIdentity.js +65 -0
  11. package/dist/changeListenerCeiling.js +6 -0
  12. package/dist/columnReference.js +17 -0
  13. package/dist/columns.js +326 -0
  14. package/dist/committedChanges.js +12 -0
  15. package/dist/connectionIdentity.js +36 -0
  16. package/dist/coreTables.js +16 -0
  17. package/dist/coreTablesRegistry.js +10 -0
  18. package/dist/dataTransfers.js +22 -0
  19. package/dist/dbCause.js +222 -0
  20. package/dist/dbMetrics.js +65 -0
  21. package/dist/decimalComparison.js +27 -0
  22. package/dist/derivedRowId.js +5 -0
  23. package/dist/dialect.js +13 -0
  24. package/dist/enumRenames.js +14 -0
  25. package/dist/expiresMixin.js +10 -0
  26. package/dist/fieldEncryption.js +98 -0
  27. package/dist/frameworkTables.js +130 -0
  28. package/dist/ginIndex.js +10 -0
  29. package/dist/hybridSearch.js +32 -0
  30. package/dist/idGenerator.js +57 -0
  31. package/dist/identifierLimits.js +32 -0
  32. package/dist/index.d.ts +18 -7
  33. package/dist/index.js +81 -2183
  34. package/dist/indexAudit.js +61 -0
  35. package/dist/indexStorage.js +58 -0
  36. package/dist/joinCompiler.js +160 -0
  37. package/dist/jsonEagerCompiler.js +436 -0
  38. package/dist/jsonPatch.js +78 -0
  39. package/dist/jsonPatchAssignments.js +13 -0
  40. package/dist/jsonPath.js +31 -0
  41. package/dist/localFirstMixin.js +12 -0
  42. package/dist/migrate.js +624 -0
  43. package/dist/migration.js +18 -0
  44. package/dist/migrations/appliedAtValue.js +8 -0
  45. package/dist/migrations/applier.js +1085 -0
  46. package/dist/migrations/boot.js +196 -0
  47. package/dist/migrations/branchRehearsal.js +122 -0
  48. package/dist/migrations/columnEquivalence.js +85 -0
  49. package/dist/migrations/ddlError.js +63 -0
  50. package/dist/migrations/declaredSchema.js +162 -0
  51. package/dist/migrations/deployLedger.js +19 -0
  52. package/dist/migrations/fileBased.js +57 -0
  53. package/dist/migrations/fileContext.js +482 -0
  54. package/dist/migrations/fileDiscovery.js +318 -0
  55. package/dist/migrations/fileSkipProbes.js +117 -0
  56. package/dist/migrations/fingerprint.js +46 -0
  57. package/dist/migrations/fkCycles.js +13 -0
  58. package/dist/migrations/frameworkLiveTables.js +25 -0
  59. package/dist/migrations/idempotentDdl.js +37 -0
  60. package/dist/migrations/indexPredicate.js +50 -0
  61. package/dist/migrations/introspect.js +799 -0
  62. package/dist/migrations/ledgerRows.js +4 -0
  63. package/dist/migrations/lock.js +91 -0
  64. package/dist/migrations/migrationTableExists.js +9 -0
  65. package/dist/migrations/mssqlColumnDependencies.js +76 -0
  66. package/dist/migrations/mysqlEngine.js +11 -0
  67. package/dist/migrations/namespaceCharset.js +51 -0
  68. package/dist/migrations/planner.js +775 -0
  69. package/dist/migrations/postgresEnumCheck.js +19 -0
  70. package/dist/migrations/reactiveTriggerDrift.js +58 -0
  71. package/dist/migrations/reactiveTriggerName.js +7 -0
  72. package/dist/migrations/renameMssqlCheck.js +21 -0
  73. package/dist/migrations/restoreSnapshots.js +206 -0
  74. package/dist/migrations/resumeLedger.js +86 -0
  75. package/dist/migrations/rollingDeploy.js +106 -0
  76. package/dist/migrations/schemaOwner.js +46 -0
  77. package/dist/migrations/schemaOwnerTable.js +4 -0
  78. package/dist/migrations/softDropSnapshot.js +20 -0
  79. package/dist/migrations/sqlLimit.js +8 -0
  80. package/dist/migrations/sqliteConstraintIdentity.js +97 -0
  81. package/dist/migrations/sqliteDecimalBoot.js +22 -0
  82. package/dist/migrations/sqliteDecimalPublication.js +257 -0
  83. package/dist/migrations/sqliteDecimalReview.js +30 -0
  84. package/dist/migrations/sqliteDecimalReviewStore.js +188 -0
  85. package/dist/migrations/sqliteDecimalReviewWitness.js +38 -0
  86. package/dist/migrations/sqliteDecimalSource.js +69 -0
  87. package/dist/migrations/sqliteDecimalStage.js +135 -0
  88. package/dist/migrations/sqliteForeignKeys.js +38 -0
  89. package/dist/migrations/sqliteMigrationBatch.js +37 -0
  90. package/dist/migrations/sqliteRetainedSource.js +106 -0
  91. package/dist/migrations/sqliteRowIdentity.js +63 -0
  92. package/dist/migrations/sqliteSchemaEpoch.js +12 -0
  93. package/dist/migrations/sqliteSnapshotGuard.js +10 -0
  94. package/dist/migrations/sqliteTableContents.js +34 -0
  95. package/dist/migrations/sqliteTableCopy.js +25 -0
  96. package/dist/migrations/sqliteTableSchema.js +19 -0
  97. package/dist/migrations/sqliteWitnessProtection.js +8 -0
  98. package/dist/migrations/squash.js +81 -0
  99. package/dist/migrations/storeCharset.js +66 -0
  100. package/dist/migrations/transactionCause.js +10 -0
  101. package/dist/migrations/undeclaredCharset.js +40 -0
  102. package/dist/migrations/uniqueActiveLowering.js +39 -0
  103. package/dist/mixins.js +22 -0
  104. package/dist/mssqlDecimalDistinct.js +51 -0
  105. package/dist/mssqlJsonValueCheck.js +42 -0
  106. package/dist/mssqlTextStorage.js +9 -0
  107. package/dist/namespace.js +21 -0
  108. package/dist/{wire-CG2j7J3f.js → optimisticVersion.js} +2 -2
  109. package/dist/pendingAttribution.js +79 -0
  110. package/dist/pluginRef.js +17 -0
  111. package/dist/predicate.js +266 -0
  112. package/dist/predicateCoercion.js +13 -0
  113. package/dist/projectedColumn.js +37 -0
  114. package/dist/queryBuilder.js +382 -0
  115. package/dist/rawSql.js +8 -0
  116. package/dist/relations.js +69 -0
  117. package/dist/relationsRegistry.js +38 -0
  118. package/dist/replaceInProgress.js +53 -0
  119. package/dist/requiredColumns.js +26 -0
  120. package/dist/residency.js +52 -0
  121. package/dist/retention.js +62 -0
  122. package/dist/rowDecoder.js +123 -0
  123. package/dist/rowKey.js +8 -0
  124. package/dist/rowSchema.js +31 -0
  125. package/dist/scopeKey.js +8 -0
  126. package/dist/seed.js +33 -0
  127. package/dist/snowflake.js +26 -0
  128. package/dist/sql.d.ts +27 -6
  129. package/dist/sql.js +41 -6905
  130. package/dist/sqlCompiler.js +343 -0
  131. package/dist/sqlError.js +8 -0
  132. package/dist/sqlPoolCapacity.js +12 -0
  133. package/dist/sqliteDecimal.js +22 -0
  134. package/dist/sqliteDecimalAggregateSql.js +61 -0
  135. package/dist/sqliteDecimalColumnSql.js +108 -0
  136. package/dist/sqliteDecimalConversion.js +15 -0
  137. package/dist/sqliteDecimalCte.js +154 -0
  138. package/dist/sqliteDecimalEncodeSql.js +32 -0
  139. package/dist/sqliteDecimalOutputs.js +74 -0
  140. package/dist/sqliteDecimalPredicate.js +66 -0
  141. package/dist/sqliteDecimalQuery.js +183 -0
  142. package/dist/sqliteDecimalQueryPlan.js +37 -0
  143. package/dist/sqliteDecimalResultSql.js +44 -0
  144. package/dist/sqliteDecimalRowQuery.js +63 -0
  145. package/dist/sqliteDecimalSetQuery.js +50 -0
  146. package/dist/sqliteDecimalSource.js +63 -0
  147. package/dist/sqliteDecimalStorage.js +29 -0
  148. package/dist/sqliteDecimalSubquery.js +19 -0
  149. package/dist/sqliteDecimalWindowQuery.js +196 -0
  150. package/dist/sqliteValueCoercion.js +4 -0
  151. package/dist/stampId.js +21 -0
  152. package/dist/storeErrors.js +9 -0
  153. package/dist/storeRefusal.js +20 -0
  154. package/dist/stream.js +36 -0
  155. package/dist/subjectGraph.js +175 -0
  156. package/dist/table.js +394 -0
  157. package/dist/tableRegistry.js +16 -0
  158. package/dist/tenantSelection.js +27 -0
  159. package/dist/transactionOutcome.js +97 -0
  160. package/dist/transientContention.js +36 -0
  161. package/dist/typedStore.js +16 -0
  162. package/dist/upsertUpdate.js +20 -0
  163. package/dist/vectorEmbedding.js +31 -0
  164. package/dist/view.js +39 -0
  165. package/dist/wire.js +6 -2
  166. package/dist/writeAttribution.js +16 -0
  167. package/dist/writeRecorder.js +37 -0
  168. package/package.json +3 -3
  169. package/dist/replaceInProgress-DC88Pdiy.js +0 -3735
@@ -0,0 +1,19 @@
1
+ //#region src/migrations/postgresEnumCheck.ts
2
+ var e = (e, t) => {
3
+ let n = (e.match(/'(?:''|[^'])*'|"(?:""|[^"])*"|::|[A-Za-z_][A-Za-z_0-9$]*|[^\s]/g) ?? []).filter((e) => e !== "(" && e !== ")"), r = 0, i = (e) => n[r]?.toLowerCase() === e && (r++, !0);
4
+ if (!i("check")) return null;
5
+ let a = n[r++];
6
+ if (a === void 0 || (a.startsWith("\"") ? a.slice(1, -1).replace(/""/g, "\"") : a.toLowerCase()) !== t) return null;
7
+ let o = () => !i("::") || i("text") || i("character") && i("varying");
8
+ if (!o() || !i("=")) return null;
9
+ let s = i("any");
10
+ if (s && (!i("array") || !i("["))) return null;
11
+ let c = [];
12
+ do {
13
+ let e = n[r++];
14
+ if (e === void 0 || !/^'(?:''|[^'])*'$/.test(e) || (c.push(e.slice(1, -1).replace(/''/g, "'")), !o())) return null;
15
+ } while (s && i(","));
16
+ return s && (!i("]") || i("::") && (!i("text") || !i("[") || !i("]"))) ? null : r === n.length ? c : null;
17
+ };
18
+ //#endregion
19
+ export { e as postgresEnumCheckValues };
@@ -0,0 +1,58 @@
1
+ import { DEFAULT_CDC_CHANNEL as e, REACTIVE_TRIGGER_PREFIX as t, notifyFunctionName as n, storedReactiveTriggerName as r } from "./reactiveTriggerName.js";
2
+ import { Effect as i } from "effect";
3
+ //#region src/migrations/reactiveTriggerDrift.ts
4
+ var a = (a, o, s = e) => i.gen(function* () {
5
+ let e = (yield* a`
6
+ SELECT p.prosrc
7
+ FROM pg_proc p
8
+ JOIN pg_namespace n ON n.oid = p.pronamespace
9
+ WHERE n.nspname = current_schema()
10
+ AND p.proname = ${n(s)}
11
+ `)[0]?.prosrc, i = e !== void 0 && !e.includes("voltro:notify:v2"), c = (yield* a`SELECT current_schema() AS schema`)[0]?.schema ?? "";
12
+ if (o.length === 0) return {
13
+ missing: [],
14
+ stale: [],
15
+ functionOutdated: i,
16
+ compared: {
17
+ schema: c,
18
+ channel: s,
19
+ present: 0
20
+ }
21
+ };
22
+ let l = yield* a`
23
+ SELECT t.tgname, c.relname
24
+ FROM pg_trigger t
25
+ JOIN pg_class c ON c.oid = t.tgrelid
26
+ JOIN pg_namespace n ON n.oid = c.relnamespace
27
+ WHERE NOT t.tgisinternal
28
+ AND n.nspname = current_schema()
29
+ AND t.tgname LIKE ${`${t}%`}
30
+ `, u = (e, t) => `${e}\u0000${t}`, d = new Set(l.map((e) => u(e.relname, e.tgname))), f = [], p = [];
31
+ for (let e of o) {
32
+ let t = d.has(u(e.tableName, r(e.tableName, s))), n = e.isReactive !== !1;
33
+ n && !t && f.push(e.tableName), !n && t && p.push(e.tableName);
34
+ }
35
+ return {
36
+ missing: f.sort(),
37
+ stale: p.sort(),
38
+ functionOutdated: i,
39
+ compared: {
40
+ schema: c,
41
+ channel: s,
42
+ present: d.size
43
+ }
44
+ };
45
+ }), o = (e) => {
46
+ let n = [];
47
+ if (e.missing.length > 0) {
48
+ let r = e.missing.slice(0, 8).join(", "), i = e.missing.length > 8 ? `, … (+${e.missing.length - 8})` : "";
49
+ n.push(`${e.missing.length} reactive table(s) have NO change trigger in the database — ${r}${i}. Writes to them will not reach CDC subscribers or wake idle queue dispatchers, including on a single instance. Run \`voltro db apply\` to install them.` + (e.compared === void 0 ? "" : ` (compared against ${e.compared.present} \`${t}*\` trigger(s) present in schema "${e.compared.schema}", channel "${e.compared.channel}")`));
50
+ }
51
+ if (e.stale.length > 0) {
52
+ let t = e.stale.slice(0, 8).join(", ");
53
+ n.push(`${e.stale.length} .nonReactive() table(s) still carry a change trigger — ${t}. They keep paying REPLICA IDENTITY FULL and a NOTIFY on every write for a subscription nobody receives. \`voltro db apply\` removes them.`);
54
+ }
55
+ return e.functionOutdated && n.push("The change-notify function in this database is OLDER than the framework installed here. Its triggers fire, so nothing looks wrong — but a row too large for a NOTIFY payload (postgres caps it at 8000 bytes) is emitted without its primary key, and a change that carries no key cannot be re-read: it reaches subscriptions as a re-query and reaches every TAP — search index, analytics mirror, cdc-out, row history — as nothing at all. Run `voltro db apply` to replace it."), n.length > 0 ? n.join("\n") : void 0;
56
+ };
57
+ //#endregion
58
+ export { t as REACTIVE_TRIGGER_PREFIX, a as detectReactiveTriggerDrift, o as formatReactiveTriggerDrift };
@@ -0,0 +1,7 @@
1
+ //#region src/migrations/reactiveTriggerName.ts
2
+ var e = "framework_changes", t = "framework_changes_", n = (e, n) => n === "framework_changes" ? `${t}${e}` : `${t}${n}_${e}`, r = (e, t) => a(n(e, t).replace(/[A-Z]/g, (e) => e.toLowerCase())), i = 63, a = (e) => {
3
+ let t = new TextEncoder().encode(e);
4
+ return t.length <= i ? e : new TextDecoder().decode(t.subarray(0, i)).replace(/�+$/u, "");
5
+ }, o = (e) => e === "framework_changes" ? "framework_notify_change" : `framework_notify_change_${e}`, s = "voltro:notify:v2";
6
+ //#endregion
7
+ export { e as DEFAULT_CDC_CHANNEL, s as NOTIFY_BODY_MARKER, t as REACTIVE_TRIGGER_PREFIX, o as notifyFunctionName, n as reactiveTriggerName, r as storedReactiveTriggerName };
@@ -0,0 +1,21 @@
1
+ import { quoteIdent as e } from "../dialect.js";
2
+ //#region src/migrations/renameMssqlCheck.ts
3
+ var t = (t, n, r) => {
4
+ let i = t.match(/'(?:''|[^'])*'|\[(?:\]\]|[^\]])*\]|\s+|[^'\[\s]+/g) ?? [];
5
+ if (i.join("") !== t || i.some((e) => !e.startsWith("'") && !e.startsWith("[") && /"|\/\*|--/.test(e))) throw Error("Cannot safely rename CHECK: unsupported catalog SQL; use an explicit migration");
6
+ let a = i.map((e, t) => ({
7
+ value: e,
8
+ index: t
9
+ })).filter((e) => e.value.trim() !== ""), o = 0;
10
+ for (let t = 0; t < a.length; t++) {
11
+ let s = a[t];
12
+ if (s.value !== e(n, "mssql")) continue;
13
+ let c = a[t - 1]?.value ?? "", l = a[t + 1]?.value ?? "";
14
+ if (c.endsWith(".") || l.startsWith(".") || l.startsWith("(")) throw Error(`Cannot safely rename CHECK reference '${n}': qualified or callable identifier; use an explicit migration`);
15
+ i[s.index] = e(r, "mssql"), o++;
16
+ }
17
+ if (o === 0 || i.join("").length === 0) throw Error(`Cannot safely rename CHECK reference '${n}': no exact catalog column token; use an explicit migration`);
18
+ return i.join("");
19
+ };
20
+ //#endregion
21
+ export { t as renameMssqlCheck };
@@ -0,0 +1,206 @@
1
+ import { acquireMigrationLock as e } from "./lock.js";
2
+ import { dialectOf as t, migrationAtomicityFor as n } from "./resumeLedger.js";
3
+ import { introspectSchema as r } from "./introspect.js";
4
+ import { readSoftDropSnapshot as i } from "./softDropSnapshot.js";
5
+ import { withMigrationTransaction as a } from "./transactionCause.js";
6
+ import { runMigrationOp as o } from "./applier.js";
7
+ import { Effect as s } from "effect";
8
+ import { SqlClient as c } from "effect/sql";
9
+ //#region src/migrations/restoreSnapshots.ts
10
+ var l = (e) => {
11
+ let t = /* @__PURE__ */ new Map();
12
+ for (let n of e) n.kind === "table" && !t.has(n.originalName) && t.set(n.originalName, n.snapshotName);
13
+ return t;
14
+ }, u = (e, t) => {
15
+ if (Object.getPrototypeOf(e) !== Array.prototype || Object.getOwnPropertySymbols(e).length > 0 || Object.hasOwn(e, "find") || Object.hasOwn(e, "some") || Object.hasOwn(e, "map") || Object.hasOwn(e, "constructor")) return !1;
16
+ for (let n = 0; n < e.length; n++) {
17
+ let r = Object.getOwnPropertyDescriptor(e, String(n));
18
+ if (r === void 0 || !("value" in r)) return !1;
19
+ let i = r.value;
20
+ if (typeof i != "object" || !i) return !1;
21
+ let a = Object.getPrototypeOf(i);
22
+ if (a !== Object.prototype && a !== null) return !1;
23
+ for (let e of t) {
24
+ let t = Object.getOwnPropertyDescriptor(i, e);
25
+ if (t === void 0 || !("value" in t)) return !1;
26
+ }
27
+ }
28
+ return !0;
29
+ }, d = (e) => s.gen(function* () {
30
+ let t = e.onDialectOrElse({
31
+ mysql: () => e`CAST(${e("operations")} AS CHAR)`,
32
+ mssql: () => e`CAST(${e("operations")} AS NVARCHAR(MAX))`,
33
+ orElse: () => e`CAST(${e("operations")} AS TEXT)`
34
+ }), n = e`(${t} LIKE ${"%softDropSnapshot%"} OR ${t} LIKE ${"%\\%"} ESCAPE '!')`, r = e.onDialectOrElse({
35
+ pg: () => e`CASE WHEN pg_typeof(${e("operations")})::text IN ('json', 'jsonb')
36
+ THEN jsonb_path_exists(${e("operations")}::jsonb, '$[*].softDropSnapshot')
37
+ ELSE ${n} END`,
38
+ orElse: () => n
39
+ }), i = [], a;
40
+ for (;;) {
41
+ let t = yield* e`
42
+ SELECT ${e("id")}, ${e("operations")} FROM ${e("_voltro_migration_plans")}
43
+ WHERE ${r} ${a === void 0 ? e`` : e`AND ${e("id")} > ${a}`}
44
+ ORDER BY ${e("id")}
45
+ ${e.onDialectOrElse({
46
+ mssql: () => e`OFFSET 0 ROWS FETCH NEXT 100 ROWS ONLY`,
47
+ orElse: () => e`LIMIT 100`
48
+ })}
49
+ `;
50
+ if (i.push(...t), t.length < 100) return i;
51
+ a = t[t.length - 1].id;
52
+ }
53
+ }), f = (e, t) => {
54
+ let n = [], r = /* @__PURE__ */ new Map(), i = (e, t) => JSON.stringify(e.kind === "table" ? ["table", e.snapshotName] : [
55
+ "column",
56
+ t.get(e.table) ?? e.table,
57
+ e.snapshotName
58
+ ]);
59
+ for (let a of e) {
60
+ let e;
61
+ try {
62
+ e = typeof a.operations == "string" ? JSON.parse(a.operations) : a.operations;
63
+ } catch {
64
+ continue;
65
+ }
66
+ if (!Array.isArray(e)) continue;
67
+ let o = [];
68
+ for (let t of e) m(t) && m(t.op) && typeof t.softDropSnapshot == "string" && typeof t.op.table == "string" && (t.op.kind === "drop-table" && o.push({
69
+ kind: "table",
70
+ table: t.op.table,
71
+ originalName: t.op.table,
72
+ snapshotName: t.softDropSnapshot
73
+ }), t.op.kind === "drop-column" && typeof t.op.column == "string" && o.push({
74
+ kind: "column",
75
+ table: t.op.table,
76
+ originalName: t.op.column,
77
+ snapshotName: t.softDropSnapshot
78
+ }));
79
+ let s = l(o);
80
+ for (let e of o) {
81
+ let t = i(e, s), n = r.get(t) ?? /* @__PURE__ */ new Set();
82
+ n.add(a.id), r.set(t, n);
83
+ }
84
+ try {
85
+ let r = g(e, t);
86
+ r.length > 0 && n.push({
87
+ planId: a.id,
88
+ objects: r
89
+ });
90
+ } catch {}
91
+ }
92
+ return n.filter((e) => {
93
+ let t = l(e.objects);
94
+ return e.objects.every((e) => r.get(i(e, t))?.size === 1);
95
+ });
96
+ }, p = (e, t) => d(e).pipe(s.map((e) => f(e, t))), m = (e) => typeof e == "object" && !!e && !Array.isArray(e), h = {
97
+ postgres: !0,
98
+ mysql: !1,
99
+ mariadb: !1,
100
+ mssql: !0,
101
+ sqlite: !0
102
+ }, g = (e, t) => {
103
+ let n = typeof e == "string" ? JSON.parse(e) : e;
104
+ if (!Array.isArray(n)) throw Error("Snapshot restoration requires an operation array");
105
+ let r = [], a = /* @__PURE__ */ new Set();
106
+ for (let e of n) {
107
+ if (!m(e) || !m(e.op) || typeof e.op.kind != "string") throw Error("Invalid migration operation history");
108
+ let t = e.op;
109
+ if (t.kind !== "drop-column" && t.kind !== "drop-table") {
110
+ if (e.softDropSnapshot !== void 0) throw Error("Snapshot metadata belongs to a non-drop operation");
111
+ continue;
112
+ }
113
+ if (typeof t.table != "string" || !t.table || t.kind === "drop-column" && (typeof t.column != "string" || !t.column)) throw Error("Invalid drop operation identity");
114
+ if (typeof e.softDropSnapshot != "string") throw Error("Snapshot ownership is unavailable for this plan. Its history has no exact soft-drop identity; automatic restoration is refused.");
115
+ let n = {
116
+ op: t.kind === "drop-table" ? {
117
+ kind: "drop-table",
118
+ table: t.table
119
+ } : {
120
+ kind: "drop-column",
121
+ table: t.table,
122
+ column: String(t.column)
123
+ },
124
+ classification: "lossy",
125
+ atomicByDialect: h,
126
+ softDropSnapshot: e.softDropSnapshot
127
+ }, o = i(n), s = {
128
+ kind: t.kind === "drop-table" ? "table" : "column",
129
+ table: t.table,
130
+ originalName: t.kind === "drop-table" ? t.table : String(t.column),
131
+ snapshotName: o
132
+ }, c = JSON.stringify([
133
+ s.kind,
134
+ s.table,
135
+ s.originalName
136
+ ]);
137
+ if (a.has(c)) throw Error("Duplicate snapshot restoration target");
138
+ a.add(c), r.push(s);
139
+ }
140
+ r.sort((e, t) => Number(e.kind === "column") - Number(t.kind === "column"));
141
+ let o = /* @__PURE__ */ new Set(), s = r.length >= 64 && u(t.tables, ["name", "columns"]), c;
142
+ if (s) {
143
+ let e = /* @__PURE__ */ new Map();
144
+ for (let n of t.tables) e.has(n.name) || e.set(n.name, n);
145
+ let n = /* @__PURE__ */ new Set(), i = l(r);
146
+ for (let t of r) {
147
+ if (t.kind !== "column") continue;
148
+ let r = e.get(i.get(t.table) ?? t.table);
149
+ if (!(r === void 0 || n.has(r))) {
150
+ if (n.add(r), !u(r.columns, ["name"])) {
151
+ s = !1;
152
+ break;
153
+ }
154
+ o.add(r);
155
+ }
156
+ }
157
+ s && (c = e);
158
+ }
159
+ let d = (e) => c === void 0 ? t.tables.find((t) => t.name === e) : c.get(e), f = l(r), p = /* @__PURE__ */ new Map(), g = (e, t) => (s && e.columns.length >= 32 && !p.has(e) && p.set(e, o.has(e) ? new Set(e.columns.map((e) => e.name)) : void 0), p.get(e)?.has(t) ?? e.columns.some((e) => e.name === t));
160
+ for (let e of r) if (e.kind === "table") {
161
+ if (c === void 0 ? t.tables.some((t) => t.name === e.originalName) : c.has(e.originalName)) throw Error(`Restore target already exists: ${e.originalName}`);
162
+ if (!(c === void 0 ? t.tables.some((t) => t.name === e.snapshotName) : c.has(e.snapshotName))) throw Error(`Recorded snapshot is missing: ${e.snapshotName}`);
163
+ } else {
164
+ let t = d(f.get(e.table) ?? e.table);
165
+ if (!t) throw Error(`Restore table is missing: ${e.table}`);
166
+ if (g(t, e.originalName)) throw Error(`Restore target already exists: ${e.table}.${e.originalName}`);
167
+ if (!g(t, e.snapshotName)) throw Error(`Recorded snapshot is missing: ${e.table}.${e.snapshotName}`);
168
+ }
169
+ return r;
170
+ }, _ = (i, l, u = {}) => s.acquireUseRelease(e(i), () => {
171
+ let e = s.gen(function* () {
172
+ let e = yield* c.SqlClient, t = yield* e`SELECT ${e("operations")} FROM ${e("_voltro_migration_plans")} WHERE ${e("id")} = ${l}`;
173
+ if (t.length !== 1) return yield* s.fail(/* @__PURE__ */ Error(`Snapshot plan not found: ${l}`));
174
+ let n = yield* r(e), i = yield* s.try({
175
+ try: () => g(t[0].operations, n),
176
+ catch: (e) => e instanceof Error ? e : Error(String(e))
177
+ });
178
+ if (i.length > 0 && !(yield* p(e, n)).some((e) => e.planId === l)) return yield* s.fail(/* @__PURE__ */ Error("Snapshot ownership is ambiguous across migration history; automatic restoration is refused."));
179
+ i.length > 0 && u.beforeExecute && (yield* u.beforeExecute(e));
180
+ for (let t of i) {
181
+ let n = {
182
+ op: t.kind === "table" ? {
183
+ kind: "rename-table",
184
+ from: t.snapshotName,
185
+ to: t.originalName
186
+ } : {
187
+ kind: "rename-column",
188
+ table: t.table,
189
+ from: t.snapshotName,
190
+ to: t.originalName
191
+ },
192
+ classification: "safe",
193
+ atomicByDialect: h
194
+ };
195
+ yield* o(e, n, []);
196
+ }
197
+ return {
198
+ restoredColumns: i.filter((e) => e.kind === "column").length,
199
+ restoredTables: i.filter((e) => e.kind === "table").length,
200
+ objects: i
201
+ };
202
+ });
203
+ return n(t(i)) === "plan-transactional" ? a(i, e) : e;
204
+ }, (e) => e.release.pipe(s.orDie));
205
+ //#endregion
206
+ export { f as findMigrationSnapshotCandidates, g as planSnapshotRestoration, p as readMigrationSnapshotCandidates, _ as restoreMigrationSnapshots };
@@ -0,0 +1,86 @@
1
+ import { id as e, integer as t, text as n, timestamp as r } from "../columns.js";
2
+ import { table as i } from "../table.js";
3
+ //#region src/migrations/resumeLedger.ts
4
+ var a = (e) => e.onDialectOrElse({
5
+ pg: () => "postgres",
6
+ mysql: () => "mysql",
7
+ mssql: () => "mssql",
8
+ sqlite: () => "sqlite",
9
+ orElse: () => "sqlite"
10
+ }), o = {
11
+ postgres: "plan-transactional",
12
+ mssql: "plan-transactional",
13
+ mysql: "per-operation",
14
+ mariadb: "per-operation",
15
+ sqlite: "per-operation",
16
+ turso: "per-operation"
17
+ }, s = (e) => o[e], c = "_voltro_migration_ops", l = i(c, {
18
+ id: e({ prefix: "mop" }),
19
+ runId: n(),
20
+ planFingerprint: n(),
21
+ opIndex: t(),
22
+ opKind: n(),
23
+ opTarget: n(),
24
+ status: n(),
25
+ payload: n(),
26
+ startedAt: r().nullable(),
27
+ appliedAt: r().nullable(),
28
+ durationMs: t().nullable(),
29
+ createdAt: r().default("now")
30
+ }), u = (e) => {
31
+ switch (e.kind) {
32
+ case "create-table": return /* @__PURE__ */ new Set([
33
+ `table:${e.table}`,
34
+ ...e.columns.map((t) => `column:${e.table}.${t.name}`),
35
+ ...e.indexes.map((t) => `index:${e.table}.${t.name}`)
36
+ ]);
37
+ case "drop-table": return /* @__PURE__ */ new Set([`table:${e.table}`]);
38
+ case "add-enum-value": return /* @__PURE__ */ new Set([`enum:${e.enumName ?? `${e.table}.${e.column}`}.${e.value}`]);
39
+ case "rename-table": return /* @__PURE__ */ new Set([`table:${e.from}`, `table:${e.to}`]);
40
+ case "add-column": return /* @__PURE__ */ new Set([`column:${e.table}.${e.column.name}`]);
41
+ case "rename-column": return /* @__PURE__ */ new Set([`column:${e.table}.${e.from}`, `column:${e.table}.${e.to}`]);
42
+ case "drop-column":
43
+ case "alter-column-nullability":
44
+ case "alter-column-type":
45
+ case "alter-column-default":
46
+ case "add-unique":
47
+ case "drop-unique":
48
+ case "add-foreign-key":
49
+ case "drop-foreign-key":
50
+ case "add-check":
51
+ case "drop-check":
52
+ case "reconcile-json-check": return /* @__PURE__ */ new Set([`column:${e.table}.${e.column}`]);
53
+ case "add-index": return /* @__PURE__ */ new Set([`index:${e.table}.${e.index.name}`]);
54
+ case "drop-index": return /* @__PURE__ */ new Set([`index:${e.table}.${e.index}`]);
55
+ case "rename-index": return /* @__PURE__ */ new Set([`index:${e.table}.${e.from}`, `index:${e.table}.${e.to}`]);
56
+ case "add-unique-composite":
57
+ case "drop-unique-composite": return /* @__PURE__ */ new Set([`unique:${e.table}.${e.name}`]);
58
+ case "convert-charset": return /* @__PURE__ */ new Set([
59
+ `table:${e.table}`,
60
+ ...e.columns.map((t) => `column:${e.table}.${t}`),
61
+ ...e.foreignKeys.map((e) => `column:${e.table}.${e.column}`)
62
+ ]);
63
+ }
64
+ }, d = (e) => [...u(e)].join(" "), f = (e, t) => {
65
+ let n = u(t);
66
+ for (let t of u(e)) if (n.has(t)) return !0;
67
+ return !1;
68
+ }, p = (e, t) => e.operations.some((e) => f(e.op, t)), m = "__voltro_rebuild", h = "__shadow", g = "__old", _ = /* @__PURE__ */ new Set([
69
+ "add-unique",
70
+ "drop-unique",
71
+ "add-unique-composite",
72
+ "drop-unique-composite",
73
+ "add-foreign-key",
74
+ "drop-foreign-key",
75
+ "add-check",
76
+ "drop-check",
77
+ "alter-column-nullability",
78
+ "alter-column-type",
79
+ "alter-column-default"
80
+ ]), v = /* @__PURE__ */ new Set(["alter-column-default", "alter-column-nullability"]), y = (e, t, n) => {
81
+ let r = n === "sqlite" || n === "turso";
82
+ return n === "postgres" && e.kind === "add-index" && t === "online-required" || e.kind === "alter-column-type" && t === "online-required" || r && _.has(e.kind) ? "artefact-repair" : v.has(e.kind) ? "idempotent" : "probe-then-replay";
83
+ };
84
+ Object.keys(o);
85
+ //#endregion
86
+ export { o as MIGRATION_ATOMICITY, c as MIGRATION_OPS_TABLE, h as SHADOW_COLUMN_SUFFIX, g as SHADOW_OLD_SUFFIX, m as SQLITE_REBUILD_SUFFIX, l as _voltroMigrationOpsTable, a as dialectOf, s as migrationAtomicityFor, p as opStillOutstanding, d as opTargetLabel, u as opTargets, f as opsShareTarget, y as resumeIdempotencyFor };
@@ -0,0 +1,106 @@
1
+ //#region src/migrations/rollingDeploy.ts
2
+ var e = (e) => new Set(e.flatMap((e) => e.op.kind === "create-table" ? [e.op.table] : [])), t = (e, t) => t !== void 0 && "table" in e && t.createdTables.has(e.table), n = { safe: !0 }, r = (e, r) => {
3
+ if (t(e, r)) return n;
4
+ switch (e.kind) {
5
+ case "add-enum-value": return { safe: !0 };
6
+ case "drop-column": return {
7
+ safe: !1,
8
+ reason: `instances on the previous release still SELECT/INSERT "${e.table}"."${e.column}"; after the drop those queries error mid-rollout`,
9
+ remedy: "stop reading the column in code and deploy that first; drop it in a LATER deploy (expand/contract)"
10
+ };
11
+ case "drop-table": return {
12
+ safe: !1,
13
+ reason: `instances on the previous release still query table "${e.table}"; after the drop every such query errors mid-rollout`,
14
+ remedy: `remove all code that touches "${e.table}" and deploy that first; drop the table in a LATER deploy`
15
+ };
16
+ case "rename-column": return {
17
+ safe: !1,
18
+ reason: `instances on the previous release reference "${e.table}"."${e.from}", which no longer exists after the rename`,
19
+ remedy: `add "${e.to}" alongside "${e.from}" + backfill + dual-write, cut code over, then drop "${e.from}" in a LATER deploy`
20
+ };
21
+ case "rename-table": return {
22
+ safe: !1,
23
+ reason: `instances on the previous release query table "${e.from}", which no longer exists after the rename`,
24
+ remedy: `create "${e.to}" as a copy + dual-write, cut code over, then drop "${e.from}" in a LATER deploy`
25
+ };
26
+ case "alter-column-type": return e.from === e.to ? {
27
+ safe: !1,
28
+ reason: `instances on the previous release read/write "${e.table}"."${e.column}" against its current ${e.from} storage; changed parameters or encoding can reject writes or change the meaning of reads`,
29
+ remedy: "inspect the plan's storage-change reason: parameter narrowing needs every writer already producing values that fit; an encoding change requires verified data conversion and matching readers/writers, using expand/contract or a deployment with no overlap"
30
+ } : {
31
+ safe: !1,
32
+ reason: `instances on the previous release read/write "${e.table}"."${e.column}" as ${e.from}; the new ${e.to} type can reject their writes or fail to decode their reads`,
33
+ remedy: "add a new column of the target type + backfill + dual-write, cut code over, then drop the old column in a LATER deploy"
34
+ };
35
+ case "alter-column-nullability": return e.toNullable ? n : {
36
+ safe: !1,
37
+ reason: `instances on the previous release may INSERT "${e.table}" without (or with NULL in) "${e.column}", which the new NOT NULL rejects`,
38
+ remedy: `make code always write "${e.column}" and deploy that first; add NOT NULL in a LATER deploy (with a default to cover the gap)`
39
+ };
40
+ case "add-unique":
41
+ case "add-unique-composite": return {
42
+ safe: !1,
43
+ reason: `instances on the previous release still write "${e.table}" without honouring the new uniqueness; a colliding insert now fails mid-rollout`,
44
+ remedy: "make code enforce uniqueness first and deploy that; add the constraint in a LATER deploy once no old writer remains"
45
+ };
46
+ case "reconcile-json-check": return e.previousValidation === "container" && e.validation === "value" ? n : {
47
+ safe: !1,
48
+ reason: `instances on the previous release write "${e.table}"."${e.column}" without the new JSON validation, which now rejects them`,
49
+ remedy: "ensure all writers supply valid JSON in the required domain before enabling this check"
50
+ };
51
+ case "add-check": return e.table.startsWith("_voltro_") ? n : {
52
+ safe: !1,
53
+ reason: `instances on the previous release may write values to "${e.table}" the new CHECK rejects`,
54
+ remedy: "make code satisfy the constraint first and deploy that; add the CHECK in a LATER deploy"
55
+ };
56
+ case "add-foreign-key": return {
57
+ safe: !1,
58
+ reason: `instances on the previous release may write "${e.table}"."${e.column}" values with no matching parent row, which the new FK rejects`,
59
+ remedy: "make code guarantee the referent first and deploy that; add the FK in a LATER deploy"
60
+ };
61
+ case "convert-charset": return e.charset === "utf8mb4" ? n : {
62
+ safe: !1,
63
+ reason: `instances on the previous release may write 4-byte characters into "${e.table}", which utf8mb3 rejects with errno 1366`,
64
+ remedy: "keep utf8mb4 while any writer on the previous release remains; narrow in a LATER deploy, after every writer refuses such input itself"
65
+ };
66
+ case "create-table":
67
+ case "add-column":
68
+ case "add-index":
69
+ case "drop-index":
70
+ case "rename-index":
71
+ case "alter-column-default":
72
+ case "drop-unique":
73
+ case "drop-unique-composite":
74
+ case "drop-foreign-key":
75
+ case "drop-check": return n;
76
+ }
77
+ }, i = (t) => {
78
+ let n = [], i = { createdTables: e(t) };
79
+ for (let e of t) {
80
+ let t = r(e.op, i);
81
+ t.safe || n.push({
82
+ op: e,
83
+ reason: t.reason,
84
+ remedy: t.remedy
85
+ });
86
+ }
87
+ return n;
88
+ }, a = (e) => {
89
+ if (!e.enabled || e.unsafeCount === 0) return {
90
+ refuse: !1,
91
+ warnForced: !1,
92
+ message: ""
93
+ };
94
+ let t = `${e.unsafeCount} operation(s) are UNSAFE under a rolling deploy (VOLTRO_ROLLING_DEPLOY=1)`;
95
+ return e.forced ? {
96
+ refuse: !1,
97
+ warnForced: !0,
98
+ message: `${t} — proceeding anyway (--force). Old instances may error mid-rollout.`
99
+ } : {
100
+ refuse: !0,
101
+ warnForced: !1,
102
+ message: `${t}. Old instances would break against the new schema mid-rollout. Split into expand → deploy → contract (see the ⚠ list above), deploy with no overlap (maintenance window / scale-to-zero), or pass --force to override.`
103
+ };
104
+ };
105
+ //#endregion
106
+ export { a as assessRollingDeployGate, r as classifyRollingDeploySafety, e as createdTablesOf, i as rollingDeployUnsafeOps };
@@ -0,0 +1,46 @@
1
+ import { dialectOf as e } from "./resumeLedger.js";
2
+ import { SCHEMA_OWNER_TABLE as t } from "./schemaOwnerTable.js";
3
+ import { appliedAtValue as n } from "./appliedAtValue.js";
4
+ import { ensureSchemaOwnerTable as r } from "./applier.js";
5
+ import { Effect as i } from "effect";
6
+ //#region src/migrations/schemaOwner.ts
7
+ var a = "owner", o = (e) => i.map(e`SELECT ${e("appName")}, ${e("claimedAt")} FROM ${e(t)} WHERE ${e("id")} = ${a}`, (e) => e[0]), s = (e, t) => e.appName === t ? { kind: "owned" } : {
8
+ kind: "foreign",
9
+ owner: e.appName,
10
+ claimedAt: e.claimedAt === void 0 || e.claimedAt === null ? void 0 : String(e.claimedAt)
11
+ }, c = (t, n) => r(t, e(t), n).pipe(i.catch(() => r(t, e(t), n))), l = (e, t, r) => i.gen(function* () {
12
+ if (!r.mayWrite) {
13
+ let n = yield* o(e).pipe(i.catch(() => i.succeed(void 0)));
14
+ return n === void 0 ? { kind: "unclaimed" } : s(n, t.appName);
15
+ }
16
+ yield* c(e, r.charset);
17
+ let l = yield* o(e);
18
+ if (l !== void 0) return s(l, t.appName);
19
+ let u = yield* n(e, (/* @__PURE__ */ new Date()).toISOString());
20
+ if (yield* e`INSERT INTO ${e("_voltro_schema_owner")} ${e.insert({
21
+ id: a,
22
+ appName: t.appName,
23
+ projectName: t.projectName ?? null,
24
+ frameworkVersion: t.frameworkVersion ?? null,
25
+ claimedAt: u
26
+ })}`.pipe(i.as(!0), i.catch(() => i.succeed(!1)))) return { kind: "claimed" };
27
+ let d = yield* o(e);
28
+ return d === void 0 ? { kind: "unclaimed" } : s(d, t.appName);
29
+ }), u = (e, r, s) => i.gen(function* () {
30
+ yield* c(e, s);
31
+ let i = yield* o(e), l = yield* n(e, (/* @__PURE__ */ new Date()).toISOString());
32
+ return i === void 0 ? yield* e`INSERT INTO ${e(t)} ${e.insert({
33
+ id: a,
34
+ appName: r.appName,
35
+ projectName: r.projectName ?? null,
36
+ frameworkVersion: r.frameworkVersion ?? null,
37
+ claimedAt: l
38
+ })}` : yield* e`UPDATE ${e(t)} SET ${e.update({
39
+ appName: r.appName,
40
+ projectName: r.projectName ?? null,
41
+ frameworkVersion: r.frameworkVersion ?? null,
42
+ claimedAt: l
43
+ })} WHERE ${e("id")} = ${a}`, { previous: i?.appName };
44
+ }), d = (e, t, n) => `this database belongs to the app '${t}'${n === void 0 ? "" : ` (claimed ${n})`}, and '${e}' is a different app. Two apps on one schema each plan to drop the other's tables, and their data is shared with no contract between them. Give this app its own database (DB_URL) or its own schema (DB_SCHEMA); if it needs the other app's data, call it — \`apis:\` on this app, \`service:\` procedures on the other. If this app was RENAMED from '${t}', run \`voltro db claim\` once to hand the schema over.`;
45
+ //#endregion
46
+ export { t as SCHEMA_OWNER_TABLE, l as checkSchemaOwner, d as foreignOwnerMessage, u as transferSchemaOwner };
@@ -0,0 +1,4 @@
1
+ //#region src/migrations/schemaOwnerTable.ts
2
+ var e = "_voltro_schema_owner";
3
+ //#endregion
4
+ export { e as SCHEMA_OWNER_TABLE };
@@ -0,0 +1,20 @@
1
+ //#region src/migrations/softDropSnapshot.ts
2
+ var e = (e, t) => {
3
+ let n = e.op;
4
+ if (n.kind !== "drop-column" && n.kind !== "drop-table") return e;
5
+ let r = n.kind === "drop-column" ? n.column : n.table, i = t.replace(/[-:T.Z]/g, "").slice(0, 14);
6
+ if (!/^\d{14}$/.test(i)) throw Error("Invalid soft-drop snapshot timestamp");
7
+ return {
8
+ ...e,
9
+ softDropSnapshot: `${r}__dropped_${i}`
10
+ };
11
+ }, t = (e) => {
12
+ if (e.softDropSnapshot === void 0) return;
13
+ let t = e.op;
14
+ if (t.kind !== "drop-column" && t.kind !== "drop-table") throw Error("Soft-drop snapshot belongs to a non-drop operation");
15
+ let n = t.kind === "drop-column" ? t.column : t.table, r = e.softDropSnapshot;
16
+ if (typeof r != "string" || !r.startsWith(`${n}__dropped_`) || !/^\d{14}$/.test(r.slice(n.length + 10))) throw Error("Soft-drop snapshot does not identify the original object");
17
+ return r;
18
+ };
19
+ //#endregion
20
+ export { e as bindSoftDropSnapshot, t as readSoftDropSnapshot };
@@ -0,0 +1,8 @@
1
+ import { dialectOf as e } from "./resumeLedger.js";
2
+ //#region src/migrations/sqlLimit.ts
3
+ var t = (t, n) => {
4
+ let r = Math.max(1, Math.floor(Number.isFinite(n) ? n : 1));
5
+ return t.literal(e(t) === "mssql" ? `OFFSET 0 ROWS FETCH NEXT ${r} ROWS ONLY` : `LIMIT ${r}`);
6
+ };
7
+ //#endregion
8
+ export { t as limitClause };