@voltro/database 0.84.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 (170) hide show
  1. package/CHANGELOG.md +1215 -0
  2. package/THIRD-PARTY-NOTICES.md +5 -89
  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 +122 -64
  33. package/dist/index.js +81 -2080
  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-B28cPjEp.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 +58 -29
  129. package/dist/sql.js +41 -6661
  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.d.ts +9 -10
  166. package/dist/wire.js +6 -2
  167. package/dist/writeAttribution.js +16 -0
  168. package/dist/writeRecorder.js +37 -0
  169. package/package.json +4 -6
  170. package/dist/replaceInProgress-DUHl5JEz.js +0 -3557
@@ -0,0 +1,318 @@
1
+ import { causeBranches as e, classifyConstraintViolation as t, extractDbCause as n } from "../dbCause.js";
2
+ import { FILE_MIGRATION_PATTERN as r, IrreversibleMigrationError as i, MigrationExpectationFailed as a, MigrationSkipped as o, TransactionBrokenByBodyError as s, irreversibleReason as c, isFileMigration as l } from "./fileBased.js";
3
+ import { acquireMigrationLock as u } from "./lock.js";
4
+ import { MIGRATION_OPS_TABLE as d, dialectOf as f } from "./resumeLedger.js";
5
+ import { migrationTableExists as p } from "./migrationTableExists.js";
6
+ import { appliedAtValue as m } from "./appliedAtValue.js";
7
+ import { ensureLedgerTable as h, ensurePlanLedgerTable as g } from "./applier.js";
8
+ import { isFileMigrationRow as _ } from "./ledgerRows.js";
9
+ import { failedExpectations as v, makeFileMigrationContext as y } from "./fileContext.js";
10
+ import { withSchemaProbeRecorder as b } from "./fileSkipProbes.js";
11
+ import { Effect as x } from "effect";
12
+ import { createHash as S } from "node:crypto";
13
+ import { SqlClient as C } from "effect/sql";
14
+ import { createLogger as w } from "@voltro/logger";
15
+ import { promises as T } from "node:fs";
16
+ import { join as E } from "node:path";
17
+ import { pathToFileURL as D } from "node:url";
18
+ //#region src/migrations/fileDiscovery.ts
19
+ var O = w({ scope: "voltro:migrate" }), k = (e) => S("sha256").update(e).digest("hex").slice(0, 16), A = [{
20
+ codemod: "0.78.0/01_file-migration-phase",
21
+ inverse: (e) => {
22
+ let t = /\n {2}phase: 'before-schema',\n {2}/g, n = /\n {2}phase: 'before-schema'(?=\r?\n[ \t]*\})/g, r = e.includes("\r\n") ? "\r\n" : "\n", i = (t) => {
23
+ let n = e.lastIndexOf("\n", t - 1) + 1;
24
+ return /^[ \t]*/.exec(e.slice(n, t))[0];
25
+ };
26
+ return [e.replace(t, (e, t) => r + i(t)).replace(n, ""), e.replace(t, " ").replace(n, "")];
27
+ }
28
+ }], j = (e) => new Set([e, ...A.flatMap((t) => t.inverse(e))].map(k)), M = /* @__PURE__ */ new Set([
29
+ "node_modules",
30
+ "dist",
31
+ "build",
32
+ ".turbo",
33
+ ".vite",
34
+ ".next",
35
+ ".git",
36
+ ".framework"
37
+ ]), N = async (e) => {
38
+ let t = [], n = async (e) => {
39
+ let i = await T.readdir(e, { withFileTypes: !0 }).catch(() => []);
40
+ for (let a of i) {
41
+ if (M.has(a.name)) continue;
42
+ let i = E(e, a.name);
43
+ a.isDirectory() ? await n(i) : a.isFile() && r.test(a.name) && t.push(i);
44
+ }
45
+ };
46
+ return await n(e), t.sort((e, t) => {
47
+ let n = e.split("/").pop() ?? e, r = t.split("/").pop() ?? t;
48
+ return n.localeCompare(r);
49
+ });
50
+ }, P = async (e) => await import(D(e).href), F = async (e, t = P) => {
51
+ let n = [];
52
+ for (let r of e) {
53
+ let e = (await t(r)).default;
54
+ if (!l(e)) {
55
+ O.warn("migration file has no default-exported `migration(...)` — skipped", { file: r });
56
+ continue;
57
+ }
58
+ let i = await T.readFile(r, "utf8").catch(() => "");
59
+ n.push({
60
+ file: r,
61
+ migration: e,
62
+ hash: k(i),
63
+ accepted: j(i)
64
+ });
65
+ }
66
+ return n;
67
+ }, I = "_voltro_migration_plans", L = (e) => R(e).pipe(x.map((e) => e.map((e) => e.id))), R = (e) => p(e, I).pipe(x.flatMap((t) => t ? e`
68
+ SELECT ${e("id")}, ${e("fingerprint")}
69
+ FROM ${e(I)}
70
+ WHERE ${_(e)}
71
+ `.pipe(x.map((e) => e.map((e) => ({
72
+ id: e.id,
73
+ hash: e.fingerprint ?? ""
74
+ })))) : x.succeed([]))), z = (e, t) => t.filter((t) => {
75
+ let n = e.get(t.migration.id);
76
+ return n !== void 0 && n !== "" && !t.accepted.has(n);
77
+ }).map((e) => e.migration.id), B = /* @__PURE__ */ new Set(), V = (e) => t(n(e))?.kind === "unique", H = class extends Error {
78
+ migrationId;
79
+ file;
80
+ cause;
81
+ _tag = "FileMigrationLedgerError";
82
+ constructor(e, t, n) {
83
+ super(V(n) ? `migration ${e}: up() SUCCEEDED here, and ANOTHER process had already recorded the same migration.\n The body has run TWICE. The ledger row is correct — do not delete it; the next run will not apply ${t} again.\n Two processes applied it without being serialised by the migration lock: check that both reach the same database and lock schema.\n Cause: ${n instanceof Error ? n.message : String(n)}` : `migration ${e}: up() SUCCEEDED but the ledger row could not be written.\n The change IS in the database. It is NOT recorded, so the next run would apply it AGAIN.\n Do not re-run ${t} until this is resolved.\n Cause: ${n instanceof Error ? n.message : String(n)}`), this.migrationId = e, this.file = t, this.cause = n, this.name = "FileMigrationLedgerError";
84
+ }
85
+ }, U = (e, t, n) => x.gen(function* () {
86
+ let r = yield* m(e, n.appliedAtIso);
87
+ yield* e`
88
+ INSERT INTO ${e("_voltro_migration_plans")}
89
+ (${e("id")}, ${e("fingerprint")}, ${e("operations")},
90
+ ${e("appliedBy")}, ${e("environment")}, ${e("source")},
91
+ ${e("durationMs")}, ${e("appliedAt")}, ${e("notes")})
92
+ VALUES
93
+ (${t.migration.id}, ${t.hash}, ${JSON.stringify([{
94
+ kind: "file",
95
+ file: t.file,
96
+ phase: t.migration.phase
97
+ }])},
98
+ ${n.appliedBy}, ${n.env}, ${"file"},
99
+ ${n.durationMs}, ${r}, ${n.notes})
100
+ `.pipe(x.mapError((e) => new H(t.migration.id, t.file, e)));
101
+ }), W = (t) => {
102
+ for (let n of e(t)) if (n instanceof o) return n.reason;
103
+ }, G = (e, t, n) => x.gen(function* () {
104
+ let r = b(t), i = r.ctx, o = e.migration.up(i), s = !x.isEffect(o), c = yield* (x.isEffect(o) ? o : x.tryPromise({
105
+ try: () => o,
106
+ catch: (e) => e
107
+ })).pipe(x.as(void 0), x.catch((e) => {
108
+ let t = W(e);
109
+ return t === void 0 ? x.fail(e) : x.succeed(t);
110
+ }));
111
+ if (c === void 0) {
112
+ let t = yield* v(n);
113
+ if (t.length > 0) return yield* x.fail(new a(e.migration.id, t));
114
+ }
115
+ return {
116
+ skipped: c,
117
+ promiseForm: s,
118
+ absent: r.absent()
119
+ };
120
+ }), K = (e, t, n, r, i, a, o, c) => x.gen(function* () {
121
+ let l = Date.now(), u = (/* @__PURE__ */ new Date()).toISOString(), p = f(e), m = `migrations:file:${n.migration.id}`, g = (e) => ({
122
+ durationMs: Date.now() - l,
123
+ notes: e.skipped === void 0 ? n.migration.description : `skipped: ${e.skipped}`
124
+ });
125
+ if (n.migration.transactional !== !1 && !a) {
126
+ if (t.run === void 0) return yield* x.fail(/* @__PURE__ */ Error(`migration ${n.migration.id}: transactional: true needs a SQL-backed store`));
127
+ let a = p === "mysql" || p === "mariadb", l = `probe_${n.migration.id}_${Date.now().toString(36)}`;
128
+ a && (yield* h(e, p, o));
129
+ let f = (e) => e.executeUnprepared(`SELECT COUNT(*) AS n FROM \`${d}\` WHERE \`runId\` = ?`, [l], void 0), _ = () => (c === void 0 ? x.scoped(x.flatMap(e.reserve, f)) : f(c)).pipe(x.map((e) => Number(e[0]?.n ?? 0) > 0)), v = yield* x.tryPromise({
130
+ try: () => t.transactional(async (t) => {
131
+ a && await t.run(e`INSERT INTO ${e(d)} (${e("id")}, ${e("runId")}, ${e("planFingerprint")}, ${e("opIndex")}, ${e("status")}, ${e("opKind")}, ${e("opTarget")}, ${e("payload")})
132
+ VALUES (${`mop_${l}`}, ${l}, ${""}, ${0}, ${"pending"}, ${"probe"}, ${""}, ${"{}"})`);
133
+ let { ctx: o, expectations: s } = y({
134
+ sql: e,
135
+ store: t,
136
+ dialect: p,
137
+ migrationId: n.migration.id,
138
+ appliedAt: u
139
+ }), c = await t.run(G(n, o, s)), f = a && await x.runPromise(_().pipe(x.provideService(C.SqlClient, e))), { durationMs: m, notes: h } = g(c);
140
+ return await t.run(U(e, n, {
141
+ env: r,
142
+ appliedBy: i,
143
+ durationMs: m,
144
+ appliedAtIso: u,
145
+ notes: f ? `${h} — transactional guarantee broken by the body (DDL on ${p})` : h
146
+ })), a && await t.run(e`DELETE FROM ${e(d)} WHERE ${e("runId")} = ${l}`), {
147
+ ...c,
148
+ durationMs: m,
149
+ broken: f
150
+ };
151
+ }),
152
+ catch: (e) => e
153
+ });
154
+ return v.broken ? yield* x.fail(new s(n.migration.id, p)) : (v.skipped !== void 0 && (yield* x.logInfo(`migration ${n.migration.id} skipped: ${v.skipped}`).pipe(x.annotateLogs({ scope: m }))), {
155
+ durationMs: v.durationMs,
156
+ skip: v.skipped === void 0 ? void 0 : {
157
+ id: n.migration.id,
158
+ reason: v.skipped,
159
+ absent: v.absent
160
+ }
161
+ });
162
+ }
163
+ let { ctx: _, expectations: v } = y({
164
+ sql: e,
165
+ store: t,
166
+ dialect: p,
167
+ migrationId: n.migration.id,
168
+ appliedAt: u
169
+ }), b = yield* G(n, _, v), { durationMs: S, notes: w } = g(b);
170
+ return b.promiseForm && b.skipped === void 0 && S < 2 && (yield* x.logWarning(`migration ${n.migration.id}: up() resolved in ${S}ms — did the body \`await\` an \`effect/sql\` Effect by accident? \`sql.unsafe(...)\` returns an Effect, not a Promise; \`await\` on it is a silent no-op. Switch the body to \`Effect.gen(function* () { yield* sql.unsafe(...) })\` form.`).pipe(x.annotateLogs({ scope: m }))), b.skipped !== void 0 && (yield* x.logInfo(`migration ${n.migration.id} skipped: ${b.skipped}`).pipe(x.annotateLogs({ scope: m }))), yield* U(e, n, {
171
+ env: r,
172
+ appliedBy: i,
173
+ durationMs: S,
174
+ appliedAtIso: u,
175
+ notes: w
176
+ }), {
177
+ durationMs: S,
178
+ skip: b.skipped === void 0 ? void 0 : {
179
+ id: n.migration.id,
180
+ reason: b.skipped,
181
+ absent: b.absent
182
+ }
183
+ };
184
+ }), q = (e, t, n, r) => x.gen(function* () {
185
+ let i = yield* x.tryPromise({
186
+ try: () => N(E(t, "migrations")),
187
+ catch: (e) => e
188
+ });
189
+ if (i.length === 0) return {
190
+ pending: [],
191
+ alreadyApplied: [],
192
+ drifted: []
193
+ };
194
+ let a = yield* x.tryPromise({
195
+ try: () => F(i, r),
196
+ catch: (e) => e
197
+ });
198
+ if (a.length === 0) return {
199
+ pending: [],
200
+ alreadyApplied: [],
201
+ drifted: []
202
+ };
203
+ let o = new Map((yield* R(e)).map((e) => [e.id, e.hash])), s = z(o, a), c = s.length >= 32 ? /* @__PURE__ */ new Map() : void 0;
204
+ if (c !== void 0) for (let e of a) c.has(e.migration.id) || c.set(e.migration.id, e);
205
+ for (let e of s) {
206
+ let t = c === void 0 ? a.find((t) => t.migration.id === e) : c.get(e), n = `${t?.file ?? e}:${t?.hash ?? ""}`;
207
+ B.has(n) || (B.add(n), O.warn("file migration: the body on disk is not the one that was applied — the file no longer describes this database", {
208
+ id: e,
209
+ file: t?.file
210
+ }));
211
+ }
212
+ let l = n === void 0 ? a : a.filter((e) => e.migration.phase === n);
213
+ return {
214
+ pending: l.filter((e) => !o.has(e.migration.id)),
215
+ alreadyApplied: l.filter((e) => o.has(e.migration.id)),
216
+ drifted: s
217
+ };
218
+ }), J = (e, t, n, r) => q(e, t, n, r).pipe(x.map((e) => e.pending.map((e) => e.migration.id))), Y = (e, t, n) => q(e, t, n).pipe(x.map((e) => e.pending.map((e) => ({
219
+ id: e.migration.id,
220
+ phase: e.migration.phase,
221
+ irreversible: c(e.migration.down)
222
+ })))), X = (e, t) => q(e, t).pipe(x.map((e) => {
223
+ let t = new Set(e.drifted);
224
+ return [...e.alreadyApplied.map((e) => ({
225
+ id: e.migration.id,
226
+ phase: e.migration.phase,
227
+ applied: !0,
228
+ bodyChanged: t.has(e.migration.id)
229
+ })), ...e.pending.map((e) => ({
230
+ id: e.migration.id,
231
+ phase: e.migration.phase,
232
+ applied: !1,
233
+ bodyChanged: !1
234
+ }))].sort((e, t) => e.id < t.id ? -1 : 1);
235
+ })), Z = (e, t) => x.gen(function* () {
236
+ let { pending: n, alreadyApplied: r } = yield* q(e, t.projectRoot, t.phase), i = r.map((e) => e.migration.id);
237
+ if (n.length === 0) return {
238
+ applied: [],
239
+ skipped: i,
240
+ skips: []
241
+ };
242
+ yield* g(e, f(e), t.charset);
243
+ let a = yield* x.acquireUseRelease(u(e, { schema: t.lockSchema }), (r) => x.gen(function* () {
244
+ let i = new Set((yield* R(e)).map((e) => e.id)), a = n.filter((e) => !i.has(e.migration.id));
245
+ a.length < n.length && (yield* x.logInfo("file-based migrations applied by another process while this one waited for the migration lock").pipe(x.annotateLogs({
246
+ scope: "voltro:migrate",
247
+ ids: n.filter((e) => i.has(e.migration.id)).map((e) => e.migration.id).join(",")
248
+ })));
249
+ let o = [];
250
+ a.length > 0 && t.beforeExecute !== void 0 && (yield* t.beforeExecute);
251
+ let s = [];
252
+ for (let n of a) {
253
+ yield* x.logInfo("file-based migration applying").pipe(x.annotateLogs({
254
+ scope: "voltro:migrate",
255
+ id: n.migration.id,
256
+ file: n.file,
257
+ phase: n.migration.phase
258
+ }));
259
+ let { durationMs: i, skip: a } = yield* K(e, t.store, n, t.env, t.appliedBy, t.enclosed === !0, t.charset, r.connection);
260
+ o.push({
261
+ id: n.migration.id,
262
+ durationMs: i
263
+ }), a !== void 0 && s.push(a), yield* x.logInfo("file-based migration applied").pipe(x.annotateLogs({
264
+ scope: "voltro:migrate",
265
+ id: n.migration.id,
266
+ durationMs: i,
267
+ phase: n.migration.phase
268
+ }));
269
+ }
270
+ return {
271
+ done: o,
272
+ skips: s
273
+ };
274
+ }), (e) => e.release.pipe(x.orDie));
275
+ return {
276
+ applied: a.done,
277
+ skipped: i,
278
+ skips: a.skips
279
+ };
280
+ }), Q = (e, t) => e`
281
+ SELECT ${e("id")}
282
+ FROM ${e("_voltro_migration_plans")}
283
+ WHERE ${e("id")} = ${t} AND ${e("source")} = 'file'
284
+ `, $ = (e, t) => x.gen(function* () {
285
+ if (!(yield* Q(e, t.id))[0]) return yield* x.fail(/* @__PURE__ */ Error(`rollback: ${t.id} not found in _voltro_migration_plans (file source)`));
286
+ let n = yield* x.tryPromise({
287
+ try: () => N(E(t.projectRoot, "migrations")),
288
+ catch: (e) => e
289
+ }), r = (yield* x.tryPromise({
290
+ try: () => F(n),
291
+ catch: (e) => e
292
+ })).find((e) => e.migration.id === t.id);
293
+ if (!r) return yield* x.fail(/* @__PURE__ */ Error(`rollback: file for migration ${t.id} not found on disk under ${t.projectRoot}/migrations/`));
294
+ let a = c(r.migration.down);
295
+ return a === void 0 ? yield* x.acquireUseRelease(u(e, { schema: t.lockSchema }), () => x.gen(function* () {
296
+ if ((yield* Q(e, t.id))[0] === void 0) return yield* x.fail(/* @__PURE__ */ Error(`rollback: ${t.id} was rolled back by another process while this one waited for the migration lock — nothing to do`));
297
+ let n = Date.now(), { ctx: i } = y({
298
+ sql: e,
299
+ store: t.store,
300
+ dialect: f(e),
301
+ migrationId: t.id,
302
+ appliedAt: (/* @__PURE__ */ new Date()).toISOString()
303
+ });
304
+ t.beforeExecute !== void 0 && (yield* t.beforeExecute);
305
+ let a = r.migration.down(i);
306
+ x.isEffect(a) ? yield* a : yield* x.tryPromise({
307
+ try: () => a,
308
+ catch: (e) => e
309
+ });
310
+ let o = Date.now() - n;
311
+ return yield* e`DELETE FROM ${e("_voltro_migration_plans")} WHERE ${e("id")} = ${t.id} AND ${e("source")} = 'file'`, {
312
+ id: t.id,
313
+ durationMs: o
314
+ };
315
+ }), (e) => e.release.pipe(x.orDie)) : yield* x.fail(new i(t.id, a));
316
+ });
317
+ //#endregion
318
+ export { r as FILE_MIGRATION_PATTERN, A as FRAMEWORK_MIGRATION_EDITS, H as FileMigrationLedgerError, j as acceptedBodyHashes, L as appliedFileMigrationIds, R as appliedFileMigrations, X as fileMigrationStatus, k as migrationBodyHash, J as pendingFileMigrationIds, Y as pendingFileMigrationSummary, $ as rollbackFileBasedMigration, Z as runFileBasedMigrations, N as walkMigrationFiles };
@@ -0,0 +1,117 @@
1
+ import { Effect as e } from "effect";
2
+ //#region src/migrations/fileSkipProbes.ts
3
+ var t = (e) => e.kind === "table" ? e.table : `${e.table}.${e.name}`, n = (e, n) => e.kind === n.kind && e.table === n.table && t(e) === t(n), r = (e) => JSON.stringify([
4
+ e.kind,
5
+ e.table,
6
+ e.kind === "table" ? null : e.name
7
+ ]), i = (e) => {
8
+ if (Object.getPrototypeOf(e) !== Object.prototype) return !1;
9
+ let t = Object.getOwnPropertyDescriptor(e, "kind");
10
+ if (t === void 0 || !("value" in t) || typeof t.value != "string") return !1;
11
+ for (let n of ["table", ...t.value === "table" ? [] : ["name"]]) {
12
+ let t = Object.getOwnPropertyDescriptor(e, n);
13
+ if (t === void 0 || !("value" in t) || typeof t.value != "string") return !1;
14
+ }
15
+ return !0;
16
+ }, a = (t) => {
17
+ let i = [], a = /* @__PURE__ */ new Set(), o = !1, s = (e) => (t) => {
18
+ if (!t) {
19
+ if (o) i.some((t) => n(t, e)) || i.push(e);
20
+ else {
21
+ let t = r(e);
22
+ a.has(t) || (a.add(t), i.push(e));
23
+ }
24
+ }
25
+ return t;
26
+ }, c = (t, n) => (...r) => t(...r).pipe(e.map(s(n(...r)))), l = {
27
+ tableExists: c(t.schema.tableExists, (e) => ({
28
+ kind: "table",
29
+ table: e
30
+ })),
31
+ columnExists: c(t.schema.columnExists, (e, t) => ({
32
+ kind: "column",
33
+ table: e,
34
+ name: t
35
+ })),
36
+ indexExists: c(t.schema.indexExists, (e, t) => ({
37
+ kind: "index",
38
+ table: e,
39
+ name: t
40
+ })),
41
+ foreignKeyExists: c(t.schema.foreignKeyExists, (e, t) => ({
42
+ kind: "foreign-key",
43
+ table: e,
44
+ name: t
45
+ }))
46
+ };
47
+ return {
48
+ ctx: {
49
+ ...t,
50
+ schema: {
51
+ ...t.schema,
52
+ ...l
53
+ }
54
+ },
55
+ absent: () => (o = !0, i.slice())
56
+ };
57
+ }, o = (e) => {
58
+ let t = [], n = /* @__PURE__ */ new Set(), i = (e) => {
59
+ let i = r(e);
60
+ n.has(i) || (n.add(i), t.push(e));
61
+ };
62
+ for (let t of e.operations) {
63
+ let e = t.op;
64
+ switch (e.kind) {
65
+ case "create-table":
66
+ i({
67
+ kind: "table",
68
+ table: e.table
69
+ });
70
+ for (let t of e.columns) i({
71
+ kind: "column",
72
+ table: e.table,
73
+ name: t.name
74
+ });
75
+ for (let t of e.indexes) i({
76
+ kind: "index",
77
+ table: e.table,
78
+ name: t.name
79
+ });
80
+ break;
81
+ case "add-column":
82
+ i({
83
+ kind: "column",
84
+ table: e.table,
85
+ name: e.column.name
86
+ });
87
+ break;
88
+ case "add-index":
89
+ i({
90
+ kind: "index",
91
+ table: e.table,
92
+ name: e.index.name
93
+ });
94
+ break;
95
+ case "add-foreign-key": i({
96
+ kind: "foreign-key",
97
+ table: e.table,
98
+ name: e.column
99
+ });
100
+ }
101
+ }
102
+ return t;
103
+ }, s = (e, t) => {
104
+ let a = o(t);
105
+ if (a.length === 0) return [];
106
+ let s = a.length >= 32 ? new Set(a.map(r)) : void 0;
107
+ return e.flatMap((e) => {
108
+ let t = e.absent.filter((e) => s !== void 0 && i(e) ? s.has(r(e)) : a.some((t) => n(t, e)));
109
+ return t.length === 0 ? [] : [{
110
+ id: e.id,
111
+ reason: e.reason,
112
+ created: t
113
+ }];
114
+ });
115
+ };
116
+ //#endregion
117
+ export { t as describeProbe, s as permanentSkips, o as plannedCreations, a as withSchemaProbeRecorder };
@@ -0,0 +1,46 @@
1
+ import { isSoftDropSnapshotName as e } from "./frameworkLiveTables.js";
2
+ import { canonicalIdColumn as t, comparableDefault as n, comparableIndex as r, comparableSpatial as i, isPgSequenceDefault as a, isSolePrimaryKeyId as o, normRef as s } from "./columnEquivalence.js";
3
+ import { createHash as c } from "node:crypto";
4
+ //#region src/migrations/fingerprint.ts
5
+ var l = (e) => {
6
+ if (e == null) return e;
7
+ if (Array.isArray(e)) return e.map(l);
8
+ if (typeof e != "object") return e;
9
+ let t = {};
10
+ for (let n of Object.keys(e).sort()) {
11
+ let r = e[n];
12
+ r !== void 0 && (t[n] = l(r));
13
+ }
14
+ return t;
15
+ }, u = (e) => {
16
+ if (!e.idScheme && !e.sensitive && !e.safe && !e.generatedAs && !e.jsonChecks && !e.narrowedFrom && !e.rawDdl && !e.charset) return e;
17
+ let { idScheme: t, sensitive: n, safe: r, generatedAs: i, jsonChecks: a, narrowedFrom: o, rawDdl: s, charset: c, ...l } = e;
18
+ return l;
19
+ }, d = (e) => {
20
+ let t = u((() => {
21
+ if (e.storageType === void 0) return e;
22
+ let { storageType: t, enumValues: n, enumName: r, arrayElement: i, ...a } = e;
23
+ return {
24
+ ...a,
25
+ type: t
26
+ };
27
+ })()), { references: r, defaultValue: o, hasDefault: c, oneOf: l, spatial: d, enumValues: f, ...p } = t, m = t.type === "id" && a(t) ? null : n(t), h = s(t.references);
28
+ return {
29
+ ...p,
30
+ hasDefault: m !== null,
31
+ ...m === null ? {} : { defaultValue: m },
32
+ ...h === null ? {} : { references: h },
33
+ ...t.oneOf !== void 0 && t.oneOf.length > 0 ? { oneOf: [...t.oneOf].sort() } : {},
34
+ ...t.spatial === void 0 ? {} : { spatial: i(t.spatial) },
35
+ ...t.enumValues === void 0 ? {} : { enumValues: [...t.enumValues].sort() }
36
+ };
37
+ }, f = (n) => n.columns.filter((t) => !e(t.name)).map((e) => d(t(e, o(n, e)))).sort((e, t) => e.name.localeCompare(t.name)), p = (e) => [...e].map(r).sort((e, t) => e.name.localeCompare(t.name)), m = (t) => [...t].filter((t) => !e(t.name)).map((e) => ({
38
+ name: e.name,
39
+ columns: f(e),
40
+ indexes: p(e.indexes)
41
+ })).sort((e, t) => e.name.localeCompare(t.name)), h = (e) => {
42
+ let t = { tables: m(e.tables) }, n = JSON.stringify(l(t));
43
+ return c("sha256").update(n).digest("hex");
44
+ }, g = (e) => e.slice(0, 16);
45
+ //#endregion
46
+ export { h as fingerprintSchema, g as shortFingerprint };
@@ -0,0 +1,13 @@
1
+ //#region src/migrations/fkCycles.ts
2
+ var e = (e, t) => `${e}::${t}`, t = (t, n) => {
3
+ let r = /* @__PURE__ */ new Set(), i = /* @__PURE__ */ new Set(), a = /* @__PURE__ */ new Set(), o = (t) => {
4
+ r.add(t);
5
+ let s = [...n(t)].filter((e) => e.target !== t).sort((e, t) => e.column.localeCompare(t.column));
6
+ for (let n of s) r.has(n.target) ? a.add(e(t, n.column)) : i.has(n.target) || o(n.target);
7
+ r.delete(t), i.add(t);
8
+ };
9
+ for (let e of [...t].sort()) i.has(e) || o(e);
10
+ return a;
11
+ };
12
+ //#endregion
13
+ export { t as findCyclicFkBackEdges, e as fkEdgeKey };
@@ -0,0 +1,25 @@
1
+ //#region src/migrations/frameworkLiveTables.ts
2
+ var e = /* @__PURE__ */ new Set(["voltro_isr_cache"]), t = (t) => e.has(t) || t.startsWith("_voltro_") || t.startsWith("_cloud_") || t.startsWith("cluster_") || t.startsWith("__turso_internal_"), n = "_voltro_staging_", r = (e) => e.startsWith(n), i = /__dropped_\d{14}$/, a = (e) => i.test(e), o = 7, s = (e) => {
3
+ let t = /__dropped_(\d{4})(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})$/.exec(e);
4
+ if (t === null) return;
5
+ let [, n, r, i, a, o, s] = t;
6
+ return new Date(Date.UTC(Number(n), Number(r) - 1, Number(i), Number(a), Number(o), Number(s)));
7
+ }, c = (e, t = {}) => {
8
+ let n = t.now ?? /* @__PURE__ */ new Date(), r = t.retentionDays ?? 7, i = [], o = (e, t) => {
9
+ let a = s(e);
10
+ if (a === void 0) return;
11
+ let o = Math.floor((n.getTime() - a.getTime()) / 864e5);
12
+ o >= r && i.push({
13
+ name: t,
14
+ droppedAt: a,
15
+ ageDays: o
16
+ });
17
+ };
18
+ for (let t of e.tables) if (o(t.name, t.name), !a(t.name)) for (let e of t.columns ?? []) o(e.name, `${t.name}.${e.name}`);
19
+ return i;
20
+ }, l = (e, t) => {
21
+ let n = e.reduce((e, t) => e.ageDays >= t.ageDays ? e : t), r = (/* @__PURE__ */ new Date(Date.now() - t * 864e5)).toISOString().slice(0, 10);
22
+ return `${e.length} soft-drop snapshot(s) older than ${t} day(s) still hold the dropped data (oldest: ${n.name}, ${n.ageDays}d) — ${e.map((e) => e.name).join(", ")}. Restore with \`voltro db restore-snapshot\` or reclaim with \`voltro db gc-snapshots --before ${r}\`; the window is \`database.softDropRetentionDays\` in app.config.`;
23
+ }, u = (e, n) => e.filter((e) => !t(e.name) || n.has(e.name));
24
+ //#endregion
25
+ export { o as DEFAULT_SOFT_DROP_RETENTION_DAYS, e as FRAMEWORK_LIVE_TABLES, n as STAGING_TABLE_PREFIX, c as agedSoftDropSnapshots, l as describeAgedSoftDropSnapshots, t as isFrameworkOwnedLiveTable, a as isSoftDropSnapshotName, r as isStagingTableName, u as plannableLiveTables, s as softDropSnapshotStamp };
@@ -0,0 +1,37 @@
1
+ //#region src/migrations/idempotentDdl.ts
2
+ var e = (e) => {
3
+ let t = e;
4
+ for (let e = 0; e < 6 && typeof t == "object" && t; e++) {
5
+ let e = t;
6
+ if (typeof e.errno == "number") return e.errno;
7
+ t = e.cause;
8
+ }
9
+ }, t = (e) => {
10
+ let t = [], n = e;
11
+ for (let e = 0; e < 6 && typeof n == "object" && n; e++) {
12
+ let e = n;
13
+ typeof e.message == "string" && t.push(e.message), n = e.cause;
14
+ }
15
+ return t.join(" | ");
16
+ }, n = (n, r) => {
17
+ if (r.length === 0) return !1;
18
+ let i = e(n), a = t(n);
19
+ return r.some((e) => e.errno !== void 0 && i !== void 0 && e.errno.includes(i) || e.messageIncludes !== void 0 && e.messageIncludes.some((e) => a.includes(e)));
20
+ }, r = {
21
+ why: "mysql has no CREATE INDEX IF NOT EXISTS; 1061 is exactly that condition",
22
+ errno: [1061]
23
+ }, i = {
24
+ why: "mysql has no DROP COLUMN IF EXISTS; 1091 is exactly that condition",
25
+ errno: [1091]
26
+ }, a = {
27
+ why: "sqlite has no DROP COLUMN IF EXISTS, and reports the absent column only in the message",
28
+ messageIncludes: ["no such column"]
29
+ }, o = {
30
+ why: "mysql/mariadb DROP CONSTRAINT has no portable IF EXISTS; 1091 / 3940 are exactly \"already gone\"",
31
+ errno: [1091, 3940]
32
+ }, s = {
33
+ why: "mysql/mariadb ADD CONSTRAINT has no IF NOT EXISTS; 1826 is exactly \"already there\"",
34
+ errno: [1826]
35
+ };
36
+ //#endregion
37
+ export { i as MYSQL_COLUMN_OR_INDEX_ABSENT, o as MYSQL_CONSTRAINT_ABSENT, s as MYSQL_FOREIGN_KEY_EXISTS, r as MYSQL_INDEX_EXISTS, a as SQLITE_COLUMN_ABSENT, n as isAlreadySatisfied };
@@ -0,0 +1,50 @@
1
+ //#region src/migrations/indexPredicate.ts
2
+ var e = /'(?:''|[^'])*'|"(?:""|[^"])*"|\[(?:\]\]|[^\]])*\]|`(?:``|[^`])*`|[A-Za-z_][A-Za-z_0-9$]*|\d+(?:\.\d+)?|::|<>|!=|<=|>=|\|\||[^\s]/g, t = (t) => {
3
+ let n = 0;
4
+ for (let r of t.matchAll(e)) if (r[0] === "(" && n++, r[0] === ")" && n--, n === 0 && r[0].toLowerCase() === "where") return t.slice(r.index + r[0].length).trim().replace(/;$/, "").trim();
5
+ }, n = (t) => {
6
+ if (t === void 0) return;
7
+ let n = t.match(e) ?? [], r = "(?:'(?:''|[^'])*'(?:\\s*::\\s*text)?|[0-9]+)", i = RegExp(`^\\s*${r}(?:\\s*,\\s*${r})*\\s*$`, "i");
8
+ for (let t = 0; t < n.length - 5; t++) {
9
+ let r = n[t + 4];
10
+ n[t] === "=" && n[t + 1]?.toLowerCase() === "any" && n[t + 2] === "(" && n[t + 3]?.toLowerCase() === "array" && r.startsWith("[") && r.endsWith("]") && n[t + 5] === ")" && i.test(r.slice(1, -1)) && n.splice(t, 6, "in", "(", ...r.slice(1, -1).match(e) ?? [], ")");
11
+ }
12
+ let a = n.filter((e, t) => !(e === "::" && n[t - 1]?.startsWith("'") && n[t + 1]?.toLowerCase() === "text" || e.toLowerCase() === "text" && n[t - 1] === "::" && n[t - 2]?.startsWith("'"))).map((e) => {
13
+ if (e.startsWith("'")) return e;
14
+ if (e.startsWith("\"") || e.startsWith("[") || e.startsWith("`")) {
15
+ let t = e.slice(1, -1).replace(/""/g, "\"").replace(/\]\]/g, "]").replace(/``/g, "`");
16
+ return /^[a-z_][a-z_0-9$]*$/.test(t) ? t : JSON.stringify(t);
17
+ }
18
+ return e.toLowerCase();
19
+ }), o = (e) => {
20
+ let t = e, n = () => {
21
+ if (t[0] !== "(" || t.at(-1) !== ")") return !1;
22
+ let e = 0;
23
+ for (let n = 0; n < t.length - 1; n++) if (t[n] === "(" && e++, t[n] === ")" && e--, e === 0) return !1;
24
+ return !0;
25
+ };
26
+ for (; n();) t = t.slice(1, -1);
27
+ if (!t.includes("between")) for (let e of ["or", "and"]) {
28
+ let n = 0, r = [[]];
29
+ for (let i of t) i === "(" && n++, i === ")" && n--, n === 0 && i === e ? r.push([]) : r[r.length - 1].push(i);
30
+ if (r.length > 1) return [e, ...r.map(o)];
31
+ }
32
+ let r = [];
33
+ for (let e = 0; e < t.length; e++) t[e] === "(" && t[e + 2] === ")" && t[e + 1] !== "(" && t[e + 1] !== ")" && (e === 0 || [
34
+ "=",
35
+ "<>",
36
+ "!=",
37
+ "<",
38
+ ">",
39
+ "<=",
40
+ ">=",
41
+ "is",
42
+ "not",
43
+ "("
44
+ ].includes(t[e - 1])) ? (r.push(t[e + 1]), e += 2) : r.push(t[e]);
45
+ return r;
46
+ };
47
+ return JSON.stringify(o(a));
48
+ };
49
+ //#endregion
50
+ export { t as extractIndexPredicate, n as normalizeIndexPredicate };