@voltro/cli 0.55.0 → 0.57.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 +691 -0
- package/bin/voltro.mjs +24 -0
- package/dist/apiBuild-B83Cb2Rv.js +2 -0
- package/dist/{apiBuild-CMvLJM_K.js → apiBuild-DDr2aNFd.js} +119 -88
- package/dist/bin.js +1 -1
- package/dist/{build-S0QOzqPT.js → build-QKP6Bm0J.js} +308 -279
- package/dist/buildReport-52gHKgfO.js +64 -0
- package/dist/{checkCommand-DNkY5kwF.js → checkCommand-BISqx1OJ.js} +1 -1
- package/dist/{checkCommand-fbj9GDjN.js → checkCommand-DUtMWjcR.js} +6 -6
- package/dist/{clusterCmd-DrVFCzSj.js → clusterCmd-CdLB1GkT.js} +1 -1
- package/dist/{codegen-SIepQtUl.js → codegen-Bth5lUTU.js} +2 -1
- package/dist/codegen-DbH7NbCR.js +2 -0
- package/dist/{codegenCommand-3TDJezom.js → codegenCommand-DnVuDxwT.js} +11 -11
- package/dist/{codemodRunner-C2zxZUIw.js → codemodRunner-BlQPfjzA.js} +222 -0
- package/dist/{commands-BBYJ7Q3B.js → commands-CRbxgxv0.js} +35 -35
- package/dist/{dashboardCommand-D2kmyCLL.js → dashboardCommand-Bf_-Ne3P.js} +5 -5
- package/dist/{dataCommand-BEPPQiTl.js → dataCommand-BoBJJ-Gb.js} +3 -3
- package/dist/{dbCommand-DZTmOFT4.js → dbCommand-CMAIz-Bf.js} +457 -441
- package/dist/dbCommand-DHi_RuDl.js +2 -0
- package/dist/dev-Bl9HqtV7.js +3 -0
- package/dist/{dev-Ca_A_S9v.js → dev-DOEJXicj.js} +2623 -2485
- package/dist/{doctorCommand-CGZJK_4o.js → doctorCommand-BrWu67JZ.js} +524 -280
- package/dist/doctorCommand-DrQv9SL3.js +2 -0
- package/dist/{dormancyCommand-DY2rYpTa.js → dormancyCommand-xn2y-pJm.js} +1 -1
- package/dist/{embeddingsCommand-BoCqZsgp.js → embeddingsCommand-Cn5MbRDM.js} +1 -1
- package/dist/emptyResultHeadline-Csa5fZOF.js +18 -0
- package/dist/{envCommand-Bxy2fOjc.js → envCommand-UJmJIbs9.js} +8 -8
- package/dist/{evolveCommand-BsbZ-XDg.js → evolveCommand-Db30twUy.js} +2 -2
- package/dist/frameworkTableAssembly-B96WCNJA.js +2 -0
- package/dist/{frameworkTableAssembly-Do-cf6RJ.js → frameworkTableAssembly-vfkzuzEo.js} +131 -104
- package/dist/index.js +1 -1
- package/dist/{infoCommand-EmM3jPKD.js → infoCommand-BjVXpMlP.js} +1 -1
- package/dist/inspect-B0hL41s0.js +2 -0
- package/dist/inspect-DZnan87F.js +1500 -0
- package/dist/{inspectCmd-niF97fAq.js → inspectCmd-CP-G0sVK.js} +1 -1
- package/dist/{inspectFetch-EMuhTG_9.js → inspectFetch-BU1NyzxV.js} +36 -24
- package/dist/{inspectMetrics-CGF94puw.js → inspectMetrics-BY0Sjb2F.js} +19 -19
- package/dist/{logsCmd-B6oNsfaZ.js → logsCmd-C5y9HyrG.js} +59 -54
- package/dist/{manifestBuild-DjX5MoXy.js → manifestBuild-DnbFKF6w.js} +1 -1
- package/dist/manifestBuild-UXrnUcXP.js +2 -0
- package/dist/{migrate-CGFZS-1a.js → migrate-DtC3lu7H.js} +4 -4
- package/dist/precompressAssets-YhTi1aWp.js +40 -0
- package/dist/{probeCommand-Bs3iVBSL.js → probeCommand-C5fuN6Z2.js} +2 -2
- package/dist/{runtimeTrace-C1BTpHGQ.js → runtimeTrace-BQL_lfz6.js} +1 -1
- package/dist/{scheduleCmd-DQRu6BZC.js → scheduleCmd-DYBUfo_T.js} +1 -1
- package/dist/{sdkgen-CXMwLg9n.js → sdkgen-CMUPrDjH.js} +1 -1
- package/dist/{seedRunner-Dgsiwk_e.js → seedRunner-DISBKow-.js} +16 -16
- package/dist/serveCommand-B_w-Mpb_.js +2544 -0
- package/dist/serveCommand-Dtb48ffg.js +2 -0
- package/dist/serveEntry.js +1 -1
- package/dist/{start-EOV7s1NZ.js → start-YaUehtDV.js} +580 -558
- package/dist/{start-DH7cat4-.js → start-s25GAIgn.js} +1 -1
- package/dist/startEntry.js +1 -1
- package/dist/staticCachePolicy-CIyj6DbS.js +15 -0
- package/dist/{test-DO27-x2P.js → test-jipIQ5Mx.js} +1 -1
- package/dist/{tracesCmd-DgtgOUdi.js → tracesCmd-C1qKa94g.js} +69 -64
- package/dist/{updateCommand-C_jN1w18.js → updateCommand-C9n_Z_oG.js} +8 -2
- package/dist/updateCommand-DsXEAHbd.js +2 -0
- package/dist/webDev-B6ZMX42w.js +2 -0
- package/dist/{webDev-B7vNj4Bq.js → webDev-BgdkyjP6.js} +1379 -1303
- package/dist/{webhooksCommand-B1LVcyO3.js → webhooksCommand-CYXTNvXq.js} +2 -2
- package/dist/{workflowsCmd-BGF-mRZ5.js → workflowsCmd-g-DNpaUc.js} +1 -1
- package/package.json +55 -19
- package/templates/AGENTS.core.md +20 -1
- package/templates/AGENTS.md +21 -2
- package/templates/agent-docs/_index.md +1 -1
- package/templates/agent-docs/_manifest.json +2 -2
- package/templates/agent-docs/ai.md +1 -1
- package/templates/agent-docs/authentication.md +34 -19
- package/templates/agent-docs/cli.md +170 -0
- package/templates/agent-docs/configuration.md +29 -4
- package/templates/agent-docs/data.md +145 -5
- package/templates/agent-docs/database/scaling.md +50 -2
- package/templates/agent-docs/deployment.md +75 -1
- package/templates/agent-docs/internationalization.md +32 -0
- package/templates/agent-docs/local-first-mobile.md +9 -2
- package/templates/agent-docs/observability.md +227 -0
- package/templates/agent-docs/plugins/audit.md +21 -5
- package/templates/agent-docs/plugins/billing.md +17 -0
- package/templates/agent-docs/plugins/broadcast.md +2 -1
- package/templates/agent-docs/plugins/ratelimit.md +6 -1
- package/templates/agent-docs/plugins/row-history.md +11 -0
- package/templates/agent-docs/plugins.md +19 -0
- package/templates/agent-docs/reference.md +1 -0
- package/templates/agent-docs/scheduling.md +23 -0
- package/templates/agent-docs/schema-driven-ui.md +81 -0
- package/templates/agent-docs/templates/appshells.md +3 -3
- package/templates/agent-docs/whats-new.md +133 -77
- package/templates/apps/api-ai/package.json +6 -6
- 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 +9 -9
- 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-row-history/package.json +8 -8
- 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-webhooks/package.json +9 -9
- package/templates/apps/changelog/package.json +7 -7
- package/templates/apps/changelog/src/pages/[locale]/page.tsx +7 -1
- package/templates/apps/edge-functions/package.json +2 -2
- package/templates/apps/frontend-admin/package.json +7 -8
- package/templates/apps/frontend-admin/src/pages/(marketing)/layout.tsx +1 -2
- package/templates/apps/frontend-admin/src/pages/admin/layout.tsx +1 -2
- package/templates/apps/frontend-app/package.json +8 -9
- package/templates/apps/frontend-app/src/pages/layout.tsx +1 -2
- package/templates/apps/frontend-auth/package.json +7 -8
- package/templates/apps/frontend-auth/src/components/AuthShell.tsx +1 -2
- package/templates/apps/frontend-blank/package.json +6 -7
- package/templates/apps/frontend-blank/src/pages/layout.tsx +1 -2
- package/templates/apps/frontend-cms/package.json +8 -9
- package/templates/apps/frontend-cms/src/pages/(app)/layout.tsx +1 -2
- package/templates/apps/frontend-collab/README.md +7 -2
- package/templates/apps/frontend-collab/package.json +9 -10
- package/templates/apps/frontend-collab/src/pages/layout.tsx +1 -2
- package/templates/apps/frontend-collab/src/pages/page.test.tsx +8 -4
- package/templates/apps/frontend-collab/src/pages/page.tsx +10 -5
- package/templates/apps/frontend-contact/package.json +7 -7
- package/templates/apps/frontend-contact/src/components/ContactForm.island.tsx +1 -1
- package/templates/apps/frontend-dashboard/package.json +6 -7
- package/templates/apps/frontend-dashboard/src/pages/(marketing)/layout.tsx +1 -2
- package/templates/apps/frontend-dashboard/src/pages/dashboard/layout.tsx +1 -2
- package/templates/apps/frontend-docs/package.json +8 -8
- 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 +7 -8
- package/templates/apps/frontend-portal/src/pages/(portal)/layout.tsx +1 -2
- package/templates/apps/frontend-saas/package.json +7 -8
- package/templates/apps/frontend-saas/src/pages/(marketing)/layout.tsx +1 -2
- package/templates/apps/frontend-saas/src/pages/dashboard/layout.tsx +1 -2
- package/templates/apps/frontend-spa/package.json +6 -7
- package/templates/apps/frontend-spa/src/pages/layout.tsx +1 -2
- package/templates/apps/frontend-ssr/package.json +6 -7
- package/templates/apps/frontend-ssr/src/pages/layout.tsx +1 -2
- package/templates/apps/frontend-ssr-api/package.json +7 -8
- package/templates/apps/frontend-ssr-api/src/pages/layout.tsx +1 -2
- package/templates/apps/frontend-static-blog/package.json +8 -8
- package/templates/apps/frontend-static-blog/src/components/ReadingProgress.island.tsx +1 -1
- package/templates/apps/frontend-static-blog/src/pages/[locale]/page.tsx +7 -1
- package/templates/apps/frontend-status/package.json +7 -8
- package/templates/apps/frontend-status/src/pages/layout.tsx +1 -2
- package/templates/apps/mobile-app/package.json +4 -4
- package/templates/baselines/compose/docker/api.Dockerfile +61 -5
- package/templates/baselines/compose/docker/web.Dockerfile +55 -10
- package/templates/baselines/compose-mariadb/docker/api.Dockerfile +61 -5
- package/templates/baselines/compose-mariadb/docker/web.Dockerfile +55 -10
- package/dist/apiBuild-Cl0IDx8c.js +0 -2
- package/dist/codegen-CN6vMM4J.js +0 -2
- package/dist/dbCommand-BTyBGhIA.js +0 -2
- package/dist/dev-DfVZaoys.js +0 -3
- package/dist/doctorCommand-djmqEcDC.js +0 -2
- package/dist/frameworkTableAssembly-Df2Ymp2f.js +0 -2
- package/dist/inspect-DCqILJ1G.js +0 -1197
- package/dist/inspect-DGJwpOAb.js +0 -2
- package/dist/manifestBuild-CJ2zvPvT.js +0 -2
- package/dist/serveCommand-C7IrCD58.js +0 -2445
- package/dist/serveCommand-Cjt5S9hD.js +0 -2
- package/dist/updateCommand-nnFjDbl4.js +0 -2
- package/dist/webDev-1XpVnYkW.js +0 -2
|
@@ -2,25 +2,41 @@ import { n as e } from "./subcommandNames-CDzfEtKV.js";
|
|
|
2
2
|
import { n as t, r as n } from "./nodeEnvironment-cGFAj1J8.js";
|
|
3
3
|
import { t as r } from "./cliError-BmdYnghb.js";
|
|
4
4
|
import { n as i } from "./appModuleLoader-C9r9mxZt.js";
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
5
|
+
import { r as a, t as o } from "./inspectGateHint-4LxkNtrz.js";
|
|
6
|
+
import { a as s, i as c, n as l, t as u } from "./cliArgs-D4p8n7EE.js";
|
|
7
|
+
import { r as d } from "./dialectDriver-czCHYpeH.js";
|
|
8
|
+
import { a as f } from "./connectionConfig-Bk9IC7D0.js";
|
|
9
|
+
import { i as p, n as m, r as h, s as g, t as _ } from "./seedRunner-DISBKow-.js";
|
|
10
|
+
import { _ as v, c as y, n as b } from "./frameworkTableAssembly-vfkzuzEo.js";
|
|
11
|
+
import { C as x, S } from "./fileConventions-l-RIXbx8.js";
|
|
12
12
|
import { basename as ee, join as C, relative as w, resolve as T } from "node:path";
|
|
13
13
|
import { promises as E, writeSync as te } from "node:fs";
|
|
14
14
|
import { pathToFileURL as D } from "node:url";
|
|
15
|
-
import { createLogger as O, logFormat as
|
|
16
|
-
import { actorsTable as
|
|
17
|
-
import {
|
|
18
|
-
import {
|
|
19
|
-
import {
|
|
20
|
-
import {
|
|
21
|
-
import { createHash as
|
|
22
|
-
//#region src/
|
|
23
|
-
var
|
|
15
|
+
import { createLogger as O, logFormat as ne } from "@voltro/logger";
|
|
16
|
+
import { actorsTable as re, branchNamespaceName as ie, decodeFieldValue as ae, encodeFieldValue as oe, frameworkTables as se, isEncrypted as k, isMigrationDefinition as ce, isRawEncoding as le, makeNamespaceBranchExecutor as ue, provisionBranch as de, quoteIdent as fe, registerCoreTables as pe, resolveBranchMechanism as me, teardownBranch as he } from "@voltro/database";
|
|
17
|
+
import { InMemoryDataStore as ge, makeFieldCipher as _e } from "@voltro/runtime";
|
|
18
|
+
import { Effect as A } from "effect";
|
|
19
|
+
import { SqlClient as j } from "@effect/sql";
|
|
20
|
+
import { DEFAULT_CDC_CHANNEL as ve, applyPlan as ye, applySchema as be, assessRollingDeployGate as xe, branchRehearsalExitCode as Se, declaredSnapshot as Ce, destructiveScope as we, fingerprintSchema as Te, formatBranchRehearsal as Ee, ignoreTablesFromEnv as M, introspectSchema as N, pendingFileMigrationIds as De, planMigrations as P, recordUpToDate as Oe, rehearseMigrationOnBranch as ke, rollbackFileBasedMigration as Ae, rollingDeployUnsafeOps as je, runFileBasedMigrations as Me, shortFingerprint as F, squashMigrationPlans as Ne, unblockLossy as Pe } from "@voltro/database/sql";
|
|
21
|
+
import { createHash as Fe } from "node:crypto";
|
|
22
|
+
//#region src/writeAllSync.ts
|
|
23
|
+
var Ie = (e) => {
|
|
24
|
+
let t = new Int32Array(new SharedArrayBuffer(4));
|
|
25
|
+
Atomics.wait(t, 0, 0, e);
|
|
26
|
+
}, Le = (e, t) => {
|
|
27
|
+
let n = Buffer.from(t, "utf8"), r = 0;
|
|
28
|
+
for (; r < n.length;) try {
|
|
29
|
+
r += te(e, n, r, n.length - r);
|
|
30
|
+
} catch (e) {
|
|
31
|
+
let t = e.code;
|
|
32
|
+
if (t === "EAGAIN") {
|
|
33
|
+
Ie(1);
|
|
34
|
+
continue;
|
|
35
|
+
}
|
|
36
|
+
if (t === "EPIPE") return;
|
|
37
|
+
throw e;
|
|
38
|
+
}
|
|
39
|
+
}, Re = (e) => typeof e != "string", ze = (e, t, n, r) => {
|
|
24
40
|
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
41
|
r > 0 && i.push({
|
|
26
42
|
kind: "target-not-empty",
|
|
@@ -37,7 +53,7 @@ var Fe = (e) => typeof e != "string", Ie = (e, t, n, r) => {
|
|
|
37
53
|
});
|
|
38
54
|
continue;
|
|
39
55
|
}
|
|
40
|
-
if (
|
|
56
|
+
if (Re(n)) {
|
|
41
57
|
c.push({
|
|
42
58
|
target: t,
|
|
43
59
|
source: n.expr,
|
|
@@ -80,23 +96,23 @@ var Fe = (e) => typeof e != "string", Ie = (e, t, n, r) => {
|
|
|
80
96
|
remintIds: f,
|
|
81
97
|
fatal: i.some((e) => e.fatal)
|
|
82
98
|
};
|
|
83
|
-
},
|
|
99
|
+
}, Be = (e, t) => [
|
|
84
100
|
`back up '${e.from}' to a restorable dump`,
|
|
85
101
|
`copy rows: INSERT INTO ${e.into} (${t.assignments.map((e) => e.target).join(", ")}) SELECT … FROM ${e.from}`,
|
|
86
102
|
...t.remintIds ? ["record old id → new id in a translation table", `rewrite every reference to '${e.from}', JSON columns included`] : [],
|
|
87
103
|
"verify: row counts match and a sample round-trips",
|
|
88
104
|
`drop '${e.from}' — LAST, and only after the verify`
|
|
89
|
-
],
|
|
105
|
+
], Ve = (e, t, n) => {
|
|
90
106
|
let r = [`adopt · ${e.from} (${n} row(s)) → ${e.into}`, ""];
|
|
91
107
|
for (let e of t.assignments) r.push(` ${e.target.padEnd(24)} ← ${e.raw ? `${e.source} (raw)` : e.source}`);
|
|
92
108
|
t.problems.length > 0 && r.push("");
|
|
93
109
|
for (let e of t.problems) r.push(` ${e.fatal ? "✗" : "⚠"} ${e.detail}`);
|
|
94
110
|
if (r.push(""), t.fatal) return r.push(" REFUSED — fix the problems above. Nothing was written."), r;
|
|
95
111
|
r.push(" would run, in this order:");
|
|
96
|
-
for (let [n, i] of
|
|
112
|
+
for (let [n, i] of Be(e, t).entries()) r.push(` ${n + 1}. ${i}`);
|
|
97
113
|
return r;
|
|
98
|
-
},
|
|
99
|
-
let a =
|
|
114
|
+
}, He = (e) => e === "mysql" || e === "mariadb" ? (e) => `\`${e}\`` : e === "mssql" ? (e) => `[${e}]` : (e) => `"${e}"`, Ue = (e) => `${e}__adopt_snapshot`, We = (e, t, n, r, i) => {
|
|
115
|
+
let a = He(n), o = Ue(e.from), s = t.assignments.map((e) => e.target), c = t.assignments.map((e) => e.raw ? e.source : a(e.source));
|
|
100
116
|
return i !== void 0 && (s.unshift(r), c.unshift(i)), {
|
|
101
117
|
snapshot: n === "mssql" ? `SELECT * INTO ${a(o)} FROM ${a(e.from)}` : `CREATE TABLE ${a(o)} AS SELECT * FROM ${a(e.from)}`,
|
|
102
118
|
copy: `INSERT INTO ${a(e.into)} (${s.map(a).join(", ")}) SELECT ${c.join(", ")} FROM ${a(e.from)}`,
|
|
@@ -106,18 +122,18 @@ var Fe = (e) => typeof e != "string", Ie = (e, t, n, r) => {
|
|
|
106
122
|
drop: `DROP TABLE ${a(e.from)}`,
|
|
107
123
|
dropSnapshot: `DROP TABLE ${a(o)}`
|
|
108
124
|
};
|
|
109
|
-
},
|
|
125
|
+
}, Ge = (e) => {
|
|
110
126
|
let t = e[0];
|
|
111
127
|
if (t === void 0) return 0;
|
|
112
128
|
let n = t.n ?? t.N ?? Object.values(t)[0];
|
|
113
129
|
return Number(n ?? 0);
|
|
114
|
-
},
|
|
130
|
+
}, Ke = async (e, t, n, r, i, a, o = {}) => {
|
|
115
131
|
if (t.fatal) throw Error("adopt: the plan was refused — nothing was run");
|
|
116
|
-
let s =
|
|
132
|
+
let s = We(e, t, n, r, i), c = Ue(e.from), l = Ge(await a(s.verifySource));
|
|
117
133
|
await a(s.snapshot);
|
|
118
134
|
for (let e of s.translations) await a(e);
|
|
119
135
|
await a(s.copy);
|
|
120
|
-
let u =
|
|
136
|
+
let u = Ge(await a(s.verifyTarget));
|
|
121
137
|
return u === l ? o.keepSource === !0 ? {
|
|
122
138
|
copied: u,
|
|
123
139
|
sourceRows: l,
|
|
@@ -137,7 +153,7 @@ var Fe = (e) => typeof e != "string", Ie = (e, t, n, r) => {
|
|
|
137
153
|
snapshotTable: c,
|
|
138
154
|
sourceDropped: !1
|
|
139
155
|
};
|
|
140
|
-
},
|
|
156
|
+
}, I = [
|
|
141
157
|
"token",
|
|
142
158
|
"secret",
|
|
143
159
|
"password",
|
|
@@ -146,7 +162,7 @@ var Fe = (e) => typeof e != "string", Ie = (e, t, n, r) => {
|
|
|
146
162
|
"credential",
|
|
147
163
|
"privatekey",
|
|
148
164
|
"private_key"
|
|
149
|
-
],
|
|
165
|
+
], qe = [
|
|
150
166
|
{
|
|
151
167
|
table: "_voltro_audit_log",
|
|
152
168
|
column: "subject"
|
|
@@ -191,7 +207,7 @@ var Fe = (e) => typeof e != "string", Ie = (e, t, n, r) => {
|
|
|
191
207
|
table: "_voltro_row_history",
|
|
192
208
|
column: "subjectId"
|
|
193
209
|
}
|
|
194
|
-
],
|
|
210
|
+
], Je = (e, t) => {
|
|
195
211
|
switch (t) {
|
|
196
212
|
case "postgres": return `LOWER(CAST(${e} AS text))`;
|
|
197
213
|
case "mysql":
|
|
@@ -200,17 +216,17 @@ var Fe = (e) => typeof e != "string", Ie = (e, t, n, r) => {
|
|
|
200
216
|
case "sqlite":
|
|
201
217
|
case "turso": return `LOWER(CAST(${e} AS TEXT))`;
|
|
202
218
|
}
|
|
203
|
-
},
|
|
219
|
+
}, Ye = (e, t, n = I) => n.map((n) => `${Je(e, t)} LIKE '%${n}%'`).join("\n OR "), Xe = ["_omitted", "__redacted"], Ze = (e, t) => {
|
|
204
220
|
let n = e.toLowerCase();
|
|
205
221
|
return t.some((e) => n.includes(e));
|
|
206
|
-
},
|
|
222
|
+
}, Qe = (e, t = []) => {
|
|
207
223
|
if (Array.isArray(e)) {
|
|
208
|
-
for (let n of e)
|
|
224
|
+
for (let n of e) Qe(n, t);
|
|
209
225
|
return t;
|
|
210
226
|
}
|
|
211
|
-
if (typeof e == "object" && e) for (let [n, r] of Object.entries(e)) t.push(n),
|
|
227
|
+
if (typeof e == "object" && e) for (let [n, r] of Object.entries(e)) t.push(n), Qe(r, t);
|
|
212
228
|
return t;
|
|
213
|
-
},
|
|
229
|
+
}, $e = (e) => Array.isArray(e) ? e.map($e) : typeof e == "object" && e ? Object.fromEntries(Object.entries(e).map(([e, t]) => [e, Xe.includes(e) ? null : $e(t)])) : e, et = (e, t = I) => {
|
|
214
230
|
let n;
|
|
215
231
|
try {
|
|
216
232
|
n = JSON.parse(e);
|
|
@@ -222,33 +238,33 @@ var Fe = (e) => typeof e != "string", Ie = (e, t, n, r) => {
|
|
|
222
238
|
};
|
|
223
239
|
}
|
|
224
240
|
return {
|
|
225
|
-
asKey:
|
|
226
|
-
redactionMarkerOnly: !
|
|
241
|
+
asKey: Qe(n).some((e) => Ze(e, t)),
|
|
242
|
+
redactionMarkerOnly: !Ze(JSON.stringify($e(n)) ?? "", t),
|
|
227
243
|
undecidable: !1
|
|
228
244
|
};
|
|
229
|
-
},
|
|
245
|
+
}, tt = (e) => ({
|
|
230
246
|
examined: e.length,
|
|
231
247
|
asKey: e.filter((e) => e.asKey).length,
|
|
232
248
|
redactionMarkerOnly: e.filter((e) => e.redactionMarkerOnly && !e.asKey).length,
|
|
233
249
|
undecidable: e.filter((e) => e.undecidable).length
|
|
234
|
-
}),
|
|
250
|
+
}), L = (e) => {
|
|
235
251
|
let t = e.classification;
|
|
236
252
|
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
|
-
},
|
|
238
|
-
let i = `WHERE ${
|
|
253
|
+
}, nt = (e, t, n, r) => {
|
|
254
|
+
let i = `WHERE ${Ye(t, n)}`, a = Je(t, n).replace(/^LOWER\((.*)\)$/s, "$1");
|
|
239
255
|
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
|
-
},
|
|
256
|
+
}, rt = async (e, t, n, r, i) => (await A.runPromise(e.unsafe(nt(t, n, r, i)))).map((e) => {
|
|
241
257
|
let t = Object.values(e)[0];
|
|
242
258
|
return typeof t == "string" ? t : JSON.stringify(t ?? null);
|
|
243
|
-
}),
|
|
244
|
-
let n = await
|
|
259
|
+
}), it = async (e, t) => {
|
|
260
|
+
let n = await A.runPromise(e.unsafe(t));
|
|
245
261
|
return Number(Object.values(n[0] ?? { n: 0 })[0] ?? 0);
|
|
246
|
-
},
|
|
262
|
+
}, at = async (e, t, n, r) => {
|
|
247
263
|
let i = [];
|
|
248
264
|
for (let a of n) {
|
|
249
265
|
let n = r(a.table), o;
|
|
250
266
|
try {
|
|
251
|
-
o = await
|
|
267
|
+
o = await it(e, `SELECT COUNT(*) AS n FROM ${n}`);
|
|
252
268
|
} catch {
|
|
253
269
|
i.push({
|
|
254
270
|
table: a.table,
|
|
@@ -260,9 +276,9 @@ var Fe = (e) => typeof e != "string", Ie = (e, t, n, r) => {
|
|
|
260
276
|
continue;
|
|
261
277
|
}
|
|
262
278
|
try {
|
|
263
|
-
let s = await
|
|
264
|
-
if (s > 0) for (let i of
|
|
265
|
-
let o = await
|
|
279
|
+
let s = await it(e, `SELECT COUNT(*) AS n FROM ${n}\n WHERE ${Ye(r(a.column), t)}`), c = [];
|
|
280
|
+
if (s > 0) for (let i of I) try {
|
|
281
|
+
let o = await it(e, `SELECT COUNT(*) AS n FROM ${n}\n WHERE ${Ye(r(a.column), t, [i])}`);
|
|
266
282
|
o > 0 && c.push({
|
|
267
283
|
needle: i,
|
|
268
284
|
rows: o
|
|
@@ -270,7 +286,7 @@ var Fe = (e) => typeof e != "string", Ie = (e, t, n, r) => {
|
|
|
270
286
|
} catch {}
|
|
271
287
|
let l;
|
|
272
288
|
if (s > 0) try {
|
|
273
|
-
l =
|
|
289
|
+
l = tt((await rt(e, n, r(a.column), t, 500)).map((e) => et(e)));
|
|
274
290
|
} catch {}
|
|
275
291
|
i.push({
|
|
276
292
|
table: a.table,
|
|
@@ -291,8 +307,8 @@ var Fe = (e) => typeof e != "string", Ie = (e, t, n, r) => {
|
|
|
291
307
|
}
|
|
292
308
|
}
|
|
293
309
|
return i;
|
|
294
|
-
},
|
|
295
|
-
let n =
|
|
310
|
+
}, ot = (e, t) => {
|
|
311
|
+
let n = I.length, r = [`credential scan · dialect ${t}`, ""], i = /* @__PURE__ */ new Set();
|
|
296
312
|
for (let t of e) {
|
|
297
313
|
if (t.unavailable === "column") {
|
|
298
314
|
r.push(` · ${t.table}.${t.column} — column is not present on this deployment (${t.scanned} row(s) exist but this column was not scanned)`);
|
|
@@ -306,14 +322,14 @@ var Fe = (e) => typeof e != "string", Ie = (e, t, n, r) => {
|
|
|
306
322
|
r.push(` · ${t.table} — table is EMPTY (0 rows scanned; this is not a clean bill of health)`);
|
|
307
323
|
continue;
|
|
308
324
|
}
|
|
309
|
-
let e =
|
|
325
|
+
let e = L(t), n = (t.hits ?? 0) === 0 ? "✓" : e ? "~" : "✗";
|
|
310
326
|
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
327
|
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 \`${
|
|
328
|
+
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 \`${Xe.join("` / `")}\`, the framework's own`, " record that a `.sensitive()` column was left OUT. Nothing to purge, nothing to rotate."));
|
|
313
329
|
}
|
|
314
|
-
let a = e.reduce((e, t) => e + (
|
|
330
|
+
let a = e.reduce((e, t) => e + (L(t) ? 0 : t.hits ?? 0), 0), o = e.reduce((e, t) => e + (L(t) ? t.hits ?? 0 : 0), 0);
|
|
315
331
|
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
|
-
},
|
|
332
|
+
}, st = (e) => e.some((e) => (e.hits ?? 0) > 0 && !L(e)) ? 1 : e.every((e) => e.unavailable !== void 0 || e.scanned === void 0 || e.scanned === 0) ? 2 : 0, ct = /\.migration\.ts$/, lt = async (e, t) => {
|
|
317
333
|
let n = [];
|
|
318
334
|
for (let r of e) {
|
|
319
335
|
let e;
|
|
@@ -323,7 +339,7 @@ var Fe = (e) => typeof e != "string", Ie = (e, t, n, r) => {
|
|
|
323
339
|
t.warn("migration: failed to import", { file: r }, e);
|
|
324
340
|
continue;
|
|
325
341
|
}
|
|
326
|
-
let i = (e.default &&
|
|
342
|
+
let i = (e.default && ce(e.default) ? e.default : void 0) ?? Object.values(e).find(ce);
|
|
327
343
|
if (!i) {
|
|
328
344
|
t.warn("migration: no defineMigration export", { file: r });
|
|
329
345
|
continue;
|
|
@@ -333,7 +349,7 @@ var Fe = (e) => typeof e != "string", Ie = (e, t, n, r) => {
|
|
|
333
349
|
idInFile: i.id,
|
|
334
350
|
idFromFile: ee(r).replace(/\.migration\.ts$/, "")
|
|
335
351
|
});
|
|
336
|
-
let a = await E.readFile(r, "utf8").catch(() => ""), o =
|
|
352
|
+
let a = await E.readFile(r, "utf8").catch(() => ""), o = Fe("sha256").update(a).digest("hex").slice(0, 16);
|
|
337
353
|
n.push({
|
|
338
354
|
file: r,
|
|
339
355
|
definition: i,
|
|
@@ -341,14 +357,14 @@ var Fe = (e) => typeof e != "string", Ie = (e, t, n, r) => {
|
|
|
341
357
|
});
|
|
342
358
|
}
|
|
343
359
|
return [...n].sort((e, t) => e.definition.id < t.definition.id ? -1 : 1);
|
|
344
|
-
},
|
|
345
|
-
let t = (process.env.DB_DIALECT ?? "postgres").toLowerCase(), n = t === "memory" ? "postgres" : t, r = n, i = t === "memory" ? void 0 : await
|
|
346
|
-
return
|
|
347
|
-
},
|
|
348
|
-
let e = yield* (yield*
|
|
360
|
+
}, ut = async (e) => {
|
|
361
|
+
let t = (process.env.DB_DIALECT ?? "postgres").toLowerCase(), n = t === "memory" ? "postgres" : t, r = n, i = t === "memory" ? void 0 : await d(n).then((e) => e.retryFilter, () => void 0);
|
|
362
|
+
return A.runPromise(be(se, r, void 0, i === void 0 ? {} : { retryFilter: i }).pipe(A.provide(e)));
|
|
363
|
+
}, dt = (e) => A.runPromise(A.gen(function* () {
|
|
364
|
+
let e = yield* (yield* j.SqlClient)`SELECT id, status, hash, "appliedAt" FROM "_voltro_migrations"`.pipe(A.orElseSucceed(() => []));
|
|
349
365
|
return new Map(e.map((e) => [e.id, e]));
|
|
350
|
-
}).pipe(
|
|
351
|
-
yield* (yield*
|
|
366
|
+
}).pipe(A.provide(e))), R = (e, t) => A.runPromise(A.gen(function* () {
|
|
367
|
+
yield* (yield* j.SqlClient)`
|
|
352
368
|
INSERT INTO "_voltro_migrations"
|
|
353
369
|
(id, name, hash, status, "appliedAt", "durationMs", "errorMessage", "errorStack")
|
|
354
370
|
VALUES
|
|
@@ -363,14 +379,14 @@ var Fe = (e) => typeof e != "string", Ie = (e, t, n, r) => {
|
|
|
363
379
|
"errorStack" = EXCLUDED."errorStack",
|
|
364
380
|
"updatedAt" = now()
|
|
365
381
|
`;
|
|
366
|
-
}).pipe(
|
|
382
|
+
}).pipe(A.provide(e))), ft = async (e, t, n) => {
|
|
367
383
|
if (e.length === 0) return {
|
|
368
384
|
applied: [],
|
|
369
385
|
failed: null,
|
|
370
386
|
skipped: []
|
|
371
387
|
};
|
|
372
|
-
await
|
|
373
|
-
let r = await
|
|
388
|
+
await ut(t);
|
|
389
|
+
let r = await dt(t), i = [], a = [], o = null;
|
|
374
390
|
for (let s of e) {
|
|
375
391
|
let e = r.get(s.definition.id);
|
|
376
392
|
if (e && e.status === "applied") {
|
|
@@ -391,7 +407,7 @@ var Fe = (e) => typeof e != "string", Ie = (e, t, n, r) => {
|
|
|
391
407
|
name: s.definition.name
|
|
392
408
|
});
|
|
393
409
|
let c = Date.now(), l = [];
|
|
394
|
-
await
|
|
410
|
+
await R(t, {
|
|
395
411
|
id: s.definition.id,
|
|
396
412
|
name: s.definition.name,
|
|
397
413
|
hash: s.hash,
|
|
@@ -405,7 +421,7 @@ var Fe = (e) => typeof e != "string", Ie = (e, t, n, r) => {
|
|
|
405
421
|
for (let e of s.definition.steps) {
|
|
406
422
|
let n = Date.now();
|
|
407
423
|
try {
|
|
408
|
-
await
|
|
424
|
+
await A.runPromise(e.apply.pipe(A.provide(t))), l.push({
|
|
409
425
|
stepName: e.name,
|
|
410
426
|
status: "applied",
|
|
411
427
|
durationMs: Date.now() - n
|
|
@@ -432,7 +448,7 @@ var Fe = (e) => typeof e != "string", Ie = (e, t, n, r) => {
|
|
|
432
448
|
...a.stack ? { stack: a.stack } : {}
|
|
433
449
|
}
|
|
434
450
|
};
|
|
435
|
-
o = f, i.push(f), await
|
|
451
|
+
o = f, i.push(f), await R(t, {
|
|
436
452
|
id: s.definition.id,
|
|
437
453
|
name: s.definition.name,
|
|
438
454
|
hash: s.hash,
|
|
@@ -451,7 +467,7 @@ var Fe = (e) => typeof e != "string", Ie = (e, t, n, r) => {
|
|
|
451
467
|
status: "applied",
|
|
452
468
|
durationMs: Date.now() - c,
|
|
453
469
|
steps: l
|
|
454
|
-
}), await
|
|
470
|
+
}), await R(t, {
|
|
455
471
|
id: s.definition.id,
|
|
456
472
|
name: s.definition.name,
|
|
457
473
|
hash: s.hash,
|
|
@@ -471,9 +487,9 @@ var Fe = (e) => typeof e != "string", Ie = (e, t, n, r) => {
|
|
|
471
487
|
failed: o,
|
|
472
488
|
skipped: a
|
|
473
489
|
};
|
|
474
|
-
},
|
|
475
|
-
await
|
|
476
|
-
let i = await
|
|
490
|
+
}, pt = async (e, t, n, r = {}) => {
|
|
491
|
+
await ut(t);
|
|
492
|
+
let i = await dt(t), a = e.filter((e) => i.get(e.definition.id)?.status === "applied");
|
|
477
493
|
if (a.length === 0) return n.info("migration: nothing to roll back"), {
|
|
478
494
|
applied: [],
|
|
479
495
|
failed: null,
|
|
@@ -491,7 +507,7 @@ var Fe = (e) => typeof e != "string", Ie = (e, t, n, r) => {
|
|
|
491
507
|
for (let n = e.definition.steps.length - 1; n >= 0; n--) {
|
|
492
508
|
let o = e.definition.steps[n], s = Date.now();
|
|
493
509
|
try {
|
|
494
|
-
await
|
|
510
|
+
await A.runPromise(o.undo.pipe(A.provide(t))), i.push({
|
|
495
511
|
stepName: o.name,
|
|
496
512
|
status: "applied",
|
|
497
513
|
durationMs: Date.now() - s
|
|
@@ -526,7 +542,7 @@ var Fe = (e) => typeof e != "string", Ie = (e, t, n, r) => {
|
|
|
526
542
|
status: "applied",
|
|
527
543
|
durationMs: Date.now() - r,
|
|
528
544
|
steps: i
|
|
529
|
-
}), await
|
|
545
|
+
}), await R(t, {
|
|
530
546
|
id: e.definition.id,
|
|
531
547
|
name: e.definition.name,
|
|
532
548
|
hash: e.hash,
|
|
@@ -542,9 +558,9 @@ var Fe = (e) => typeof e != "string", Ie = (e, t, n, r) => {
|
|
|
542
558
|
failed: c,
|
|
543
559
|
skipped: []
|
|
544
560
|
};
|
|
545
|
-
},
|
|
546
|
-
await
|
|
547
|
-
let n = await
|
|
561
|
+
}, mt = async (e, t) => {
|
|
562
|
+
await ut(t);
|
|
563
|
+
let n = await dt(t);
|
|
548
564
|
return e.map((e) => {
|
|
549
565
|
let t = n.get(e.definition.id);
|
|
550
566
|
return t ? {
|
|
@@ -561,7 +577,7 @@ var Fe = (e) => typeof e != "string", Ie = (e, t, n, r) => {
|
|
|
561
577
|
hashMatches: !0
|
|
562
578
|
};
|
|
563
579
|
});
|
|
564
|
-
},
|
|
580
|
+
}, ht = (e) => {
|
|
565
581
|
if (!e.failed) return "";
|
|
566
582
|
let t = e.failed, n = [
|
|
567
583
|
`✗ Migration failed: ${t.migrationId} / step '${t.error?.stepName ?? "?"}'`,
|
|
@@ -574,22 +590,22 @@ var Fe = (e) => typeof e != "string", Ie = (e, t, n, r) => {
|
|
|
574
590
|
""
|
|
575
591
|
];
|
|
576
592
|
return e.applied.length > 0 && n.unshift("", `(${e.applied.length} migration(s) applied successfully before the failure.)`), n.join("\n");
|
|
577
|
-
},
|
|
593
|
+
}, gt = (e) => {
|
|
578
594
|
let t = e.split(".");
|
|
579
595
|
if (t.length !== 2 || !t[0] || !t[1]) throw Error(`voltro db encrypt-column: expected <table>.<column>, got "${e}"`);
|
|
580
596
|
return {
|
|
581
597
|
table: t[0],
|
|
582
598
|
column: t[1]
|
|
583
599
|
};
|
|
584
|
-
},
|
|
585
|
-
let n =
|
|
600
|
+
}, _t = (e) => 49 + 4 * Math.ceil(e / 3), vt = (e, t) => {
|
|
601
|
+
let n = _t(e);
|
|
586
602
|
return {
|
|
587
603
|
longestPlaintextBytes: e,
|
|
588
604
|
longestCiphertext: n,
|
|
589
605
|
declaredMaxLength: t,
|
|
590
606
|
fits: t === void 0 || n <= t
|
|
591
607
|
};
|
|
592
|
-
},
|
|
608
|
+
}, yt = (e, t) => `${e.table}.${e.column} is too narrow: its longest value encodes to ${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.`, bt = (e, t) => {
|
|
593
609
|
let n = t ? "would encrypt" : "encrypted";
|
|
594
610
|
return [
|
|
595
611
|
` ${e.table}.${e.column}`,
|
|
@@ -599,25 +615,25 @@ var Fe = (e) => typeof e != "string", Ie = (e, t, n, r) => {
|
|
|
599
615
|
` null / empty ${e.empty}`,
|
|
600
616
|
` ${n.padEnd(18)} ${e.encrypted}`
|
|
601
617
|
].join("\n");
|
|
602
|
-
},
|
|
618
|
+
}, xt = (e, t, n) => {
|
|
603
619
|
if (e == null) return;
|
|
604
620
|
let r = typeof e == "string" ? e : String(e);
|
|
605
|
-
if (r === "" ||
|
|
606
|
-
let i =
|
|
621
|
+
if (r === "" || k(r)) return;
|
|
622
|
+
let i = oe(t, r), a;
|
|
607
623
|
try {
|
|
608
|
-
a =
|
|
624
|
+
a = ae(t, i);
|
|
609
625
|
} catch (e) {
|
|
610
626
|
throw Error(`${n.table}.${n.column}: the value could not be read back the way the app reads it (${e.message ?? String(e)}). Nothing was written.`);
|
|
611
627
|
}
|
|
612
628
|
if (a !== r) throw Error(`${n.table}.${n.column}: round-trip mismatch — reading the encrypted value back did not return what it was given. Nothing was written.`);
|
|
613
629
|
return i;
|
|
614
|
-
},
|
|
630
|
+
}, St = (e) => Buffer.byteLength(e, "utf8"), Ct = (e) => St(JSON.stringify(typeof e == "string" ? e : String(e))), wt = (e, t) => {
|
|
615
631
|
if (e !== void 0) try {
|
|
616
632
|
return t.decrypt(e), !0;
|
|
617
633
|
} catch {
|
|
618
634
|
return !1;
|
|
619
635
|
}
|
|
620
|
-
},
|
|
636
|
+
}, Tt = (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.`, Et = (e) => [
|
|
621
637
|
"",
|
|
622
638
|
" NEXT — this converted the rows that exist. New writes are still plaintext",
|
|
623
639
|
" until the column carries the modifier:",
|
|
@@ -627,25 +643,25 @@ var Fe = (e) => typeof e != "string", Ie = (e, t, n, r) => {
|
|
|
627
643
|
" Order does not matter: a read returns a non-ciphertext value unchanged, so",
|
|
628
644
|
" the column may hold a mix while you deploy. Run this again afterwards to",
|
|
629
645
|
" catch anything written in between — it skips what is already encrypted."
|
|
630
|
-
].join("\n"),
|
|
631
|
-
if (typeof e != "string" || !
|
|
646
|
+
].join("\n"), Dt = (e, t, n) => {
|
|
647
|
+
if (typeof e != "string" || !k(e)) return;
|
|
632
648
|
let r;
|
|
633
649
|
try {
|
|
634
|
-
r =
|
|
650
|
+
r = le(t, e);
|
|
635
651
|
} catch {
|
|
636
652
|
return;
|
|
637
653
|
}
|
|
638
654
|
if (r !== !0) return;
|
|
639
|
-
let i =
|
|
655
|
+
let i = ae(t, e);
|
|
640
656
|
if (typeof i != "string") return;
|
|
641
|
-
let a =
|
|
642
|
-
if (
|
|
657
|
+
let a = oe(t, i);
|
|
658
|
+
if (ae(t, a) !== i) throw Error(`${n.table}.${n.column}: re-encoding did not round-trip. Nothing was written.`);
|
|
643
659
|
return a;
|
|
644
|
-
},
|
|
660
|
+
}, Ot = (e, t, n) => e === "sqlite" ? `SELECT NULL AS n FROM pragma_table_info('${t.table}') WHERE name = '${t.column}'` : `SELECT character_maximum_length AS n FROM information_schema.columns\n WHERE ${n} AND table_name = '${t.table}' AND column_name = '${t.column}'`, kt = (e) => {
|
|
645
661
|
if (e == null) return;
|
|
646
662
|
let t = Number(e);
|
|
647
663
|
return Number.isFinite(t) && t >= 0 ? t : void 0;
|
|
648
|
-
},
|
|
664
|
+
}, z = O({ scope: "voltro:db" }), At = /* @__PURE__ */ new Set([
|
|
649
665
|
"node_modules",
|
|
650
666
|
"dist",
|
|
651
667
|
"build",
|
|
@@ -653,17 +669,17 @@ var Fe = (e) => typeof e != "string", Ie = (e, t, n, r) => {
|
|
|
653
669
|
".vite",
|
|
654
670
|
".next",
|
|
655
671
|
".git"
|
|
656
|
-
]),
|
|
672
|
+
]), B = async (e, t) => {
|
|
657
673
|
let n = [], r = async (e) => {
|
|
658
674
|
let i = await E.readdir(e, { withFileTypes: !0 }).catch(() => []);
|
|
659
675
|
for (let a of i) {
|
|
660
|
-
if (
|
|
676
|
+
if (At.has(a.name)) continue;
|
|
661
677
|
let i = C(e, a.name);
|
|
662
678
|
a.isDirectory() ? await r(i) : a.isFile() && t.test(a.name) && n.push(i);
|
|
663
679
|
}
|
|
664
680
|
};
|
|
665
681
|
return await r(e), n;
|
|
666
|
-
},
|
|
682
|
+
}, jt = [
|
|
667
683
|
"--against",
|
|
668
684
|
"--batch",
|
|
669
685
|
"--before",
|
|
@@ -682,63 +698,63 @@ var Fe = (e) => typeof e != "string", Ie = (e, t, n, r) => {
|
|
|
682
698
|
"--store",
|
|
683
699
|
"--to",
|
|
684
700
|
"--token"
|
|
685
|
-
],
|
|
701
|
+
], V = (e) => T(u(e, jt) ?? process.cwd()), H = () => f("migration"), Mt = (e) => {
|
|
686
702
|
let t = e;
|
|
687
703
|
if (typeof t != "object" || !t) return !1;
|
|
688
704
|
if (t.code === "ERR_OUT_OF_RANGE" || t.cause?.code === "ERR_OUT_OF_RANGE") return !0;
|
|
689
705
|
let n = `${t.message ?? ""} ${t.cause?.message ?? ""} ${t.stack ?? ""}`;
|
|
690
706
|
return /ERR_OUT_OF_RANGE|out of range/i.test(n) && /pg-protocol|parseDataRow|buffer-reader|BufferReader/i.test(n);
|
|
691
|
-
},
|
|
707
|
+
}, Nt = "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`.)", U = () => {
|
|
692
708
|
let e = (process.env.DB_DIALECT ?? "postgres").toLowerCase();
|
|
693
709
|
if (e === "memory") throw new r("voltro db: subcommands require a real dialect, not DB_DIALECT=memory.");
|
|
694
710
|
return e;
|
|
695
|
-
},
|
|
696
|
-
let t =
|
|
697
|
-
if (r.length === 0) return
|
|
711
|
+
}, W = (e) => fe(e, U()), G = (e) => e === "mysql" || e === "mariadb" ? "table_schema = DATABASE()" : e === "mssql" ? "table_schema = SCHEMA_NAME()" : "table_schema = 'public'", K = async () => (await d(U())).makeSqlLayer(H()), Pt = async (e) => {
|
|
712
|
+
let t = V(e), n = e.includes("--dry-run"), r = await lt(await B(t, ct), z);
|
|
713
|
+
if (r.length === 0) return z.info("no migrations found", { root: t }), 0;
|
|
698
714
|
if (n) {
|
|
699
715
|
let e = /* @__PURE__ */ new Set();
|
|
700
716
|
try {
|
|
701
|
-
let t = await
|
|
717
|
+
let t = await mt(r, await K());
|
|
702
718
|
e = new Set(t.filter((e) => e.status === "applied").map((e) => e.id));
|
|
703
719
|
} catch {
|
|
704
|
-
|
|
720
|
+
z.warn("migrate --dry-run: could not read the migration ledger — showing ALL as pending");
|
|
705
721
|
}
|
|
706
722
|
let t = r.filter((t) => !e.has(t.definition.id));
|
|
707
|
-
if (t.length === 0) return
|
|
708
|
-
|
|
723
|
+
if (t.length === 0) return z.info("migrate --dry-run: nothing pending — every migration is applied"), 0;
|
|
724
|
+
z.info(`migrate --dry-run: ${t.length} pending (nothing applied)`);
|
|
709
725
|
for (let e of t) console.log(` pending ${e.definition.id}`);
|
|
710
726
|
return 0;
|
|
711
727
|
}
|
|
712
|
-
let i = await
|
|
713
|
-
return i.failed ? (process.stderr.write(
|
|
728
|
+
let i = await ft(r, await K(), z);
|
|
729
|
+
return i.failed ? (process.stderr.write(ht(i) + "\n"), 1) : (z.info("migrate complete", {
|
|
714
730
|
applied: i.applied.length,
|
|
715
731
|
skipped: i.skipped.length
|
|
716
732
|
}), 0);
|
|
717
|
-
},
|
|
718
|
-
let t =
|
|
719
|
-
if (r.length === 0) return
|
|
720
|
-
let i = await
|
|
721
|
-
return i.failed ? (process.stderr.write(
|
|
722
|
-
},
|
|
723
|
-
let t = await
|
|
733
|
+
}, Ft = async (e) => {
|
|
734
|
+
let t = V(e), n = l(e, "--to"), r = await lt(await B(t, ct), z);
|
|
735
|
+
if (r.length === 0) return z.info("no migrations found", { root: t }), 0;
|
|
736
|
+
let i = await pt(r, await K(), z, n ? { to: n } : {});
|
|
737
|
+
return i.failed ? (process.stderr.write(ht(i) + "\n"), 1) : (z.info("rollback complete", { reverted: i.applied.length }), 0);
|
|
738
|
+
}, It = async (e) => {
|
|
739
|
+
let t = await lt(await B(V(e), ct), z);
|
|
724
740
|
if (t.length === 0) return console.log("no migrations found"), 0;
|
|
725
|
-
let n = await
|
|
741
|
+
let n = await mt(t, await K());
|
|
726
742
|
for (let e of n) {
|
|
727
743
|
let t = e.status === "applied" ? "✓" : e.status === "failed" ? "✗" : e.status === "reverted" ? "↩" : "·", n = e.hashMatches ? "" : " (hash drift!)", r = e.appliedAt ? ` @ ${e.appliedAt.toISOString()}` : "";
|
|
728
744
|
console.log(` ${t} ${e.id} [${e.status}] ${e.name}${r}${n}`);
|
|
729
745
|
}
|
|
730
746
|
return 0;
|
|
731
|
-
},
|
|
732
|
-
let t = (
|
|
733
|
-
if (t === "memory") return
|
|
734
|
-
store: new
|
|
747
|
+
}, Lt = async (e) => {
|
|
748
|
+
let t = (l(e, "--store") ?? process.env.DB_DIALECT ?? "postgres").toLowerCase();
|
|
749
|
+
if (t === "memory") return z.warn("voltro db seed: using in-memory store (explicit opt-in via --store memory / DB_DIALECT=memory)"), z.warn(" rows written by this run will NOT persist beyond the process. set DB_DIALECT=postgres or unset to seed the real DB."), {
|
|
750
|
+
store: new ge(),
|
|
735
751
|
dispose: async () => {},
|
|
736
752
|
dialectId: "memory"
|
|
737
753
|
};
|
|
738
|
-
let n = await
|
|
739
|
-
|
|
754
|
+
let n = await d(t);
|
|
755
|
+
z.info(`voltro db seed: target store=${n.id} — rows will persist`);
|
|
740
756
|
let r = await n.makeStore({
|
|
741
|
-
sqlLayer: n.makeSqlLayer(
|
|
757
|
+
sqlLayer: n.makeSqlLayer(H()),
|
|
742
758
|
changeStrategy: "inline"
|
|
743
759
|
}), i = r;
|
|
744
760
|
return {
|
|
@@ -748,79 +764,79 @@ var Fe = (e) => typeof e != "string", Ie = (e, t, n, r) => {
|
|
|
748
764
|
},
|
|
749
765
|
dialectId: n.id
|
|
750
766
|
};
|
|
751
|
-
},
|
|
752
|
-
let t =
|
|
753
|
-
if (a.length === 0) return
|
|
754
|
-
let { store: o, dispose:
|
|
767
|
+
}, Rt = async (e) => {
|
|
768
|
+
let t = V(e), n = l(e, "--id"), r = l(e, "--lifecycle"), i = await B(t, x), a = await m(i, z);
|
|
769
|
+
if (a.length === 0) return z.info("no seeds found", { root: t }), 0;
|
|
770
|
+
let { store: o, dispose: s, dialectId: c } = await Lt(e);
|
|
755
771
|
try {
|
|
756
|
-
if (n) return +((await
|
|
772
|
+
if (n) return +((await p(a, n, o, z)).status === "failed");
|
|
757
773
|
let e = r ? a.filter((e) => e.definition.lifecycle === r) : a.filter((e) => e.definition.lifecycle === "boot");
|
|
758
|
-
return +((await
|
|
774
|
+
return +((await h(e.map((e) => ({
|
|
759
775
|
...e,
|
|
760
776
|
definition: {
|
|
761
777
|
...e.definition,
|
|
762
778
|
lifecycle: "boot"
|
|
763
779
|
}
|
|
764
|
-
})), o,
|
|
780
|
+
})), o, z, { force: !0 })).filter((e) => e.status === "failed").length > 0);
|
|
765
781
|
} finally {
|
|
766
|
-
await
|
|
767
|
-
|
|
782
|
+
await s().catch((e) => {
|
|
783
|
+
z.warn("voltro db seed: store dispose failed", { error: String(e) });
|
|
768
784
|
});
|
|
769
785
|
}
|
|
770
|
-
},
|
|
771
|
-
let t = await
|
|
786
|
+
}, zt = (e) => typeof e == "object" && !!e && "tableName" in e && "fields" in e && typeof e.tableName == "string", Bt = async (e) => {
|
|
787
|
+
let t = await B(e, S), n = [];
|
|
772
788
|
for (let r of t) {
|
|
773
789
|
let t;
|
|
774
790
|
try {
|
|
775
791
|
t = await import(D(r).href);
|
|
776
792
|
} catch (t) {
|
|
777
|
-
|
|
793
|
+
z.warn("db: failed to import schema file", {
|
|
778
794
|
file: w(e, r),
|
|
779
795
|
error: String(t)
|
|
780
796
|
});
|
|
781
797
|
continue;
|
|
782
798
|
}
|
|
783
|
-
for (let e of Object.values(t))
|
|
799
|
+
for (let e of Object.values(t)) zt(e) && n.push(e);
|
|
784
800
|
}
|
|
785
801
|
return n;
|
|
786
|
-
},
|
|
802
|
+
}, Vt = (e) => {
|
|
787
803
|
let t = new Map(e.map((e) => [e.tableName, e]));
|
|
788
|
-
t.has("actors") || t.set("actors",
|
|
804
|
+
t.has("actors") || t.set("actors", re);
|
|
789
805
|
let n = t.get("actors"), r = t.get("tenants");
|
|
790
|
-
return
|
|
806
|
+
return pe({
|
|
791
807
|
actors: () => n,
|
|
792
808
|
...r === void 0 ? {} : { tenants: () => r }
|
|
793
809
|
}), [...t.values()];
|
|
794
|
-
},
|
|
810
|
+
}, Ht = (e) => {
|
|
795
811
|
let t = /* @__PURE__ */ new Map();
|
|
796
812
|
for (let n of e) {
|
|
797
813
|
let e = n.tableName;
|
|
798
814
|
e !== void 0 && !t.has(e) && t.set(e, n);
|
|
799
815
|
}
|
|
800
816
|
return [...t.values()];
|
|
801
|
-
},
|
|
802
|
-
let t =
|
|
817
|
+
}, Ut = (e) => {
|
|
818
|
+
let t = we(process.env.VOLTRO_DESTRUCTIVE_OK);
|
|
803
819
|
if (t === void 0 || !e.operations.filter((e) => e.blocked).every((e) => e.classification === "lossy")) return {
|
|
804
820
|
plan: e,
|
|
805
821
|
acknowledged: !1
|
|
806
822
|
};
|
|
807
|
-
let n =
|
|
823
|
+
let n = Pe(e, t);
|
|
808
824
|
return {
|
|
809
825
|
plan: n,
|
|
810
826
|
acknowledged: !n.operations.some((e) => e.blocked)
|
|
811
827
|
};
|
|
812
|
-
},
|
|
828
|
+
}, q = async (e, t) => A.runPromise(t.pipe(A.provide(e))), J = async (e) => {
|
|
813
829
|
try {
|
|
814
|
-
return await
|
|
830
|
+
return await q(e, j.SqlClient.pipe(A.flatMap(N)));
|
|
815
831
|
} catch (e) {
|
|
816
832
|
let t = String(e?.message ?? e);
|
|
817
|
-
if (
|
|
818
|
-
let n = /* @__PURE__ */ Error(`${
|
|
833
|
+
if (Mt(e) || /connection error/i.test(t)) {
|
|
834
|
+
let n = /* @__PURE__ */ Error(`${Nt}\n\nOriginal error: ${t}`);
|
|
819
835
|
throw n.cause = e, n;
|
|
820
836
|
}
|
|
821
837
|
throw e;
|
|
822
838
|
}
|
|
823
|
-
},
|
|
839
|
+
}, Wt = {
|
|
824
840
|
safe: "\x1B[32m✓\x1B[0m",
|
|
825
841
|
"needs-default": "\x1B[32m⊕\x1B[0m",
|
|
826
842
|
"needs-backfill": "\x1B[33m⊕\x1B[0m",
|
|
@@ -828,7 +844,7 @@ var Fe = (e) => typeof e != "string", Ie = (e, t, n, r) => {
|
|
|
828
844
|
lossy: "\x1B[31m✗\x1B[0m",
|
|
829
845
|
"online-required": "\x1B[36m◷\x1B[0m",
|
|
830
846
|
"multi-step": "\x1B[35m⇉\x1B[0m"
|
|
831
|
-
},
|
|
847
|
+
}, Gt = (e) => {
|
|
832
848
|
let t = e.op, n;
|
|
833
849
|
switch (t.kind) {
|
|
834
850
|
case "create-table":
|
|
@@ -890,9 +906,9 @@ var Fe = (e) => typeof e != "string", Ie = (e, t, n, r) => {
|
|
|
890
906
|
break;
|
|
891
907
|
case "drop-check": n = `ALTER TABLE ${t.table} DROP CHECK ${t.column}`;
|
|
892
908
|
}
|
|
893
|
-
let r =
|
|
909
|
+
let r = Wt[e.classification], i = e.reason ? ` [2m# ${e.reason}[0m` : "", a = e.blocked ? `\n [31m! ${e.blocked.fix}[0m` : "";
|
|
894
910
|
return ` ${r} ${n}${i}${a}`;
|
|
895
|
-
},
|
|
911
|
+
}, Kt = async (e, t) => {
|
|
896
912
|
let n = e.operations.filter((e) => e.classification === "needs-default" && e.op.kind === "add-column");
|
|
897
913
|
if (n.length === 0) return;
|
|
898
914
|
let r = [];
|
|
@@ -901,7 +917,7 @@ var Fe = (e) => typeof e != "string", Ie = (e, t, n, r) => {
|
|
|
901
917
|
if (i === void 0 || a === void 0) continue;
|
|
902
918
|
let o = 0;
|
|
903
919
|
try {
|
|
904
|
-
let e = await
|
|
920
|
+
let e = await q(t, j.SqlClient.pipe(A.flatMap((e) => e.unsafe(`SELECT COUNT(*) AS n FROM ${fe(i, U())}`))));
|
|
905
921
|
o = Number(Object.values(e[0] ?? { n: 0 })[0] ?? 0);
|
|
906
922
|
} catch {
|
|
907
923
|
continue;
|
|
@@ -913,28 +929,28 @@ var Fe = (e) => typeof e != "string", Ie = (e, t, n, r) => {
|
|
|
913
929
|
for (let e of r) console.log(e);
|
|
914
930
|
console.log(" (the shape is correct either way — this is the VALUE a state diff cannot know)");
|
|
915
931
|
}
|
|
916
|
-
},
|
|
917
|
-
if (
|
|
918
|
-
let n =
|
|
932
|
+
}, qt = async (e, t) => {
|
|
933
|
+
if (U() !== "postgres") return;
|
|
934
|
+
let n = ve, r;
|
|
919
935
|
try {
|
|
920
|
-
r = await
|
|
936
|
+
r = await q(t, j.SqlClient.pipe(A.flatMap((t) => g(t, e, n))));
|
|
921
937
|
} catch {
|
|
922
938
|
return;
|
|
923
939
|
}
|
|
924
940
|
let { drift: i, statements: a } = r;
|
|
925
941
|
if (a.length === 0) return;
|
|
926
|
-
|
|
942
|
+
z.info(i.missing.length + i.stale.length > 0 ? `db apply: installing change triggers on ${i.missing.length} table(s)` + (i.stale.length > 0 ? `, removing ${i.stale.length} stale one(s)` : "") + (i.functionOutdated ? ", replacing an outdated notify function" : "") : "db apply: replacing an outdated change-notify function (no table drift)");
|
|
927
943
|
let o = 0;
|
|
928
|
-
for (let e of a) await
|
|
929
|
-
|
|
944
|
+
for (let e of a) await q(t, j.SqlClient.pipe(A.flatMap((t) => t.unsafe(e)))), o += 1;
|
|
945
|
+
z.info(`db apply: change triggers converged (${o} statement(s))`, {
|
|
930
946
|
installed: i.missing.length,
|
|
931
947
|
removed: i.stale.length,
|
|
932
948
|
functionReplaced: i.functionOutdated
|
|
933
949
|
});
|
|
934
|
-
},
|
|
950
|
+
}, Y = (e) => {
|
|
935
951
|
let t = e.summary, n = e.operations.length;
|
|
936
|
-
if (
|
|
937
|
-
|
|
952
|
+
if (ne() === "json") {
|
|
953
|
+
z.info(`schema diff: ${n} operation${n === 1 ? "" : "s"}, ${t.blocked} blocked${n === 0 ? " — schema is up to date" : ""}`, {
|
|
938
954
|
operations: n,
|
|
939
955
|
blocked: t.blocked,
|
|
940
956
|
safe: t.safe,
|
|
@@ -942,23 +958,23 @@ var Fe = (e) => typeof e != "string", Ie = (e, t, n, r) => {
|
|
|
942
958
|
needsBackfill: t.needsBackfill,
|
|
943
959
|
rename: t.needsRenameAnnotation,
|
|
944
960
|
lossy: t.lossy,
|
|
945
|
-
ops: e.operations.map((e) =>
|
|
961
|
+
ops: e.operations.map((e) => Gt(e).trim()),
|
|
946
962
|
fingerprintLive: e.fromFingerprint.slice(0, 16),
|
|
947
963
|
fingerprintDeclared: e.toFingerprint.slice(0, 16)
|
|
948
|
-
}),
|
|
964
|
+
}), Jt(e);
|
|
949
965
|
return;
|
|
950
966
|
}
|
|
951
967
|
if (console.log(`\nschema diff: ${n} operation${n === 1 ? "" : "s"}, ${t.blocked} blocked\n`), n === 0) {
|
|
952
968
|
console.log(" (schema is up to date)\n");
|
|
953
969
|
return;
|
|
954
970
|
}
|
|
955
|
-
for (let t of e.operations) console.log(
|
|
956
|
-
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: live ${e.fromFingerprint.slice(0, 16)} · declared ${e.toFingerprint.slice(0, 16)}`), console.log(" (measured differently — a live hash never equals a declared one. The plan above is"), console.log(" what says whether they agree; these two are not meant to match.)\n"),
|
|
957
|
-
},
|
|
958
|
-
let t =
|
|
971
|
+
for (let t of e.operations) console.log(Gt(t));
|
|
972
|
+
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: live ${e.fromFingerprint.slice(0, 16)} · declared ${e.toFingerprint.slice(0, 16)}`), console.log(" (measured differently — a live hash never equals a declared one. The plan above is"), console.log(" what says whether they agree; these two are not meant to match.)\n"), Jt(e);
|
|
973
|
+
}, Jt = (e) => {
|
|
974
|
+
let t = je(e.operations);
|
|
959
975
|
if (t.length !== 0) {
|
|
960
|
-
if (
|
|
961
|
-
|
|
976
|
+
if (ne() === "json") {
|
|
977
|
+
z.warn(`${t.length} operation(s) UNSAFE under a rolling deploy — old code breaks against the new schema`, {
|
|
962
978
|
unsafe: t.map((e) => ({
|
|
963
979
|
kind: e.op.op.kind,
|
|
964
980
|
reason: e.reason,
|
|
@@ -972,204 +988,204 @@ var Fe = (e) => typeof e != "string", Ie = (e, t, n, r) => {
|
|
|
972
988
|
for (let e of t) console.log(` • ${e.op.op.kind}: ${e.reason}`), console.log(` → ${e.remedy}`);
|
|
973
989
|
console.log(" Deploy with a maintenance window / scale-to-zero, or split into"), console.log(" expand → deploy → contract. See docs: deployment/production-hardening.\n");
|
|
974
990
|
}
|
|
975
|
-
},
|
|
976
|
-
let n =
|
|
991
|
+
}, Yt = (e, t) => {
|
|
992
|
+
let n = xe({
|
|
977
993
|
enabled: process.env.VOLTRO_ROLLING_DEPLOY === "1",
|
|
978
994
|
forced: t.includes("--force"),
|
|
979
|
-
unsafeCount:
|
|
995
|
+
unsafeCount: je(e.operations).length
|
|
980
996
|
});
|
|
981
|
-
return n.refuse ? (
|
|
982
|
-
},
|
|
983
|
-
let t =
|
|
997
|
+
return n.refuse ? (z.error(`db apply: ${n.message}`), !0) : (n.warnForced && z.warn(`db apply: ${n.message}`), !1);
|
|
998
|
+
}, Xt = (e) => {
|
|
999
|
+
let t = l(e, "--online-after") ?? process.env.VOLTRO_ONLINE_THRESHOLD;
|
|
984
1000
|
if (t === void 0) return;
|
|
985
1001
|
let n = Number(t);
|
|
986
1002
|
if (!Number.isInteger(n) || n < 0) throw Error(`--online-after / VOLTRO_ONLINE_THRESHOLD must be a non-negative integer, got "${t}"`);
|
|
987
1003
|
return n;
|
|
988
|
-
},
|
|
989
|
-
let t = await
|
|
1004
|
+
}, X = async (e) => {
|
|
1005
|
+
let t = await Bt(e);
|
|
990
1006
|
if (t.length === 0) return;
|
|
991
|
-
let n = await
|
|
992
|
-
return
|
|
1007
|
+
let n = await b({ root: e });
|
|
1008
|
+
return Vt(Ht([
|
|
993
1009
|
...t,
|
|
994
|
-
...
|
|
1010
|
+
...se,
|
|
995
1011
|
...n
|
|
996
1012
|
]));
|
|
997
|
-
},
|
|
1013
|
+
}, Z = async (e, t, n) => {
|
|
998
1014
|
let r;
|
|
999
1015
|
try {
|
|
1000
|
-
let t = await
|
|
1001
|
-
await
|
|
1002
|
-
|
|
1016
|
+
let t = await y(e);
|
|
1017
|
+
await q(n, v(t, (e, t) => {
|
|
1018
|
+
z.info(`db apply: ${e}`, t ?? {});
|
|
1003
1019
|
}));
|
|
1004
1020
|
} catch (e) {
|
|
1005
1021
|
r = e;
|
|
1006
1022
|
}
|
|
1007
|
-
return await
|
|
1008
|
-
},
|
|
1009
|
-
let n = { close: async () => {} }, r = await
|
|
1023
|
+
return await qt(t, n), r === void 0 ? 0 : (z.error("db apply: a plugin migration FAILED — the schema was applied, the plugin steps were not", { error: r instanceof Error ? r.message : String(r) }), 1);
|
|
1024
|
+
}, Zt = async (e, t) => {
|
|
1025
|
+
let n = { close: async () => {} }, r = await B(e, x);
|
|
1010
1026
|
if (r.length === 0) return n;
|
|
1011
|
-
let i = await
|
|
1027
|
+
let i = await m(r, z), a = i.filter((e) => e.definition.lifecycle === "onSchemaChange");
|
|
1012
1028
|
if (a.length === 0) return n;
|
|
1013
|
-
let { store: o, dispose: s } = await
|
|
1014
|
-
return
|
|
1029
|
+
let { store: o, dispose: s } = await Lt(t), c = _(i, o, z.child("seeds"));
|
|
1030
|
+
return z.info(`db apply: ${a.length} onSchemaChange seed(s) armed for this apply`, { ids: a.map((e) => e.definition.id) }), { close: async () => {
|
|
1015
1031
|
c(), await s().catch((e) => {
|
|
1016
|
-
|
|
1032
|
+
z.warn("db apply: seed store dispose failed", { error: String(e) });
|
|
1017
1033
|
});
|
|
1018
1034
|
} };
|
|
1019
|
-
},
|
|
1020
|
-
let t =
|
|
1021
|
-
if (r === void 0) return
|
|
1035
|
+
}, Qt = async (e) => {
|
|
1036
|
+
let t = Xt(e), n = V(e), r = await X(n);
|
|
1037
|
+
if (r === void 0) return z.warn("db plan: no schema files found", {
|
|
1022
1038
|
root: n,
|
|
1023
1039
|
hint: "looked for *.entity.ts / *.schema.ts / schema.ts"
|
|
1024
1040
|
}), 1;
|
|
1025
|
-
let i =
|
|
1041
|
+
let i = l(e, "--against");
|
|
1026
1042
|
if (i) {
|
|
1027
|
-
let n = i,
|
|
1028
|
-
|
|
1043
|
+
let n = i, s = l(e, "--token") ?? process.env.VOLTRO_INSPECT_TOKEN, c = { accept: "application/json" };
|
|
1044
|
+
s && (c.authorization = `Bearer ${s}`);
|
|
1029
1045
|
try {
|
|
1030
|
-
let e = n.endsWith("/_voltro/inspect/migrations") ? n : `${n.replace(/\/+$/, "")}/_voltro/inspect/migrations`, i = await fetch(e, { headers:
|
|
1031
|
-
if (!i.ok) return
|
|
1032
|
-
let
|
|
1033
|
-
if (!
|
|
1034
|
-
let
|
|
1046
|
+
let e = n.endsWith("/_voltro/inspect/migrations") ? n : `${n.replace(/\/+$/, "")}/_voltro/inspect/migrations`, i = await fetch(e, { headers: c });
|
|
1047
|
+
if (!i.ok) return z.error(`db plan --against: remote returned ${i.status}`, { url: e }), a(i.status) && console.error(` ${o(i.status)}`), 1;
|
|
1048
|
+
let s = (await i.json()).drift?.liveSnapshot;
|
|
1049
|
+
if (!s) return z.error("db plan --against: remote inspect payload missing drift.liveSnapshot", { url: e }), 1;
|
|
1050
|
+
let l = P({
|
|
1035
1051
|
declared: r,
|
|
1036
|
-
live:
|
|
1037
|
-
dialect:
|
|
1038
|
-
...
|
|
1052
|
+
live: s,
|
|
1053
|
+
dialect: U(),
|
|
1054
|
+
...M(),
|
|
1039
1055
|
...t === void 0 ? {} : { onlineAfter: t }
|
|
1040
1056
|
});
|
|
1041
|
-
return console.log(`\n[--against ${e}] — diff vs remote live schema:\n`),
|
|
1057
|
+
return console.log(`\n[--against ${e}] — diff vs remote live schema:\n`), Y(l), l.summary.blocked > 0 ? 2 : 0;
|
|
1042
1058
|
} catch (e) {
|
|
1043
|
-
return
|
|
1059
|
+
return z.error(`db plan --against: ${e.message}`), 1;
|
|
1044
1060
|
}
|
|
1045
1061
|
}
|
|
1046
|
-
let
|
|
1062
|
+
let s = await J(await K()), c = P({
|
|
1047
1063
|
declared: r,
|
|
1048
|
-
live:
|
|
1049
|
-
dialect:
|
|
1050
|
-
...
|
|
1064
|
+
live: s,
|
|
1065
|
+
dialect: U(),
|
|
1066
|
+
...M(),
|
|
1051
1067
|
...t === void 0 ? {} : { onlineAfter: t }
|
|
1052
1068
|
});
|
|
1053
|
-
return e.includes("--json") ? (
|
|
1054
|
-
},
|
|
1069
|
+
return e.includes("--json") ? (Le(1, `${JSON.stringify(c, null, 2)}\n`), c.summary.blocked > 0 ? 2 : 0) : (Y(c), c.summary.blocked > 0 ? 2 : 0);
|
|
1070
|
+
}, Q = n, $t = async (e, t, n) => {
|
|
1055
1071
|
let r;
|
|
1056
1072
|
try {
|
|
1057
1073
|
let t = await E.readFile(T(e), "utf8");
|
|
1058
1074
|
r = JSON.parse(t);
|
|
1059
1075
|
} catch (t) {
|
|
1060
|
-
return
|
|
1076
|
+
return z.error(`db apply --plan: cannot read/parse plan file '${e}': ${t.message}`), 1;
|
|
1061
1077
|
}
|
|
1062
|
-
if (!Array.isArray(r?.operations) || typeof r?.fromFingerprint != "string" || typeof r?.toFingerprint != "string") return
|
|
1063
|
-
let i = await
|
|
1064
|
-
if (i === void 0) return
|
|
1065
|
-
let a = await
|
|
1066
|
-
if (o.length > 0) return
|
|
1078
|
+
if (!Array.isArray(r?.operations) || typeof r?.fromFingerprint != "string" || typeof r?.toFingerprint != "string") return z.error(`db apply --plan: '${e}' is not a valid plan file (expected the output of \`voltro db plan --json\`).`), 1;
|
|
1079
|
+
let i = await X(n);
|
|
1080
|
+
if (i === void 0) return z.warn("db apply --plan: no schema files found", { root: n }), 1;
|
|
1081
|
+
let a = await K(), o = await q(a, j.SqlClient.pipe(A.flatMap((e) => De(e, n)))).catch(() => []);
|
|
1082
|
+
if (o.length > 0) return z.error(`db apply --plan: refusing — ${o.length} pending file-based migration(s).`, {
|
|
1067
1083
|
pending: o,
|
|
1068
1084
|
why: "file migrations perform changes a state diff cannot infer, so they change the shape this plan was computed against",
|
|
1069
1085
|
fix: "voltro db files . && voltro db plan --json > plan.json && voltro db apply --plan plan.json"
|
|
1070
1086
|
}), 1;
|
|
1071
|
-
let
|
|
1087
|
+
let s = await J(a), c = P({
|
|
1072
1088
|
declared: i,
|
|
1073
|
-
live:
|
|
1074
|
-
dialect:
|
|
1075
|
-
...
|
|
1089
|
+
live: s,
|
|
1090
|
+
dialect: U(),
|
|
1091
|
+
...M()
|
|
1076
1092
|
});
|
|
1077
|
-
if (
|
|
1093
|
+
if (c.fromFingerprint !== r.fromFingerprint) return z.error("db apply --plan: refusing — the live schema has drifted since this plan was generated.", {
|
|
1078
1094
|
planFrom: r.fromFingerprint.slice(0, 16),
|
|
1079
|
-
live:
|
|
1095
|
+
live: c.fromFingerprint.slice(0, 16),
|
|
1080
1096
|
fix: "regenerate against the current live schema: voltro db plan --json > plan.json"
|
|
1081
1097
|
}), 2;
|
|
1082
|
-
if (
|
|
1098
|
+
if (c.toFingerprint !== r.toFingerprint) return z.error("db apply --plan: refusing — the declared schema differs from what this plan targets.", {
|
|
1083
1099
|
planTo: r.toFingerprint.slice(0, 16),
|
|
1084
|
-
declared:
|
|
1100
|
+
declared: c.toFingerprint.slice(0, 16),
|
|
1085
1101
|
fix: "the schema files changed since the plan was generated — regenerate the plan"
|
|
1086
1102
|
}), 2;
|
|
1087
|
-
let u =
|
|
1088
|
-
if (d.summary.blocked > 0 && !u.acknowledged) return
|
|
1089
|
-
if (u.acknowledged &&
|
|
1103
|
+
let u = Ut(c), d = u.plan;
|
|
1104
|
+
if (d.summary.blocked > 0 && !u.acknowledged) return Y(d), z.error(`db apply --plan: refusing — ${c.summary.blocked} blocked operation(s). See the fix hints above.`), process.env.VOLTRO_DESTRUCTIVE_OK !== void 0 && (z.error(" VOLTRO_DESTRUCTIVE_OK is set but did not cover this plan — it only relaxes"), z.error(" `lossy` operations, and only the tables it names. Anything else stays refused.")), 2;
|
|
1105
|
+
if (u.acknowledged && z.warn("db apply --plan: proceeding with acknowledged lossy operation(s) (VOLTRO_DESTRUCTIVE_OK)"), Yt(d, t)) return 2;
|
|
1090
1106
|
if (d.operations.length === 0) {
|
|
1091
|
-
let e = await
|
|
1107
|
+
let e = await q(a, j.SqlClient.pipe(A.flatMap((e) => Oe(e, d, {
|
|
1092
1108
|
appliedBy: process.env.USER ? `cli:${process.env.USER}` : "cli:plan",
|
|
1093
|
-
environment:
|
|
1109
|
+
environment: Q(),
|
|
1094
1110
|
source: "file"
|
|
1095
1111
|
}))));
|
|
1096
|
-
return
|
|
1112
|
+
return z.info(e === "recorded" ? `db apply --plan: schema is up to date — no DDL to apply, recorded fingerprint=${d.toFingerprint.slice(0, 16)} (change triggers are reconciled separately below)` : "db apply --plan: schema is up to date — no DDL to apply (change triggers are reconciled separately below)"), Z(n, i, a);
|
|
1097
1113
|
}
|
|
1098
|
-
|
|
1099
|
-
let f =
|
|
1114
|
+
Y(d);
|
|
1115
|
+
let f = l(t, "--note"), p = await q(a, j.SqlClient.pipe(A.flatMap((e) => ye(e, d, {
|
|
1100
1116
|
declared: i,
|
|
1101
1117
|
appliedBy: process.env.USER ? `cli:${process.env.USER}` : "cli:plan",
|
|
1102
|
-
environment:
|
|
1118
|
+
environment: Q(),
|
|
1103
1119
|
source: "file",
|
|
1104
1120
|
...f ? { notes: f } : {},
|
|
1105
|
-
replan: (e) =>
|
|
1121
|
+
replan: (e) => N(e).pipe(A.map((e) => P({
|
|
1106
1122
|
declared: i,
|
|
1107
1123
|
live: e,
|
|
1108
|
-
dialect:
|
|
1109
|
-
...
|
|
1124
|
+
dialect: U(),
|
|
1125
|
+
...M()
|
|
1110
1126
|
})))
|
|
1111
1127
|
}))));
|
|
1112
|
-
return
|
|
1128
|
+
return z.info(`db apply --plan: applied ${p.appliedOps} op(s) in ${p.durationMs}ms`, {
|
|
1113
1129
|
id: p.id,
|
|
1114
1130
|
fingerprint: p.fingerprint.slice(0, 16)
|
|
1115
|
-
}),
|
|
1116
|
-
},
|
|
1117
|
-
let t =
|
|
1131
|
+
}), Z(n, i, a);
|
|
1132
|
+
}, en = async (e) => {
|
|
1133
|
+
let t = V(e), n = await Zt(t, e);
|
|
1118
1134
|
try {
|
|
1119
|
-
return await
|
|
1135
|
+
return await tn(e, t);
|
|
1120
1136
|
} finally {
|
|
1121
1137
|
await n.close();
|
|
1122
1138
|
}
|
|
1123
|
-
},
|
|
1124
|
-
let r =
|
|
1125
|
-
if (i) return
|
|
1126
|
-
if ((process.env.NODE_ENV ?? "").toLowerCase() === "production") return
|
|
1127
|
-
let a = await
|
|
1128
|
-
if (a === void 0) return
|
|
1129
|
-
let o = await
|
|
1130
|
-
if ("failed" in
|
|
1131
|
-
failed:
|
|
1139
|
+
}, tn = async (e, n) => {
|
|
1140
|
+
let r = Xt(e), i = l(e, "--plan");
|
|
1141
|
+
if (i) return $t(i, e, n);
|
|
1142
|
+
if ((process.env.NODE_ENV ?? "").toLowerCase() === "production") return z.error(`voltro db apply: refusing — auto-apply on prod is not allowed.${t()}`, { fix: "in production, schema changes go through `voltro db plan --json > plan.json`, then `voltro db apply --plan plan.json` as an explicit deploy step" }), 3;
|
|
1143
|
+
let a = await X(n);
|
|
1144
|
+
if (a === void 0) return z.warn("db apply: no schema files found", { root: n }), 1;
|
|
1145
|
+
let o = await K(), s = await vn(n, o);
|
|
1146
|
+
if ("failed" in s) return z.error("db apply: a file-based migration FAILED — the schema diff was NOT run", {
|
|
1147
|
+
failed: s.failed,
|
|
1132
1148
|
fix: "fix the migration and re-run; `voltro db files` runs them alone"
|
|
1133
1149
|
}), 1;
|
|
1134
|
-
let
|
|
1150
|
+
let c = await J(o), u = P({
|
|
1135
1151
|
declared: a,
|
|
1136
|
-
live:
|
|
1137
|
-
dialect:
|
|
1138
|
-
...
|
|
1152
|
+
live: c,
|
|
1153
|
+
dialect: U(),
|
|
1154
|
+
...M(),
|
|
1139
1155
|
...r === void 0 ? {} : { onlineAfter: r }
|
|
1140
1156
|
});
|
|
1141
|
-
|
|
1142
|
-
let d =
|
|
1143
|
-
if (f.summary.blocked > 0 && !d.acknowledged) return
|
|
1144
|
-
if (d.acknowledged &&
|
|
1157
|
+
Y(u), await Kt(u, o);
|
|
1158
|
+
let d = Ut(u), f = d.plan;
|
|
1159
|
+
if (f.summary.blocked > 0 && !d.acknowledged) return z.error(`db apply: refusing — ${f.summary.blocked} blocked operation(s). See the fix hints above.`), process.env.VOLTRO_DESTRUCTIVE_OK !== void 0 && (z.error(" VOLTRO_DESTRUCTIVE_OK is set but did not cover this plan — it only relaxes"), z.error(" `lossy` operations, and only the tables it names. Anything else stays refused.")), 2;
|
|
1160
|
+
if (d.acknowledged && z.warn(`db apply: proceeding with ${u.summary.blocked} acknowledged lossy operation(s) (VOLTRO_DESTRUCTIVE_OK)`), Yt(f, e)) return 2;
|
|
1145
1161
|
if (f.operations.length === 0) {
|
|
1146
|
-
|
|
1147
|
-
let t = await
|
|
1162
|
+
z.info("db apply: schema is up to date — no DDL to apply (change triggers are reconciled separately below)");
|
|
1163
|
+
let t = await q(o, j.SqlClient.pipe(A.flatMap((e) => Oe(e, f, {
|
|
1148
1164
|
appliedBy: process.env.USER ? `cli:${process.env.USER}` : "cli:apply",
|
|
1149
|
-
environment:
|
|
1165
|
+
environment: Q(),
|
|
1150
1166
|
source: "auto-diff"
|
|
1151
1167
|
}))));
|
|
1152
|
-
return t === "recorded" &&
|
|
1168
|
+
return t === "recorded" && z.info(`db apply: recorded fingerprint=${f.toFingerprint.slice(0, 16)} (0 operations)`), await pn(o), l(e, "--note") !== void 0 && t !== "recorded" && z.warn("db apply: --note was ignored — this schema is already the latest recorded one, so there is no new history row to attach it to.", { hint: "to record the CURRENT live schema as the accepted baseline, use `voltro db drift --accept`" }), Z(n, a, o);
|
|
1153
1169
|
}
|
|
1154
|
-
let p =
|
|
1170
|
+
let p = l(e, "--note"), m = await q(o, j.SqlClient.pipe(A.flatMap((e) => ye(e, f, {
|
|
1155
1171
|
declared: a,
|
|
1156
1172
|
appliedBy: process.env.USER ? `cli:${process.env.USER}` : "cli:user",
|
|
1157
|
-
environment:
|
|
1173
|
+
environment: Q(),
|
|
1158
1174
|
source: "auto-diff",
|
|
1159
1175
|
...p ? { notes: p } : {},
|
|
1160
|
-
replan: (e) =>
|
|
1176
|
+
replan: (e) => N(e).pipe(A.map((e) => P({
|
|
1161
1177
|
declared: a,
|
|
1162
1178
|
live: e,
|
|
1163
|
-
dialect:
|
|
1164
|
-
...
|
|
1179
|
+
dialect: U(),
|
|
1180
|
+
...M(),
|
|
1165
1181
|
...r === void 0 ? {} : { onlineAfter: r }
|
|
1166
1182
|
})))
|
|
1167
1183
|
}))));
|
|
1168
|
-
return
|
|
1184
|
+
return z.info(`db apply: applied ${m.appliedOps} op(s) in ${m.durationMs}ms`, {
|
|
1169
1185
|
id: m.id,
|
|
1170
1186
|
fingerprint: m.fingerprint.slice(0, 16)
|
|
1171
|
-
}),
|
|
1172
|
-
},
|
|
1187
|
+
}), Z(n, a, o);
|
|
1188
|
+
}, nn = (e) => {
|
|
1173
1189
|
let t = [];
|
|
1174
1190
|
for (let n of e.tables) for (let e of n.columns) {
|
|
1175
1191
|
let r = e.references;
|
|
@@ -1182,7 +1198,7 @@ var Fe = (e) => typeof e != "string", Ie = (e, t, n, r) => {
|
|
|
1182
1198
|
});
|
|
1183
1199
|
}
|
|
1184
1200
|
return t;
|
|
1185
|
-
},
|
|
1201
|
+
}, rn = (e) => {
|
|
1186
1202
|
let t = [];
|
|
1187
1203
|
for (let n of e.tables) for (let e of n.indexes) e.expression !== !0 && e.name !== `${n.name}_pkey` && e.columns.length !== 0 && t.push({
|
|
1188
1204
|
table: n.name,
|
|
@@ -1191,7 +1207,7 @@ var Fe = (e) => typeof e != "string", Ie = (e, t, n, r) => {
|
|
|
1191
1207
|
name: e.name
|
|
1192
1208
|
});
|
|
1193
1209
|
return t;
|
|
1194
|
-
},
|
|
1210
|
+
}, an = (e, t) => {
|
|
1195
1211
|
let n = (e) => JSON.stringify(e.op), r = new Set(e.operations.map(n)), i = new Set(t.operations.map(n)), a = [...t.operations.filter((e) => !r.has(n(e))), ...e.operations.filter((e) => !i.has(n(e)))];
|
|
1196
1212
|
return {
|
|
1197
1213
|
...t,
|
|
@@ -1201,57 +1217,57 @@ var Fe = (e) => typeof e != "string", Ie = (e, t, n, r) => {
|
|
|
1201
1217
|
blocked: 0
|
|
1202
1218
|
}
|
|
1203
1219
|
};
|
|
1204
|
-
},
|
|
1220
|
+
}, on = (e) => {
|
|
1205
1221
|
if (e === void 0) return "empty";
|
|
1206
1222
|
if (e === "empty" || e === "copy") return e;
|
|
1207
1223
|
throw e === "fresh" ? new r("voltro db branch: --seed fresh is not implemented — the branch lifecycle has no executor step that runs boot seeds, so the branch would come up EMPTY while this reported it as seeded. Use --seed copy (the parent's rows) or --seed empty (schema only).") : new r(`voltro db branch: --seed must be one of empty|copy, got "${e}"`);
|
|
1208
|
-
},
|
|
1224
|
+
}, sn = (e) => {
|
|
1209
1225
|
if (e !== void 0) {
|
|
1210
1226
|
if (e === "namespace" || e === "neon-cow") return e;
|
|
1211
1227
|
throw new r(`voltro db branch: --prefer must be one of namespace|neon-cow, got "${e}"`);
|
|
1212
1228
|
}
|
|
1213
|
-
},
|
|
1214
|
-
let t =
|
|
1215
|
-
if (
|
|
1216
|
-
let
|
|
1217
|
-
if (
|
|
1229
|
+
}, cn = async (e) => {
|
|
1230
|
+
let t = V(e), n = on(l(e, "--seed")), i = sn(l(e, "--prefer")), a = e.includes("--keep"), o = e.includes("--json"), s = Xt(e), c = U();
|
|
1231
|
+
if (c !== "postgres") return z.error(`voltro db branch: not supported on '${c}'.`), console.error(" The branch PLAN is dialect-agnostic; the shipped namespace EXECUTOR is Postgres-only"), console.error(" (CREATE SCHEMA / CREATE TABLE … LIKE … INCLUDING ALL / \"-quoted identifiers)."), console.error(" MySQL, MariaDB, SQLite and SQL Server need their own BranchExecutor — see"), console.error(" makeNamespaceBranchExecutor in @voltro/database."), 1;
|
|
1232
|
+
let u = await X(t);
|
|
1233
|
+
if (u === void 0) return z.warn("db branch: no schema files found", {
|
|
1218
1234
|
root: t,
|
|
1219
1235
|
hint: "looked for *.entity.ts / *.schema.ts / schema.ts"
|
|
1220
1236
|
}), 1;
|
|
1221
|
-
let f =
|
|
1237
|
+
let f = H(), p = me({
|
|
1222
1238
|
seed: n,
|
|
1223
1239
|
...f.url === void 0 ? {} : { dbUrl: f.url },
|
|
1224
1240
|
...i === void 0 ? {} : { prefer: i }
|
|
1225
1241
|
});
|
|
1226
|
-
if (p === "neon-cow") return
|
|
1227
|
-
let m = f.schema ?? "public", h =
|
|
1242
|
+
if (p === "neon-cow") return z.error("voltro db branch: this connection resolves to the Neon copy-on-write mechanism, which the CLI cannot execute."), console.error(" A Neon CoW branch is a single call to Neon's branch REST API and needs a Neon API token;"), console.error(" the framework ships the PLAN, and the cloud control plane executes it."), console.error(" Re-run with `--prefer namespace` to rehearse on a schema-per-branch copy instead"), console.error(` (portable, no vendor API — it copies ${n === "copy" ? "every row" : "the schema only"}).`), 1;
|
|
1243
|
+
let m = f.schema ?? "public", h = l(e, "--name") ?? ee(t), g = l(e, "--pr"), _ = g === void 0 ? 0 : Number(g);
|
|
1228
1244
|
if (!Number.isInteger(_) || _ < 0) throw new r(`voltro db branch: --pr must be a non-negative integer, got "${String(g)}"`);
|
|
1229
|
-
let v =
|
|
1245
|
+
let v = ie(h, _), y = await d(c), b = y.makeSqlLayer(f), x = y.makeSqlLayer({
|
|
1230
1246
|
...f,
|
|
1231
1247
|
schema: v
|
|
1232
|
-
}), S = await
|
|
1233
|
-
declared:
|
|
1248
|
+
}), S = await J(b), C = P({
|
|
1249
|
+
declared: u,
|
|
1234
1250
|
live: S,
|
|
1235
|
-
dialect:
|
|
1236
|
-
...
|
|
1237
|
-
...
|
|
1238
|
-
}), w = S.tables.map((e) => e.name), T =
|
|
1239
|
-
declared:
|
|
1240
|
-
live: await
|
|
1241
|
-
dialect:
|
|
1242
|
-
...
|
|
1243
|
-
...
|
|
1244
|
-
}),
|
|
1251
|
+
dialect: c,
|
|
1252
|
+
...M(),
|
|
1253
|
+
...s === void 0 ? {} : { onlineAfter: s }
|
|
1254
|
+
}), w = S.tables.map((e) => e.name), T = nn(S), E = rn(S), te = ue({ run: (e) => q(b, j.SqlClient.pipe(A.flatMap((t) => t.unsafe(e)), A.asVoid)) }), D = async () => P({
|
|
1255
|
+
declared: u,
|
|
1256
|
+
live: await J(x),
|
|
1257
|
+
dialect: c,
|
|
1258
|
+
...M(),
|
|
1259
|
+
...s === void 0 ? {} : { onlineAfter: s }
|
|
1260
|
+
}), O = await ke({
|
|
1245
1261
|
branchId: v,
|
|
1246
1262
|
mechanism: p,
|
|
1247
1263
|
branchedTables: w.length,
|
|
1248
1264
|
replayedForeignKeys: T.length,
|
|
1249
1265
|
keep: a,
|
|
1250
1266
|
log: (e) => {
|
|
1251
|
-
o ||
|
|
1267
|
+
o || z.info(`db branch: ${e}`);
|
|
1252
1268
|
},
|
|
1253
1269
|
provision: async () => {
|
|
1254
|
-
await
|
|
1270
|
+
await de({
|
|
1255
1271
|
appSlug: h,
|
|
1256
1272
|
prNumber: _,
|
|
1257
1273
|
seed: n,
|
|
@@ -1261,28 +1277,28 @@ var Fe = (e) => typeof e != "string", Ie = (e, t, n, r) => {
|
|
|
1261
1277
|
indexNames: E,
|
|
1262
1278
|
prefer: "namespace",
|
|
1263
1279
|
...f.url === void 0 ? {} : { dbUrl: f.url }
|
|
1264
|
-
},
|
|
1280
|
+
}, te);
|
|
1265
1281
|
},
|
|
1266
|
-
fidelityPlan: async () =>
|
|
1267
|
-
plan:
|
|
1282
|
+
fidelityPlan: async () => an(C, await D()),
|
|
1283
|
+
plan: D,
|
|
1268
1284
|
apply: async (e) => {
|
|
1269
|
-
await
|
|
1270
|
-
declared:
|
|
1285
|
+
await q(x, j.SqlClient.pipe(A.flatMap((t) => ye(t, e, {
|
|
1286
|
+
declared: u,
|
|
1271
1287
|
appliedBy: "cli:db-branch",
|
|
1272
|
-
environment:
|
|
1288
|
+
environment: Q(),
|
|
1273
1289
|
source: "auto-diff",
|
|
1274
1290
|
notes: `migration rehearsal on branch ${v}`,
|
|
1275
|
-
replan: () =>
|
|
1291
|
+
replan: () => A.promise(D)
|
|
1276
1292
|
}))));
|
|
1277
1293
|
},
|
|
1278
|
-
replan: async () => await
|
|
1294
|
+
replan: async () => await D(),
|
|
1279
1295
|
teardown: async () => {
|
|
1280
|
-
await
|
|
1296
|
+
await he(v, "namespace", te);
|
|
1281
1297
|
}
|
|
1282
1298
|
});
|
|
1283
|
-
return o ?
|
|
1284
|
-
},
|
|
1285
|
-
let t = Math.max(1, Number(
|
|
1299
|
+
return o ? Le(1, `${JSON.stringify(O, null, 2)}\n`) : console.log(`\n${Ee(O)}\n`), Se(O);
|
|
1300
|
+
}, ln = async (e) => {
|
|
1301
|
+
let t = Math.max(1, Number(l(e, "--limit") ?? "20")), n = await q(await K(), j.SqlClient.pipe(A.flatMap((e) => e`
|
|
1286
1302
|
SELECT ${e("id")}, ${e("fingerprint")}, ${e("operations")},
|
|
1287
1303
|
${e("appliedBy")}, ${e("environment")}, ${e("source")},
|
|
1288
1304
|
${e("durationMs")}, ${e("notes")}, ${e("appliedAt")}
|
|
@@ -1290,25 +1306,25 @@ var Fe = (e) => typeof e != "string", Ie = (e, t, n, r) => {
|
|
|
1290
1306
|
ORDER BY ${e("appliedAt")} DESC
|
|
1291
1307
|
LIMIT ${t}
|
|
1292
1308
|
`)));
|
|
1293
|
-
if (n.length === 0) return
|
|
1309
|
+
if (n.length === 0) return z.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;
|
|
1294
1310
|
console.log(`\n${n.length} plan${n.length === 1 ? "" : "s"} (newest first):\n`);
|
|
1295
1311
|
for (let e of n) {
|
|
1296
1312
|
let t = 0;
|
|
1297
1313
|
try {
|
|
1298
|
-
t = JSON.parse(
|
|
1314
|
+
t = JSON.parse(mn(e.operations)).length;
|
|
1299
1315
|
} catch {}
|
|
1300
|
-
console.log(` ${e.id} fp=${
|
|
1316
|
+
console.log(` ${e.id} fp=${F(e.fingerprint)} env=${e.environment} src=${e.source} ${t} op(s) ${e.durationMs}ms ${hn(e.appliedAt)} by=${e.appliedBy}`), e.notes && console.log(` [2mnote: ${e.notes}[0m`);
|
|
1301
1317
|
}
|
|
1302
1318
|
return console.log(), 0;
|
|
1303
|
-
},
|
|
1319
|
+
}, un = async (e) => {
|
|
1304
1320
|
let t = e[0];
|
|
1305
1321
|
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;
|
|
1306
|
-
let n = await
|
|
1322
|
+
let n = await K(), r = await q(n, j.SqlClient.pipe(A.flatMap((e) => e`SELECT ${e("id")}, ${e("operations")}
|
|
1307
1323
|
FROM ${e("_voltro_migration_plans")} WHERE ${e("id")} = ${t}`)));
|
|
1308
1324
|
if (r.length === 0) return console.error(`restore-snapshot: plan '${t}' not found`), 1;
|
|
1309
1325
|
let i;
|
|
1310
1326
|
try {
|
|
1311
|
-
i = JSON.parse(
|
|
1327
|
+
i = JSON.parse(mn(r[0].operations));
|
|
1312
1328
|
} catch {
|
|
1313
1329
|
return console.error(`restore-snapshot: plan '${t}' has corrupted operations JSON`), 1;
|
|
1314
1330
|
}
|
|
@@ -1322,9 +1338,9 @@ var Fe = (e) => typeof e != "string", Ie = (e, t, n, r) => {
|
|
|
1322
1338
|
if (a.length === 0 && o.length === 0) return console.log(`restore-snapshot: plan '${t}' has no drop-column / drop-table ops to restore`), 0;
|
|
1323
1339
|
let s = 0;
|
|
1324
1340
|
for (let e of a) {
|
|
1325
|
-
let t = await
|
|
1341
|
+
let t = await q(n, j.SqlClient.pipe(A.flatMap((t) => t`
|
|
1326
1342
|
SELECT column_name FROM information_schema.columns
|
|
1327
|
-
WHERE ${t.unsafe(
|
|
1343
|
+
WHERE ${t.unsafe(G(U()))}
|
|
1328
1344
|
AND table_name = ${e.table}
|
|
1329
1345
|
AND column_name LIKE ${e.column + "__dropped_%"}
|
|
1330
1346
|
ORDER BY column_name DESC LIMIT 1
|
|
@@ -1334,13 +1350,13 @@ var Fe = (e) => typeof e != "string", Ie = (e, t, n, r) => {
|
|
|
1334
1350
|
continue;
|
|
1335
1351
|
}
|
|
1336
1352
|
let r = t[0].column_name;
|
|
1337
|
-
await
|
|
1353
|
+
await q(n, j.SqlClient.pipe(A.flatMap((t) => t.unsafe(`ALTER TABLE ${W(e.table)} RENAME COLUMN ${W(r)} TO ${W(e.column)}`)))), s++, console.log(` ↩ restored ${e.table}.${e.column} (from ${r})`);
|
|
1338
1354
|
}
|
|
1339
1355
|
let c = 0;
|
|
1340
1356
|
for (let e of o) {
|
|
1341
|
-
let t = await
|
|
1357
|
+
let t = await q(n, j.SqlClient.pipe(A.flatMap((t) => t`
|
|
1342
1358
|
SELECT table_name FROM information_schema.tables
|
|
1343
|
-
WHERE ${t.unsafe(
|
|
1359
|
+
WHERE ${t.unsafe(G(U()))}
|
|
1344
1360
|
AND table_name LIKE ${e.table + "__dropped_%"}
|
|
1345
1361
|
ORDER BY table_name DESC LIMIT 1
|
|
1346
1362
|
`)));
|
|
@@ -1349,57 +1365,57 @@ var Fe = (e) => typeof e != "string", Ie = (e, t, n, r) => {
|
|
|
1349
1365
|
continue;
|
|
1350
1366
|
}
|
|
1351
1367
|
let r = t[0].table_name;
|
|
1352
|
-
await
|
|
1368
|
+
await q(n, j.SqlClient.pipe(A.flatMap((t) => t.unsafe(`ALTER TABLE ${W(r)} RENAME TO ${W(e.table)}`)))), c++, console.log(` ↩ restored table ${e.table} (from ${r})`);
|
|
1353
1369
|
}
|
|
1354
1370
|
return console.log(`\n✓ restored ${s} column(s) + ${c} table(s) from plan '${t}'\n`), 0;
|
|
1355
|
-
},
|
|
1356
|
-
let t =
|
|
1371
|
+
}, dn = async (e) => {
|
|
1372
|
+
let t = l(e, "--before");
|
|
1357
1373
|
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;
|
|
1358
1374
|
let n = t.replace(/[^0-9]/g, "");
|
|
1359
1375
|
if (n.length < 8) return console.error(`gc-snapshots: --before must be at least a full date (YYYY-MM-DD), got "${t}"`), 2;
|
|
1360
|
-
let r = (n + "00000000000000").slice(0, 14), i = e.includes("--dry-run"), a = await
|
|
1376
|
+
let r = (n + "00000000000000").slice(0, 14), i = e.includes("--dry-run"), a = await K(), o = await q(a, j.SqlClient.pipe(A.flatMap((e) => e`
|
|
1361
1377
|
SELECT table_name, column_name FROM information_schema.columns
|
|
1362
|
-
WHERE ${e.unsafe(
|
|
1363
|
-
`))),
|
|
1378
|
+
WHERE ${e.unsafe(G(U()))} AND column_name LIKE ${"%__dropped_%"}
|
|
1379
|
+
`))), s = await q(a, j.SqlClient.pipe(A.flatMap((e) => e`
|
|
1364
1380
|
SELECT table_name FROM information_schema.tables
|
|
1365
|
-
WHERE ${e.unsafe(
|
|
1366
|
-
`))),
|
|
1381
|
+
WHERE ${e.unsafe(G(U()))} AND table_name LIKE ${"%__dropped_%"}
|
|
1382
|
+
`))), c = (e) => {
|
|
1367
1383
|
let t = /__dropped_(\d{14})$/.exec(e);
|
|
1368
1384
|
return t ? t[1] : null;
|
|
1369
1385
|
}, u = 0;
|
|
1370
1386
|
for (let e of o) {
|
|
1371
|
-
let t =
|
|
1372
|
-
t === null || t >= r || (console.log(` ${i ? "[dry-run] would drop column" : "dropping column"} ${e.table_name}.${e.column_name}`), i || await
|
|
1387
|
+
let t = c(e.column_name);
|
|
1388
|
+
t === null || t >= r || (console.log(` ${i ? "[dry-run] would drop column" : "dropping column"} ${e.table_name}.${e.column_name}`), i || await q(a, j.SqlClient.pipe(A.flatMap((t) => t.unsafe(`ALTER TABLE ${W(e.table_name)} DROP COLUMN ${W(e.column_name)}`)))), u++);
|
|
1373
1389
|
}
|
|
1374
|
-
for (let e of
|
|
1375
|
-
let t =
|
|
1376
|
-
t === null || t >= r || (console.log(` ${i ? "[dry-run] would drop table" : "dropping table"} ${e.table_name}`), i || await
|
|
1390
|
+
for (let e of s) {
|
|
1391
|
+
let t = c(e.table_name);
|
|
1392
|
+
t === null || t >= r || (console.log(` ${i ? "[dry-run] would drop table" : "dropping table"} ${e.table_name}`), i || await q(a, j.SqlClient.pipe(A.flatMap((t) => t.unsafe(`DROP TABLE ${W(e.table_name)}`)))), u++);
|
|
1377
1393
|
}
|
|
1378
1394
|
return console.log(`\n${i ? `[dry-run] ${u} snapshot(s) would be dropped` : `✓ dropped ${u} snapshot(s)`} (older than ${r}).\n`), 0;
|
|
1379
|
-
},
|
|
1380
|
-
let t =
|
|
1395
|
+
}, fn = async (e) => {
|
|
1396
|
+
let t = l(e, "--before");
|
|
1381
1397
|
if (!t) return console.error("voltro db squash --before <iso-date> [--note \"<text>\"]"), 2;
|
|
1382
1398
|
let n = t;
|
|
1383
1399
|
if (Number.isNaN(new Date(n).getTime())) return console.error(`voltro db squash: --before '${n}' is not a valid ISO date`), 2;
|
|
1384
|
-
let r =
|
|
1385
|
-
if (a === void 0) return
|
|
1386
|
-
let o =
|
|
1400
|
+
let r = l(e, "--note"), i = V(e), a = await X(i);
|
|
1401
|
+
if (a === void 0) return z.warn("db squash: no schema files found", { root: i }), 1;
|
|
1402
|
+
let o = Te(Ce(a)), s = await K();
|
|
1387
1403
|
try {
|
|
1388
|
-
let e = await
|
|
1404
|
+
let e = await q(s, Ne({
|
|
1389
1405
|
before: n,
|
|
1390
1406
|
fingerprint: o,
|
|
1391
1407
|
appliedBy: process.env.USER ?? "cli",
|
|
1392
|
-
environment:
|
|
1408
|
+
environment: Q(),
|
|
1393
1409
|
...r === void 0 ? {} : { note: r }
|
|
1394
1410
|
}));
|
|
1395
|
-
return console.log(`\n✓ squashed ${e.squashedCount} migration plan(s)\n snapshot id: ${e.snapshotId}\n fingerprint: ${
|
|
1411
|
+
return console.log(`\n✓ squashed ${e.squashedCount} migration plan(s)\n snapshot id: ${e.snapshotId}\n fingerprint: ${F(e.snapshotFingerprint)}\n`), 0;
|
|
1396
1412
|
} catch (e) {
|
|
1397
1413
|
return console.error(`squash refused: ${e.message}`), 1;
|
|
1398
1414
|
}
|
|
1399
|
-
},
|
|
1415
|
+
}, pn = async (e) => {
|
|
1400
1416
|
try {
|
|
1401
|
-
let t = await
|
|
1402
|
-
await
|
|
1417
|
+
let t = await J(e), n = Te(t);
|
|
1418
|
+
await q(e, j.SqlClient.pipe(A.flatMap((e) => e`
|
|
1403
1419
|
UPDATE ${e("_voltro_migration_plans")}
|
|
1404
1420
|
SET ${e("liveFingerprint")} = ${n}
|
|
1405
1421
|
WHERE ${e("id")} = (
|
|
@@ -1411,7 +1427,7 @@ var Fe = (e) => typeof e != "string", Ie = (e, t, n, r) => {
|
|
|
1411
1427
|
AND ${e("liveFingerprint")} IS NULL
|
|
1412
1428
|
`)));
|
|
1413
1429
|
} catch {}
|
|
1414
|
-
},
|
|
1430
|
+
}, mn = (e) => typeof e == "string" ? e : e == null ? "" : JSON.stringify(e), hn = (e) => e instanceof Date ? e.toISOString() : String(e ?? ""), gn = (e) => e === void 0 ? {
|
|
1415
1431
|
accept: !1,
|
|
1416
1432
|
reason: "no-declared-schema"
|
|
1417
1433
|
} : e > 0 ? {
|
|
@@ -1420,38 +1436,38 @@ var Fe = (e) => typeof e != "string", Ie = (e, t, n, r) => {
|
|
|
1420
1436
|
} : {
|
|
1421
1437
|
accept: !0,
|
|
1422
1438
|
reason: "converged"
|
|
1423
|
-
},
|
|
1424
|
-
let t = e.includes("--accept"), n = await
|
|
1439
|
+
}, _n = async (e) => {
|
|
1440
|
+
let t = e.includes("--accept"), n = await K(), r = await q(n, j.SqlClient.pipe(A.flatMap((e) => e`
|
|
1425
1441
|
SELECT * FROM ${e("_voltro_migration_plans")}
|
|
1426
1442
|
ORDER BY ${e("appliedAt")} DESC
|
|
1427
1443
|
LIMIT 1
|
|
1428
|
-
`))), i = await
|
|
1429
|
-
if (r.length === 0) return
|
|
1444
|
+
`))), i = await J(n), a = Te(i);
|
|
1445
|
+
if (r.length === 0) return z.info("db drift: no _voltro_migration_plans rows yet"), console.log(`live user-schema fingerprint: ${F(a)}`), console.log("No baseline to compare against. Run `voltro db apply` to lock in the current state."), 0;
|
|
1430
1446
|
let o = r[0];
|
|
1431
|
-
if (o.liveFingerprint === null || o.liveFingerprint === void 0) return
|
|
1432
|
-
if (o.liveFingerprint === a) return
|
|
1447
|
+
if (o.liveFingerprint === null || o.liveFingerprint === void 0) return z.warn("db drift: no drift baseline recorded yet — cannot compare"), console.log(), console.log(` last applied: ${o.id} at ${hn(o.appliedAt)}`), console.log(` live now: ${F(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;
|
|
1448
|
+
if (o.liveFingerprint === a) return z.info(`db drift: live matches last applied (${F(a)})`), 0;
|
|
1433
1449
|
let s = await (async () => {
|
|
1434
1450
|
try {
|
|
1435
|
-
let t = await
|
|
1436
|
-
return t === void 0 ? void 0 :
|
|
1451
|
+
let t = await X(V(e));
|
|
1452
|
+
return t === void 0 ? void 0 : P({
|
|
1437
1453
|
declared: t,
|
|
1438
1454
|
live: i,
|
|
1439
|
-
dialect:
|
|
1440
|
-
...
|
|
1455
|
+
dialect: U(),
|
|
1456
|
+
...M()
|
|
1441
1457
|
}).operations;
|
|
1442
1458
|
} catch {
|
|
1443
1459
|
return;
|
|
1444
1460
|
}
|
|
1445
1461
|
})();
|
|
1446
1462
|
if (t) {
|
|
1447
|
-
let e =
|
|
1448
|
-
if (e.reason === "no-declared-schema") return
|
|
1463
|
+
let e = gn(s?.length);
|
|
1464
|
+
if (e.reason === "no-declared-schema") return z.error("db drift --accept: no schema files found — cannot verify the live schema is current."), 1;
|
|
1449
1465
|
if (!e.accept && s !== void 0) {
|
|
1450
|
-
|
|
1466
|
+
z.error(`db drift --accept: refusing — ${s.length} operation(s) still pending against your declared schema.`), console.log();
|
|
1451
1467
|
for (let e of s.slice(0, 10)) console.log(` - ${e.op.kind} ${"table" in e.op ? `[${e.op.table}]` : ""}`);
|
|
1452
1468
|
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;
|
|
1453
1469
|
}
|
|
1454
|
-
return await
|
|
1470
|
+
return await q(n, j.SqlClient.pipe(A.flatMap((e) => e`
|
|
1455
1471
|
UPDATE ${e("_voltro_migration_plans")}
|
|
1456
1472
|
SET ${e("liveFingerprint")} = ${a}
|
|
1457
1473
|
WHERE ${e("id")} = (
|
|
@@ -1460,43 +1476,43 @@ var Fe = (e) => typeof e != "string", Ie = (e, t, n, r) => {
|
|
|
1460
1476
|
ORDER BY ${e("appliedAt")} DESC LIMIT 1
|
|
1461
1477
|
) AS ${e("newest")}
|
|
1462
1478
|
)
|
|
1463
|
-
`))),
|
|
1479
|
+
`))), z.info(`db drift: accepted the live schema as the baseline (${F(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;
|
|
1464
1480
|
}
|
|
1465
|
-
return
|
|
1466
|
-
},
|
|
1467
|
-
let n =
|
|
1481
|
+
return z.error("db drift: live schema DIVERGED from last applied state"), console.log(), console.log(` baseline: ${F(o.liveFingerprint)} at ${hn(o.appliedAt)} (${o.id})`), console.log(` live now: ${F(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);
|
|
1482
|
+
}, vn = async (e, t) => {
|
|
1483
|
+
let n = Q();
|
|
1468
1484
|
try {
|
|
1469
|
-
let r = await
|
|
1485
|
+
let r = await q(t, j.SqlClient.pipe(A.flatMap((t) => Me(t, {
|
|
1470
1486
|
projectRoot: e,
|
|
1471
1487
|
env: n,
|
|
1472
1488
|
appliedBy: process.env.USER ? `cli:${process.env.USER}` : "cli:user"
|
|
1473
1489
|
}))));
|
|
1474
|
-
return r.applied.length > 0 &&
|
|
1490
|
+
return r.applied.length > 0 && z.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 };
|
|
1475
1491
|
} catch (e) {
|
|
1476
1492
|
return { failed: e };
|
|
1477
1493
|
}
|
|
1478
|
-
},
|
|
1479
|
-
let t =
|
|
1480
|
-
await
|
|
1481
|
-
let i = await
|
|
1494
|
+
}, yn = async (e) => {
|
|
1495
|
+
let t = V(e), n = await K(), r = Q();
|
|
1496
|
+
await q(n, be(se, U(), void 0, { retryFilter: (await d(U())).retryFilter }));
|
|
1497
|
+
let i = await q(n, j.SqlClient.pipe(A.flatMap((e) => Me(e, {
|
|
1482
1498
|
projectRoot: t,
|
|
1483
1499
|
env: r,
|
|
1484
1500
|
appliedBy: process.env.USER ? `cli:${process.env.USER}` : "cli:user"
|
|
1485
1501
|
}))));
|
|
1486
|
-
return i.applied.length === 0 ? (
|
|
1502
|
+
return i.applied.length === 0 ? (z.info(`db files: no pending file-based migrations (${i.skipped.length} already applied)`), 0) : (z.info(`db files: applied ${i.applied.length} migration(s)`, {
|
|
1487
1503
|
applied: i.applied.map((e) => `${e.id} (${e.durationMs}ms)`),
|
|
1488
1504
|
skipped: i.skipped.length
|
|
1489
1505
|
}), 0);
|
|
1490
|
-
},
|
|
1491
|
-
let n =
|
|
1492
|
-
if (!r) return
|
|
1493
|
-
if ((process.env.NODE_ENV ?? "").toLowerCase() === "production") return
|
|
1494
|
-
let i = await
|
|
1506
|
+
}, bn = async (e) => {
|
|
1507
|
+
let n = V(e), r = u(e, jt);
|
|
1508
|
+
if (!r) return z.error("voltro db rollback-file <migration-id>"), 2;
|
|
1509
|
+
if ((process.env.NODE_ENV ?? "").toLowerCase() === "production") return z.error("voltro db rollback-file: refusing on prod — schema rollback runs as an explicit deploy step." + t()), 3;
|
|
1510
|
+
let i = await q(await K(), j.SqlClient.pipe(A.flatMap((e) => Ae(e, {
|
|
1495
1511
|
projectRoot: n,
|
|
1496
1512
|
id: r
|
|
1497
1513
|
}))));
|
|
1498
|
-
return
|
|
1499
|
-
},
|
|
1514
|
+
return z.info(`db rollback-file: rolled back ${i.id} in ${i.durationMs}ms`), 0;
|
|
1515
|
+
}, xn = () => {
|
|
1500
1516
|
let e = /* @__PURE__ */ new Date();
|
|
1501
1517
|
return [
|
|
1502
1518
|
e.getUTCFullYear(),
|
|
@@ -1506,7 +1522,7 @@ var Fe = (e) => typeof e != "string", Ie = (e, t, n, r) => {
|
|
|
1506
1522
|
String(e.getUTCMinutes()).padStart(2, "0"),
|
|
1507
1523
|
String(e.getUTCSeconds()).padStart(2, "0")
|
|
1508
1524
|
].join("");
|
|
1509
|
-
},
|
|
1525
|
+
}, Sn = (e) => e.trim().toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_+|_+$/g, "") || "migration", Cn = (e, t) => `import { defineMigration, SqlClient } from '@voltro/database'
|
|
1510
1526
|
import { Effect } from 'effect'
|
|
1511
1527
|
|
|
1512
1528
|
// A hand-written migration. Reach for this when the DECLARATIVE differ can't
|
|
@@ -1538,25 +1554,25 @@ export default defineMigration({
|
|
|
1538
1554
|
}),
|
|
1539
1555
|
],
|
|
1540
1556
|
})
|
|
1541
|
-
`,
|
|
1542
|
-
plan:
|
|
1543
|
-
apply:
|
|
1544
|
-
branch:
|
|
1545
|
-
plans:
|
|
1546
|
-
drift:
|
|
1547
|
-
files:
|
|
1548
|
-
"rollback-file":
|
|
1549
|
-
migrate:
|
|
1550
|
-
rollback:
|
|
1551
|
-
status:
|
|
1552
|
-
seed:
|
|
1557
|
+
`, $ = {
|
|
1558
|
+
plan: Qt,
|
|
1559
|
+
apply: en,
|
|
1560
|
+
branch: cn,
|
|
1561
|
+
plans: ln,
|
|
1562
|
+
drift: _n,
|
|
1563
|
+
files: yn,
|
|
1564
|
+
"rollback-file": bn,
|
|
1565
|
+
migrate: Pt,
|
|
1566
|
+
rollback: Ft,
|
|
1567
|
+
status: It,
|
|
1568
|
+
seed: Rt,
|
|
1553
1569
|
generate: async (e) => {
|
|
1554
|
-
let { value: t, rest: n } =
|
|
1555
|
-
return await E.mkdir(
|
|
1570
|
+
let { value: t, rest: n } = s(e, "--root"), { value: r, rest: i } = s(n, "--name"), a = t ? T(t) : process.cwd(), o = Sn(r ?? u(i, []) ?? "migration"), c = `${xn()}_${o}`, l = C(a, "migrations"), d = C(l, `${c}.migration.ts`);
|
|
1571
|
+
return await E.mkdir(l, { recursive: !0 }), await E.writeFile(d, Cn(c, o), "utf8"), z.info(`created ${w(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;
|
|
1556
1572
|
},
|
|
1557
|
-
squash:
|
|
1558
|
-
"restore-snapshot":
|
|
1559
|
-
"gc-snapshots":
|
|
1573
|
+
squash: fn,
|
|
1574
|
+
"restore-snapshot": un,
|
|
1575
|
+
"gc-snapshots": dn,
|
|
1560
1576
|
adopt: async (e) => {
|
|
1561
1577
|
let t = (t) => {
|
|
1562
1578
|
let n = e.indexOf(`--${t}`);
|
|
@@ -1570,22 +1586,22 @@ export default defineMigration({
|
|
|
1570
1586
|
into: r,
|
|
1571
1587
|
map: o.map,
|
|
1572
1588
|
...o.leaveUnset ? { leaveUnset: o.leaveUnset } : {}
|
|
1573
|
-
}, c =
|
|
1589
|
+
}, c = U(), l = await K(), u = await q(l, j.SqlClient.pipe(A.flatMap(N))), d = (e) => u.tables.find((t) => t.name === e)?.columns.map((e) => ({
|
|
1574
1590
|
name: e.name,
|
|
1575
1591
|
nullable: e.nullable,
|
|
1576
1592
|
hasDefault: e.defaultValue !== void 0 && e.defaultValue !== null
|
|
1577
1593
|
})), f = d(n), p = d(r);
|
|
1578
1594
|
if (f === void 0) return console.error(`voltro db adopt: '${n}' does not exist in the database`), 1;
|
|
1579
1595
|
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;
|
|
1580
|
-
let m = async (e) => await
|
|
1581
|
-
for (let e of
|
|
1596
|
+
let m = async (e) => await q(l, j.SqlClient.pipe(A.flatMap((t) => t.unsafe(e)))), h = async (e) => Number(Object.values((await m(`SELECT COUNT(*) AS n FROM "${e}"`))[0] ?? { n: 0 })[0] ?? 0), g = ze(s, f, p, await h(r));
|
|
1597
|
+
for (let e of Ve(s, g, await h(n))) console.log(e);
|
|
1582
1598
|
if (g.fatal) return 1;
|
|
1583
1599
|
if (!e.includes("--apply")) return console.log(""), console.log(" (dry run — nothing written. Re-run with --apply.)"), 0;
|
|
1584
|
-
let _ = await
|
|
1600
|
+
let _ = await Ke(s, g, c, "id", void 0, m, { keepSource: e.includes("--keep-source") });
|
|
1585
1601
|
return console.log(""), console.log(` copied ${_.copied}/${_.sourceRows} row(s)`), console.log(` snapshot: ${_.snapshotTable} — restore from here, then drop it yourself`), _.verified ? (console.log(_.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);
|
|
1586
1602
|
},
|
|
1587
1603
|
"scan-credentials": async (e) => {
|
|
1588
|
-
let t =
|
|
1604
|
+
let t = U(), n = [];
|
|
1589
1605
|
for (let t = 0; t < e.length; t += 1) {
|
|
1590
1606
|
if (e[t] !== "--table") continue;
|
|
1591
1607
|
let i = e[t + 1];
|
|
@@ -1596,29 +1612,29 @@ export default defineMigration({
|
|
|
1596
1612
|
column: o
|
|
1597
1613
|
});
|
|
1598
1614
|
}
|
|
1599
|
-
let i = [...
|
|
1600
|
-
return console.log(
|
|
1615
|
+
let i = [...qe, ...n], a = await q(await K(), j.SqlClient.pipe(A.flatMap((e) => A.promise(() => at(e, t, i, W)))));
|
|
1616
|
+
return console.log(ot(a, t)), st(a);
|
|
1601
1617
|
},
|
|
1602
1618
|
"encrypt-column": async (e) => {
|
|
1603
|
-
let t =
|
|
1619
|
+
let t = U(), n = e.includes("--dry-run"), i = e.includes("--yes"), a = (t) => {
|
|
1604
1620
|
let n = e.indexOf(t);
|
|
1605
1621
|
return n >= 0 ? e[n + 1] : void 0;
|
|
1606
|
-
},
|
|
1607
|
-
if (!l) throw new r(`voltro db encrypt-column: ${
|
|
1608
|
-
let u =
|
|
1622
|
+
}, o = Math.max(1, Number(a("--batch") ?? 500)), s = a("--key-env") ?? "FIELD_ENCRYPTION_KEY", l = process.env[s];
|
|
1623
|
+
if (!l) throw new r(`voltro db encrypt-column: ${s} 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.`);
|
|
1624
|
+
let u = _e(l), d = c(e, jt).filter((e) => e.includes(".")).map(gt);
|
|
1609
1625
|
if (d.length === 0) throw new r("voltro db encrypt-column: name at least one <table>.<column>");
|
|
1610
|
-
let f = await
|
|
1626
|
+
let f = await K(), p = [];
|
|
1611
1627
|
for (let e of d) {
|
|
1612
|
-
let a =
|
|
1613
|
-
let i = await
|
|
1628
|
+
let a = W(e.table), c = W(e.column), l = await q(f, j.SqlClient.pipe(A.flatMap((n) => A.promise(async () => {
|
|
1629
|
+
let i = await A.runPromise(n.unsafe(Ot(t, e, G(t))));
|
|
1614
1630
|
if (i.length === 0) throw new r(`voltro db encrypt-column: ${e.table}.${e.column} does not exist on this database`);
|
|
1615
|
-
return
|
|
1616
|
-
})))), d = await
|
|
1617
|
-
for (let e of d) typeof e.v != "string" ||
|
|
1618
|
-
let h =
|
|
1619
|
-
if (!h.fits) return console.error(` ✗ ${
|
|
1620
|
-
if (
|
|
1621
|
-
let g = d.filter((t) =>
|
|
1631
|
+
return kt(Object.values(i[0])[0]);
|
|
1632
|
+
})))), d = await q(f, j.SqlClient.pipe(A.flatMap((e) => A.promise(async () => await A.runPromise(e.unsafe(`SELECT ${W("id")} AS id, ${c} AS v FROM ${a}`)))))), m = 0;
|
|
1633
|
+
for (let e of d) typeof e.v != "string" || k(e.v) || (m = Math.max(m, Ct(e.v)));
|
|
1634
|
+
let h = vt(m, l);
|
|
1635
|
+
if (!h.fits) return console.error(` ✗ ${yt(e, h)}`), 1;
|
|
1636
|
+
if (wt(d.map((e) => e.v).find((e) => typeof e == "string" && k(e)), u) === !1) return console.error(` ✗ ${Tt(e, s)}`), 1;
|
|
1637
|
+
let g = d.filter((t) => xt(t.v, u, e) !== void 0 || Dt(t.v, u, e) !== void 0).length;
|
|
1622
1638
|
if (!n && !i) return console.error(` ✗ ${e.table}.${e.column}: ${g} row(s) would be rewritten in place. Re-run with --yes, or --dry-run to see the counts without writing.`), 1;
|
|
1623
1639
|
let _ = 0, v = 0, y = 0, b = 0, x = [];
|
|
1624
1640
|
for (let t of d) {
|
|
@@ -1626,8 +1642,8 @@ export default defineMigration({
|
|
|
1626
1642
|
y += 1;
|
|
1627
1643
|
continue;
|
|
1628
1644
|
}
|
|
1629
|
-
if (typeof t.v == "string" &&
|
|
1630
|
-
let n =
|
|
1645
|
+
if (typeof t.v == "string" && k(t.v)) {
|
|
1646
|
+
let n = Dt(t.v, u, e);
|
|
1631
1647
|
if (n === void 0) {
|
|
1632
1648
|
_ += 1;
|
|
1633
1649
|
continue;
|
|
@@ -1638,7 +1654,7 @@ export default defineMigration({
|
|
|
1638
1654
|
}), v += 1;
|
|
1639
1655
|
continue;
|
|
1640
1656
|
}
|
|
1641
|
-
let n =
|
|
1657
|
+
let n = xt(t.v, u, e);
|
|
1642
1658
|
if (n === void 0) {
|
|
1643
1659
|
y += 1;
|
|
1644
1660
|
continue;
|
|
@@ -1648,11 +1664,11 @@ export default defineMigration({
|
|
|
1648
1664
|
value: n
|
|
1649
1665
|
}), b += 1;
|
|
1650
1666
|
}
|
|
1651
|
-
if (!n && x.length > 0) for (let t = 0; t < x.length; t +=
|
|
1652
|
-
let n = x.slice(t, t +
|
|
1653
|
-
await
|
|
1654
|
-
for (let r of n) await
|
|
1655
|
-
})))),
|
|
1667
|
+
if (!n && x.length > 0) for (let t = 0; t < x.length; t += o) {
|
|
1668
|
+
let n = x.slice(t, t + o);
|
|
1669
|
+
await q(f, j.SqlClient.pipe(A.flatMap((t) => A.promise(async () => {
|
|
1670
|
+
for (let r of n) await A.runPromise(t`UPDATE ${t(e.table)} SET ${t(e.column)} = ${r.value} WHERE ${t("id")} = ${r.id}`);
|
|
1671
|
+
})))), z.info(`encrypt-column ${e.table}.${e.column}: ${Math.min(t + n.length, x.length)}/${x.length}`);
|
|
1656
1672
|
}
|
|
1657
1673
|
p.push({
|
|
1658
1674
|
table: e.table,
|
|
@@ -1665,21 +1681,21 @@ export default defineMigration({
|
|
|
1665
1681
|
});
|
|
1666
1682
|
}
|
|
1667
1683
|
console.log(`field encryption · dialect ${t}${n ? " · DRY RUN, nothing written" : ""}`), console.log("");
|
|
1668
|
-
for (let e of p) console.log(
|
|
1669
|
-
return n || console.log(
|
|
1684
|
+
for (let e of p) console.log(bt(e, n));
|
|
1685
|
+
return n || console.log(Et(d)), 0;
|
|
1670
1686
|
}
|
|
1671
|
-
},
|
|
1687
|
+
}, wn = (e) => Object.prototype.hasOwnProperty.call($, e), Tn = async (t) => {
|
|
1672
1688
|
let [n, ...r] = t;
|
|
1673
1689
|
if (!n) {
|
|
1674
1690
|
console.error("voltro db <subcommand> [args]"), console.error("subcommands:");
|
|
1675
|
-
for (let e of Object.keys(
|
|
1691
|
+
for (let e of Object.keys($)) console.error(` ${e}`);
|
|
1676
1692
|
return 1;
|
|
1677
1693
|
}
|
|
1678
|
-
if (!
|
|
1679
|
-
let i =
|
|
1680
|
-
(process.env.DB_DIRECT_URL !== void 0 || process.env.DB_MIGRATE_URL !== void 0) &&
|
|
1694
|
+
if (!wn(n)) return console.error(`voltro db: unknown subcommand '${n}'`), console.error(` known: ${e.join(", ")}`), 2;
|
|
1695
|
+
let i = $[n];
|
|
1696
|
+
(process.env.DB_DIRECT_URL !== void 0 || process.env.DB_MIGRATE_URL !== void 0) && z.info("using a DIRECT (non-pooler) connection for the db-command path (DB_DIRECT_URL/DB_MIGRATE_URL set)");
|
|
1681
1697
|
let a = (e) => {
|
|
1682
|
-
|
|
1698
|
+
Mt(e) ? z.error(Nt) : console.error(e), process.exit(1);
|
|
1683
1699
|
};
|
|
1684
1700
|
process.on("uncaughtException", a);
|
|
1685
1701
|
try {
|
|
@@ -1689,4 +1705,4 @@ export default defineMigration({
|
|
|
1689
1705
|
}
|
|
1690
1706
|
};
|
|
1691
1707
|
//#endregion
|
|
1692
|
-
export {
|
|
1708
|
+
export { Vt as a, Tn as c, gn as i, H as n, Mt as o, Bt as r, Y as s, Nt as t };
|