@voltro/database 0.85.0 → 0.87.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 (173) hide show
  1. package/CHANGELOG.md +5987 -0
  2. package/THIRD-PARTY-NOTICES.md +5 -257
  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 +23 -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 +121 -0
  27. package/dist/frameworkTables.js +143 -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 +166 -22
  33. package/dist/index.js +82 -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 +644 -0
  43. package/dist/migration.js +18 -0
  44. package/dist/migrations/appliedAtValue.js +8 -0
  45. package/dist/migrations/applier.js +1100 -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/ddlStatements.js +9 -0
  51. package/dist/migrations/declaredSchema.js +162 -0
  52. package/dist/migrations/deployLedger.js +19 -0
  53. package/dist/migrations/fileBased.js +57 -0
  54. package/dist/migrations/fileContext.js +482 -0
  55. package/dist/migrations/fileDiscovery.js +332 -0
  56. package/dist/migrations/fileSkipProbes.js +117 -0
  57. package/dist/migrations/fingerprint.js +46 -0
  58. package/dist/migrations/fkCycles.js +13 -0
  59. package/dist/migrations/frameworkLiveTables.js +25 -0
  60. package/dist/migrations/idempotentDdl.js +37 -0
  61. package/dist/migrations/indexPredicate.js +50 -0
  62. package/dist/migrations/introspect.js +801 -0
  63. package/dist/migrations/ledgerRows.js +4 -0
  64. package/dist/migrations/lock.js +91 -0
  65. package/dist/migrations/migrationTableExists.js +9 -0
  66. package/dist/migrations/mssqlColumnDependencies.js +76 -0
  67. package/dist/migrations/mysqlEngine.js +11 -0
  68. package/dist/migrations/namespaceCharset.js +51 -0
  69. package/dist/migrations/planner.js +785 -0
  70. package/dist/migrations/postgresEnumCheck.js +22 -0
  71. package/dist/migrations/reactiveTriggerDrift.js +58 -0
  72. package/dist/migrations/reactiveTriggerName.js +7 -0
  73. package/dist/migrations/renameMssqlCheck.js +21 -0
  74. package/dist/migrations/restoreSnapshots.js +206 -0
  75. package/dist/migrations/resumeLedger.js +86 -0
  76. package/dist/migrations/rollingDeploy.js +106 -0
  77. package/dist/migrations/schemaOwner.js +46 -0
  78. package/dist/migrations/schemaOwnerTable.js +4 -0
  79. package/dist/migrations/softDropSnapshot.js +20 -0
  80. package/dist/migrations/sqlLimit.js +8 -0
  81. package/dist/migrations/sqliteCatalogName.js +13 -0
  82. package/dist/migrations/sqliteConstraintIdentity.js +97 -0
  83. package/dist/migrations/sqliteDecimalBoot.js +22 -0
  84. package/dist/migrations/sqliteDecimalPublication.js +257 -0
  85. package/dist/migrations/sqliteDecimalReview.js +30 -0
  86. package/dist/migrations/sqliteDecimalReviewStore.js +188 -0
  87. package/dist/migrations/sqliteDecimalReviewWitness.js +38 -0
  88. package/dist/migrations/sqliteDecimalSource.js +69 -0
  89. package/dist/migrations/sqliteDecimalStage.js +135 -0
  90. package/dist/migrations/sqliteForeignKeys.js +38 -0
  91. package/dist/migrations/sqliteMigrationBatch.js +37 -0
  92. package/dist/migrations/sqliteRetainedSource.js +106 -0
  93. package/dist/migrations/sqliteRowIdentity.js +63 -0
  94. package/dist/migrations/sqliteSchemaEpoch.js +12 -0
  95. package/dist/migrations/sqliteSnapshotGuard.js +10 -0
  96. package/dist/migrations/sqliteTableContents.js +34 -0
  97. package/dist/migrations/sqliteTableCopy.js +25 -0
  98. package/dist/migrations/sqliteTableSchema.js +19 -0
  99. package/dist/migrations/sqliteWitnessProtection.js +8 -0
  100. package/dist/migrations/squash.js +81 -0
  101. package/dist/migrations/storeCharset.js +66 -0
  102. package/dist/migrations/transactionCause.js +10 -0
  103. package/dist/migrations/undeclaredCharset.js +40 -0
  104. package/dist/migrations/uniqueActiveLowering.js +39 -0
  105. package/dist/mixins.js +22 -0
  106. package/dist/mssqlDecimalDistinct.js +51 -0
  107. package/dist/mssqlJsonValueCheck.js +42 -0
  108. package/dist/mssqlTextStorage.js +9 -0
  109. package/dist/namespace.js +21 -0
  110. package/dist/{wire-CG2j7J3f.js → optimisticVersion.js} +2 -2
  111. package/dist/pendingAttribution.js +79 -0
  112. package/dist/pluginRef.js +17 -0
  113. package/dist/predicate.js +273 -0
  114. package/dist/predicateCoercion.js +13 -0
  115. package/dist/projectedColumn.js +37 -0
  116. package/dist/queryBuilder.js +388 -0
  117. package/dist/rawSql.js +8 -0
  118. package/dist/relations.js +69 -0
  119. package/dist/relationsRegistry.js +38 -0
  120. package/dist/replaceInProgress.js +53 -0
  121. package/dist/requiredColumns.js +26 -0
  122. package/dist/residency.js +52 -0
  123. package/dist/retention.js +62 -0
  124. package/dist/rowDecoder.js +173 -0
  125. package/dist/rowKey.js +8 -0
  126. package/dist/rowSchema.js +31 -0
  127. package/dist/scopeKey.js +8 -0
  128. package/dist/seed.js +33 -0
  129. package/dist/snowflake.js +26 -0
  130. package/dist/sql.d.ts +2792 -3773
  131. package/dist/sql.js +42 -6905
  132. package/dist/sqlCompiler.js +360 -0
  133. package/dist/sqlError.js +8 -0
  134. package/dist/sqlMutation.js +55 -0
  135. package/dist/sqlPoolCapacity.js +12 -0
  136. package/dist/sqliteDecimal.js +22 -0
  137. package/dist/sqliteDecimalAggregateSql.js +61 -0
  138. package/dist/sqliteDecimalColumnSql.js +108 -0
  139. package/dist/sqliteDecimalConversion.js +15 -0
  140. package/dist/sqliteDecimalCte.js +154 -0
  141. package/dist/sqliteDecimalEncodeSql.js +32 -0
  142. package/dist/sqliteDecimalOutputs.js +74 -0
  143. package/dist/sqliteDecimalPredicate.js +66 -0
  144. package/dist/sqliteDecimalQuery.js +183 -0
  145. package/dist/sqliteDecimalQueryPlan.js +37 -0
  146. package/dist/sqliteDecimalResultSql.js +44 -0
  147. package/dist/sqliteDecimalRowQuery.js +63 -0
  148. package/dist/sqliteDecimalSetQuery.js +50 -0
  149. package/dist/sqliteDecimalSource.js +63 -0
  150. package/dist/sqliteDecimalStorage.js +29 -0
  151. package/dist/sqliteDecimalSubquery.js +19 -0
  152. package/dist/sqliteDecimalWindowQuery.js +196 -0
  153. package/dist/sqliteValueCoercion.js +4 -0
  154. package/dist/stampId.js +21 -0
  155. package/dist/storeErrors.js +9 -0
  156. package/dist/storeRefusal.js +20 -0
  157. package/dist/stream.js +36 -0
  158. package/dist/subjectGraph.js +175 -0
  159. package/dist/table.js +394 -0
  160. package/dist/tableRegistry.js +16 -0
  161. package/dist/tenantSelection.js +27 -0
  162. package/dist/transactionOutcome.js +100 -0
  163. package/dist/transientContention.js +36 -0
  164. package/dist/typeId.js +51 -0
  165. package/dist/typedStore.js +16 -0
  166. package/dist/upsertUpdate.js +20 -0
  167. package/dist/vectorEmbedding.js +31 -0
  168. package/dist/view.js +39 -0
  169. package/dist/wire.js +6 -2
  170. package/dist/writeAttribution.js +16 -0
  171. package/dist/writeRecorder.js +38 -0
  172. package/package.json +3 -4
  173. package/dist/replaceInProgress-DC88Pdiy.js +0 -3735
