@voltro/cli 0.32.0 → 0.33.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +205 -0
- package/THIRD-PARTY-NOTICES.md +1 -29
- package/dist/apiBuild-C-x9YacA.js +2 -0
- package/dist/{apiBuild-C4uEbs3s.js → apiBuild-h9VHtnlw.js} +2 -2
- package/dist/bin.js +3 -3
- package/dist/checkCommand-DRovTKza.js +2 -0
- package/dist/{checkCommand-dm7OHtPt.js → checkCommand-xGhRFFg2.js} +15 -15
- package/dist/{commands-C0nEePif.js → commands-CJfepbm4.js} +97 -13
- package/dist/{dbCommand-ifOMafuG.js → dbCommand-DTLKAfbA.js} +401 -241
- package/dist/dbCommand-b1gum4td.js +2 -0
- package/dist/{dev-5ficNnvF.js → dev-C_P8FLSx.js} +1539 -1487
- package/dist/{dev-rc3fwPSZ.js → dev-iiMtlkfs.js} +1 -1
- package/dist/index.js +1 -1
- package/dist/{inspectMetrics-BqO4E9G0.js → inspectMetrics-1xzTKAFx.js} +573 -550
- package/dist/{sdkgen-B_5mHQS2.js → sdkgen-C81QIkiL.js} +1 -1
- package/dist/serveCommand-DdaM4Hup.js +1608 -0
- package/dist/serveEntry.js +2 -2
- package/dist/{start-D1-8eKrO.js → start-C0koT0UO.js} +1 -1
- package/dist/startEntry.js +2 -2
- package/package.json +31 -18
- package/templates/AGENTS.md +1 -1
- package/templates/agent-docs/_index.md +1 -1
- package/templates/agent-docs/database/misc.md +37 -0
- package/templates/agent-docs/database/schema.md +4 -2
- package/templates/agent-docs/deployment.md +87 -1
- package/templates/agent-docs/plugins.md +50 -0
- package/templates/agent-docs/scheduling.md +4 -1
- package/templates/agent-docs/security.md +23 -2
- package/templates/agent-docs/whats-new.md +128 -86
- package/templates/apps/api-ai/package.json +7 -7
- package/templates/apps/api-auth/package.json +8 -8
- package/templates/apps/api-backend/package.json +7 -7
- package/templates/apps/api-backend-deactivation/package.json +7 -7
- package/templates/apps/api-backend-mail/package.json +8 -8
- package/templates/apps/api-backend-mariadb/package.json +9 -9
- package/templates/apps/api-backend-sqlite/package.json +8 -8
- package/templates/apps/api-backend-storage/package.json +8 -8
- package/templates/apps/api-cms/package.json +10 -10
- package/templates/apps/api-collab/package.json +8 -8
- package/templates/apps/api-data-advanced/package.json +8 -8
- package/templates/apps/api-durable/package.json +8 -8
- package/templates/apps/api-feature-flags/package.json +9 -9
- package/templates/apps/api-governance/package.json +8 -8
- package/templates/apps/api-kv/package.json +8 -8
- package/templates/apps/api-moderation/package.json +8 -8
- package/templates/apps/api-observability/package.json +8 -8
- package/templates/apps/api-ratelimit/package.json +8 -8
- package/templates/apps/api-rbac/package.json +8 -8
- package/templates/apps/api-rest/package.json +7 -7
- package/templates/apps/api-saas/package.json +11 -11
- package/templates/apps/api-saas-starter/package.json +10 -10
- package/templates/apps/api-search/package.json +8 -8
- package/templates/apps/api-status/package.json +8 -8
- package/templates/apps/api-versioning/package.json +8 -8
- package/templates/apps/api-webhooks/package.json +9 -9
- package/templates/apps/changelog/package.json +6 -6
- package/templates/apps/edge-functions/package.json +2 -2
- package/templates/apps/frontend-admin/package.json +8 -8
- package/templates/apps/frontend-app/package.json +8 -8
- package/templates/apps/frontend-auth/package.json +8 -8
- package/templates/apps/frontend-blank/package.json +7 -7
- package/templates/apps/frontend-cms/package.json +9 -9
- package/templates/apps/frontend-collab/package.json +9 -9
- package/templates/apps/frontend-contact/package.json +7 -7
- package/templates/apps/frontend-dashboard/package.json +7 -7
- package/templates/apps/frontend-docs/package.json +7 -7
- package/templates/apps/frontend-i18n/package.json +6 -6
- package/templates/apps/frontend-landing/package.json +7 -7
- package/templates/apps/frontend-portal/package.json +8 -8
- package/templates/apps/frontend-saas/package.json +8 -8
- package/templates/apps/frontend-spa/package.json +7 -7
- package/templates/apps/frontend-ssr/package.json +7 -7
- package/templates/apps/frontend-ssr-api/package.json +8 -8
- package/templates/apps/frontend-static-blog/package.json +6 -6
- package/templates/apps/frontend-status/package.json +8 -8
- package/templates/apps/mobile-app/package.json +2 -2
- package/dist/apiBuild-OJEjtwcn.js +0 -2
- package/dist/checkCommand-CwMrzAgV.js +0 -2
- package/dist/dbCommand-By__Ev0R.js +0 -2
- package/dist/serveCommand-PxMmn96o.js +0 -1578
|
@@ -8,11 +8,11 @@ import { pathToFileURL as h } from "node:url";
|
|
|
8
8
|
import { Effect as g } from "effect";
|
|
9
9
|
import { createLogger as _ } from "@voltro/logger";
|
|
10
10
|
import { promises as v, writeSync as y } from "node:fs";
|
|
11
|
-
import { InMemoryDataStore as ee } from "@voltro/runtime";
|
|
12
|
-
import { SqlClient as
|
|
13
|
-
import { actorsTable as te, frameworkTables as
|
|
14
|
-
import { createHash as
|
|
15
|
-
import { DEFAULT_CDC_CHANNEL as
|
|
11
|
+
import { InMemoryDataStore as b, makeFieldCipher as ee } from "@voltro/runtime";
|
|
12
|
+
import { SqlClient as x } from "@effect/sql";
|
|
13
|
+
import { actorsTable as te, frameworkTables as S, isEncrypted as C, isMigrationDefinition as ne, quoteIdent as re, registerCoreTables as ie } from "@voltro/database";
|
|
14
|
+
import { createHash as ae } from "node:crypto";
|
|
15
|
+
import { DEFAULT_CDC_CHANNEL as oe, applyPlan as se, applySchema as w, assessRollingDeployGate as ce, declaredSnapshot as le, destructiveScope as ue, detectReactiveTriggerDrift as de, fingerprintSchema as fe, ignoreTablesFromEnv as T, introspectSchema as pe, pendingFileMigrationIds as me, planMigrations as E, reactiveTriggerRepairSql as he, rollbackFileBasedMigration as ge, rollingDeployUnsafeOps as _e, runFileBasedMigrations as ve, shortFingerprint as D, squashMigrationPlans as ye, unblockLossy as be } from "@voltro/database/sql";
|
|
16
16
|
//#region src/cliError.ts
|
|
17
17
|
var O = class extends Error {
|
|
18
18
|
isCliError = !0;
|
|
@@ -20,7 +20,7 @@ var O = class extends Error {
|
|
|
20
20
|
constructor(e, t = 1) {
|
|
21
21
|
super(e), this.name = "CliError", this.code = t;
|
|
22
22
|
}
|
|
23
|
-
},
|
|
23
|
+
}, xe = (e) => typeof e == "object" && !!e && e.isCliError === !0, Se = (e) => typeof e != "string", Ce = (e, t, n, r) => {
|
|
24
24
|
let i = [], a = new Map(t.map((e) => [e.name, e])), o = new Map(n.map((e) => [e.name, e])), s = new Set(e.leaveUnset ?? []);
|
|
25
25
|
r > 0 && i.push({
|
|
26
26
|
kind: "target-not-empty",
|
|
@@ -37,7 +37,7 @@ var O = class extends Error {
|
|
|
37
37
|
});
|
|
38
38
|
continue;
|
|
39
39
|
}
|
|
40
|
-
if (
|
|
40
|
+
if (Se(n)) {
|
|
41
41
|
c.push({
|
|
42
42
|
target: t,
|
|
43
43
|
source: n.expr,
|
|
@@ -80,23 +80,23 @@ var O = class extends Error {
|
|
|
80
80
|
remintIds: f,
|
|
81
81
|
fatal: i.some((e) => e.fatal)
|
|
82
82
|
};
|
|
83
|
-
},
|
|
83
|
+
}, we = (e, t) => [
|
|
84
84
|
`back up '${e.from}' to a restorable dump`,
|
|
85
85
|
`copy rows: INSERT INTO ${e.into} (${t.assignments.map((e) => e.target).join(", ")}) SELECT … FROM ${e.from}`,
|
|
86
86
|
...t.remintIds ? ["record old id → new id in a translation table", `rewrite every reference to '${e.from}', JSON columns included`] : [],
|
|
87
87
|
"verify: row counts match and a sample round-trips",
|
|
88
88
|
`drop '${e.from}' — LAST, and only after the verify`
|
|
89
|
-
],
|
|
89
|
+
], Te = (e, t, n) => {
|
|
90
90
|
let r = [`adopt · ${e.from} (${n} row(s)) → ${e.into}`, ""];
|
|
91
91
|
for (let e of t.assignments) r.push(` ${e.target.padEnd(24)} ← ${e.raw ? `${e.source} (raw)` : e.source}`);
|
|
92
92
|
t.problems.length > 0 && r.push("");
|
|
93
93
|
for (let e of t.problems) r.push(` ${e.fatal ? "✗" : "⚠"} ${e.detail}`);
|
|
94
94
|
if (r.push(""), t.fatal) return r.push(" REFUSED — fix the problems above. Nothing was written."), r;
|
|
95
95
|
r.push(" would run, in this order:");
|
|
96
|
-
for (let [n, i] of
|
|
96
|
+
for (let [n, i] of we(e, t).entries()) r.push(` ${n + 1}. ${i}`);
|
|
97
97
|
return r;
|
|
98
|
-
},
|
|
99
|
-
let a =
|
|
98
|
+
}, Ee = (e) => e === "mysql" || e === "mariadb" ? (e) => `\`${e}\`` : e === "mssql" ? (e) => `[${e}]` : (e) => `"${e}"`, De = (e) => `${e}__adopt_snapshot`, Oe = (e, t, n, r, i) => {
|
|
99
|
+
let a = Ee(n), o = De(e.from), s = t.assignments.map((e) => e.target), c = t.assignments.map((e) => e.raw ? e.source : a(e.source));
|
|
100
100
|
return i !== void 0 && (s.unshift(r), c.unshift(i)), {
|
|
101
101
|
snapshot: n === "mssql" ? `SELECT * INTO ${a(o)} FROM ${a(e.from)}` : `CREATE TABLE ${a(o)} AS SELECT * FROM ${a(e.from)}`,
|
|
102
102
|
copy: `INSERT INTO ${a(e.into)} (${s.map(a).join(", ")}) SELECT ${c.join(", ")} FROM ${a(e.from)}`,
|
|
@@ -106,18 +106,18 @@ var O = class extends Error {
|
|
|
106
106
|
drop: `DROP TABLE ${a(e.from)}`,
|
|
107
107
|
dropSnapshot: `DROP TABLE ${a(o)}`
|
|
108
108
|
};
|
|
109
|
-
},
|
|
109
|
+
}, ke = (e) => {
|
|
110
110
|
let t = e[0];
|
|
111
111
|
if (t === void 0) return 0;
|
|
112
112
|
let n = t.n ?? t.N ?? Object.values(t)[0];
|
|
113
113
|
return Number(n ?? 0);
|
|
114
|
-
},
|
|
114
|
+
}, Ae = async (e, t, n, r, i, a, o = {}) => {
|
|
115
115
|
if (t.fatal) throw Error("adopt: the plan was refused — nothing was run");
|
|
116
|
-
let s =
|
|
116
|
+
let s = Oe(e, t, n, r, i), c = De(e.from), l = ke(await a(s.verifySource));
|
|
117
117
|
await a(s.snapshot);
|
|
118
118
|
for (let e of s.translations) await a(e);
|
|
119
119
|
await a(s.copy);
|
|
120
|
-
let u =
|
|
120
|
+
let u = ke(await a(s.verifyTarget));
|
|
121
121
|
return u === l ? o.keepSource === !0 ? {
|
|
122
122
|
copied: u,
|
|
123
123
|
sourceRows: l,
|
|
@@ -146,7 +146,7 @@ var O = class extends Error {
|
|
|
146
146
|
"credential",
|
|
147
147
|
"privatekey",
|
|
148
148
|
"private_key"
|
|
149
|
-
],
|
|
149
|
+
], je = [
|
|
150
150
|
{
|
|
151
151
|
table: "_voltro_audit_log",
|
|
152
152
|
column: "subject"
|
|
@@ -191,7 +191,7 @@ var O = class extends Error {
|
|
|
191
191
|
table: "_voltro_row_history",
|
|
192
192
|
column: "subjectId"
|
|
193
193
|
}
|
|
194
|
-
],
|
|
194
|
+
], Me = (e, t) => {
|
|
195
195
|
switch (t) {
|
|
196
196
|
case "postgres": return `LOWER(CAST(${e} AS text))`;
|
|
197
197
|
case "mysql":
|
|
@@ -200,15 +200,55 @@ var O = class extends Error {
|
|
|
200
200
|
case "sqlite":
|
|
201
201
|
case "turso": return `LOWER(CAST(${e} AS TEXT))`;
|
|
202
202
|
}
|
|
203
|
-
},
|
|
203
|
+
}, A = (e, t, n = k) => n.map((n) => `${Me(e, t)} LIKE '%${n}%'`).join("\n OR "), Ne = ["_omitted", "__redacted"], Pe = (e, t) => {
|
|
204
|
+
let n = e.toLowerCase();
|
|
205
|
+
return t.some((e) => n.includes(e));
|
|
206
|
+
}, j = (e, t = []) => {
|
|
207
|
+
if (Array.isArray(e)) {
|
|
208
|
+
for (let n of e) j(n, t);
|
|
209
|
+
return t;
|
|
210
|
+
}
|
|
211
|
+
if (typeof e == "object" && e) for (let [n, r] of Object.entries(e)) t.push(n), j(r, t);
|
|
212
|
+
return t;
|
|
213
|
+
}, M = (e) => Array.isArray(e) ? e.map(M) : typeof e == "object" && e ? Object.fromEntries(Object.entries(e).map(([e, t]) => [e, Ne.includes(e) ? null : M(t)])) : e, Fe = (e, t = k) => {
|
|
214
|
+
let n;
|
|
215
|
+
try {
|
|
216
|
+
n = JSON.parse(e);
|
|
217
|
+
} catch {
|
|
218
|
+
return {
|
|
219
|
+
asKey: !1,
|
|
220
|
+
redactionMarkerOnly: !1,
|
|
221
|
+
undecidable: !0
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
return {
|
|
225
|
+
asKey: j(n).some((e) => Pe(e, t)),
|
|
226
|
+
redactionMarkerOnly: !Pe(JSON.stringify(M(n)) ?? "", t),
|
|
227
|
+
undecidable: !1
|
|
228
|
+
};
|
|
229
|
+
}, Ie = (e) => ({
|
|
230
|
+
examined: e.length,
|
|
231
|
+
asKey: e.filter((e) => e.asKey).length,
|
|
232
|
+
redactionMarkerOnly: e.filter((e) => e.redactionMarkerOnly && !e.asKey).length,
|
|
233
|
+
undecidable: e.filter((e) => e.undecidable).length
|
|
234
|
+
}), N = (e) => {
|
|
235
|
+
let t = e.classification;
|
|
236
|
+
return t === void 0 || e.hits === void 0 || e.hits === 0 ? !1 : t.examined === e.hits && t.undecidable === 0 && t.asKey === 0 && t.redactionMarkerOnly === e.hits;
|
|
237
|
+
}, Le = (e, t, n, r) => {
|
|
238
|
+
let i = `WHERE ${A(t, n)}`, a = Me(t, n).replace(/^LOWER\((.*)\)$/s, "$1");
|
|
239
|
+
return n === "mssql" ? `SELECT TOP (${r}) ${a} AS v FROM ${e}\n ${i}` : `SELECT ${a} AS v FROM ${e}\n ${i}\n LIMIT ${r}`;
|
|
240
|
+
}, Re = async (e, t, n, r, i) => (await g.runPromise(e.unsafe(Le(t, n, r, i)))).map((e) => {
|
|
241
|
+
let t = Object.values(e)[0];
|
|
242
|
+
return typeof t == "string" ? t : JSON.stringify(t ?? null);
|
|
243
|
+
}), P = async (e, t) => {
|
|
204
244
|
let n = await g.runPromise(e.unsafe(t));
|
|
205
245
|
return Number(Object.values(n[0] ?? { n: 0 })[0] ?? 0);
|
|
206
|
-
},
|
|
246
|
+
}, ze = async (e, t, n, r) => {
|
|
207
247
|
let i = [];
|
|
208
248
|
for (let a of n) {
|
|
209
249
|
let n = r(a.table), o;
|
|
210
250
|
try {
|
|
211
|
-
o = await
|
|
251
|
+
o = await P(e, `SELECT COUNT(*) AS n FROM ${n}`);
|
|
212
252
|
} catch {
|
|
213
253
|
i.push({
|
|
214
254
|
table: a.table,
|
|
@@ -220,20 +260,25 @@ var O = class extends Error {
|
|
|
220
260
|
continue;
|
|
221
261
|
}
|
|
222
262
|
try {
|
|
223
|
-
let s = await
|
|
263
|
+
let s = await P(e, `SELECT COUNT(*) AS n FROM ${n}\n WHERE ${A(r(a.column), t)}`), c = [];
|
|
224
264
|
if (s > 0) for (let i of k) try {
|
|
225
|
-
let o = await
|
|
265
|
+
let o = await P(e, `SELECT COUNT(*) AS n FROM ${n}\n WHERE ${A(r(a.column), t, [i])}`);
|
|
226
266
|
o > 0 && c.push({
|
|
227
267
|
needle: i,
|
|
228
268
|
rows: o
|
|
229
269
|
});
|
|
230
270
|
} catch {}
|
|
271
|
+
let l;
|
|
272
|
+
if (s > 0) try {
|
|
273
|
+
l = Ie((await Re(e, n, r(a.column), t, 500)).map((e) => Fe(e)));
|
|
274
|
+
} catch {}
|
|
231
275
|
i.push({
|
|
232
276
|
table: a.table,
|
|
233
277
|
column: a.column,
|
|
234
278
|
scanned: o,
|
|
235
279
|
hits: s,
|
|
236
|
-
matchedNeedles: c
|
|
280
|
+
matchedNeedles: c,
|
|
281
|
+
...l === void 0 ? {} : { classification: l }
|
|
237
282
|
});
|
|
238
283
|
} catch {
|
|
239
284
|
i.push({
|
|
@@ -246,7 +291,7 @@ var O = class extends Error {
|
|
|
246
291
|
}
|
|
247
292
|
}
|
|
248
293
|
return i;
|
|
249
|
-
},
|
|
294
|
+
}, Be = (e, t) => {
|
|
250
295
|
let n = k.length, r = [`credential scan · dialect ${t}`, ""], i = /* @__PURE__ */ new Set();
|
|
251
296
|
for (let t of e) {
|
|
252
297
|
if (t.unavailable === "column") {
|
|
@@ -261,12 +306,14 @@ var O = class extends Error {
|
|
|
261
306
|
r.push(` · ${t.table} — table is EMPTY (0 rows scanned; this is not a clean bill of health)`);
|
|
262
307
|
continue;
|
|
263
308
|
}
|
|
264
|
-
let e = (t.hits ?? 0)
|
|
265
|
-
r.push(` ${
|
|
309
|
+
let e = N(t), n = (t.hits ?? 0) === 0 ? "✓" : e ? "~" : "✗";
|
|
310
|
+
r.push(` ${n} ${t.table}.${t.column} — ${t.hits} of ${t.scanned} row(s) contain a credential-shaped NAME (anywhere in the value)`), (t.matchedNeedles?.length ?? 0) > 0 && r.push(` matched (rows per needle, may overlap): ${t.matchedNeedles.map((e) => `${e.needle} (${e.rows})`).join(", ")}`);
|
|
311
|
+
let a = t.classification;
|
|
312
|
+
a !== void 0 && (r.push(` examined ${a.examined} of ${t.hits} matched row(s)${a.examined < (t.hits ?? 0) ? " (capped at 500)" : ""}: ${a.asKey} as a JSON KEY, ${a.redactionMarkerOnly} only inside a redaction marker${a.undecidable > 0 ? `, ${a.undecidable} not parseable as JSON` : ""}`), e && r.push(` EXPLAINED — every match is \`${Ne.join("` / `")}\`, the framework's own`, " record that a `.sensitive()` column was left OUT. Nothing to purge, nothing to rotate."));
|
|
266
313
|
}
|
|
267
|
-
let a = e.reduce((e, t) => e + (t.hits ?? 0), 0);
|
|
268
|
-
return r.push(""), a > 0 ? r.push(` ${a} row(s) carry something credential-shaped.`, " Purge them AND rotate the credentials — assume anything written to a log", " table has been read. Then stop putting the credential on the Subject:", " `connectionCredentials(...)` keeps it in the framework vault.") : e.every((e) => e.scanned === void 0 || e.scanned === 0 || e.unavailable !== void 0) ? r.push(" NOTHING WAS SCANNED. This run proves nothing about your data.") : r.push(" No credential-shaped keys found in the rows that exist."), r.push("", ` These are NAME heuristics (${n} of them), not proof. A credential in a`, " column whose key is not credential-shaped — `keyValue`, `pat`, `authz` —", " is invisible here. A clean run means \"no credential-
|
|
269
|
-
},
|
|
314
|
+
let a = e.reduce((e, t) => e + (N(t) ? 0 : t.hits ?? 0), 0), o = e.reduce((e, t) => e + (N(t) ? t.hits ?? 0 : 0), 0);
|
|
315
|
+
return r.push(""), o > 0 && r.push(` ${o} row(s) matched only the framework's own redaction markers and are NOT findings.`), a > 0 ? r.push(` ${a} row(s) carry something credential-shaped.`, " Purge them AND rotate the credentials — assume anything written to a log", " table has been read. Then stop putting the credential on the Subject:", " `connectionCredentials(...)` keeps it in the framework vault.") : e.every((e) => e.scanned === void 0 || e.scanned === 0 || e.unavailable !== void 0) ? r.push(" NOTHING WAS SCANNED. This run proves nothing about your data.") : r.push(" No credential-shaped keys found in the rows that exist."), r.push("", ` These are NAME heuristics (${n} of them), not proof. A credential in a`, " column whose key is not credential-shaped — `keyValue`, `pat`, `authz` —", " is invisible here. A clean run means \"no credential-shaped NAME found\",", " which is not the same as \"no credential\".", "", " Run this on EVERY environment. A dev database is not a sample of production."), r.join("\n");
|
|
316
|
+
}, Ve = (e) => e.some((e) => (e.hits ?? 0) > 0 && !N(e)) ? 1 : e.every((e) => e.unavailable !== void 0 || e.scanned === void 0 || e.scanned === 0) ? 2 : 0, F = /\.migration\.ts$/, I = async (e, t) => {
|
|
270
317
|
let n = [];
|
|
271
318
|
for (let r of e) {
|
|
272
319
|
let e;
|
|
@@ -276,7 +323,7 @@ var O = class extends Error {
|
|
|
276
323
|
t.warn("migration: failed to import", { file: r }, e);
|
|
277
324
|
continue;
|
|
278
325
|
}
|
|
279
|
-
let i = (e.default &&
|
|
326
|
+
let i = (e.default && ne(e.default) ? e.default : void 0) ?? Object.values(e).find(ne);
|
|
280
327
|
if (!i) {
|
|
281
328
|
t.warn("migration: no defineMigration export", { file: r });
|
|
282
329
|
continue;
|
|
@@ -286,7 +333,7 @@ var O = class extends Error {
|
|
|
286
333
|
idInFile: i.id,
|
|
287
334
|
idFromFile: d(r).replace(/\.migration\.ts$/, "")
|
|
288
335
|
});
|
|
289
|
-
let a = await v.readFile(r, "utf8").catch(() => ""), o =
|
|
336
|
+
let a = await v.readFile(r, "utf8").catch(() => ""), o = ae("sha256").update(a).digest("hex").slice(0, 16);
|
|
290
337
|
n.push({
|
|
291
338
|
file: r,
|
|
292
339
|
definition: i,
|
|
@@ -294,14 +341,14 @@ var O = class extends Error {
|
|
|
294
341
|
});
|
|
295
342
|
}
|
|
296
343
|
return [...n].sort((e, t) => e.definition.id < t.definition.id ? -1 : 1);
|
|
297
|
-
},
|
|
344
|
+
}, L = (e) => {
|
|
298
345
|
let t = (process.env.DB_DIALECT ?? "postgres").toLowerCase(), n = t === "memory" ? "postgres" : t;
|
|
299
|
-
return g.runPromise(
|
|
300
|
-
},
|
|
301
|
-
let e = yield* (yield*
|
|
346
|
+
return g.runPromise(w(S, n).pipe(g.provide(e)));
|
|
347
|
+
}, R = (e) => g.runPromise(g.gen(function* () {
|
|
348
|
+
let e = yield* (yield* x.SqlClient)`SELECT id, status, hash, "appliedAt" FROM "_voltro_migrations"`.pipe(g.orElseSucceed(() => []));
|
|
302
349
|
return new Map(e.map((e) => [e.id, e]));
|
|
303
|
-
}).pipe(g.provide(e))),
|
|
304
|
-
yield* (yield*
|
|
350
|
+
}).pipe(g.provide(e))), z = (e, t) => g.runPromise(g.gen(function* () {
|
|
351
|
+
yield* (yield* x.SqlClient)`
|
|
305
352
|
INSERT INTO "_voltro_migrations"
|
|
306
353
|
(id, name, hash, status, "appliedAt", "durationMs", "errorMessage", "errorStack")
|
|
307
354
|
VALUES
|
|
@@ -316,14 +363,14 @@ var O = class extends Error {
|
|
|
316
363
|
"errorStack" = EXCLUDED."errorStack",
|
|
317
364
|
"updatedAt" = now()
|
|
318
365
|
`;
|
|
319
|
-
}).pipe(g.provide(e))),
|
|
366
|
+
}).pipe(g.provide(e))), He = async (e, t, n) => {
|
|
320
367
|
if (e.length === 0) return {
|
|
321
368
|
applied: [],
|
|
322
369
|
failed: null,
|
|
323
370
|
skipped: []
|
|
324
371
|
};
|
|
325
|
-
await
|
|
326
|
-
let r = await
|
|
372
|
+
await L(t);
|
|
373
|
+
let r = await R(t), i = [], a = [], o = null;
|
|
327
374
|
for (let s of e) {
|
|
328
375
|
let e = r.get(s.definition.id);
|
|
329
376
|
if (e && e.status === "applied") {
|
|
@@ -344,7 +391,7 @@ var O = class extends Error {
|
|
|
344
391
|
name: s.definition.name
|
|
345
392
|
});
|
|
346
393
|
let c = Date.now(), l = [];
|
|
347
|
-
await
|
|
394
|
+
await z(t, {
|
|
348
395
|
id: s.definition.id,
|
|
349
396
|
name: s.definition.name,
|
|
350
397
|
hash: s.hash,
|
|
@@ -385,7 +432,7 @@ var O = class extends Error {
|
|
|
385
432
|
...a.stack ? { stack: a.stack } : {}
|
|
386
433
|
}
|
|
387
434
|
};
|
|
388
|
-
o = f, i.push(f), await
|
|
435
|
+
o = f, i.push(f), await z(t, {
|
|
389
436
|
id: s.definition.id,
|
|
390
437
|
name: s.definition.name,
|
|
391
438
|
hash: s.hash,
|
|
@@ -404,7 +451,7 @@ var O = class extends Error {
|
|
|
404
451
|
status: "applied",
|
|
405
452
|
durationMs: Date.now() - c,
|
|
406
453
|
steps: l
|
|
407
|
-
}), await
|
|
454
|
+
}), await z(t, {
|
|
408
455
|
id: s.definition.id,
|
|
409
456
|
name: s.definition.name,
|
|
410
457
|
hash: s.hash,
|
|
@@ -424,9 +471,9 @@ var O = class extends Error {
|
|
|
424
471
|
failed: o,
|
|
425
472
|
skipped: a
|
|
426
473
|
};
|
|
427
|
-
},
|
|
428
|
-
await
|
|
429
|
-
let i = await
|
|
474
|
+
}, Ue = async (e, t, n, r = {}) => {
|
|
475
|
+
await L(t);
|
|
476
|
+
let i = await R(t), a = e.filter((e) => i.get(e.definition.id)?.status === "applied");
|
|
430
477
|
if (a.length === 0) return n.info("migration: nothing to roll back"), {
|
|
431
478
|
applied: [],
|
|
432
479
|
failed: null,
|
|
@@ -479,7 +526,7 @@ var O = class extends Error {
|
|
|
479
526
|
status: "applied",
|
|
480
527
|
durationMs: Date.now() - r,
|
|
481
528
|
steps: i
|
|
482
|
-
}), await
|
|
529
|
+
}), await z(t, {
|
|
483
530
|
id: e.definition.id,
|
|
484
531
|
name: e.definition.name,
|
|
485
532
|
hash: e.hash,
|
|
@@ -495,9 +542,9 @@ var O = class extends Error {
|
|
|
495
542
|
failed: c,
|
|
496
543
|
skipped: []
|
|
497
544
|
};
|
|
498
|
-
},
|
|
499
|
-
await
|
|
500
|
-
let n = await
|
|
545
|
+
}, We = async (e, t) => {
|
|
546
|
+
await L(t);
|
|
547
|
+
let n = await R(t);
|
|
501
548
|
return e.map((e) => {
|
|
502
549
|
let t = n.get(e.definition.id);
|
|
503
550
|
return t ? {
|
|
@@ -514,7 +561,7 @@ var O = class extends Error {
|
|
|
514
561
|
hashMatches: !0
|
|
515
562
|
};
|
|
516
563
|
});
|
|
517
|
-
},
|
|
564
|
+
}, Ge = (e) => {
|
|
518
565
|
if (!e.failed) return "";
|
|
519
566
|
let t = e.failed, n = [
|
|
520
567
|
`✗ Migration failed: ${t.migrationId} / step '${t.error?.stepName ?? "?"}'`,
|
|
@@ -527,7 +574,59 @@ var O = class extends Error {
|
|
|
527
574
|
""
|
|
528
575
|
];
|
|
529
576
|
return e.applied.length > 0 && n.unshift("", `(${e.applied.length} migration(s) applied successfully before the failure.)`), n.join("\n");
|
|
530
|
-
},
|
|
577
|
+
}, Ke = (e) => {
|
|
578
|
+
let t = e.split(".");
|
|
579
|
+
if (t.length !== 2 || !t[0] || !t[1]) throw Error(`voltro db encrypt-column: expected <table>.<column>, got "${e}"`);
|
|
580
|
+
return {
|
|
581
|
+
table: t[0],
|
|
582
|
+
column: t[1]
|
|
583
|
+
};
|
|
584
|
+
}, qe = (e) => 49 + 4 * Math.ceil(e / 3), Je = (e, t) => {
|
|
585
|
+
let n = qe(e);
|
|
586
|
+
return {
|
|
587
|
+
longestPlaintextBytes: e,
|
|
588
|
+
longestCiphertext: n,
|
|
589
|
+
declaredMaxLength: t,
|
|
590
|
+
fits: t === void 0 || n <= t
|
|
591
|
+
};
|
|
592
|
+
}, Ye = (e, t) => `${e.table}.${e.column} is too narrow: its longest value is ${t.longestPlaintextBytes} byte(s), which encrypts to ${t.longestCiphertext} characters, and the column is declared as ${t.declaredMaxLength}. Widen it first (\`.maxLength(${t.longestCiphertext})\` or more, then \`voltro db apply\`) and run this again. Nothing was written.`, Xe = (e, t) => {
|
|
593
|
+
let n = t ? "would encrypt" : "encrypted";
|
|
594
|
+
return [
|
|
595
|
+
` ${e.table}.${e.column}`,
|
|
596
|
+
` scanned ${e.scanned}`,
|
|
597
|
+
` already encrypted ${e.alreadyEncrypted} (skipped — a re-run is safe)`,
|
|
598
|
+
` null / empty ${e.empty}`,
|
|
599
|
+
` ${n.padEnd(18)} ${e.encrypted}`
|
|
600
|
+
].join("\n");
|
|
601
|
+
}, Ze = (e, t, n) => {
|
|
602
|
+
if (e == null) return;
|
|
603
|
+
let r = typeof e == "string" ? e : String(e);
|
|
604
|
+
if (r === "" || C(r)) return;
|
|
605
|
+
let i = t.encrypt(r), a;
|
|
606
|
+
try {
|
|
607
|
+
a = t.decrypt(i);
|
|
608
|
+
} catch (e) {
|
|
609
|
+
throw Error(`${n.table}.${n.column}: the cipher produced a value it cannot read back (${e.message ?? String(e)}). Nothing was written.`);
|
|
610
|
+
}
|
|
611
|
+
if (a !== r) throw Error(`${n.table}.${n.column}: round-trip mismatch — the cipher did not return what it was given. Nothing was written.`);
|
|
612
|
+
return i;
|
|
613
|
+
}, Qe = (e) => Buffer.byteLength(e, "utf8"), $e = (e, t) => {
|
|
614
|
+
if (e !== void 0) try {
|
|
615
|
+
return t.decrypt(e), !0;
|
|
616
|
+
} catch {
|
|
617
|
+
return !1;
|
|
618
|
+
}
|
|
619
|
+
}, et = (e, t) => `${e.table}.${e.column} already holds values encrypted with a DIFFERENT key than ${t} — the active key cannot decrypt them. Continuing would encrypt the remaining rows under a second key, and the column would then be readable with neither key alone. Nothing was written. Point ${t} at the key the existing rows were written with.`, tt = (e) => [
|
|
620
|
+
"",
|
|
621
|
+
" NEXT — this converted the rows that exist. New writes are still plaintext",
|
|
622
|
+
" until the column carries the modifier:",
|
|
623
|
+
"",
|
|
624
|
+
...e.map((e) => ` ${e.column}: text().encrypted() // ${e.table}`),
|
|
625
|
+
"",
|
|
626
|
+
" Order does not matter: a read returns a non-ciphertext value unchanged, so",
|
|
627
|
+
" the column may hold a mix while you deploy. Run this again afterwards to",
|
|
628
|
+
" catch anything written in between — it skips what is already encrypted."
|
|
629
|
+
].join("\n"), B = _({ scope: "voltro:db" }), nt = /* @__PURE__ */ new Set([
|
|
531
630
|
"node_modules",
|
|
532
631
|
"dist",
|
|
533
632
|
"build",
|
|
@@ -535,17 +634,17 @@ var O = class extends Error {
|
|
|
535
634
|
".vite",
|
|
536
635
|
".next",
|
|
537
636
|
".git"
|
|
538
|
-
]),
|
|
637
|
+
]), V = async (e, t) => {
|
|
539
638
|
let n = [], r = async (e) => {
|
|
540
639
|
let i = await v.readdir(e, { withFileTypes: !0 }).catch(() => []);
|
|
541
640
|
for (let a of i) {
|
|
542
|
-
if (
|
|
641
|
+
if (nt.has(a.name)) continue;
|
|
543
642
|
let i = f(e, a.name);
|
|
544
643
|
a.isDirectory() ? await r(i) : a.isFile() && t.test(a.name) && n.push(i);
|
|
545
644
|
}
|
|
546
645
|
};
|
|
547
646
|
return await r(e), n;
|
|
548
|
-
},
|
|
647
|
+
}, H = (e) => m(e.find((e) => !e.startsWith("-")) ?? process.cwd()), U = () => {
|
|
549
648
|
let e = process.env.DB_MAX_CONNECTIONS ?? process.env.PG_MAX_CONNECTIONS, t = e !== void 0 && e !== "" ? Number(e) : NaN, n = Number.isInteger(t) && t > 0 ? { maxConnections: t } : {}, r = process.env.DB_SCHEMA, i = r && r.length > 0 ? { schema: r } : {}, a = process.env.DB_DIRECT_URL ?? process.env.DB_MIGRATE_URL ?? process.env.DB_URL ?? process.env.DB_PRIMARY_URL;
|
|
550
649
|
return a ? {
|
|
551
650
|
url: a,
|
|
@@ -560,63 +659,63 @@ var O = class extends Error {
|
|
|
560
659
|
...n,
|
|
561
660
|
...i
|
|
562
661
|
};
|
|
563
|
-
},
|
|
662
|
+
}, rt = (e) => {
|
|
564
663
|
let t = e;
|
|
565
664
|
if (typeof t != "object" || !t) return !1;
|
|
566
665
|
if (t.code === "ERR_OUT_OF_RANGE" || t.cause?.code === "ERR_OUT_OF_RANGE") return !0;
|
|
567
666
|
let n = `${t.message ?? ""} ${t.cause?.message ?? ""} ${t.stack ?? ""}`;
|
|
568
667
|
return /ERR_OUT_OF_RANGE|out of range/i.test(n) && /pg-protocol|parseDataRow|buffer-reader|BufferReader/i.test(n);
|
|
569
|
-
},
|
|
668
|
+
}, it = "Live-DB introspection got a corrupt / misaligned response from the database. This happens when `voltro db plan` / `apply` / `drift` / `migrate` runs through a connection POOLER (Supabase Supavisor / PgBouncer) on a LARGE schema — the pooler mis-frames a big catalog response. The introspection already batches by table to keep responses small; if a batch still trips it:\n 1. Lower the batch: set `VOLTRO_INTROSPECT_BATCH` to a smaller number of tables (default 20 — try 5 or 1). This keeps `db plan` on the normal pooler, no direct connection needed.\n 2. Or use a DIRECT (non-pooler) connection: set `DB_DIRECT_URL` (or `DB_MIGRATE_URL`) to the direct database URL — the runtime keeps `DB_URL`, so only the migration path is affected. (Supabase: direct = `db.<project-ref>.supabase.co` [IPv6-only on newer projects]; pooler = `aws-0-<region>.pooler.supabase.com`.)", W = () => {
|
|
570
669
|
let e = (process.env.DB_DIALECT ?? "postgres").toLowerCase();
|
|
571
670
|
if (e === "memory") throw new O("voltro db: subcommands require a real dialect, not DB_DIALECT=memory.");
|
|
572
671
|
return e;
|
|
573
|
-
}, G = (e) =>
|
|
574
|
-
let t =
|
|
575
|
-
if (r.length === 0) return
|
|
672
|
+
}, G = (e) => re(e, W()), K = (e) => e === "mysql" || e === "mariadb" ? "table_schema = DATABASE()" : e === "mssql" ? "table_schema = SCHEMA_NAME()" : "table_schema = 'public'", q = async () => (await r(W())).makeSqlLayer(U()), at = async (e) => {
|
|
673
|
+
let t = H(e), n = e.includes("--dry-run"), r = await I(await V(t, F), B);
|
|
674
|
+
if (r.length === 0) return B.info("no migrations found", { root: t }), 0;
|
|
576
675
|
if (n) {
|
|
577
676
|
let e = /* @__PURE__ */ new Set();
|
|
578
677
|
try {
|
|
579
|
-
let t = await
|
|
678
|
+
let t = await We(r, await q());
|
|
580
679
|
e = new Set(t.filter((e) => e.status === "applied").map((e) => e.id));
|
|
581
680
|
} catch {
|
|
582
|
-
|
|
681
|
+
B.warn("migrate --dry-run: could not read the migration ledger — showing ALL as pending");
|
|
583
682
|
}
|
|
584
683
|
let t = r.filter((t) => !e.has(t.definition.id));
|
|
585
|
-
if (t.length === 0) return
|
|
586
|
-
|
|
684
|
+
if (t.length === 0) return B.info("migrate --dry-run: nothing pending — every migration is applied"), 0;
|
|
685
|
+
B.info(`migrate --dry-run: ${t.length} pending (nothing applied)`);
|
|
587
686
|
for (let e of t) console.log(` pending ${e.definition.id}`);
|
|
588
687
|
return 0;
|
|
589
688
|
}
|
|
590
|
-
let i = await
|
|
591
|
-
return i.failed ? (process.stderr.write(
|
|
689
|
+
let i = await He(r, await q(), B);
|
|
690
|
+
return i.failed ? (process.stderr.write(Ge(i) + "\n"), 1) : (B.info("migrate complete", {
|
|
592
691
|
applied: i.applied.length,
|
|
593
692
|
skipped: i.skipped.length
|
|
594
693
|
}), 0);
|
|
595
|
-
},
|
|
596
|
-
let t =
|
|
597
|
-
if (r.length === 0) return
|
|
598
|
-
let i = await
|
|
599
|
-
return i.failed ? (process.stderr.write(
|
|
600
|
-
},
|
|
601
|
-
let t = await
|
|
694
|
+
}, ot = async (e) => {
|
|
695
|
+
let t = H(e), n = l(e, "--to"), r = await I(await V(t, F), B);
|
|
696
|
+
if (r.length === 0) return B.info("no migrations found", { root: t }), 0;
|
|
697
|
+
let i = await Ue(r, await q(), B, n ? { to: n } : {});
|
|
698
|
+
return i.failed ? (process.stderr.write(Ge(i) + "\n"), 1) : (B.info("rollback complete", { reverted: i.applied.length }), 0);
|
|
699
|
+
}, st = async (e) => {
|
|
700
|
+
let t = await I(await V(H(e), F), B);
|
|
602
701
|
if (t.length === 0) return console.log("no migrations found"), 0;
|
|
603
|
-
let n = await
|
|
702
|
+
let n = await We(t, await q());
|
|
604
703
|
for (let e of n) {
|
|
605
704
|
let t = e.status === "applied" ? "✓" : e.status === "failed" ? "✗" : e.status === "reverted" ? "↩" : "·", n = e.hashMatches ? "" : " (hash drift!)", r = e.appliedAt ? ` @ ${e.appliedAt.toISOString()}` : "";
|
|
606
705
|
console.log(` ${t} ${e.id} [${e.status}] ${e.name}${r}${n}`);
|
|
607
706
|
}
|
|
608
707
|
return 0;
|
|
609
|
-
},
|
|
708
|
+
}, ct = async (e) => {
|
|
610
709
|
let t = (l(e, "--store") ?? process.env.DB_DIALECT ?? "postgres").toLowerCase();
|
|
611
|
-
if (t === "memory") return
|
|
612
|
-
store: new
|
|
710
|
+
if (t === "memory") return B.warn("voltro db seed: using in-memory store (explicit opt-in via --store memory / DB_DIALECT=memory)"), B.warn(" rows written by this run will NOT persist beyond the process. set DB_DIALECT=postgres or unset to seed the real DB."), {
|
|
711
|
+
store: new b(),
|
|
613
712
|
dispose: async () => {},
|
|
614
713
|
dialectId: "memory"
|
|
615
714
|
};
|
|
616
715
|
let n = await r(t);
|
|
617
|
-
|
|
716
|
+
B.info(`voltro db seed: target store=${n.id} — rows will persist`);
|
|
618
717
|
let i = await n.makeStore({
|
|
619
|
-
sqlLayer: n.makeSqlLayer(
|
|
718
|
+
sqlLayer: n.makeSqlLayer(U()),
|
|
620
719
|
changeStrategy: "inline"
|
|
621
720
|
}), a = i;
|
|
622
721
|
return {
|
|
@@ -626,77 +725,77 @@ var O = class extends Error {
|
|
|
626
725
|
},
|
|
627
726
|
dialectId: n.id
|
|
628
727
|
};
|
|
629
|
-
},
|
|
630
|
-
let a =
|
|
631
|
-
if (c.length === 0) return
|
|
632
|
-
let { store: u, dispose: d, dialectId: f } = await
|
|
728
|
+
}, lt = async (r) => {
|
|
729
|
+
let a = H(r), o = l(r, "--id"), s = l(r, "--lifecycle"), c = await t(await V(a, i), B);
|
|
730
|
+
if (c.length === 0) return B.info("no seeds found", { root: a }), 0;
|
|
731
|
+
let { store: u, dispose: d, dialectId: f } = await ct(r);
|
|
633
732
|
try {
|
|
634
|
-
return o ? +((await e(c, o, u,
|
|
733
|
+
return o ? +((await e(c, o, u, B)).status === "failed") : +((await n((s ? c.filter((e) => e.definition.lifecycle === s) : c.filter((e) => e.definition.lifecycle === "boot")).map((e) => ({
|
|
635
734
|
...e,
|
|
636
735
|
definition: {
|
|
637
736
|
...e.definition,
|
|
638
737
|
lifecycle: "boot"
|
|
639
738
|
}
|
|
640
|
-
})), u,
|
|
739
|
+
})), u, B, { force: !0 })).filter((e) => e.status === "failed").length > 0);
|
|
641
740
|
} finally {
|
|
642
741
|
await d().catch((e) => {
|
|
643
|
-
|
|
742
|
+
B.warn("voltro db seed: store dispose failed", { error: String(e) });
|
|
644
743
|
});
|
|
645
744
|
}
|
|
646
|
-
},
|
|
647
|
-
let t = await
|
|
745
|
+
}, ut = (e) => typeof e == "object" && !!e && "tableName" in e && "fields" in e && typeof e.tableName == "string", dt = async (e) => {
|
|
746
|
+
let t = await V(e, s), n = [];
|
|
648
747
|
for (let r of t) {
|
|
649
748
|
let t;
|
|
650
749
|
try {
|
|
651
750
|
t = await import(h(r).href);
|
|
652
751
|
} catch (t) {
|
|
653
|
-
|
|
752
|
+
B.warn("db: failed to import schema file", {
|
|
654
753
|
file: p(e, r),
|
|
655
754
|
error: String(t)
|
|
656
755
|
});
|
|
657
756
|
continue;
|
|
658
757
|
}
|
|
659
|
-
for (let e of Object.values(t))
|
|
758
|
+
for (let e of Object.values(t)) ut(e) && n.push(e);
|
|
660
759
|
}
|
|
661
760
|
return n;
|
|
662
|
-
},
|
|
761
|
+
}, ft = (e) => {
|
|
663
762
|
let t = new Map(e.map((e) => [e.tableName, e]));
|
|
664
763
|
t.has("actors") || t.set("actors", te);
|
|
665
764
|
let n = t.get("actors"), r = t.get("tenants");
|
|
666
|
-
return
|
|
765
|
+
return ie({
|
|
667
766
|
actors: () => n,
|
|
668
767
|
...r === void 0 ? {} : { tenants: () => r }
|
|
669
768
|
}), [...t.values()];
|
|
670
|
-
},
|
|
769
|
+
}, pt = (e) => {
|
|
671
770
|
let t = /* @__PURE__ */ new Map();
|
|
672
771
|
for (let n of e) {
|
|
673
772
|
let e = n.tableName;
|
|
674
773
|
e !== void 0 && !t.has(e) && t.set(e, n);
|
|
675
774
|
}
|
|
676
775
|
return [...t.values()];
|
|
677
|
-
},
|
|
678
|
-
let t =
|
|
776
|
+
}, mt = (e) => {
|
|
777
|
+
let t = ue(process.env.VOLTRO_DESTRUCTIVE_OK);
|
|
679
778
|
if (t === void 0 || !e.operations.filter((e) => e.blocked).every((e) => e.classification === "lossy")) return {
|
|
680
779
|
plan: e,
|
|
681
780
|
acknowledged: !1
|
|
682
781
|
};
|
|
683
|
-
let n =
|
|
782
|
+
let n = be(e, t);
|
|
684
783
|
return {
|
|
685
784
|
plan: n,
|
|
686
785
|
acknowledged: !n.operations.some((e) => e.blocked)
|
|
687
786
|
};
|
|
688
787
|
}, J = async (e, t) => g.runPromise(t.pipe(g.provide(e))), Y = async (e) => {
|
|
689
788
|
try {
|
|
690
|
-
return await J(e,
|
|
789
|
+
return await J(e, x.SqlClient.pipe(g.flatMap(pe)));
|
|
691
790
|
} catch (e) {
|
|
692
791
|
let t = String(e?.message ?? e);
|
|
693
|
-
if (
|
|
694
|
-
let n = /* @__PURE__ */ Error(`${
|
|
792
|
+
if (rt(e) || /connection error/i.test(t)) {
|
|
793
|
+
let n = /* @__PURE__ */ Error(`${it}\n\nOriginal error: ${t}`);
|
|
695
794
|
throw n.cause = e, n;
|
|
696
795
|
}
|
|
697
796
|
throw e;
|
|
698
797
|
}
|
|
699
|
-
},
|
|
798
|
+
}, ht = {
|
|
700
799
|
safe: "\x1B[32m✓\x1B[0m",
|
|
701
800
|
"needs-default": "\x1B[32m⊕\x1B[0m",
|
|
702
801
|
"needs-backfill": "\x1B[33m⊕\x1B[0m",
|
|
@@ -704,7 +803,7 @@ var O = class extends Error {
|
|
|
704
803
|
lossy: "\x1B[31m✗\x1B[0m",
|
|
705
804
|
"online-required": "\x1B[36m◷\x1B[0m",
|
|
706
805
|
"multi-step": "\x1B[35m⇉\x1B[0m"
|
|
707
|
-
},
|
|
806
|
+
}, gt = (e) => {
|
|
708
807
|
let t = e.op, n;
|
|
709
808
|
switch (t.kind) {
|
|
710
809
|
case "create-table":
|
|
@@ -768,9 +867,9 @@ var O = class extends Error {
|
|
|
768
867
|
n = `ALTER TABLE ${t.table} DROP CHECK ${t.column}`;
|
|
769
868
|
break;
|
|
770
869
|
}
|
|
771
|
-
let r =
|
|
870
|
+
let r = ht[e.classification], i = e.reason ? ` [2m# ${e.reason}[0m` : "", a = e.blocked ? `\n [31m! ${e.blocked.fix}[0m` : "";
|
|
772
871
|
return ` ${r} ${n}${i}${a}`;
|
|
773
|
-
},
|
|
872
|
+
}, _t = async (e, t) => {
|
|
774
873
|
let n = e.operations.filter((e) => e.classification === "needs-default" && e.op.kind === "add-column");
|
|
775
874
|
if (n.length === 0) return;
|
|
776
875
|
let r = [];
|
|
@@ -779,7 +878,7 @@ var O = class extends Error {
|
|
|
779
878
|
if (i === void 0 || a === void 0) continue;
|
|
780
879
|
let o = 0;
|
|
781
880
|
try {
|
|
782
|
-
let e = await J(t,
|
|
881
|
+
let e = await J(t, x.SqlClient.pipe(g.flatMap((e) => e.unsafe(`SELECT COUNT(*) AS n FROM ${re(i, W())}`))));
|
|
783
882
|
o = Number(Object.values(e[0] ?? { n: 0 })[0] ?? 0);
|
|
784
883
|
} catch {
|
|
785
884
|
continue;
|
|
@@ -793,13 +892,13 @@ var O = class extends Error {
|
|
|
793
892
|
}
|
|
794
893
|
}, X = async (e, t) => {
|
|
795
894
|
if (W() !== "postgres") return;
|
|
796
|
-
let n =
|
|
895
|
+
let n = oe, r;
|
|
797
896
|
try {
|
|
798
|
-
r = await J(t,
|
|
897
|
+
r = await J(t, x.SqlClient.pipe(g.flatMap((t) => de(t, e, n))));
|
|
799
898
|
} catch {
|
|
800
899
|
return;
|
|
801
900
|
}
|
|
802
|
-
let i =
|
|
901
|
+
let i = he({
|
|
803
902
|
tables: e,
|
|
804
903
|
dialect: "postgres",
|
|
805
904
|
missing: r.missing,
|
|
@@ -807,10 +906,10 @@ var O = class extends Error {
|
|
|
807
906
|
channel: n
|
|
808
907
|
});
|
|
809
908
|
if (i.length === 0) return;
|
|
810
|
-
|
|
909
|
+
B.info(`db apply: installing change triggers on ${r.missing.length} table(s)` + (r.stale.length > 0 ? `, removing ${r.stale.length} stale one(s)` : ""));
|
|
811
910
|
let a = 0;
|
|
812
|
-
for (let e of i) await J(t,
|
|
813
|
-
|
|
911
|
+
for (let e of i) await J(t, x.SqlClient.pipe(g.flatMap((t) => t.unsafe(e)))), a += 1;
|
|
912
|
+
B.info(`db apply: change triggers converged (${a} statement(s))`, {
|
|
814
913
|
installed: r.missing.length,
|
|
815
914
|
removed: r.stale.length
|
|
816
915
|
});
|
|
@@ -820,40 +919,40 @@ var O = class extends Error {
|
|
|
820
919
|
console.log(" (schema is up to date)\n");
|
|
821
920
|
return;
|
|
822
921
|
}
|
|
823
|
-
for (let t of e.operations) console.log(
|
|
824
|
-
console.log(), console.log(` safe: ${t.safe} needs-default: ${t.needsDefault} needs-backfill: ${t.needsBackfill}`), console.log(` rename: ${t.needsRenameAnnotation} lossy: ${t.lossy} blocked: ${t.blocked}`), console.log(` fingerprint: ${e.fromFingerprint.slice(0, 16)} → ${e.toFingerprint.slice(0, 16)}\n`),
|
|
825
|
-
},
|
|
826
|
-
let t =
|
|
922
|
+
for (let t of e.operations) console.log(gt(t));
|
|
923
|
+
console.log(), console.log(` safe: ${t.safe} needs-default: ${t.needsDefault} needs-backfill: ${t.needsBackfill}`), console.log(` rename: ${t.needsRenameAnnotation} lossy: ${t.lossy} blocked: ${t.blocked}`), console.log(` fingerprint: ${e.fromFingerprint.slice(0, 16)} → ${e.toFingerprint.slice(0, 16)}\n`), vt(e);
|
|
924
|
+
}, vt = (e) => {
|
|
925
|
+
let t = _e(e.operations);
|
|
827
926
|
if (t.length !== 0) {
|
|
828
927
|
console.log(` ⚠ ${t.length} operation(s) UNSAFE under a rolling deploy`), console.log(" (old + new instances overlap → old code breaks against the new schema):");
|
|
829
928
|
for (let e of t) console.log(` • ${e.op.op.kind}: ${e.reason}`), console.log(` → ${e.remedy}`);
|
|
830
929
|
console.log(" Deploy with a maintenance window / scale-to-zero, or split into"), console.log(" expand → deploy → contract. See docs: deployment/production-hardening.\n");
|
|
831
930
|
}
|
|
832
|
-
},
|
|
833
|
-
let n =
|
|
931
|
+
}, yt = (e, t) => {
|
|
932
|
+
let n = ce({
|
|
834
933
|
enabled: process.env.VOLTRO_ROLLING_DEPLOY === "1",
|
|
835
934
|
forced: t.includes("--force"),
|
|
836
|
-
unsafeCount:
|
|
935
|
+
unsafeCount: _e(e.operations).length
|
|
837
936
|
});
|
|
838
|
-
return n.refuse ? (
|
|
839
|
-
},
|
|
937
|
+
return n.refuse ? (B.error(`db apply: ${n.message}`), !0) : (n.warnForced && B.warn(`db apply: ${n.message}`), !1);
|
|
938
|
+
}, bt = (e) => {
|
|
840
939
|
let t = l(e, "--online-after") ?? process.env.VOLTRO_ONLINE_THRESHOLD;
|
|
841
940
|
if (t === void 0) return;
|
|
842
941
|
let n = Number(t);
|
|
843
942
|
if (!Number.isInteger(n) || n < 0) throw Error(`--online-after / VOLTRO_ONLINE_THRESHOLD must be a non-negative integer, got "${t}"`);
|
|
844
943
|
return n;
|
|
845
944
|
}, Q = async (e) => {
|
|
846
|
-
let t = await
|
|
945
|
+
let t = await dt(e);
|
|
847
946
|
if (t.length === 0) return;
|
|
848
947
|
let n = await o({ root: e });
|
|
849
|
-
return
|
|
948
|
+
return ft(pt([
|
|
850
949
|
...t,
|
|
851
|
-
...
|
|
950
|
+
...S,
|
|
852
951
|
...n
|
|
853
952
|
]));
|
|
854
|
-
},
|
|
855
|
-
let t =
|
|
856
|
-
if (r === void 0) return
|
|
953
|
+
}, xt = async (e) => {
|
|
954
|
+
let t = bt(e), n = H(e), r = await Q(n);
|
|
955
|
+
if (r === void 0) return B.warn("db plan: no schema files found", {
|
|
857
956
|
root: n,
|
|
858
957
|
hint: "looked for *.entity.ts / *.schema.ts / schema.ts"
|
|
859
958
|
}), 1;
|
|
@@ -863,9 +962,9 @@ var O = class extends Error {
|
|
|
863
962
|
a && (o.authorization = `Bearer ${a}`);
|
|
864
963
|
try {
|
|
865
964
|
let e = n.endsWith("/_voltro/inspect/migrations") ? n : `${n.replace(/\/+$/, "")}/_voltro/inspect/migrations`, i = await fetch(e, { headers: o });
|
|
866
|
-
if (!i.ok) return
|
|
965
|
+
if (!i.ok) return B.error(`db plan --against: remote returned ${i.status}`, { url: e }), 1;
|
|
867
966
|
let a = (await i.json()).drift?.liveSnapshot;
|
|
868
|
-
if (!a) return
|
|
967
|
+
if (!a) return B.error("db plan --against: remote inspect payload missing drift.liveSnapshot", { url: e }), 1;
|
|
869
968
|
let s = E({
|
|
870
969
|
declared: r,
|
|
871
970
|
live: a,
|
|
@@ -875,7 +974,7 @@ var O = class extends Error {
|
|
|
875
974
|
});
|
|
876
975
|
return console.log(`\n[--against ${e}] — diff vs remote live schema:\n`), Z(s), s.summary.blocked > 0 ? 2 : 0;
|
|
877
976
|
} catch (e) {
|
|
878
|
-
return
|
|
977
|
+
return B.error(`db plan --against: ${e.message}`), 1;
|
|
879
978
|
}
|
|
880
979
|
}
|
|
881
980
|
let a = E({
|
|
@@ -886,35 +985,35 @@ var O = class extends Error {
|
|
|
886
985
|
...t === void 0 ? {} : { onlineAfter: t }
|
|
887
986
|
});
|
|
888
987
|
return e.includes("--json") ? (y(1, `${JSON.stringify(a, null, 2)}\n`), a.summary.blocked > 0 ? 2 : 0) : (Z(a), a.summary.blocked > 0 ? 2 : 0);
|
|
889
|
-
},
|
|
988
|
+
}, St = async (e, t, n) => {
|
|
890
989
|
let r;
|
|
891
990
|
try {
|
|
892
991
|
let t = await v.readFile(m(e), "utf8");
|
|
893
992
|
r = JSON.parse(t);
|
|
894
993
|
} catch (t) {
|
|
895
|
-
return
|
|
994
|
+
return B.error(`db apply --plan: cannot read/parse plan file '${e}': ${t.message}`), 1;
|
|
896
995
|
}
|
|
897
|
-
if (!Array.isArray(r?.operations) || typeof r?.fromFingerprint != "string" || typeof r?.toFingerprint != "string") return
|
|
996
|
+
if (!Array.isArray(r?.operations) || typeof r?.fromFingerprint != "string" || typeof r?.toFingerprint != "string") return B.error(`db apply --plan: '${e}' is not a valid plan file (expected the output of \`voltro db plan --json\`).`), 1;
|
|
898
997
|
let i = await Q(n);
|
|
899
|
-
if (i === void 0) return
|
|
998
|
+
if (i === void 0) return B.warn("db apply --plan: no schema files found", { root: n }), 1;
|
|
900
999
|
let a = await q();
|
|
901
|
-
await J(a,
|
|
902
|
-
let o = await J(a,
|
|
903
|
-
if (o.length > 0) return
|
|
1000
|
+
await J(a, w(S, W()));
|
|
1001
|
+
let o = await J(a, x.SqlClient.pipe(g.flatMap((e) => me(e, n)))).catch(() => []);
|
|
1002
|
+
if (o.length > 0) return B.error(`db apply --plan: refusing — ${o.length} pending file-based migration(s).`), console.error(` ${o.join("\n ")}`), console.error(""), console.error(" These perform the changes a state diff cannot infer, so they change the shape"), console.error(" this plan was computed against. Apply them and regenerate the plan:"), console.error(""), console.error(" voltro db files ."), console.error(" voltro db plan --json > plan.json"), console.error(" voltro db apply --plan plan.json"), 1;
|
|
904
1003
|
let s = E({
|
|
905
1004
|
declared: i,
|
|
906
1005
|
live: await Y(a),
|
|
907
1006
|
dialect: W(),
|
|
908
1007
|
...T()
|
|
909
1008
|
});
|
|
910
|
-
if (s.fromFingerprint !== r.fromFingerprint) return
|
|
911
|
-
if (s.toFingerprint !== r.toFingerprint) return
|
|
912
|
-
let c =
|
|
913
|
-
if (u.summary.blocked > 0 && !c.acknowledged) return Z(u),
|
|
914
|
-
if (c.acknowledged &&
|
|
915
|
-
if (u.operations.length === 0) return
|
|
1009
|
+
if (s.fromFingerprint !== r.fromFingerprint) return B.error("db apply --plan: refusing — the live schema has drifted since this plan was generated."), console.error(` plan.from=${r.fromFingerprint.slice(0, 16)} live=${s.fromFingerprint.slice(0, 16)}`), console.error(" Regenerate against the current live schema: `voltro db plan --json > plan.json`"), 2;
|
|
1010
|
+
if (s.toFingerprint !== r.toFingerprint) return B.error("db apply --plan: refusing — the declared schema differs from what this plan targets."), console.error(` plan.to=${r.toFingerprint.slice(0, 16)} declared=${s.toFingerprint.slice(0, 16)}`), console.error(" The schema files changed since the plan was generated. Regenerate the plan."), 2;
|
|
1011
|
+
let c = mt(s), u = c.plan;
|
|
1012
|
+
if (u.summary.blocked > 0 && !c.acknowledged) return Z(u), B.error(`db apply --plan: refusing — ${s.summary.blocked} blocked operation(s). See the fix hints above.`), process.env.VOLTRO_DESTRUCTIVE_OK !== void 0 && (B.error(" VOLTRO_DESTRUCTIVE_OK is set but did not cover this plan — it only relaxes"), B.error(" `lossy` operations, and only the tables it names. Anything else stays refused.")), 2;
|
|
1013
|
+
if (c.acknowledged && B.warn("db apply --plan: proceeding with acknowledged lossy operation(s) (VOLTRO_DESTRUCTIVE_OK)"), yt(u, t)) return 2;
|
|
1014
|
+
if (u.operations.length === 0) return B.info("db apply --plan: schema is up to date — no DDL to apply (change triggers are reconciled separately below)"), await X(i, a), 0;
|
|
916
1015
|
Z(u);
|
|
917
|
-
let d = (process.env.NODE_ENV ?? "dev").toLowerCase(), f = l(t, "--note"), p = await J(a,
|
|
1016
|
+
let d = (process.env.NODE_ENV ?? "dev").toLowerCase(), f = l(t, "--note"), p = await J(a, x.SqlClient.pipe(g.flatMap((e) => se(e, u, {
|
|
918
1017
|
declared: i,
|
|
919
1018
|
appliedBy: process.env.USER ? `cli:${process.env.USER}` : "cli:plan",
|
|
920
1019
|
environment: d === "production" ? "prod" : d === "staging" ? "staging" : "dev",
|
|
@@ -927,19 +1026,19 @@ var O = class extends Error {
|
|
|
927
1026
|
...T()
|
|
928
1027
|
})))
|
|
929
1028
|
}))));
|
|
930
|
-
return
|
|
1029
|
+
return B.info(`db apply --plan: applied ${p.appliedOps} op(s) in ${p.durationMs}ms`, {
|
|
931
1030
|
id: p.id,
|
|
932
1031
|
fingerprint: p.fingerprint.slice(0, 16)
|
|
933
1032
|
}), await X(i, a), 0;
|
|
934
|
-
},
|
|
935
|
-
let t =
|
|
936
|
-
if (r) return
|
|
937
|
-
if ((process.env.NODE_ENV ?? "").toLowerCase() === "production") return
|
|
1033
|
+
}, Ct = async (e) => {
|
|
1034
|
+
let t = H(e), n = bt(e), r = l(e, "--plan");
|
|
1035
|
+
if (r) return St(r, e, t);
|
|
1036
|
+
if ((process.env.NODE_ENV ?? "").toLowerCase() === "production") return B.error("voltro db apply: refusing — auto-apply on prod is not allowed."), console.error("In production, schema changes go through `voltro db plan --json > plan.json`"), console.error("then `voltro db apply --plan plan.json` as an explicit step in your deploy pipeline."), 3;
|
|
938
1037
|
let i = await Q(t);
|
|
939
|
-
if (i === void 0) return
|
|
940
|
-
let a = await q(), o = await
|
|
941
|
-
if ("failed" in o) return
|
|
942
|
-
await J(a,
|
|
1038
|
+
if (i === void 0) return B.warn("db apply: no schema files found", { root: t }), 1;
|
|
1039
|
+
let a = await q(), o = await Mt(t, a);
|
|
1040
|
+
if ("failed" in o) return B.error("db apply: a file-based migration FAILED — the schema diff was NOT run"), console.error(o.failed), console.error("Fix the migration and re-run. `voltro db files` runs them alone."), 1;
|
|
1041
|
+
await J(a, w(S, W()));
|
|
943
1042
|
let s = E({
|
|
944
1043
|
declared: i,
|
|
945
1044
|
live: await Y(a),
|
|
@@ -947,12 +1046,12 @@ var O = class extends Error {
|
|
|
947
1046
|
...T(),
|
|
948
1047
|
...n === void 0 ? {} : { onlineAfter: n }
|
|
949
1048
|
});
|
|
950
|
-
Z(s), await
|
|
951
|
-
let c =
|
|
952
|
-
if (u.summary.blocked > 0 && !c.acknowledged) return
|
|
953
|
-
if (c.acknowledged &&
|
|
954
|
-
if (u.operations.length === 0) return
|
|
955
|
-
let d = l(e, "--note"), f = await J(a,
|
|
1049
|
+
Z(s), await _t(s, a);
|
|
1050
|
+
let c = mt(s), u = c.plan;
|
|
1051
|
+
if (u.summary.blocked > 0 && !c.acknowledged) return B.error(`db apply: refusing — ${u.summary.blocked} blocked operation(s). See the fix hints above.`), process.env.VOLTRO_DESTRUCTIVE_OK !== void 0 && (B.error(" VOLTRO_DESTRUCTIVE_OK is set but did not cover this plan — it only relaxes"), B.error(" `lossy` operations, and only the tables it names. Anything else stays refused.")), 2;
|
|
1052
|
+
if (c.acknowledged && B.warn(`db apply: proceeding with ${s.summary.blocked} acknowledged lossy operation(s) (VOLTRO_DESTRUCTIVE_OK)`), yt(u, e)) return 2;
|
|
1053
|
+
if (u.operations.length === 0) return B.info("db apply: schema is up to date — no DDL to apply (change triggers are reconciled separately below)"), await Ot(a), l(e, "--note") !== void 0 && (B.warn("db apply: --note was ignored — an empty plan writes no history row to attach it to."), console.error("To record that the CURRENT live schema is the accepted baseline, use `voltro db drift --accept`.")), await X(i, a), 0;
|
|
1054
|
+
let d = l(e, "--note"), f = await J(a, x.SqlClient.pipe(g.flatMap((e) => se(e, u, {
|
|
956
1055
|
declared: i,
|
|
957
1056
|
appliedBy: process.env.USER ? `cli:${process.env.USER}` : "cli:user",
|
|
958
1057
|
environment: (process.env.NODE_ENV ?? "dev").toLowerCase() === "staging" ? "staging" : "dev",
|
|
@@ -966,12 +1065,12 @@ var O = class extends Error {
|
|
|
966
1065
|
...n === void 0 ? {} : { onlineAfter: n }
|
|
967
1066
|
})))
|
|
968
1067
|
}))));
|
|
969
|
-
return
|
|
1068
|
+
return B.info(`db apply: applied ${f.appliedOps} op(s) in ${f.durationMs}ms`, {
|
|
970
1069
|
id: f.id,
|
|
971
1070
|
fingerprint: f.fingerprint.slice(0, 16)
|
|
972
1071
|
}), await X(i, a), 0;
|
|
973
|
-
},
|
|
974
|
-
let t = Math.max(1, Number(l(e, "--limit") ?? "20")), n = await J(await q(),
|
|
1072
|
+
}, wt = async (e) => {
|
|
1073
|
+
let t = Math.max(1, Number(l(e, "--limit") ?? "20")), n = await J(await q(), x.SqlClient.pipe(g.flatMap((e) => e`
|
|
975
1074
|
SELECT ${e("id")}, ${e("fingerprint")}, ${e("operations")},
|
|
976
1075
|
${e("appliedBy")}, ${e("environment")}, ${e("source")},
|
|
977
1076
|
${e("durationMs")}, ${e("notes")}, ${e("appliedAt")}
|
|
@@ -979,25 +1078,25 @@ var O = class extends Error {
|
|
|
979
1078
|
ORDER BY ${e("appliedAt")} DESC
|
|
980
1079
|
LIMIT ${t}
|
|
981
1080
|
`)));
|
|
982
|
-
if (n.length === 0) return
|
|
1081
|
+
if (n.length === 0) return B.info("db plans: no planner-based migrations applied yet"), console.log("Run `voltro db apply` after a schema change to start a plan history."), 0;
|
|
983
1082
|
console.log(`\n${n.length} plan${n.length === 1 ? "" : "s"} (newest first):\n`);
|
|
984
1083
|
for (let e of n) {
|
|
985
1084
|
let t = 0;
|
|
986
1085
|
try {
|
|
987
|
-
t = JSON.parse(
|
|
1086
|
+
t = JSON.parse(kt(e.operations)).length;
|
|
988
1087
|
} catch {}
|
|
989
1088
|
console.log(` ${e.id} fp=${D(e.fingerprint)} env=${e.environment} src=${e.source} ${t} op(s) ${e.durationMs}ms ${$(e.appliedAt)} by=${e.appliedBy}`), e.notes && console.log(` [2mnote: ${e.notes}[0m`);
|
|
990
1089
|
}
|
|
991
1090
|
return console.log(), 0;
|
|
992
|
-
},
|
|
1091
|
+
}, Tt = async (e) => {
|
|
993
1092
|
let t = e[0];
|
|
994
1093
|
if (!t) return console.error("voltro db restore-snapshot <plan-id>"), console.error(" Renames soft-dropped columns/tables from a plan back to their original names."), console.error(" Requires the plan to have been applied with VOLTRO_SOFT_DROP=1."), 2;
|
|
995
|
-
let n = await q(), r = await J(n,
|
|
1094
|
+
let n = await q(), r = await J(n, x.SqlClient.pipe(g.flatMap((e) => e`SELECT ${e("id")}, ${e("operations")}
|
|
996
1095
|
FROM ${e("_voltro_migration_plans")} WHERE ${e("id")} = ${t}`)));
|
|
997
1096
|
if (r.length === 0) return console.error(`restore-snapshot: plan '${t}' not found`), 1;
|
|
998
1097
|
let i;
|
|
999
1098
|
try {
|
|
1000
|
-
i = JSON.parse(
|
|
1099
|
+
i = JSON.parse(kt(r[0].operations));
|
|
1001
1100
|
} catch {
|
|
1002
1101
|
return console.error(`restore-snapshot: plan '${t}' has corrupted operations JSON`), 1;
|
|
1003
1102
|
}
|
|
@@ -1011,7 +1110,7 @@ var O = class extends Error {
|
|
|
1011
1110
|
if (a.length === 0 && o.length === 0) return console.log(`restore-snapshot: plan '${t}' has no drop-column / drop-table ops to restore`), 0;
|
|
1012
1111
|
let s = 0;
|
|
1013
1112
|
for (let e of a) {
|
|
1014
|
-
let t = await J(n,
|
|
1113
|
+
let t = await J(n, x.SqlClient.pipe(g.flatMap((t) => t`
|
|
1015
1114
|
SELECT column_name FROM information_schema.columns
|
|
1016
1115
|
WHERE ${t.unsafe(K(W()))}
|
|
1017
1116
|
AND table_name = ${e.table}
|
|
@@ -1023,11 +1122,11 @@ var O = class extends Error {
|
|
|
1023
1122
|
continue;
|
|
1024
1123
|
}
|
|
1025
1124
|
let r = t[0].column_name;
|
|
1026
|
-
await J(n,
|
|
1125
|
+
await J(n, x.SqlClient.pipe(g.flatMap((t) => t.unsafe(`ALTER TABLE ${G(e.table)} RENAME COLUMN ${G(r)} TO ${G(e.column)}`)))), s++, console.log(` ↩ restored ${e.table}.${e.column} (from ${r})`);
|
|
1027
1126
|
}
|
|
1028
1127
|
let c = 0;
|
|
1029
1128
|
for (let e of o) {
|
|
1030
|
-
let t = await J(n,
|
|
1129
|
+
let t = await J(n, x.SqlClient.pipe(g.flatMap((t) => t`
|
|
1031
1130
|
SELECT table_name FROM information_schema.tables
|
|
1032
1131
|
WHERE ${t.unsafe(K(W()))}
|
|
1033
1132
|
AND table_name LIKE ${e.table + "__dropped_%"}
|
|
@@ -1038,18 +1137,18 @@ var O = class extends Error {
|
|
|
1038
1137
|
continue;
|
|
1039
1138
|
}
|
|
1040
1139
|
let r = t[0].table_name;
|
|
1041
|
-
await J(n,
|
|
1140
|
+
await J(n, x.SqlClient.pipe(g.flatMap((t) => t.unsafe(`ALTER TABLE ${G(r)} RENAME TO ${G(e.table)}`)))), c++, console.log(` ↩ restored table ${e.table} (from ${r})`);
|
|
1042
1141
|
}
|
|
1043
1142
|
return console.log(`\n✓ restored ${s} column(s) + ${c} table(s) from plan '${t}'\n`), 0;
|
|
1044
|
-
},
|
|
1143
|
+
}, Et = async (e) => {
|
|
1045
1144
|
let t = l(e, "--before");
|
|
1046
1145
|
if (!t) return console.error("voltro db gc-snapshots --before <date> [--dry-run]"), console.error(" Permanently drops soft-dropped columns/tables (<name>__dropped_<ts>) older than <date>."), console.error(" <date> is ISO — e.g. 2026-01-01 or 2026-01-01T00:00:00Z. NOT reversible."), 2;
|
|
1047
1146
|
let n = t.replace(/[^0-9]/g, "");
|
|
1048
1147
|
if (n.length < 8) return console.error(`gc-snapshots: --before must be at least a full date (YYYY-MM-DD), got "${t}"`), 2;
|
|
1049
|
-
let r = (n + "00000000000000").slice(0, 14), i = e.includes("--dry-run"), a = await q(), o = await J(a,
|
|
1148
|
+
let r = (n + "00000000000000").slice(0, 14), i = e.includes("--dry-run"), a = await q(), o = await J(a, x.SqlClient.pipe(g.flatMap((e) => e`
|
|
1050
1149
|
SELECT table_name, column_name FROM information_schema.columns
|
|
1051
1150
|
WHERE ${e.unsafe(K(W()))} AND column_name LIKE ${"%__dropped_%"}
|
|
1052
|
-
`))), s = await J(a,
|
|
1151
|
+
`))), s = await J(a, x.SqlClient.pipe(g.flatMap((e) => e`
|
|
1053
1152
|
SELECT table_name FROM information_schema.tables
|
|
1054
1153
|
WHERE ${e.unsafe(K(W()))} AND table_name LIKE ${"%__dropped_%"}
|
|
1055
1154
|
`))), c = (e) => {
|
|
@@ -1058,23 +1157,23 @@ var O = class extends Error {
|
|
|
1058
1157
|
}, u = 0;
|
|
1059
1158
|
for (let e of o) {
|
|
1060
1159
|
let t = c(e.column_name);
|
|
1061
|
-
t === null || t >= r || (console.log(` ${i ? "[dry-run] would drop column" : "dropping column"} ${e.table_name}.${e.column_name}`), i || await J(a,
|
|
1160
|
+
t === null || t >= r || (console.log(` ${i ? "[dry-run] would drop column" : "dropping column"} ${e.table_name}.${e.column_name}`), i || await J(a, x.SqlClient.pipe(g.flatMap((t) => t.unsafe(`ALTER TABLE ${G(e.table_name)} DROP COLUMN ${G(e.column_name)}`)))), u++);
|
|
1062
1161
|
}
|
|
1063
1162
|
for (let e of s) {
|
|
1064
1163
|
let t = c(e.table_name);
|
|
1065
|
-
t === null || t >= r || (console.log(` ${i ? "[dry-run] would drop table" : "dropping table"} ${e.table_name}`), i || await J(a,
|
|
1164
|
+
t === null || t >= r || (console.log(` ${i ? "[dry-run] would drop table" : "dropping table"} ${e.table_name}`), i || await J(a, x.SqlClient.pipe(g.flatMap((t) => t.unsafe(`DROP TABLE ${G(e.table_name)}`)))), u++);
|
|
1066
1165
|
}
|
|
1067
1166
|
return console.log(`\n${i ? `[dry-run] ${u} snapshot(s) would be dropped` : `✓ dropped ${u} snapshot(s)`} (older than ${r}).\n`), 0;
|
|
1068
|
-
},
|
|
1167
|
+
}, Dt = async (e) => {
|
|
1069
1168
|
let t = l(e, "--before");
|
|
1070
1169
|
if (!t) return console.error("voltro db squash --before <iso-date> [--note \"<text>\"]"), 2;
|
|
1071
1170
|
let n = t;
|
|
1072
1171
|
if (Number.isNaN(new Date(n).getTime())) return console.error(`voltro db squash: --before '${n}' is not a valid ISO date`), 2;
|
|
1073
|
-
let r = l(e, "--note"), i =
|
|
1074
|
-
if (a === void 0) return
|
|
1075
|
-
let o =
|
|
1172
|
+
let r = l(e, "--note"), i = H(e), a = await Q(i);
|
|
1173
|
+
if (a === void 0) return B.warn("db squash: no schema files found", { root: i }), 1;
|
|
1174
|
+
let o = fe(le(a)), s = await q();
|
|
1076
1175
|
try {
|
|
1077
|
-
let e = await J(s,
|
|
1176
|
+
let e = await J(s, ye({
|
|
1078
1177
|
before: n,
|
|
1079
1178
|
fingerprint: o,
|
|
1080
1179
|
appliedBy: process.env.USER ?? "cli",
|
|
@@ -1085,10 +1184,10 @@ var O = class extends Error {
|
|
|
1085
1184
|
} catch (e) {
|
|
1086
1185
|
return console.error(`squash refused: ${e.message}`), 1;
|
|
1087
1186
|
}
|
|
1088
|
-
},
|
|
1187
|
+
}, Ot = async (e) => {
|
|
1089
1188
|
try {
|
|
1090
|
-
let t =
|
|
1091
|
-
await J(e,
|
|
1189
|
+
let t = fe(await Y(e));
|
|
1190
|
+
await J(e, x.SqlClient.pipe(g.flatMap((e) => e`
|
|
1092
1191
|
UPDATE ${e("_voltro_migration_plans")}
|
|
1093
1192
|
SET ${e("liveFingerprint")} = ${t}
|
|
1094
1193
|
WHERE ${e("id")} = (
|
|
@@ -1100,7 +1199,7 @@ var O = class extends Error {
|
|
|
1100
1199
|
AND ${e("liveFingerprint")} IS NULL
|
|
1101
1200
|
`)));
|
|
1102
1201
|
} catch {}
|
|
1103
|
-
},
|
|
1202
|
+
}, kt = (e) => typeof e == "string" ? e : e == null ? "" : JSON.stringify(e), $ = (e) => e instanceof Date ? e.toISOString() : String(e ?? ""), At = (e) => e === void 0 ? {
|
|
1104
1203
|
accept: !1,
|
|
1105
1204
|
reason: "no-declared-schema"
|
|
1106
1205
|
} : e > 0 ? {
|
|
@@ -1109,19 +1208,19 @@ var O = class extends Error {
|
|
|
1109
1208
|
} : {
|
|
1110
1209
|
accept: !0,
|
|
1111
1210
|
reason: "converged"
|
|
1112
|
-
},
|
|
1113
|
-
let t = e.includes("--accept"), n = await q(), r = await J(n,
|
|
1211
|
+
}, jt = async (e) => {
|
|
1212
|
+
let t = e.includes("--accept"), n = await q(), r = await J(n, x.SqlClient.pipe(g.flatMap((e) => e`
|
|
1114
1213
|
SELECT * FROM ${e("_voltro_migration_plans")}
|
|
1115
1214
|
ORDER BY ${e("appliedAt")} DESC
|
|
1116
1215
|
LIMIT 1
|
|
1117
|
-
`))), i = await Y(n), a =
|
|
1118
|
-
if (r.length === 0) return
|
|
1216
|
+
`))), i = await Y(n), a = fe(i);
|
|
1217
|
+
if (r.length === 0) return B.info("db drift: no _voltro_migration_plans rows yet"), console.log(`live user-schema fingerprint: ${D(a)}`), console.log("No baseline to compare against. Run `voltro db apply` to lock in the current state."), 0;
|
|
1119
1218
|
let o = r[0];
|
|
1120
|
-
if (o.liveFingerprint === null || o.liveFingerprint === void 0) return
|
|
1121
|
-
if (o.liveFingerprint === a) return
|
|
1219
|
+
if (o.liveFingerprint === null || o.liveFingerprint === void 0) return B.warn("db drift: no drift baseline recorded yet — cannot compare"), console.log(), console.log(` last applied: ${o.id} at ${$(o.appliedAt)}`), console.log(` live now: ${D(a)}`), console.log(), console.log("That row was written before the framework recorded a live-schema baseline,"), console.log("and the value it does carry is the DECLARED snapshot's hash — not comparable"), console.log("with a live one. Run `voltro db apply` once to establish the baseline (a no-op"), console.log("apply backfills it too); use `voltro db plan` meanwhile, which compares declared"), console.log("against live directly."), console.log(), console.log("Exit code 3 — NOT 0. \"Did not compare\" is not \"clean\", and a gate on the exit"), console.log("code has to be able to tell them apart."), 3;
|
|
1220
|
+
if (o.liveFingerprint === a) return B.info(`db drift: live matches last applied (${D(a)})`), 0;
|
|
1122
1221
|
let s = await (async () => {
|
|
1123
1222
|
try {
|
|
1124
|
-
let t = await Q(
|
|
1223
|
+
let t = await Q(H(e));
|
|
1125
1224
|
return t === void 0 ? void 0 : E({
|
|
1126
1225
|
declared: t,
|
|
1127
1226
|
live: i,
|
|
@@ -1133,14 +1232,14 @@ var O = class extends Error {
|
|
|
1133
1232
|
}
|
|
1134
1233
|
})();
|
|
1135
1234
|
if (t) {
|
|
1136
|
-
let e =
|
|
1137
|
-
if (e.reason === "no-declared-schema") return
|
|
1235
|
+
let e = At(s?.length);
|
|
1236
|
+
if (e.reason === "no-declared-schema") return B.error("db drift --accept: no schema files found — cannot verify the live schema is current."), 1;
|
|
1138
1237
|
if (!e.accept && s !== void 0) {
|
|
1139
|
-
|
|
1238
|
+
B.error(`db drift --accept: refusing — ${s.length} operation(s) still pending against your declared schema.`), console.log();
|
|
1140
1239
|
for (let e of s.slice(0, 10)) console.log(` - ${e.op.kind} ${"table" in e.op ? `[${e.op.table}]` : ""}`);
|
|
1141
1240
|
return s.length > 10 && console.log(` … and ${s.length - 10} more`), console.log(), console.log("`--accept` records the CURRENT live schema as the applied baseline. It is for a"), console.log("change that already landed out of band and left the schema correct — not for one"), console.log("that has not landed. Run `voltro db apply` instead; it writes a real baseline."), 4;
|
|
1142
1241
|
}
|
|
1143
|
-
return await J(n,
|
|
1242
|
+
return await J(n, x.SqlClient.pipe(g.flatMap((e) => e`
|
|
1144
1243
|
UPDATE ${e("_voltro_migration_plans")}
|
|
1145
1244
|
SET ${e("liveFingerprint")} = ${a}
|
|
1146
1245
|
WHERE ${e("id")} = (
|
|
@@ -1149,43 +1248,43 @@ var O = class extends Error {
|
|
|
1149
1248
|
ORDER BY ${e("appliedAt")} DESC LIMIT 1
|
|
1150
1249
|
) AS ${e("newest")}
|
|
1151
1250
|
)
|
|
1152
|
-
`))),
|
|
1251
|
+
`))), B.info(`db drift: accepted the live schema as the baseline (${D(a)})`), console.log(), console.log("`voltro db plan` reports 0 operations against this schema, so the live state already"), console.log("matches your declaration — only the ledger did not know. It does now."), console.log(), console.log("No history entry was invented: the latest row's baseline was updated in place,"), console.log("because no migration ran."), 0;
|
|
1153
1252
|
}
|
|
1154
|
-
return
|
|
1155
|
-
},
|
|
1253
|
+
return B.error("db drift: live schema DIVERGED from last applied state"), console.log(), console.log(` baseline: ${D(o.liveFingerprint)} at ${$(o.appliedAt)} (${o.id})`), console.log(` live now: ${D(a)}`), console.log(), console.log("Something changed the live schema after the last apply. This command can see"), console.log("THAT it changed, not what or who — the fingerprints are hashes, not a diff."), console.log(), s === void 0 ? (console.log("No schema files found here, so the declared side could not be compared."), console.log("Run `voltro db plan` from your project root for the actual difference."), 4) : s.length === 0 ? (console.log("Your DECLARED schema is already satisfied — `voltro db plan` reports 0 operations"), console.log("against this database. So the live schema is not wrong, only unrecorded: something"), console.log("applied a change without going through the planner (a hand-run ALTER, a DBA"), console.log("window, a restored dump), or it touched a table your code does not declare."), console.log(), console.log("If that was deliberate and the schema is right, record it:"), console.log(), console.log(" voltro db drift --accept"), console.log(), console.log("It updates the latest ledger row's baseline to the live schema and invents no"), console.log("history entry. Drift then measures from here."), 4) : (console.log(`\`voltro db plan\` reports ${s.length} operation(s) still pending against your`), console.log("declared schema, so this is not merely an unrecorded change — run it for the diff,"), console.log("then `voltro db apply`, which writes a fresh baseline of its own."), 4);
|
|
1254
|
+
}, Mt = async (e, t) => {
|
|
1156
1255
|
let n = (process.env.NODE_ENV ?? "").toLowerCase() === "production" ? "prod" : (process.env.NODE_ENV ?? "").toLowerCase() === "staging" ? "staging" : "dev";
|
|
1157
1256
|
try {
|
|
1158
|
-
let r = await J(t,
|
|
1257
|
+
let r = await J(t, x.SqlClient.pipe(g.flatMap((t) => ve(t, {
|
|
1159
1258
|
projectRoot: e,
|
|
1160
1259
|
env: n,
|
|
1161
1260
|
appliedBy: process.env.USER ? `cli:${process.env.USER}` : "cli:user"
|
|
1162
1261
|
}))));
|
|
1163
|
-
return r.applied.length > 0 &&
|
|
1262
|
+
return r.applied.length > 0 && B.info(`db apply: applied ${r.applied.length} pending file-based migration(s) first`, { applied: r.applied.map((e) => `${e.id} (${e.durationMs}ms)`) }), { applied: r.applied.length };
|
|
1164
1263
|
} catch (e) {
|
|
1165
1264
|
return { failed: e };
|
|
1166
1265
|
}
|
|
1167
|
-
},
|
|
1168
|
-
let t =
|
|
1169
|
-
await J(n,
|
|
1170
|
-
let i = await J(n,
|
|
1266
|
+
}, Nt = async (e) => {
|
|
1267
|
+
let t = H(e), n = await q(), r = (process.env.NODE_ENV ?? "").toLowerCase() === "production" ? "prod" : (process.env.NODE_ENV ?? "").toLowerCase() === "staging" ? "staging" : "dev";
|
|
1268
|
+
await J(n, w(S, W()));
|
|
1269
|
+
let i = await J(n, x.SqlClient.pipe(g.flatMap((e) => ve(e, {
|
|
1171
1270
|
projectRoot: t,
|
|
1172
1271
|
env: r,
|
|
1173
1272
|
appliedBy: process.env.USER ? `cli:${process.env.USER}` : "cli:user"
|
|
1174
1273
|
}))));
|
|
1175
|
-
return i.applied.length === 0 ? (
|
|
1274
|
+
return i.applied.length === 0 ? (B.info(`db files: no pending file-based migrations (${i.skipped.length} already applied)`), 0) : (B.info(`db files: applied ${i.applied.length} migration(s)`, {
|
|
1176
1275
|
applied: i.applied.map((e) => `${e.id} (${e.durationMs}ms)`),
|
|
1177
1276
|
skipped: i.skipped.length
|
|
1178
1277
|
}), 0);
|
|
1179
|
-
},
|
|
1180
|
-
let t =
|
|
1181
|
-
if (!n) return
|
|
1182
|
-
if ((process.env.NODE_ENV ?? "").toLowerCase() === "production") return
|
|
1183
|
-
let r = await J(await q(),
|
|
1278
|
+
}, Pt = async (e) => {
|
|
1279
|
+
let t = H(e), n = e.find((e) => !e.startsWith("--"));
|
|
1280
|
+
if (!n) return B.error("voltro db rollback-file <migration-id>"), 2;
|
|
1281
|
+
if ((process.env.NODE_ENV ?? "").toLowerCase() === "production") return B.error("voltro db rollback-file: refusing on prod — schema rollback runs as an explicit deploy step."), 3;
|
|
1282
|
+
let r = await J(await q(), x.SqlClient.pipe(g.flatMap((e) => ge(e, {
|
|
1184
1283
|
projectRoot: t,
|
|
1185
1284
|
id: n
|
|
1186
1285
|
}))));
|
|
1187
|
-
return
|
|
1188
|
-
},
|
|
1286
|
+
return B.info(`db rollback-file: rolled back ${r.id} in ${r.durationMs}ms`), 0;
|
|
1287
|
+
}, Ft = () => {
|
|
1189
1288
|
let e = /* @__PURE__ */ new Date();
|
|
1190
1289
|
return [
|
|
1191
1290
|
e.getUTCFullYear(),
|
|
@@ -1195,7 +1294,7 @@ var O = class extends Error {
|
|
|
1195
1294
|
String(e.getUTCMinutes()).padStart(2, "0"),
|
|
1196
1295
|
String(e.getUTCSeconds()).padStart(2, "0")
|
|
1197
1296
|
].join("");
|
|
1198
|
-
},
|
|
1297
|
+
}, It = (e) => e.trim().toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_+|_+$/g, "") || "migration", Lt = (e, t) => `import { defineMigration, SqlClient } from '@voltro/database'
|
|
1199
1298
|
import { Effect } from 'effect'
|
|
1200
1299
|
|
|
1201
1300
|
// A hand-written migration. Reach for this when the DECLARATIVE differ can't
|
|
@@ -1227,24 +1326,24 @@ export default defineMigration({
|
|
|
1227
1326
|
}),
|
|
1228
1327
|
],
|
|
1229
1328
|
})
|
|
1230
|
-
`,
|
|
1231
|
-
plan:
|
|
1232
|
-
apply:
|
|
1233
|
-
plans:
|
|
1234
|
-
drift:
|
|
1235
|
-
files:
|
|
1236
|
-
"rollback-file":
|
|
1237
|
-
migrate:
|
|
1238
|
-
rollback:
|
|
1239
|
-
status:
|
|
1240
|
-
seed:
|
|
1329
|
+
`, Rt = {
|
|
1330
|
+
plan: xt,
|
|
1331
|
+
apply: Ct,
|
|
1332
|
+
plans: wt,
|
|
1333
|
+
drift: jt,
|
|
1334
|
+
files: Nt,
|
|
1335
|
+
"rollback-file": Pt,
|
|
1336
|
+
migrate: at,
|
|
1337
|
+
rollback: ot,
|
|
1338
|
+
status: st,
|
|
1339
|
+
seed: lt,
|
|
1241
1340
|
generate: async (e) => {
|
|
1242
|
-
let { value: t, rest: n } = c(e, "--root"), { value: r, rest: i } = c(n, "--name"), a = t ? m(t) : process.cwd(), o =
|
|
1243
|
-
return await v.mkdir(l, { recursive: !0 }), await v.writeFile(d,
|
|
1341
|
+
let { value: t, rest: n } = c(e, "--root"), { value: r, rest: i } = c(n, "--name"), a = t ? m(t) : process.cwd(), o = It(r ?? u(i) ?? "migration"), s = `${Ft()}_${o}`, l = f(a, "migrations"), d = f(l, `${s}.migration.ts`);
|
|
1342
|
+
return await v.mkdir(l, { recursive: !0 }), await v.writeFile(d, Lt(s, o), "utf8"), B.info(`created ${p(a, d)}`), console.error("Fill in the apply/undo steps, then run `voltro db migrate` (rollback with `voltro db rollback`)."), console.error("For a plain schema shape change you may not need this — `voltro db apply` diffs declared vs live."), 0;
|
|
1244
1343
|
},
|
|
1245
|
-
squash:
|
|
1246
|
-
"restore-snapshot":
|
|
1247
|
-
"gc-snapshots":
|
|
1344
|
+
squash: Dt,
|
|
1345
|
+
"restore-snapshot": Tt,
|
|
1346
|
+
"gc-snapshots": Et,
|
|
1248
1347
|
adopt: async (e) => {
|
|
1249
1348
|
let t = (t) => {
|
|
1250
1349
|
let n = e.indexOf(`--${t}`);
|
|
@@ -1258,18 +1357,18 @@ export default defineMigration({
|
|
|
1258
1357
|
into: r,
|
|
1259
1358
|
map: o.map,
|
|
1260
1359
|
...o.leaveUnset ? { leaveUnset: o.leaveUnset } : {}
|
|
1261
|
-
}, c = W(), l = await q(), u = await J(l,
|
|
1360
|
+
}, c = W(), l = await q(), u = await J(l, x.SqlClient.pipe(g.flatMap(pe))), d = (e) => u.tables.find((t) => t.name === e)?.columns.map((e) => ({
|
|
1262
1361
|
name: e.name,
|
|
1263
1362
|
nullable: e.nullable,
|
|
1264
1363
|
hasDefault: e.defaultValue !== void 0 && e.defaultValue !== null
|
|
1265
1364
|
})), f = d(n), p = d(r);
|
|
1266
1365
|
if (f === void 0) return console.error(`voltro db adopt: '${n}' does not exist in the database`), 1;
|
|
1267
1366
|
if (p === void 0) return console.error(`voltro db adopt: '${r}' does not exist in the database — boot once so the plugin's table is created`), 1;
|
|
1268
|
-
let h = async (e) => await J(l,
|
|
1269
|
-
for (let e of
|
|
1367
|
+
let h = async (e) => await J(l, x.SqlClient.pipe(g.flatMap((t) => t.unsafe(e)))), _ = async (e) => Number(Object.values((await h(`SELECT COUNT(*) AS n FROM "${e}"`))[0] ?? { n: 0 })[0] ?? 0), v = Ce(s, f, p, await _(r));
|
|
1368
|
+
for (let e of Te(s, v, await _(n))) console.log(e);
|
|
1270
1369
|
if (v.fatal) return 1;
|
|
1271
1370
|
if (!e.includes("--apply")) return console.log(""), console.log(" (dry run — nothing written. Re-run with --apply.)"), 0;
|
|
1272
|
-
let y = await
|
|
1371
|
+
let y = await Ae(s, v, c, "id", void 0, h, { keepSource: e.includes("--keep-source") });
|
|
1273
1372
|
return console.log(""), console.log(` copied ${y.copied}/${y.sourceRows} row(s)`), console.log(` snapshot: ${y.snapshotTable} — restore from here, then drop it yourself`), y.verified ? (console.log(y.sourceDropped ? ` dropped ${n}` : ` ${n} left in place (--keep-source)`), 0) : (console.error(" ✗ counts DIFFER — the source was NOT dropped. Compare the two tables before doing anything else."), 1);
|
|
1274
1373
|
},
|
|
1275
1374
|
"scan-credentials": async (e) => {
|
|
@@ -1284,21 +1383,82 @@ export default defineMigration({
|
|
|
1284
1383
|
column: a
|
|
1285
1384
|
});
|
|
1286
1385
|
}
|
|
1287
|
-
let r = [...
|
|
1288
|
-
return console.log(
|
|
1386
|
+
let r = [...je, ...n], i = await J(await q(), x.SqlClient.pipe(g.flatMap((e) => g.promise(() => ze(e, t, r, G)))));
|
|
1387
|
+
return console.log(Be(i, t)), Ve(i);
|
|
1388
|
+
},
|
|
1389
|
+
"encrypt-column": async (e) => {
|
|
1390
|
+
let t = W(), n = e.includes("--dry-run"), r = e.includes("--yes"), i = (t) => {
|
|
1391
|
+
let n = e.indexOf(t);
|
|
1392
|
+
return n >= 0 ? e[n + 1] : void 0;
|
|
1393
|
+
}, a = Math.max(1, Number(i("--batch") ?? 500)), o = i("--key-env") ?? "FIELD_ENCRYPTION_KEY", s = process.env[o];
|
|
1394
|
+
if (!s) throw new O(`voltro db encrypt-column: ${o} is not set. It must be the SAME key your app runs with (\`governancePlugin({ fieldEncryption: { key } })\`) — encrypting with a different one produces rows the app cannot read. Use --key-env to name a different variable.`);
|
|
1395
|
+
let c = ee(s), l = e.filter((e) => !e.startsWith("--") && e.includes(".")).map(Ke);
|
|
1396
|
+
if (l.length === 0) throw new O("voltro db encrypt-column: name at least one <table>.<column>");
|
|
1397
|
+
let u = await q(), d = [];
|
|
1398
|
+
for (let e of l) {
|
|
1399
|
+
let i = G(e.table), s = G(e.column), l = await J(u, x.SqlClient.pipe(g.flatMap((n) => g.promise(async () => {
|
|
1400
|
+
let r = await g.runPromise(n.unsafe(`SELECT character_maximum_length AS n FROM information_schema.columns\n WHERE ${K(t)} AND table_name = '${e.table}' AND column_name = '${e.column}'`));
|
|
1401
|
+
if (r.length === 0) throw new O(`voltro db encrypt-column: ${e.table}.${e.column} does not exist on this database`);
|
|
1402
|
+
let i = Object.values(r[0])[0];
|
|
1403
|
+
return i == null ? void 0 : Number(i);
|
|
1404
|
+
})))), f = await J(u, x.SqlClient.pipe(g.flatMap((e) => g.promise(async () => await g.runPromise(e.unsafe(`SELECT ${G("id")} AS id, ${s} AS v FROM ${i}`)))))), p = 0;
|
|
1405
|
+
for (let e of f) typeof e.v != "string" || C(e.v) || (p = Math.max(p, Qe(e.v)));
|
|
1406
|
+
let m = Je(p, l);
|
|
1407
|
+
if (!m.fits) return console.error(` ✗ ${Ye(e, m)}`), 1;
|
|
1408
|
+
if ($e(f.map((e) => e.v).find((e) => typeof e == "string" && C(e)), c) === !1) return console.error(` ✗ ${et(e, o)}`), 1;
|
|
1409
|
+
let h = f.filter((t) => Ze(t.v, c, e) !== void 0).length;
|
|
1410
|
+
if (!n && !r) return console.error(` ✗ ${e.table}.${e.column}: ${h} row(s) would be rewritten in place. Re-run with --yes, or --dry-run to see the counts without writing.`), 1;
|
|
1411
|
+
let _ = 0, v = 0, y = 0, b = [];
|
|
1412
|
+
for (let t of f) {
|
|
1413
|
+
if (t.v === null || t.v === void 0 || t.v === "") {
|
|
1414
|
+
v += 1;
|
|
1415
|
+
continue;
|
|
1416
|
+
}
|
|
1417
|
+
if (typeof t.v == "string" && C(t.v)) {
|
|
1418
|
+
_ += 1;
|
|
1419
|
+
continue;
|
|
1420
|
+
}
|
|
1421
|
+
let n = Ze(t.v, c, e);
|
|
1422
|
+
if (n === void 0) {
|
|
1423
|
+
v += 1;
|
|
1424
|
+
continue;
|
|
1425
|
+
}
|
|
1426
|
+
b.push({
|
|
1427
|
+
id: t.id,
|
|
1428
|
+
value: n
|
|
1429
|
+
}), y += 1;
|
|
1430
|
+
}
|
|
1431
|
+
if (!n && b.length > 0) for (let t = 0; t < b.length; t += a) {
|
|
1432
|
+
let n = b.slice(t, t + a);
|
|
1433
|
+
await J(u, x.SqlClient.pipe(g.flatMap((t) => g.promise(async () => {
|
|
1434
|
+
for (let r of n) await g.runPromise(t`UPDATE ${t(e.table)} SET ${t(e.column)} = ${r.value} WHERE ${t("id")} = ${r.id}`);
|
|
1435
|
+
})))), B.info(`encrypt-column ${e.table}.${e.column}: ${Math.min(t + n.length, b.length)}/${b.length}`);
|
|
1436
|
+
}
|
|
1437
|
+
d.push({
|
|
1438
|
+
table: e.table,
|
|
1439
|
+
column: e.column,
|
|
1440
|
+
scanned: f.length,
|
|
1441
|
+
alreadyEncrypted: _,
|
|
1442
|
+
empty: v,
|
|
1443
|
+
encrypted: y
|
|
1444
|
+
});
|
|
1445
|
+
}
|
|
1446
|
+
console.log(`field encryption · dialect ${t}${n ? " · DRY RUN, nothing written" : ""}`), console.log("");
|
|
1447
|
+
for (let e of d) console.log(Xe(e, n));
|
|
1448
|
+
return n || console.log(tt(l)), 0;
|
|
1289
1449
|
}
|
|
1290
|
-
},
|
|
1450
|
+
}, zt = async (e) => {
|
|
1291
1451
|
let [t, ...n] = e;
|
|
1292
1452
|
if (!t) {
|
|
1293
1453
|
console.error("voltro db <subcommand> [args]"), console.error("subcommands:");
|
|
1294
|
-
for (let e of Object.keys(
|
|
1454
|
+
for (let e of Object.keys(Rt)) console.error(` ${e}`);
|
|
1295
1455
|
return 1;
|
|
1296
1456
|
}
|
|
1297
|
-
let r =
|
|
1457
|
+
let r = Rt[t];
|
|
1298
1458
|
if (!r) return console.error(`voltro db: unknown subcommand '${t}'`), 2;
|
|
1299
|
-
(process.env.DB_DIRECT_URL !== void 0 || process.env.DB_MIGRATE_URL !== void 0) &&
|
|
1459
|
+
(process.env.DB_DIRECT_URL !== void 0 || process.env.DB_MIGRATE_URL !== void 0) && B.info("using a DIRECT (non-pooler) connection for the db-command path (DB_DIRECT_URL/DB_MIGRATE_URL set)");
|
|
1300
1460
|
let i = (e) => {
|
|
1301
|
-
|
|
1461
|
+
rt(e) ? B.error(it) : console.error(e), process.exit(1);
|
|
1302
1462
|
};
|
|
1303
1463
|
process.on("uncaughtException", i);
|
|
1304
1464
|
try {
|
|
@@ -1308,4 +1468,4 @@ export default defineMigration({
|
|
|
1308
1468
|
}
|
|
1309
1469
|
};
|
|
1310
1470
|
//#endregion
|
|
1311
|
-
export {
|
|
1471
|
+
export { ft as a, O as c, At as i, xe as l, U as n, rt as o, dt as r, zt as s, it as t };
|