@@ -0,0 +1,332 @@
1
+ import { causeBranches as e, classifyConstraintViolation as t, extractDbCause as n } from "../dbCause.js";
2
+ import { derivedRowId as r } from "../derivedRowId.js";
3
+ import { FILE_MIGRATION_PATTERN as i, IrreversibleMigrationError as a, MigrationExpectationFailed as o, MigrationSkipped as s, TransactionBrokenByBodyError as c, irreversibleReason as l, isFileMigration as u } from "./fileBased.js";
4
+ import { acquireMigrationLock as d } from "./lock.js";
5
+ import { MIGRATION_OPS_TABLE as f, dialectOf as p } from "./resumeLedger.js";
6
+ import { migrationTableExists as m } from "./migrationTableExists.js";
7
+ import { appliedAtValue as h } from "./appliedAtValue.js";
8
+ import { ensureLedgerTable as g, ensurePlanLedgerTable as _ } from "./applier.js";
9
+ import { isFileMigrationRow as v } from "./ledgerRows.js";
10
+ import { failedExpectations as y, makeFileMigrationContext as b } from "./fileContext.js";
11
+ import { withSchemaProbeRecorder as x } from "./fileSkipProbes.js";
12
+ import { Effect as S } from "effect";
13
+ import { createHash as C } from "node:crypto";
14
+ import { SqlClient as w } from "effect/sql";
15
+ import { createLogger as T } from "@voltro/logger";
16
+ import { promises as E } from "node:fs";
17
+ import { join as D } from "node:path";
18
+ import { pathToFileURL as O } from "node:url";
19
+ //#region src/migrations/fileDiscovery.ts
20
+ var k = T({ scope: "voltro:migrate" }), A = (e) => C("sha256").update(e).digest("hex").slice(0, 16), j = [{
21
+ codemod: "0.78.0/01_file-migration-phase",
22
+ inverse: (e) => {
23
+ 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) => {
24
+ let n = e.lastIndexOf("\n", t - 1) + 1;
25
+ return /^[ \t]*/.exec(e.slice(n, t))[0];
26
+ };
27
+ return [e.replace(t, (e, t) => r + i(t)).replace(n, ""), e.replace(t, " ").replace(n, "")];
28
+ }
29
+ }], M = (e) => new Set([e, ...j.flatMap((t) => t.inverse(e))].map(A)), N = /* @__PURE__ */ new Set([
30
+ "node_modules",
31
+ "dist",
32
+ "build",
33
+ ".turbo",
34
+ ".vite",
35
+ ".next",
36
+ ".git",
37
+ ".framework"
38
+ ]), P = async (e) => {
39
+ let t = [], n = async (e) => {
40
+ let r = await E.readdir(e, { withFileTypes: !0 }).catch(() => []);
41
+ for (let a of r) {
42
+ if (N.has(a.name)) continue;
43
+ let r = D(e, a.name);
44
+ a.isDirectory() ? await n(r) : a.isFile() && i.test(a.name) && t.push(r);
45
+ }
46
+ };
47
+ return await n(e), t.sort((e, t) => {
48
+ let n = e.split("/").pop() ?? e, r = t.split("/").pop() ?? t;
49
+ return n.localeCompare(r);
50
+ });
51
+ }, F = async (e) => await import(O(e).href), I = async (e, t = F) => {
52
+ let n = [];
53
+ for (let r of e) {
54
+ let e = (await t(r)).default;
55
+ if (!u(e)) {
56
+ k.warn("migration file has no default-exported `migration(...)` — skipped", { file: r });
57
+ continue;
58
+ }
59
+ let i = await E.readFile(r, "utf8").catch(() => "");
60
+ n.push({
61
+ file: r,
62
+ migration: e,
63
+ hash: A(i),
64
+ accepted: M(i)
65
+ });
66
+ }
67
+ return n;
68
+ }, L = "_voltro_migration_plans", R = (e) => z(e).pipe(S.map((e) => e.map((e) => e.id))), z = (e) => m(e, L).pipe(S.flatMap((t) => t ? e`
69
+ SELECT ${e("id")}, ${e("fingerprint")}
70
+ FROM ${e(L)}
71
+ WHERE ${v(e)}
72
+ `.pipe(S.map((e) => e.map((e) => ({
73
+ id: e.id,
74
+ hash: e.fingerprint ?? ""
75
+ })))) : S.succeed([]))), B = (e, t) => t.filter((t) => {
76
+ let n = e.get(t.migration.id);
77
+ return n !== void 0 && n !== "" && !t.accepted.has(n);
78
+ }).map((e) => e.migration.id), V = /* @__PURE__ */ new Set(), H = (e) => t(n(e))?.kind === "unique", U = class extends Error {
79
+ migrationId;
80
+ file;
81
+ cause;
82
+ _tag = "FileMigrationLedgerError";
83
+ constructor(e, t, n) {
84
+ super(H(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";
85
+ }
86
+ }, W = /* @__PURE__ */ new Set([
87
+ "mysql",
88
+ "mariadb",
89
+ "mssql"
90
+ ]), G = (e, t) => W.has(e) ? t.filter((e) => e.length > 64) : [], K = class extends Error {
91
+ dialect;
92
+ migrationIds;
93
+ _tag = "MigrationIdTooLongError";
94
+ constructor(e, t) {
95
+ super(`migration id(s) longer than 64 characters cannot be recorded on ${e}: ` + t.map((e) => `${e} (${e.length})`).join(", ") + ". Nothing was applied. Rename the migration file and its `id` to at most 64 characters."), this.dialect = e, this.migrationIds = t, this.name = "MigrationIdTooLongError";
96
+ }
97
+ }, q = (e, t, n) => S.gen(function* () {
98
+ let r = yield* h(e, n.appliedAtIso);
99
+ yield* e`
100
+ INSERT INTO ${e("_voltro_migration_plans")}
101
+ (${e("id")}, ${e("fingerprint")}, ${e("operations")},
102
+ ${e("appliedBy")}, ${e("environment")}, ${e("source")},
103
+ ${e("durationMs")}, ${e("appliedAt")}, ${e("notes")})
104
+ VALUES
105
+ (${t.migration.id}, ${t.hash}, ${JSON.stringify([{
106
+ kind: "file",
107
+ file: t.file,
108
+ phase: t.migration.phase
109
+ }])},
110
+ ${n.appliedBy}, ${n.env}, ${"file"},
111
+ ${n.durationMs}, ${r}, ${n.notes})
112
+ `.pipe(S.mapError((e) => new U(t.migration.id, t.file, e)));
113
+ }), J = (t) => {
114
+ for (let n of e(t)) if (n instanceof s) return n.reason;
115
+ }, Y = (e, t, n) => S.gen(function* () {
116
+ let r = x(t), i = r.ctx, a = e.migration.up(i), s = !S.isEffect(a), c = yield* (S.isEffect(a) ? a : S.tryPromise({
117
+ try: () => a,
118
+ catch: (e) => e
119
+ })).pipe(S.as(void 0), S.catch((e) => {
120
+ let t = J(e);
121
+ return t === void 0 ? S.fail(e) : S.succeed(t);
122
+ }));
123
+ if (c === void 0) {
124
+ let t = yield* y(n);
125
+ if (t.length > 0) return yield* S.fail(new o(e.migration.id, t));
126
+ }
127
+ return {
128
+ skipped: c,
129
+ promiseForm: s,
130
+ absent: r.absent()
131
+ };
132
+ }), X = (e, t, n, i, a, o, s, l) => S.gen(function* () {
133
+ let u = Date.now(), d = (/* @__PURE__ */ new Date()).toISOString(), m = p(e), h = `migrations:file:${n.migration.id}`, _ = (e) => ({
134
+ durationMs: Date.now() - u,
135
+ notes: e.skipped === void 0 ? n.migration.description : `skipped: ${e.skipped}`
136
+ });
137
+ if (n.migration.transactional !== !1 && !o) {
138
+ if (t.run === void 0) return yield* S.fail(/* @__PURE__ */ Error(`migration ${n.migration.id}: transactional: true needs a SQL-backed store`));
139
+ let o = m === "mysql" || m === "mariadb", u = `probe_${n.migration.id}_${Date.now().toString(36)}`;
140
+ o && (yield* g(e, m, s));
141
+ let p = (e) => e.executeUnprepared(`SELECT COUNT(*) AS n FROM \`${f}\` WHERE \`runId\` = ?`, [u], void 0), v = () => (l === void 0 ? S.scoped(S.flatMap(e.reserve, p)) : p(l)).pipe(S.map((e) => Number(e[0]?.n ?? 0) > 0)), y = yield* S.tryPromise({
142
+ try: () => t.transactional(async (t) => {
143
+ o && await t.run(e`INSERT INTO ${e(f)} (${e("id")}, ${e("runId")}, ${e("planFingerprint")}, ${e("opIndex")}, ${e("status")}, ${e("opKind")}, ${e("opTarget")}, ${e("payload")})
144
+ VALUES (${r("mop", u)}, ${u}, ${""}, ${0}, ${"pending"}, ${"probe"}, ${""}, ${"{}"})`);
145
+ let { ctx: s, expectations: c } = b({
146
+ sql: e,
147
+ store: t,
148
+ dialect: m,
149
+ migrationId: n.migration.id,
150
+ appliedAt: d
151
+ }), l = await t.run(Y(n, s, c)), p = o && await S.runPromise(v().pipe(S.provideService(w.SqlClient, e))), { durationMs: h, notes: g } = _(l);
152
+ return await t.run(q(e, n, {
153
+ env: i,
154
+ appliedBy: a,
155
+ durationMs: h,
156
+ appliedAtIso: d,
157
+ notes: p ? `${g} — transactional guarantee broken by the body (DDL on ${m})` : g
158
+ })), o && await t.run(e`DELETE FROM ${e(f)} WHERE ${e("runId")} = ${u}`), {
159
+ ...l,
160
+ durationMs: h,
161
+ broken: p
162
+ };
163
+ }),
164
+ catch: (e) => e
165
+ });
166
+ return y.broken ? yield* S.fail(new c(n.migration.id, m)) : (y.skipped !== void 0 && (yield* S.logInfo(`migration ${n.migration.id} skipped: ${y.skipped}`).pipe(S.annotateLogs({ scope: h }))), {
167
+ durationMs: y.durationMs,
168
+ skip: y.skipped === void 0 ? void 0 : {
169
+ id: n.migration.id,
170
+ reason: y.skipped,
171
+ absent: y.absent
172
+ }
173
+ });
174
+ }
175
+ let { ctx: v, expectations: y } = b({
176
+ sql: e,
177
+ store: t,
178
+ dialect: m,
179
+ migrationId: n.migration.id,
180
+ appliedAt: d
181
+ }), x = yield* Y(n, v, y), { durationMs: C, notes: T } = _(x);
182
+ return x.promiseForm && x.skipped === void 0 && C < 2 && (yield* S.logWarning(`migration ${n.migration.id}: up() resolved in ${C}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(S.annotateLogs({ scope: h }))), x.skipped !== void 0 && (yield* S.logInfo(`migration ${n.migration.id} skipped: ${x.skipped}`).pipe(S.annotateLogs({ scope: h }))), yield* q(e, n, {
183
+ env: i,
184
+ appliedBy: a,
185
+ durationMs: C,
186
+ appliedAtIso: d,
187
+ notes: T
188
+ }), {
189
+ durationMs: C,
190
+ skip: x.skipped === void 0 ? void 0 : {
191
+ id: n.migration.id,
192
+ reason: x.skipped,
193
+ absent: x.absent
194
+ }
195
+ };
196
+ }), Z = (e, t, n, r) => S.gen(function* () {
197
+ let i = yield* S.tryPromise({
198
+ try: () => P(D(t, "migrations")),
199
+ catch: (e) => e
200
+ });
201
+ if (i.length === 0) return {
202
+ pending: [],
203
+ alreadyApplied: [],
204
+ drifted: []
205
+ };
206
+ let a = yield* S.tryPromise({
207
+ try: () => I(i, r),
208
+ catch: (e) => e
209
+ });
210
+ if (a.length === 0) return {
211
+ pending: [],
212
+ alreadyApplied: [],
213
+ drifted: []
214
+ };
215
+ let o = new Map((yield* z(e)).map((e) => [e.id, e.hash])), s = B(o, a), c = s.length >= 32 ? /* @__PURE__ */ new Map() : void 0;
216
+ if (c !== void 0) for (let e of a) c.has(e.migration.id) || c.set(e.migration.id, e);
217
+ for (let e of s) {
218
+ let t = c === void 0 ? a.find((t) => t.migration.id === e) : c.get(e), n = `${t?.file ?? e}:${t?.hash ?? ""}`;
219
+ V.has(n) || (V.add(n), k.warn("file migration: the body on disk is not the one that was applied — the file no longer describes this database", {
220
+ id: e,
221
+ file: t?.file
222
+ }));
223
+ }
224
+ let l = n === void 0 ? a : a.filter((e) => e.migration.phase === n);
225
+ return {
226
+ pending: l.filter((e) => !o.has(e.migration.id)),
227
+ alreadyApplied: l.filter((e) => o.has(e.migration.id)),
228
+ drifted: s
229
+ };
230
+ }), Q = (e, t, n, r) => Z(e, t, n, r).pipe(S.map((e) => e.pending.map((e) => e.migration.id))), ee = (e, t, n) => Z(e, t, n).pipe(S.map((e) => e.pending.map((e) => ({
231
+ id: e.migration.id,
232
+ phase: e.migration.phase,
233
+ irreversible: l(e.migration.down)
234
+ })))), te = (e, t) => Z(e, t).pipe(S.map((e) => {
235
+ let t = new Set(e.drifted);
236
+ return [...e.alreadyApplied.map((e) => ({
237
+ id: e.migration.id,
238
+ phase: e.migration.phase,
239
+ applied: !0,
240
+ bodyChanged: t.has(e.migration.id)
241
+ })), ...e.pending.map((e) => ({
242
+ id: e.migration.id,
243
+ phase: e.migration.phase,
244
+ applied: !1,
245
+ bodyChanged: !1
246
+ }))].sort((e, t) => e.id < t.id ? -1 : 1);
247
+ })), ne = (e, t) => S.gen(function* () {
248
+ let { pending: n, alreadyApplied: r } = yield* Z(e, t.projectRoot, t.phase), i = r.map((e) => e.migration.id);
249
+ if (n.length === 0) return {
250
+ applied: [],
251
+ skipped: i,
252
+ skips: []
253
+ };
254
+ let a = G(p(e), n.map((e) => e.migration.id));
255
+ if (a.length > 0) return yield* S.fail(new K(p(e), a));
256
+ yield* _(e, p(e), t.charset);
257
+ let o = yield* S.acquireUseRelease(d(e, { schema: t.lockSchema }), (r) => S.gen(function* () {
258
+ let i = new Set((yield* z(e)).map((e) => e.id)), a = n.filter((e) => !i.has(e.migration.id));
259
+ a.length < n.length && (yield* S.logInfo("file-based migrations applied by another process while this one waited for the migration lock").pipe(S.annotateLogs({
260
+ scope: "voltro:migrate",
261
+ ids: n.filter((e) => i.has(e.migration.id)).map((e) => e.migration.id).join(",")
262
+ })));
263
+ let o = [];
264
+ a.length > 0 && t.beforeExecute !== void 0 && (yield* t.beforeExecute);
265
+ let s = [];
266
+ for (let n of a) {
267
+ yield* S.logInfo("file-based migration applying").pipe(S.annotateLogs({
268
+ scope: "voltro:migrate",
269
+ id: n.migration.id,
270
+ file: n.file,
271
+ phase: n.migration.phase
272
+ }));
273
+ let { durationMs: i, skip: a } = yield* X(e, t.store, n, t.env, t.appliedBy, t.enclosed === !0, t.charset, r.connection);
274
+ o.push({
275
+ id: n.migration.id,
276
+ durationMs: i
277
+ }), a !== void 0 && s.push(a), yield* S.logInfo("file-based migration applied").pipe(S.annotateLogs({
278
+ scope: "voltro:migrate",
279
+ id: n.migration.id,
280
+ durationMs: i,
281
+ phase: n.migration.phase
282
+ }));
283
+ }
284
+ return {
285
+ done: o,
286
+ skips: s
287
+ };
288
+ }), (e) => e.release.pipe(S.orDie));
289
+ return {
290
+ applied: o.done,
291
+ skipped: i,
292
+ skips: o.skips
293
+ };
294
+ }), $ = (e, t) => e`
295
+ SELECT ${e("id")}
296
+ FROM ${e("_voltro_migration_plans")}
297
+ WHERE ${e("id")} = ${t} AND ${e("source")} = 'file'
298
+ `, re = (e, t) => S.gen(function* () {
299
+ if (!(yield* $(e, t.id))[0]) return yield* S.fail(/* @__PURE__ */ Error(`rollback: ${t.id} not found in _voltro_migration_plans (file source)`));
300
+ let n = yield* S.tryPromise({
301
+ try: () => P(D(t.projectRoot, "migrations")),
302
+ catch: (e) => e
303
+ }), r = (yield* S.tryPromise({
304
+ try: () => I(n),
305
+ catch: (e) => e
306
+ })).find((e) => e.migration.id === t.id);
307
+ if (!r) return yield* S.fail(/* @__PURE__ */ Error(`rollback: file for migration ${t.id} not found on disk under ${t.projectRoot}/migrations/`));
308
+ let i = l(r.migration.down);
309
+ return i === void 0 ? yield* S.acquireUseRelease(d(e, { schema: t.lockSchema }), () => S.gen(function* () {
310
+ if ((yield* $(e, t.id))[0] === void 0) return yield* S.fail(/* @__PURE__ */ Error(`rollback: ${t.id} was rolled back by another process while this one waited for the migration lock — nothing to do`));
311
+ let n = Date.now(), { ctx: i } = b({
312
+ sql: e,
313
+ store: t.store,
314
+ dialect: p(e),
315
+ migrationId: t.id,
316
+ appliedAt: (/* @__PURE__ */ new Date()).toISOString()
317
+ });
318
+ t.beforeExecute !== void 0 && (yield* t.beforeExecute);
319
+ let a = r.migration.down(i);
320
+ S.isEffect(a) ? yield* a : yield* S.tryPromise({
321
+ try: () => a,
322
+ catch: (e) => e
323
+ });
324
+ let o = Date.now() - n;
325
+ return yield* e`DELETE FROM ${e("_voltro_migration_plans")} WHERE ${e("id")} = ${t.id} AND ${e("source")} = 'file'`, {
326
+ id: t.id,
327
+ durationMs: o
328
+ };
329
+ }), (e) => e.release.pipe(S.orDie)) : yield* S.fail(new a(t.id, i));
330
+ });
331
+ //#endregion
332
+ export { i as FILE_MIGRATION_PATTERN, j as FRAMEWORK_MIGRATION_EDITS, U as FileMigrationLedgerError, K as MigrationIdTooLongError, M as acceptedBodyHashes, R as appliedFileMigrationIds, z as appliedFileMigrations, te as fileMigrationStatus, A as migrationBodyHash, Q as pendingFileMigrationIds, ee as pendingFileMigrationSummary, re as rollbackFileBasedMigration, ne as runFileBasedMigrations, G as unrecordableMigrationIds, P 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 };