@voltro/cli 0.22.1 → 0.24.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 +427 -0
- package/bin/voltro.mjs +39 -4
- package/dist/{apiBuild-DiWxVz-M.js → apiBuild-B2m4XK_8.js} +45 -23
- package/dist/apiBuild-UEM3QBke.js +2 -0
- package/dist/bin.js +3 -3
- package/dist/{commands-BxRaIOBG.js → commands-CaIhTsC2.js} +2944 -2557
- package/dist/{dbCommand-DVASmZj2.js → dbCommand-CC61CsAc.js} +474 -263
- package/dist/dbCommand-Cr__4ATv.js +2 -0
- package/dist/{dev-Dd3EZzj5.js → dev-DPkQVUTP.js} +1 -1
- package/dist/{dev-BRiPgbKw.js → dev-NCuEhNxs.js} +1593 -1575
- package/dist/index.js +1 -1
- package/dist/{inspectMetrics-9ZSuDeqD.js → inspectMetrics-dcC7gnxu.js} +566 -552
- package/dist/{serveCommand-DXJOARkO.js → serveCommand-C4gzDZzc.js} +425 -411
- package/dist/serveEntry.js +2 -2
- package/dist/{start-CsIjcOi-.js → start-C_4VUDT8.js} +327 -325
- package/dist/startEntry.js +2 -2
- package/package.json +17 -17
- package/templates/AGENTS.md +1 -1
- package/templates/agent-docs/_index.md +1 -1
- package/templates/agent-docs/_manifest.json +1 -1
- package/templates/agent-docs/authentication.md +21 -0
- package/templates/agent-docs/caching.md +21 -5
- package/templates/agent-docs/cli.md +61 -0
- package/templates/agent-docs/data.md +208 -170
- package/templates/agent-docs/database/migrations.md +123 -0
- package/templates/agent-docs/database/seedsdialects.md +12 -0
- package/templates/agent-docs/deployment.md +66 -1
- package/templates/agent-docs/plugins.md +42 -0
- package/templates/agent-docs/routing.md +23 -1
- package/templates/agent-docs/templates/apibackends.md +2 -2
- package/templates/agent-docs/whats-new.md +281 -32
- package/templates/agent-docs/workflows.md +30 -0
- package/templates/apps/api-ai/package.json +8 -7
- package/templates/apps/api-auth/package.json +9 -8
- package/templates/apps/api-backend/package.json +8 -7
- package/templates/apps/api-backend-deactivation/package.json +8 -7
- package/templates/apps/api-backend-mail/package.json +9 -8
- package/templates/apps/api-backend-mariadb/package.json +10 -9
- package/templates/apps/api-backend-storage/package.json +9 -8
- package/templates/apps/api-data-advanced/package.json +9 -8
- package/templates/apps/api-durable/package.json +9 -8
- package/templates/apps/api-feature-flags/package.json +10 -9
- package/templates/apps/api-governance/package.json +9 -8
- package/templates/apps/api-kv/package.json +9 -8
- package/templates/apps/api-moderation/package.json +9 -8
- package/templates/apps/api-observability/package.json +9 -8
- package/templates/apps/api-ratelimit/package.json +9 -8
- package/templates/apps/api-rbac/package.json +9 -8
- package/templates/apps/api-rest/package.json +8 -7
- package/templates/apps/api-saas/package.json +12 -11
- package/templates/apps/api-search/package.json +9 -8
- package/templates/apps/api-versioning/app.config.ts +6 -4
- package/templates/apps/api-versioning/package.json +9 -8
- package/templates/apps/api-webhooks/package.json +10 -9
- package/templates/apps/changelog/package.json +7 -6
- package/templates/apps/edge-functions/package.json +3 -2
- package/templates/apps/frontend-admin/package.json +9 -8
- package/templates/apps/frontend-app/package.json +9 -8
- package/templates/apps/frontend-blank/package.json +8 -7
- package/templates/apps/frontend-contact/package.json +8 -7
- package/templates/apps/frontend-contact/src/globals.d.ts +6 -0
- package/templates/apps/frontend-dashboard/package.json +8 -7
- package/templates/apps/frontend-docs/package.json +8 -7
- package/templates/apps/frontend-docs/src/globals.d.ts +6 -0
- package/templates/apps/frontend-i18n/package.json +7 -6
- package/templates/apps/frontend-landing/package.json +8 -7
- package/templates/apps/frontend-landing/src/globals.d.ts +6 -0
- package/templates/apps/frontend-spa/package.json +8 -7
- package/templates/apps/frontend-spa/src/globals.d.ts +6 -0
- package/templates/apps/frontend-ssr/package.json +8 -7
- package/templates/apps/frontend-ssr/src/globals.d.ts +6 -0
- package/templates/apps/frontend-ssr-api/package.json +9 -8
- package/templates/apps/frontend-static-blog/package.json +7 -6
- package/templates/apps/frontend-static-blog/src/globals.d.ts +6 -0
- package/dist/apiBuild-COyPDf3R.js +0 -2
- package/dist/dbCommand-CO3eSAZR.js +0 -2
|
@@ -1,45 +1,163 @@
|
|
|
1
1
|
import { i as e, n as t, r as n, s as r, t as i } from "./seedRunner-D6eu-u5U.js";
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
2
|
+
import { n as a } from "./appModuleLoader-C9r9mxZt.js";
|
|
3
|
+
import { t as o } from "./frameworkTableAssembly-sg41s14F.js";
|
|
4
|
+
import { g as s } from "./fileConventions-CBNPZVOV.js";
|
|
5
|
+
import { a as c, n as l, t as u } from "./cliArgs-qdZSElM3.js";
|
|
6
|
+
import { basename as d, join as f, relative as p, resolve as m } from "node:path";
|
|
7
|
+
import { pathToFileURL as h } from "node:url";
|
|
8
|
+
import { Effect as g } from "effect";
|
|
9
|
+
import { createLogger as _ } from "@voltro/logger";
|
|
10
|
+
import { promises as v, writeSync as y } from "node:fs";
|
|
11
|
+
import { InMemoryDataStore as ee } from "@voltro/runtime";
|
|
12
|
+
import { SqlClient as b } from "@effect/sql";
|
|
13
|
+
import { actorsTable as te, frameworkTables as x, isMigrationDefinition as ne, quoteIdent as re, registerCoreTables as ie } from "@voltro/database";
|
|
13
14
|
import { createHash as ae } from "node:crypto";
|
|
14
|
-
import { applyPlan as
|
|
15
|
+
import { DEFAULT_CDC_CHANNEL as oe, applyPlan as se, applySchema as S, declaredSnapshot as ce, destructiveScope as le, detectReactiveTriggerDrift as ue, fingerprintSchema as C, ignoreTablesFromEnv as w, introspectSchema as T, pendingFileMigrationIds as de, planMigrations as E, reactiveTriggerRepairSql as fe, rollbackFileBasedMigration as pe, rollingDeployUnsafeOps as me, runFileBasedMigrations as he, shortFingerprint as D, squashMigrationPlans as ge, unblockLossy as _e } from "@voltro/database/sql";
|
|
15
16
|
//#region src/cliError.ts
|
|
16
|
-
var
|
|
17
|
+
var ve = class extends Error {
|
|
17
18
|
isCliError = !0;
|
|
18
19
|
code;
|
|
19
20
|
constructor(e, t = 1) {
|
|
20
21
|
super(e), this.name = "CliError", this.code = t;
|
|
21
22
|
}
|
|
22
|
-
},
|
|
23
|
+
}, ye = (e) => typeof e == "object" && !!e && e.isCliError === !0, be = (e) => typeof e != "string", xe = (e, t, n, r) => {
|
|
24
|
+
let i = [], a = new Map(t.map((e) => [e.name, e])), o = new Map(n.map((e) => [e.name, e])), s = new Set(e.leaveUnset ?? []);
|
|
25
|
+
r > 0 && i.push({
|
|
26
|
+
kind: "target-not-empty",
|
|
27
|
+
detail: `'${e.into}' already holds ${r} row(s) — adopt MOVES rows into a table, it does not merge into one`,
|
|
28
|
+
fatal: !0
|
|
29
|
+
});
|
|
30
|
+
let c = [], l = /* @__PURE__ */ new Set();
|
|
31
|
+
for (let [t, n] of Object.entries(e.map)) {
|
|
32
|
+
if (!o.has(t)) {
|
|
33
|
+
i.push({
|
|
34
|
+
kind: "unknown-target-column",
|
|
35
|
+
detail: `'${e.into}' has no column '${t}'`,
|
|
36
|
+
fatal: !0
|
|
37
|
+
});
|
|
38
|
+
continue;
|
|
39
|
+
}
|
|
40
|
+
if (be(n)) {
|
|
41
|
+
c.push({
|
|
42
|
+
target: t,
|
|
43
|
+
source: n.expr,
|
|
44
|
+
raw: !0
|
|
45
|
+
});
|
|
46
|
+
continue;
|
|
47
|
+
}
|
|
48
|
+
if (!a.has(n)) {
|
|
49
|
+
i.push({
|
|
50
|
+
kind: "unknown-source-column",
|
|
51
|
+
detail: `'${e.from}' has no column '${n}' (mapped to '${t}')`,
|
|
52
|
+
fatal: !0
|
|
53
|
+
});
|
|
54
|
+
continue;
|
|
55
|
+
}
|
|
56
|
+
l.add(n), c.push({
|
|
57
|
+
target: t,
|
|
58
|
+
source: n,
|
|
59
|
+
raw: !1
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
for (let t of n) t.name in e.map || s.has(t.name) || t.nullable || t.hasDefault || i.push({
|
|
63
|
+
kind: "required-target-unmapped",
|
|
64
|
+
detail: `'${e.into}.${t.name}' is NOT NULL with no default and nothing maps to it`,
|
|
65
|
+
fatal: !0
|
|
66
|
+
});
|
|
67
|
+
for (let n of t) l.has(n.name) || i.push({
|
|
68
|
+
kind: "source-column-unmapped",
|
|
69
|
+
detail: `'${e.from}.${n.name}' is not carried across — its values are lost when the table is dropped`,
|
|
70
|
+
fatal: !1
|
|
71
|
+
});
|
|
72
|
+
let u = t.find((e) => e.idPrefix !== void 0), d = n.find((e) => e.idPrefix !== void 0), f = u !== void 0 && d !== void 0 && u.idPrefix !== d.idPrefix;
|
|
73
|
+
return f && i.push({
|
|
74
|
+
kind: "id-prefix-differs",
|
|
75
|
+
detail: `id prefix '${u.idPrefix}' → '${d.idPrefix}': every row gets a NEW id, so every reference to '${e.from}' elsewhere must be rewritten from the translation table — including ids inside JSON columns`,
|
|
76
|
+
fatal: !1
|
|
77
|
+
}), {
|
|
78
|
+
problems: i,
|
|
79
|
+
assignments: c.sort((e, t) => e.target.localeCompare(t.target)),
|
|
80
|
+
remintIds: f,
|
|
81
|
+
fatal: i.some((e) => e.fatal)
|
|
82
|
+
};
|
|
83
|
+
}, Se = (e, t) => [
|
|
84
|
+
`back up '${e.from}' to a restorable dump`,
|
|
85
|
+
`copy rows: INSERT INTO ${e.into} (${t.assignments.map((e) => e.target).join(", ")}) SELECT … FROM ${e.from}`,
|
|
86
|
+
...t.remintIds ? ["record old id → new id in a translation table", `rewrite every reference to '${e.from}', JSON columns included`] : [],
|
|
87
|
+
"verify: row counts match and a sample round-trips",
|
|
88
|
+
`drop '${e.from}' — LAST, and only after the verify`
|
|
89
|
+
], Ce = (e, t, n) => {
|
|
90
|
+
let r = [`adopt · ${e.from} (${n} row(s)) → ${e.into}`, ""];
|
|
91
|
+
for (let e of t.assignments) r.push(` ${e.target.padEnd(24)} ← ${e.raw ? `${e.source} (raw)` : e.source}`);
|
|
92
|
+
t.problems.length > 0 && r.push("");
|
|
93
|
+
for (let e of t.problems) r.push(` ${e.fatal ? "✗" : "⚠"} ${e.detail}`);
|
|
94
|
+
if (r.push(""), t.fatal) return r.push(" REFUSED — fix the problems above. Nothing was written."), r;
|
|
95
|
+
r.push(" would run, in this order:");
|
|
96
|
+
for (let [n, i] of Se(e, t).entries()) r.push(` ${n + 1}. ${i}`);
|
|
97
|
+
return r;
|
|
98
|
+
}, we = (e) => e === "mysql" || e === "mariadb" ? (e) => `\`${e}\`` : e === "mssql" ? (e) => `[${e}]` : (e) => `"${e}"`, O = (e) => `${e}__adopt_snapshot`, Te = (e, t, n, r, i) => {
|
|
99
|
+
let a = we(n), o = O(e.from), s = t.assignments.map((e) => e.target), c = t.assignments.map((e) => e.raw ? e.source : a(e.source));
|
|
100
|
+
return i !== void 0 && (s.unshift(r), c.unshift(i)), {
|
|
101
|
+
snapshot: n === "mssql" ? `SELECT * INTO ${a(o)} FROM ${a(e.from)}` : `CREATE TABLE ${a(o)} AS SELECT * FROM ${a(e.from)}`,
|
|
102
|
+
copy: `INSERT INTO ${a(e.into)} (${s.map(a).join(", ")}) SELECT ${c.join(", ")} FROM ${a(e.from)}`,
|
|
103
|
+
translations: i === void 0 ? [] : [`CREATE TABLE ${a(`${o}__ids`)} (old_id TEXT, new_id TEXT)`],
|
|
104
|
+
verifySource: `SELECT COUNT(*) AS n FROM ${a(e.from)}`,
|
|
105
|
+
verifyTarget: `SELECT COUNT(*) AS n FROM ${a(e.into)}`,
|
|
106
|
+
drop: `DROP TABLE ${a(e.from)}`,
|
|
107
|
+
dropSnapshot: `DROP TABLE ${a(o)}`
|
|
108
|
+
};
|
|
109
|
+
}, k = (e) => {
|
|
110
|
+
let t = e[0];
|
|
111
|
+
if (t === void 0) return 0;
|
|
112
|
+
let n = t.n ?? t.N ?? Object.values(t)[0];
|
|
113
|
+
return Number(n ?? 0);
|
|
114
|
+
}, Ee = async (e, t, n, r, i, a, o = {}) => {
|
|
115
|
+
if (t.fatal) throw Error("adopt: the plan was refused — nothing was run");
|
|
116
|
+
let s = Te(e, t, n, r, i), c = O(e.from), l = k(await a(s.verifySource));
|
|
117
|
+
await a(s.snapshot);
|
|
118
|
+
for (let e of s.translations) await a(e);
|
|
119
|
+
await a(s.copy);
|
|
120
|
+
let u = k(await a(s.verifyTarget));
|
|
121
|
+
return u === l ? o.keepSource === !0 ? {
|
|
122
|
+
copied: u,
|
|
123
|
+
sourceRows: l,
|
|
124
|
+
verified: !0,
|
|
125
|
+
snapshotTable: c,
|
|
126
|
+
sourceDropped: !1
|
|
127
|
+
} : (await a(s.drop), {
|
|
128
|
+
copied: u,
|
|
129
|
+
sourceRows: l,
|
|
130
|
+
verified: !0,
|
|
131
|
+
snapshotTable: c,
|
|
132
|
+
sourceDropped: !0
|
|
133
|
+
}) : {
|
|
134
|
+
copied: u,
|
|
135
|
+
sourceRows: l,
|
|
136
|
+
verified: !1,
|
|
137
|
+
snapshotTable: c,
|
|
138
|
+
sourceDropped: !1
|
|
139
|
+
};
|
|
140
|
+
}, A = /\.migration\.ts$/, j = async (e, t) => {
|
|
23
141
|
let n = [];
|
|
24
142
|
for (let r of e) {
|
|
25
143
|
let e;
|
|
26
144
|
try {
|
|
27
|
-
e = await import(
|
|
145
|
+
e = await import(h(r).href);
|
|
28
146
|
} catch (e) {
|
|
29
147
|
t.warn("migration: failed to import", { file: r }, e);
|
|
30
148
|
continue;
|
|
31
149
|
}
|
|
32
|
-
let i = (e.default &&
|
|
150
|
+
let i = (e.default && ne(e.default) ? e.default : void 0) ?? Object.values(e).find(ne);
|
|
33
151
|
if (!i) {
|
|
34
152
|
t.warn("migration: no defineMigration export", { file: r });
|
|
35
153
|
continue;
|
|
36
154
|
}
|
|
37
|
-
i.id !==
|
|
155
|
+
i.id !== d(r).replace(/\.migration\.ts$/, "") && t.warn("migration: id does not match filename — id wins for ordering, but rename the file for clarity", {
|
|
38
156
|
file: r,
|
|
39
157
|
idInFile: i.id,
|
|
40
|
-
idFromFile:
|
|
158
|
+
idFromFile: d(r).replace(/\.migration\.ts$/, "")
|
|
41
159
|
});
|
|
42
|
-
let a = await
|
|
160
|
+
let a = await v.readFile(r, "utf8").catch(() => ""), o = ae("sha256").update(a).digest("hex").slice(0, 16);
|
|
43
161
|
n.push({
|
|
44
162
|
file: r,
|
|
45
163
|
definition: i,
|
|
@@ -47,14 +165,14 @@ var E = class extends Error {
|
|
|
47
165
|
});
|
|
48
166
|
}
|
|
49
167
|
return [...n].sort((e, t) => e.definition.id < t.definition.id ? -1 : 1);
|
|
50
|
-
},
|
|
168
|
+
}, M = (e) => {
|
|
51
169
|
let t = (process.env.DB_DIALECT ?? "postgres").toLowerCase(), n = t === "memory" ? "postgres" : t;
|
|
52
|
-
return
|
|
53
|
-
},
|
|
54
|
-
let e = yield* (yield*
|
|
170
|
+
return g.runPromise(S(x, n).pipe(g.provide(e)));
|
|
171
|
+
}, N = (e) => g.runPromise(g.gen(function* () {
|
|
172
|
+
let e = yield* (yield* b.SqlClient)`SELECT id, status, hash, "appliedAt" FROM "_voltro_migrations"`.pipe(g.orElseSucceed(() => []));
|
|
55
173
|
return new Map(e.map((e) => [e.id, e]));
|
|
56
|
-
}).pipe(
|
|
57
|
-
yield* (yield*
|
|
174
|
+
}).pipe(g.provide(e))), P = (e, t) => g.runPromise(g.gen(function* () {
|
|
175
|
+
yield* (yield* b.SqlClient)`
|
|
58
176
|
INSERT INTO "_voltro_migrations"
|
|
59
177
|
(id, name, hash, status, "appliedAt", "durationMs", "errorMessage", "errorStack")
|
|
60
178
|
VALUES
|
|
@@ -69,14 +187,14 @@ var E = class extends Error {
|
|
|
69
187
|
"errorStack" = EXCLUDED."errorStack",
|
|
70
188
|
"updatedAt" = now()
|
|
71
189
|
`;
|
|
72
|
-
}).pipe(
|
|
190
|
+
}).pipe(g.provide(e))), De = async (e, t, n) => {
|
|
73
191
|
if (e.length === 0) return {
|
|
74
192
|
applied: [],
|
|
75
193
|
failed: null,
|
|
76
194
|
skipped: []
|
|
77
195
|
};
|
|
78
|
-
await
|
|
79
|
-
let r = await
|
|
196
|
+
await M(t);
|
|
197
|
+
let r = await N(t), i = [], a = [], o = null;
|
|
80
198
|
for (let s of e) {
|
|
81
199
|
let e = r.get(s.definition.id);
|
|
82
200
|
if (e && e.status === "applied") {
|
|
@@ -97,7 +215,7 @@ var E = class extends Error {
|
|
|
97
215
|
name: s.definition.name
|
|
98
216
|
});
|
|
99
217
|
let c = Date.now(), l = [];
|
|
100
|
-
await
|
|
218
|
+
await P(t, {
|
|
101
219
|
id: s.definition.id,
|
|
102
220
|
name: s.definition.name,
|
|
103
221
|
hash: s.hash,
|
|
@@ -111,7 +229,7 @@ var E = class extends Error {
|
|
|
111
229
|
for (let e of s.definition.steps) {
|
|
112
230
|
let n = Date.now();
|
|
113
231
|
try {
|
|
114
|
-
await
|
|
232
|
+
await g.runPromise(e.apply.pipe(g.provide(t))), l.push({
|
|
115
233
|
stepName: e.name,
|
|
116
234
|
status: "applied",
|
|
117
235
|
durationMs: Date.now() - n
|
|
@@ -138,7 +256,7 @@ var E = class extends Error {
|
|
|
138
256
|
...a.stack ? { stack: a.stack } : {}
|
|
139
257
|
}
|
|
140
258
|
};
|
|
141
|
-
o = f, i.push(f), await
|
|
259
|
+
o = f, i.push(f), await P(t, {
|
|
142
260
|
id: s.definition.id,
|
|
143
261
|
name: s.definition.name,
|
|
144
262
|
hash: s.hash,
|
|
@@ -157,7 +275,7 @@ var E = class extends Error {
|
|
|
157
275
|
status: "applied",
|
|
158
276
|
durationMs: Date.now() - c,
|
|
159
277
|
steps: l
|
|
160
|
-
}), await
|
|
278
|
+
}), await P(t, {
|
|
161
279
|
id: s.definition.id,
|
|
162
280
|
name: s.definition.name,
|
|
163
281
|
hash: s.hash,
|
|
@@ -177,9 +295,9 @@ var E = class extends Error {
|
|
|
177
295
|
failed: o,
|
|
178
296
|
skipped: a
|
|
179
297
|
};
|
|
180
|
-
},
|
|
181
|
-
await
|
|
182
|
-
let i = await
|
|
298
|
+
}, Oe = async (e, t, n, r = {}) => {
|
|
299
|
+
await M(t);
|
|
300
|
+
let i = await N(t), a = e.filter((e) => i.get(e.definition.id)?.status === "applied");
|
|
183
301
|
if (a.length === 0) return n.info("migration: nothing to roll back"), {
|
|
184
302
|
applied: [],
|
|
185
303
|
failed: null,
|
|
@@ -197,7 +315,7 @@ var E = class extends Error {
|
|
|
197
315
|
for (let n = e.definition.steps.length - 1; n >= 0; n--) {
|
|
198
316
|
let o = e.definition.steps[n], s = Date.now();
|
|
199
317
|
try {
|
|
200
|
-
await
|
|
318
|
+
await g.runPromise(o.undo.pipe(g.provide(t))), i.push({
|
|
201
319
|
stepName: o.name,
|
|
202
320
|
status: "applied",
|
|
203
321
|
durationMs: Date.now() - s
|
|
@@ -232,7 +350,7 @@ var E = class extends Error {
|
|
|
232
350
|
status: "applied",
|
|
233
351
|
durationMs: Date.now() - r,
|
|
234
352
|
steps: i
|
|
235
|
-
}), await
|
|
353
|
+
}), await P(t, {
|
|
236
354
|
id: e.definition.id,
|
|
237
355
|
name: e.definition.name,
|
|
238
356
|
hash: e.hash,
|
|
@@ -248,9 +366,9 @@ var E = class extends Error {
|
|
|
248
366
|
failed: c,
|
|
249
367
|
skipped: []
|
|
250
368
|
};
|
|
251
|
-
},
|
|
252
|
-
await
|
|
253
|
-
let n = await
|
|
369
|
+
}, F = async (e, t) => {
|
|
370
|
+
await M(t);
|
|
371
|
+
let n = await N(t);
|
|
254
372
|
return e.map((e) => {
|
|
255
373
|
let t = n.get(e.definition.id);
|
|
256
374
|
return t ? {
|
|
@@ -267,7 +385,7 @@ var E = class extends Error {
|
|
|
267
385
|
hashMatches: !0
|
|
268
386
|
};
|
|
269
387
|
});
|
|
270
|
-
},
|
|
388
|
+
}, I = (e) => {
|
|
271
389
|
if (!e.failed) return "";
|
|
272
390
|
let t = e.failed, n = [
|
|
273
391
|
`✗ Migration failed: ${t.migrationId} / step '${t.error?.stepName ?? "?"}'`,
|
|
@@ -280,7 +398,7 @@ var E = class extends Error {
|
|
|
280
398
|
""
|
|
281
399
|
];
|
|
282
400
|
return e.applied.length > 0 && n.unshift("", `(${e.applied.length} migration(s) applied successfully before the failure.)`), n.join("\n");
|
|
283
|
-
},
|
|
401
|
+
}, L = _({ scope: "voltro:db" }), ke = /* @__PURE__ */ new Set([
|
|
284
402
|
"node_modules",
|
|
285
403
|
"dist",
|
|
286
404
|
"build",
|
|
@@ -288,17 +406,17 @@ var E = class extends Error {
|
|
|
288
406
|
".vite",
|
|
289
407
|
".next",
|
|
290
408
|
".git"
|
|
291
|
-
]),
|
|
409
|
+
]), R = async (e, t) => {
|
|
292
410
|
let n = [], r = async (e) => {
|
|
293
|
-
let i = await
|
|
411
|
+
let i = await v.readdir(e, { withFileTypes: !0 }).catch(() => []);
|
|
294
412
|
for (let a of i) {
|
|
295
|
-
if (
|
|
296
|
-
let i =
|
|
413
|
+
if (ke.has(a.name)) continue;
|
|
414
|
+
let i = f(e, a.name);
|
|
297
415
|
a.isDirectory() ? await r(i) : a.isFile() && t.test(a.name) && n.push(i);
|
|
298
416
|
}
|
|
299
417
|
};
|
|
300
418
|
return await r(e), n;
|
|
301
|
-
},
|
|
419
|
+
}, z = (e) => m(e.find((e) => !e.startsWith("-")) ?? process.cwd()), B = () => {
|
|
302
420
|
let e = process.env.DB_MAX_CONNECTIONS ?? process.env.PG_MAX_CONNECTIONS, t = e !== void 0 && e !== "" ? Number(e) : NaN, n = Number.isInteger(t) && t > 0 ? { maxConnections: t } : {}, r = process.env.DB_SCHEMA, i = r && r.length > 0 ? { schema: r } : {}, a = process.env.DB_DIRECT_URL ?? process.env.DB_MIGRATE_URL ?? process.env.DB_URL ?? process.env.DB_PRIMARY_URL;
|
|
303
421
|
return a ? {
|
|
304
422
|
url: a,
|
|
@@ -313,63 +431,63 @@ var E = class extends Error {
|
|
|
313
431
|
...n,
|
|
314
432
|
...i
|
|
315
433
|
};
|
|
316
|
-
},
|
|
434
|
+
}, V = (e) => {
|
|
317
435
|
let t = e;
|
|
318
436
|
if (typeof t != "object" || !t) return !1;
|
|
319
437
|
if (t.code === "ERR_OUT_OF_RANGE" || t.cause?.code === "ERR_OUT_OF_RANGE") return !0;
|
|
320
438
|
let n = `${t.message ?? ""} ${t.cause?.message ?? ""} ${t.stack ?? ""}`;
|
|
321
439
|
return /ERR_OUT_OF_RANGE|out of range/i.test(n) && /pg-protocol|parseDataRow|buffer-reader|BufferReader/i.test(n);
|
|
322
|
-
},
|
|
440
|
+
}, H = "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 = () => {
|
|
323
441
|
let e = (process.env.DB_DIALECT ?? "postgres").toLowerCase();
|
|
324
|
-
if (e === "memory") throw new
|
|
442
|
+
if (e === "memory") throw new ve("voltro db: subcommands require a real dialect, not DB_DIALECT=memory.");
|
|
325
443
|
return e;
|
|
326
|
-
},
|
|
327
|
-
let t =
|
|
328
|
-
if (r.length === 0) return
|
|
444
|
+
}, W = async () => (await r(U())).makeSqlLayer(B()), Ae = async (e) => {
|
|
445
|
+
let t = z(e), n = e.includes("--dry-run"), r = await j(await R(t, A), L);
|
|
446
|
+
if (r.length === 0) return L.info("no migrations found", { root: t }), 0;
|
|
329
447
|
if (n) {
|
|
330
448
|
let e = /* @__PURE__ */ new Set();
|
|
331
449
|
try {
|
|
332
|
-
let t = await
|
|
450
|
+
let t = await F(r, await W());
|
|
333
451
|
e = new Set(t.filter((e) => e.status === "applied").map((e) => e.id));
|
|
334
452
|
} catch {
|
|
335
|
-
|
|
453
|
+
L.warn("migrate --dry-run: could not read the migration ledger — showing ALL as pending");
|
|
336
454
|
}
|
|
337
455
|
let t = r.filter((t) => !e.has(t.definition.id));
|
|
338
|
-
if (t.length === 0) return
|
|
339
|
-
|
|
456
|
+
if (t.length === 0) return L.info("migrate --dry-run: nothing pending — every migration is applied"), 0;
|
|
457
|
+
L.info(`migrate --dry-run: ${t.length} pending (nothing applied)`);
|
|
340
458
|
for (let e of t) console.log(` pending ${e.definition.id}`);
|
|
341
459
|
return 0;
|
|
342
460
|
}
|
|
343
|
-
let i = await
|
|
344
|
-
return i.failed ? (process.stderr.write(
|
|
461
|
+
let i = await De(r, await W(), L);
|
|
462
|
+
return i.failed ? (process.stderr.write(I(i) + "\n"), 1) : (L.info("migrate complete", {
|
|
345
463
|
applied: i.applied.length,
|
|
346
464
|
skipped: i.skipped.length
|
|
347
465
|
}), 0);
|
|
348
|
-
},
|
|
349
|
-
let t =
|
|
350
|
-
if (r.length === 0) return
|
|
351
|
-
let i = await
|
|
352
|
-
return i.failed ? (process.stderr.write(
|
|
353
|
-
},
|
|
354
|
-
let t = await
|
|
466
|
+
}, je = async (e) => {
|
|
467
|
+
let t = z(e), n = l(e, "--to"), r = await j(await R(t, A), L);
|
|
468
|
+
if (r.length === 0) return L.info("no migrations found", { root: t }), 0;
|
|
469
|
+
let i = await Oe(r, await W(), L, n ? { to: n } : {});
|
|
470
|
+
return i.failed ? (process.stderr.write(I(i) + "\n"), 1) : (L.info("rollback complete", { reverted: i.applied.length }), 0);
|
|
471
|
+
}, Me = async (e) => {
|
|
472
|
+
let t = await j(await R(z(e), A), L);
|
|
355
473
|
if (t.length === 0) return console.log("no migrations found"), 0;
|
|
356
|
-
let n = await
|
|
474
|
+
let n = await F(t, await W());
|
|
357
475
|
for (let e of n) {
|
|
358
476
|
let t = e.status === "applied" ? "✓" : e.status === "failed" ? "✗" : e.status === "reverted" ? "↩" : "·", n = e.hashMatches ? "" : " (hash drift!)", r = e.appliedAt ? ` @ ${e.appliedAt.toISOString()}` : "";
|
|
359
477
|
console.log(` ${t} ${e.id} [${e.status}] ${e.name}${r}${n}`);
|
|
360
478
|
}
|
|
361
479
|
return 0;
|
|
362
|
-
},
|
|
363
|
-
let t = (
|
|
364
|
-
if (t === "memory") return
|
|
365
|
-
store: new
|
|
480
|
+
}, Ne = async (e) => {
|
|
481
|
+
let t = (l(e, "--store") ?? process.env.DB_DIALECT ?? "postgres").toLowerCase();
|
|
482
|
+
if (t === "memory") return L.warn("voltro db seed: using in-memory store (explicit opt-in via --store memory / DB_DIALECT=memory)"), L.warn(" rows written by this run will NOT persist beyond the process. set DB_DIALECT=postgres or unset to seed the real DB."), {
|
|
483
|
+
store: new ee(),
|
|
366
484
|
dispose: async () => {},
|
|
367
485
|
dialectId: "memory"
|
|
368
486
|
};
|
|
369
487
|
let n = await r(t);
|
|
370
|
-
|
|
488
|
+
L.info(`voltro db seed: target store=${n.id} — rows will persist`);
|
|
371
489
|
let i = await n.makeStore({
|
|
372
|
-
sqlLayer: n.makeSqlLayer(
|
|
490
|
+
sqlLayer: n.makeSqlLayer(B()),
|
|
373
491
|
changeStrategy: "inline"
|
|
374
492
|
}), a = i;
|
|
375
493
|
return {
|
|
@@ -379,77 +497,77 @@ var E = class extends Error {
|
|
|
379
497
|
},
|
|
380
498
|
dialectId: n.id
|
|
381
499
|
};
|
|
382
|
-
},
|
|
383
|
-
let a =
|
|
384
|
-
if (
|
|
385
|
-
let { store: u, dispose: d, dialectId: f } = await
|
|
500
|
+
}, Pe = async (r) => {
|
|
501
|
+
let a = z(r), o = l(r, "--id"), s = l(r, "--lifecycle"), c = await t(await R(a, i), L);
|
|
502
|
+
if (c.length === 0) return L.info("no seeds found", { root: a }), 0;
|
|
503
|
+
let { store: u, dispose: d, dialectId: f } = await Ne(r);
|
|
386
504
|
try {
|
|
387
|
-
return o ? +((await e(
|
|
505
|
+
return o ? +((await e(c, o, u, L)).status === "failed") : +((await n((s ? c.filter((e) => e.definition.lifecycle === s) : c.filter((e) => e.definition.lifecycle === "boot")).map((e) => ({
|
|
388
506
|
...e,
|
|
389
507
|
definition: {
|
|
390
508
|
...e.definition,
|
|
391
509
|
lifecycle: "boot"
|
|
392
510
|
}
|
|
393
|
-
})), u,
|
|
511
|
+
})), u, L, { force: !0 })).filter((e) => e.status === "failed").length > 0);
|
|
394
512
|
} finally {
|
|
395
513
|
await d().catch((e) => {
|
|
396
|
-
|
|
514
|
+
L.warn("voltro db seed: store dispose failed", { error: String(e) });
|
|
397
515
|
});
|
|
398
516
|
}
|
|
399
|
-
},
|
|
400
|
-
let t = await
|
|
517
|
+
}, Fe = (e) => typeof e == "object" && !!e && "tableName" in e && "fields" in e && typeof e.tableName == "string", G = async (e) => {
|
|
518
|
+
let t = await R(e, s), n = [];
|
|
401
519
|
for (let r of t) {
|
|
402
520
|
let t;
|
|
403
521
|
try {
|
|
404
|
-
t = await import(
|
|
522
|
+
t = await import(h(r).href);
|
|
405
523
|
} catch (t) {
|
|
406
|
-
|
|
407
|
-
file:
|
|
524
|
+
L.warn("db: failed to import schema file", {
|
|
525
|
+
file: p(e, r),
|
|
408
526
|
error: String(t)
|
|
409
527
|
});
|
|
410
528
|
continue;
|
|
411
529
|
}
|
|
412
|
-
for (let e of Object.values(t))
|
|
530
|
+
for (let e of Object.values(t)) Fe(e) && n.push(e);
|
|
413
531
|
}
|
|
414
532
|
return n;
|
|
415
|
-
},
|
|
533
|
+
}, K = (e) => {
|
|
416
534
|
let t = new Map(e.map((e) => [e.tableName, e]));
|
|
417
|
-
t.has("actors") || t.set("actors",
|
|
535
|
+
t.has("actors") || t.set("actors", te);
|
|
418
536
|
let n = t.get("actors"), r = t.get("tenants");
|
|
419
537
|
return ie({
|
|
420
538
|
actors: () => n,
|
|
421
539
|
...r === void 0 ? {} : { tenants: () => r }
|
|
422
540
|
}), [...t.values()];
|
|
423
|
-
},
|
|
541
|
+
}, Ie = (e) => {
|
|
424
542
|
let t = /* @__PURE__ */ new Map();
|
|
425
543
|
for (let n of e) {
|
|
426
544
|
let e = n.tableName;
|
|
427
545
|
e !== void 0 && !t.has(e) && t.set(e, n);
|
|
428
546
|
}
|
|
429
547
|
return [...t.values()];
|
|
430
|
-
},
|
|
431
|
-
let t =
|
|
548
|
+
}, q = (e) => {
|
|
549
|
+
let t = le(process.env.VOLTRO_DESTRUCTIVE_OK);
|
|
432
550
|
if (t === void 0 || !e.operations.filter((e) => e.blocked).every((e) => e.classification === "lossy")) return {
|
|
433
551
|
plan: e,
|
|
434
552
|
acknowledged: !1
|
|
435
553
|
};
|
|
436
|
-
let n =
|
|
554
|
+
let n = _e(e, t);
|
|
437
555
|
return {
|
|
438
556
|
plan: n,
|
|
439
557
|
acknowledged: !n.operations.some((e) => e.blocked)
|
|
440
558
|
};
|
|
441
|
-
},
|
|
559
|
+
}, J = async (e, t) => g.runPromise(t.pipe(g.provide(e))), Y = async (e) => {
|
|
442
560
|
try {
|
|
443
|
-
return await
|
|
561
|
+
return await J(e, b.SqlClient.pipe(g.flatMap(T)));
|
|
444
562
|
} catch (e) {
|
|
445
563
|
let t = String(e?.message ?? e);
|
|
446
|
-
if (
|
|
447
|
-
let n = /* @__PURE__ */ Error(`${
|
|
564
|
+
if (V(e) || /connection error/i.test(t)) {
|
|
565
|
+
let n = /* @__PURE__ */ Error(`${H}\n\nOriginal error: ${t}`);
|
|
448
566
|
throw n.cause = e, n;
|
|
449
567
|
}
|
|
450
568
|
throw e;
|
|
451
569
|
}
|
|
452
|
-
},
|
|
570
|
+
}, Le = {
|
|
453
571
|
safe: "\x1B[32m✓\x1B[0m",
|
|
454
572
|
"needs-default": "\x1B[32m⊕\x1B[0m",
|
|
455
573
|
"needs-backfill": "\x1B[33m⊕\x1B[0m",
|
|
@@ -457,7 +575,7 @@ var E = class extends Error {
|
|
|
457
575
|
lossy: "\x1B[31m✗\x1B[0m",
|
|
458
576
|
"online-required": "\x1B[36m◷\x1B[0m",
|
|
459
577
|
"multi-step": "\x1B[35m⇉\x1B[0m"
|
|
460
|
-
},
|
|
578
|
+
}, Re = (e) => {
|
|
461
579
|
let t = e.op, n;
|
|
462
580
|
switch (t.kind) {
|
|
463
581
|
case "create-table":
|
|
@@ -521,147 +639,201 @@ var E = class extends Error {
|
|
|
521
639
|
n = `ALTER TABLE ${t.table} DROP CHECK ${t.column}`;
|
|
522
640
|
break;
|
|
523
641
|
}
|
|
524
|
-
let r =
|
|
642
|
+
let r = Le[e.classification], i = e.reason ? ` [2m# ${e.reason}[0m` : "", a = e.blocked ? `\n [31m! ${e.blocked.fix}[0m` : "";
|
|
525
643
|
return ` ${r} ${n}${i}${a}`;
|
|
526
|
-
},
|
|
644
|
+
}, ze = async (e, t) => {
|
|
645
|
+
let n = e.operations.filter((e) => e.classification === "needs-default" && e.op.kind === "add-column");
|
|
646
|
+
if (n.length === 0) return;
|
|
647
|
+
let r = [];
|
|
648
|
+
for (let e of n) {
|
|
649
|
+
let n = e.op, i = n.table, a = n.column?.name;
|
|
650
|
+
if (i === void 0 || a === void 0) continue;
|
|
651
|
+
let o = 0;
|
|
652
|
+
try {
|
|
653
|
+
let e = await J(t, b.SqlClient.pipe(g.flatMap((e) => e.unsafe(`SELECT COUNT(*) AS n FROM ${re(i, U())}`))));
|
|
654
|
+
o = Number(Object.values(e[0] ?? { n: 0 })[0] ?? 0);
|
|
655
|
+
} catch {
|
|
656
|
+
continue;
|
|
657
|
+
}
|
|
658
|
+
o !== 0 && r.push(` ${o.toLocaleString("en-US")} existing row(s) in ${i} will get the default for ${a}`);
|
|
659
|
+
}
|
|
660
|
+
if (r.length !== 0) {
|
|
661
|
+
console.log("\n existing rows that a default will fill:");
|
|
662
|
+
for (let e of r) console.log(e);
|
|
663
|
+
console.log(" (the shape is correct either way — this is the VALUE a state diff cannot know)");
|
|
664
|
+
}
|
|
665
|
+
}, X = async (e, t) => {
|
|
666
|
+
if (U() !== "postgres") return;
|
|
667
|
+
let n = oe, r;
|
|
668
|
+
try {
|
|
669
|
+
r = await J(t, b.SqlClient.pipe(g.flatMap((t) => ue(t, e, n))));
|
|
670
|
+
} catch {
|
|
671
|
+
return;
|
|
672
|
+
}
|
|
673
|
+
let i = fe({
|
|
674
|
+
tables: e,
|
|
675
|
+
dialect: "postgres",
|
|
676
|
+
missing: r.missing,
|
|
677
|
+
stale: r.stale,
|
|
678
|
+
channel: n
|
|
679
|
+
});
|
|
680
|
+
if (i.length === 0) return;
|
|
681
|
+
L.info(`db apply: installing change triggers on ${r.missing.length} table(s)` + (r.stale.length > 0 ? `, removing ${r.stale.length} stale one(s)` : ""));
|
|
682
|
+
let a = 0;
|
|
683
|
+
for (let e of i) await J(t, b.SqlClient.pipe(g.flatMap((t) => t.unsafe(e)))), a += 1;
|
|
684
|
+
L.info(`db apply: change triggers converged (${a} statement(s))`, {
|
|
685
|
+
installed: r.missing.length,
|
|
686
|
+
removed: r.stale.length
|
|
687
|
+
});
|
|
688
|
+
}, Z = (e) => {
|
|
527
689
|
let t = e.summary, n = e.operations.length;
|
|
528
690
|
if (console.log(`\nschema diff: ${n} operation${n === 1 ? "" : "s"}, ${t.blocked} blocked\n`), n === 0) {
|
|
529
691
|
console.log(" (schema is up to date)\n");
|
|
530
692
|
return;
|
|
531
693
|
}
|
|
532
|
-
for (let t of e.operations) console.log(
|
|
533
|
-
console.log(), console.log(` safe: ${t.safe} needs-default: ${t.needsDefault} needs-backfill: ${t.needsBackfill}`), console.log(` rename: ${t.needsRenameAnnotation} lossy: ${t.lossy} blocked: ${t.blocked}`), console.log(` fingerprint: ${e.fromFingerprint.slice(0, 16)} → ${e.toFingerprint.slice(0, 16)}\n`);
|
|
534
|
-
},
|
|
535
|
-
let t =
|
|
694
|
+
for (let t of e.operations) console.log(Re(t));
|
|
695
|
+
console.log(), console.log(` safe: ${t.safe} needs-default: ${t.needsDefault} needs-backfill: ${t.needsBackfill}`), console.log(` rename: ${t.needsRenameAnnotation} lossy: ${t.lossy} blocked: ${t.blocked}`), console.log(` fingerprint: ${e.fromFingerprint.slice(0, 16)} → ${e.toFingerprint.slice(0, 16)}\n`), Be(e);
|
|
696
|
+
}, Be = (e) => {
|
|
697
|
+
let t = me(e.operations);
|
|
698
|
+
if (t.length !== 0) {
|
|
699
|
+
console.log(` ⚠ ${t.length} operation(s) UNSAFE under a rolling deploy`), console.log(" (old + new instances overlap → old code breaks against the new schema):");
|
|
700
|
+
for (let e of t) console.log(` • ${e.op.op.kind}: ${e.reason}`), console.log(` → ${e.remedy}`);
|
|
701
|
+
console.log(" Deploy with a maintenance window / scale-to-zero, or split into"), console.log(" expand → deploy → contract. See docs: deployment/production-hardening.\n");
|
|
702
|
+
}
|
|
703
|
+
}, Ve = (e) => {
|
|
704
|
+
let t = l(e, "--online-after") ?? process.env.VOLTRO_ONLINE_THRESHOLD;
|
|
536
705
|
if (t === void 0) return;
|
|
537
706
|
let n = Number(t);
|
|
538
707
|
if (!Number.isInteger(n) || n < 0) throw Error(`--online-after / VOLTRO_ONLINE_THRESHOLD must be a non-negative integer, got "${t}"`);
|
|
539
708
|
return n;
|
|
540
|
-
},
|
|
541
|
-
let t = await
|
|
709
|
+
}, Q = async (e) => {
|
|
710
|
+
let t = await G(e);
|
|
542
711
|
if (t.length === 0) return;
|
|
543
|
-
let n = await
|
|
544
|
-
return
|
|
712
|
+
let n = await o({ root: e });
|
|
713
|
+
return K(Ie([
|
|
545
714
|
...t,
|
|
546
|
-
...
|
|
715
|
+
...x,
|
|
547
716
|
...n
|
|
548
717
|
]));
|
|
549
|
-
},
|
|
550
|
-
let t =
|
|
551
|
-
if (r === void 0) return
|
|
718
|
+
}, He = async (e) => {
|
|
719
|
+
let t = Ve(e), n = z(e), r = await Q(n);
|
|
720
|
+
if (r === void 0) return L.warn("db plan: no schema files found", {
|
|
552
721
|
root: n,
|
|
553
722
|
hint: "looked for *.entity.ts / *.schema.ts / schema.ts"
|
|
554
723
|
}), 1;
|
|
555
|
-
let i =
|
|
724
|
+
let i = l(e, "--against");
|
|
556
725
|
if (i) {
|
|
557
|
-
let n = i, a =
|
|
726
|
+
let n = i, a = l(e, "--token") ?? process.env.VOLTRO_INSPECT_TOKEN, o = { accept: "application/json" };
|
|
558
727
|
a && (o.authorization = `Bearer ${a}`);
|
|
559
728
|
try {
|
|
560
729
|
let e = n.endsWith("/_voltro/inspect/migrations") ? n : `${n.replace(/\/+$/, "")}/_voltro/inspect/migrations`, i = await fetch(e, { headers: o });
|
|
561
|
-
if (!i.ok) return
|
|
730
|
+
if (!i.ok) return L.error(`db plan --against: remote returned ${i.status}`, { url: e }), 1;
|
|
562
731
|
let a = (await i.json()).drift?.liveSnapshot;
|
|
563
|
-
if (!a) return
|
|
564
|
-
let s =
|
|
732
|
+
if (!a) return L.error("db plan --against: remote inspect payload missing drift.liveSnapshot", { url: e }), 1;
|
|
733
|
+
let s = E({
|
|
565
734
|
declared: r,
|
|
566
735
|
live: a,
|
|
567
|
-
dialect:
|
|
568
|
-
...
|
|
736
|
+
dialect: U(),
|
|
737
|
+
...w(),
|
|
569
738
|
...t === void 0 ? {} : { onlineAfter: t }
|
|
570
739
|
});
|
|
571
|
-
return console.log(`\n[--against ${e}] — diff vs remote live schema:\n`),
|
|
740
|
+
return console.log(`\n[--against ${e}] — diff vs remote live schema:\n`), Z(s), s.summary.blocked > 0 ? 2 : 0;
|
|
572
741
|
} catch (e) {
|
|
573
|
-
return
|
|
742
|
+
return L.error(`db plan --against: ${e.message}`), 1;
|
|
574
743
|
}
|
|
575
744
|
}
|
|
576
|
-
let a =
|
|
745
|
+
let a = E({
|
|
577
746
|
declared: r,
|
|
578
|
-
live: await
|
|
579
|
-
dialect:
|
|
580
|
-
...
|
|
747
|
+
live: await Y(await W()),
|
|
748
|
+
dialect: U(),
|
|
749
|
+
...w(),
|
|
581
750
|
...t === void 0 ? {} : { onlineAfter: t }
|
|
582
751
|
});
|
|
583
|
-
return e.includes("--json") ? (
|
|
584
|
-
},
|
|
752
|
+
return e.includes("--json") ? (y(1, `${JSON.stringify(a, null, 2)}\n`), a.summary.blocked > 0 ? 2 : 0) : (Z(a), a.summary.blocked > 0 ? 2 : 0);
|
|
753
|
+
}, Ue = async (e, t, n) => {
|
|
585
754
|
let r;
|
|
586
755
|
try {
|
|
587
|
-
let t = await
|
|
756
|
+
let t = await v.readFile(m(e), "utf8");
|
|
588
757
|
r = JSON.parse(t);
|
|
589
758
|
} catch (t) {
|
|
590
|
-
return
|
|
591
|
-
}
|
|
592
|
-
if (!Array.isArray(r?.operations) || typeof r?.fromFingerprint != "string" || typeof r?.toFingerprint != "string") return
|
|
593
|
-
let i = await
|
|
594
|
-
if (i === void 0) return
|
|
595
|
-
let a = await
|
|
596
|
-
await
|
|
597
|
-
let o =
|
|
759
|
+
return L.error(`db apply --plan: cannot read/parse plan file '${e}': ${t.message}`), 1;
|
|
760
|
+
}
|
|
761
|
+
if (!Array.isArray(r?.operations) || typeof r?.fromFingerprint != "string" || typeof r?.toFingerprint != "string") return L.error(`db apply --plan: '${e}' is not a valid plan file (expected the output of \`voltro db plan --json\`).`), 1;
|
|
762
|
+
let i = await Q(n);
|
|
763
|
+
if (i === void 0) return L.warn("db apply --plan: no schema files found", { root: n }), 1;
|
|
764
|
+
let a = await W();
|
|
765
|
+
await J(a, S(x, U()));
|
|
766
|
+
let o = await J(a, b.SqlClient.pipe(g.flatMap((e) => de(e, n)))).catch(() => []);
|
|
767
|
+
if (o.length > 0) return L.error(`db apply --plan: refusing — ${o.length} pending file-based migration(s).`), console.error(` ${o.join("\n ")}`), console.error(""), console.error(" These perform the changes a state diff cannot infer, so they change the shape"), console.error(" this plan was computed against. Apply them and regenerate the plan:"), console.error(""), console.error(" voltro db files ."), console.error(" voltro db plan --json > plan.json"), console.error(" voltro db apply --plan plan.json"), 1;
|
|
768
|
+
let s = E({
|
|
598
769
|
declared: i,
|
|
599
|
-
live: await
|
|
600
|
-
dialect:
|
|
601
|
-
...
|
|
770
|
+
live: await Y(a),
|
|
771
|
+
dialect: U(),
|
|
772
|
+
...w()
|
|
602
773
|
});
|
|
603
|
-
if (
|
|
604
|
-
if (
|
|
605
|
-
let
|
|
606
|
-
if (
|
|
607
|
-
if (
|
|
608
|
-
|
|
609
|
-
let
|
|
774
|
+
if (s.fromFingerprint !== r.fromFingerprint) return L.error("db apply --plan: refusing — the live schema has drifted since this plan was generated."), console.error(` plan.from=${r.fromFingerprint.slice(0, 16)} live=${s.fromFingerprint.slice(0, 16)}`), console.error(" Regenerate against the current live schema: `voltro db plan --json > plan.json`"), 2;
|
|
775
|
+
if (s.toFingerprint !== r.toFingerprint) return L.error("db apply --plan: refusing — the declared schema differs from what this plan targets."), console.error(` plan.to=${r.toFingerprint.slice(0, 16)} declared=${s.toFingerprint.slice(0, 16)}`), console.error(" The schema files changed since the plan was generated. Regenerate the plan."), 2;
|
|
776
|
+
let c = q(s), u = c.plan;
|
|
777
|
+
if (u.summary.blocked > 0 && !c.acknowledged) return Z(u), L.error(`db apply --plan: refusing — ${s.summary.blocked} blocked operation(s). See the fix hints above.`), process.env.VOLTRO_DESTRUCTIVE_OK !== void 0 && (L.error(" VOLTRO_DESTRUCTIVE_OK is set but did not cover this plan — it only relaxes"), L.error(" `lossy` operations, and only the tables it names. Anything else stays refused.")), 2;
|
|
778
|
+
if (c.acknowledged && L.warn("db apply --plan: proceeding with acknowledged lossy operation(s) (VOLTRO_DESTRUCTIVE_OK)"), u.operations.length === 0) return L.info("db apply --plan: schema is up to date — nothing to apply"), await X(i, a), 0;
|
|
779
|
+
Z(u);
|
|
780
|
+
let d = (process.env.NODE_ENV ?? "dev").toLowerCase(), f = l(t, "--note"), p = await J(a, b.SqlClient.pipe(g.flatMap((e) => se(e, u, {
|
|
610
781
|
declared: i,
|
|
611
782
|
appliedBy: process.env.USER ? `cli:${process.env.USER}` : "cli:plan",
|
|
612
|
-
environment:
|
|
783
|
+
environment: d === "production" ? "prod" : d === "staging" ? "staging" : "dev",
|
|
613
784
|
source: "file",
|
|
614
|
-
...
|
|
615
|
-
replan: () =>
|
|
785
|
+
...f ? { notes: f } : {},
|
|
786
|
+
replan: () => g.promise(() => Y(a)).pipe(g.map((e) => E({
|
|
616
787
|
declared: i,
|
|
617
788
|
live: e,
|
|
618
|
-
dialect:
|
|
619
|
-
...
|
|
789
|
+
dialect: U(),
|
|
790
|
+
...w()
|
|
620
791
|
})))
|
|
621
792
|
}))));
|
|
622
|
-
return
|
|
623
|
-
id:
|
|
624
|
-
fingerprint:
|
|
625
|
-
}), 0;
|
|
626
|
-
},
|
|
627
|
-
let t =
|
|
628
|
-
if (r) return
|
|
629
|
-
if ((process.env.NODE_ENV ?? "").toLowerCase() === "production") return
|
|
630
|
-
let i = await
|
|
631
|
-
if (i === void 0) return
|
|
632
|
-
let a = await
|
|
633
|
-
|
|
634
|
-
|
|
793
|
+
return L.info(`db apply --plan: applied ${p.appliedOps} op(s) in ${p.durationMs}ms`, {
|
|
794
|
+
id: p.id,
|
|
795
|
+
fingerprint: p.fingerprint.slice(0, 16)
|
|
796
|
+
}), await X(i, a), 0;
|
|
797
|
+
}, We = async (e) => {
|
|
798
|
+
let t = z(e), n = Ve(e), r = l(e, "--plan");
|
|
799
|
+
if (r) return Ue(r, e, t);
|
|
800
|
+
if ((process.env.NODE_ENV ?? "").toLowerCase() === "production") return L.error("voltro db apply: refusing — auto-apply on prod is not allowed."), console.error("In production, schema changes go through `voltro db plan --json > plan.json`"), console.error("then `voltro db apply --plan plan.json` as an explicit step in your deploy pipeline."), 3;
|
|
801
|
+
let i = await Q(t);
|
|
802
|
+
if (i === void 0) return L.warn("db apply: no schema files found", { root: t }), 1;
|
|
803
|
+
let a = await W(), o = await $e(t, a);
|
|
804
|
+
if ("failed" in o) return L.error("db apply: a file-based migration FAILED — the schema diff was NOT run"), console.error(o.failed), console.error("Fix the migration and re-run. `voltro db files` runs them alone."), 1;
|
|
805
|
+
await J(a, S(x, U()));
|
|
806
|
+
let s = E({
|
|
635
807
|
declared: i,
|
|
636
|
-
live: await
|
|
637
|
-
dialect:
|
|
638
|
-
...
|
|
808
|
+
live: await Y(a),
|
|
809
|
+
dialect: U(),
|
|
810
|
+
...w(),
|
|
639
811
|
...n === void 0 ? {} : { onlineAfter: n }
|
|
640
812
|
});
|
|
641
|
-
|
|
642
|
-
let
|
|
643
|
-
if (
|
|
644
|
-
if (
|
|
645
|
-
let
|
|
813
|
+
Z(s), await ze(s, a);
|
|
814
|
+
let c = q(s), u = c.plan;
|
|
815
|
+
if (u.summary.blocked > 0 && !c.acknowledged) return L.error(`db apply: refusing — ${u.summary.blocked} blocked operation(s). See the fix hints above.`), process.env.VOLTRO_DESTRUCTIVE_OK !== void 0 && (L.error(" VOLTRO_DESTRUCTIVE_OK is set but did not cover this plan — it only relaxes"), L.error(" `lossy` operations, and only the tables it names. Anything else stays refused.")), 2;
|
|
816
|
+
if (c.acknowledged && L.warn(`db apply: proceeding with ${s.summary.blocked} acknowledged lossy operation(s) (VOLTRO_DESTRUCTIVE_OK)`), u.operations.length === 0) return L.info("db apply: schema is up to date — nothing to apply"), await Ye(a), l(e, "--note") !== void 0 && (L.warn("db apply: --note was ignored — an empty plan writes no history row to attach it to."), console.error("To record that the CURRENT live schema is the accepted baseline, use `voltro db drift --accept`.")), await X(i, a), 0;
|
|
817
|
+
let d = l(e, "--note"), f = await J(a, b.SqlClient.pipe(g.flatMap((e) => se(e, u, {
|
|
646
818
|
declared: i,
|
|
647
819
|
appliedBy: process.env.USER ? `cli:${process.env.USER}` : "cli:user",
|
|
648
820
|
environment: (process.env.NODE_ENV ?? "dev").toLowerCase() === "staging" ? "staging" : "dev",
|
|
649
821
|
source: "auto-diff",
|
|
650
|
-
...
|
|
651
|
-
replan: () =>
|
|
822
|
+
...d ? { notes: d } : {},
|
|
823
|
+
replan: () => g.promise(() => Y(a)).pipe(g.map((e) => E({
|
|
652
824
|
declared: i,
|
|
653
825
|
live: e,
|
|
654
|
-
dialect:
|
|
655
|
-
...
|
|
826
|
+
dialect: U(),
|
|
827
|
+
...w(),
|
|
656
828
|
...n === void 0 ? {} : { onlineAfter: n }
|
|
657
829
|
})))
|
|
658
830
|
}))));
|
|
659
|
-
return
|
|
660
|
-
id:
|
|
661
|
-
fingerprint:
|
|
662
|
-
}), 0;
|
|
663
|
-
},
|
|
664
|
-
let t = Math.max(1, Number(
|
|
831
|
+
return L.info(`db apply: applied ${f.appliedOps} op(s) in ${f.durationMs}ms`, {
|
|
832
|
+
id: f.id,
|
|
833
|
+
fingerprint: f.fingerprint.slice(0, 16)
|
|
834
|
+
}), await X(i, a), 0;
|
|
835
|
+
}, Ge = async (e) => {
|
|
836
|
+
let t = Math.max(1, Number(l(e, "--limit") ?? "20")), n = await J(await W(), b.SqlClient.pipe(g.flatMap((e) => e`
|
|
665
837
|
SELECT ${e("id")}, ${e("fingerprint")}, ${e("operations")},
|
|
666
838
|
${e("appliedBy")}, ${e("environment")}, ${e("source")},
|
|
667
839
|
${e("durationMs")}, ${e("notes")}, ${e("appliedAt")}
|
|
@@ -669,25 +841,25 @@ var E = class extends Error {
|
|
|
669
841
|
ORDER BY ${e("appliedAt")} DESC
|
|
670
842
|
LIMIT ${t}
|
|
671
843
|
`)));
|
|
672
|
-
if (n.length === 0) return
|
|
844
|
+
if (n.length === 0) return L.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;
|
|
673
845
|
console.log(`\n${n.length} plan${n.length === 1 ? "" : "s"} (newest first):\n`);
|
|
674
846
|
for (let e of n) {
|
|
675
847
|
let t = 0;
|
|
676
848
|
try {
|
|
677
|
-
t = JSON.parse(
|
|
849
|
+
t = JSON.parse(Xe(e.operations)).length;
|
|
678
850
|
} catch {}
|
|
679
|
-
console.log(` ${e.id} fp=${
|
|
851
|
+
console.log(` ${e.id} fp=${D(e.fingerprint)} env=${e.environment} src=${e.source} ${t} op(s) ${e.durationMs}ms ${$(e.appliedAt)} by=${e.appliedBy}`), e.notes && console.log(` [2mnote: ${e.notes}[0m`);
|
|
680
852
|
}
|
|
681
853
|
return console.log(), 0;
|
|
682
|
-
},
|
|
854
|
+
}, Ke = async (e) => {
|
|
683
855
|
let t = e[0];
|
|
684
856
|
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;
|
|
685
|
-
let n = await
|
|
857
|
+
let n = await W(), r = await J(n, b.SqlClient.pipe(g.flatMap((e) => e`SELECT ${e("id")}, ${e("operations")}
|
|
686
858
|
FROM ${e("_voltro_migration_plans")} WHERE ${e("id")} = ${t}`)));
|
|
687
859
|
if (r.length === 0) return console.error(`restore-snapshot: plan '${t}' not found`), 1;
|
|
688
860
|
let i;
|
|
689
861
|
try {
|
|
690
|
-
i = JSON.parse(
|
|
862
|
+
i = JSON.parse(Xe(r[0].operations));
|
|
691
863
|
} catch {
|
|
692
864
|
return console.error(`restore-snapshot: plan '${t}' has corrupted operations JSON`), 1;
|
|
693
865
|
}
|
|
@@ -701,7 +873,7 @@ var E = class extends Error {
|
|
|
701
873
|
if (a.length === 0 && o.length === 0) return console.log(`restore-snapshot: plan '${t}' has no drop-column / drop-table ops to restore`), 0;
|
|
702
874
|
let s = 0;
|
|
703
875
|
for (let e of a) {
|
|
704
|
-
let t = await
|
|
876
|
+
let t = await J(n, b.SqlClient.pipe(g.flatMap((t) => t`
|
|
705
877
|
SELECT column_name FROM information_schema.columns
|
|
706
878
|
WHERE table_schema = 'public'
|
|
707
879
|
AND table_name = ${e.table}
|
|
@@ -713,11 +885,11 @@ var E = class extends Error {
|
|
|
713
885
|
continue;
|
|
714
886
|
}
|
|
715
887
|
let r = t[0].column_name;
|
|
716
|
-
await
|
|
888
|
+
await J(n, b.SqlClient.pipe(g.flatMap((t) => t.unsafe(`ALTER TABLE "${e.table}" RENAME COLUMN "${r}" TO "${e.column}"`)))), s++, console.log(` ↩ restored ${e.table}.${e.column} (from ${r})`);
|
|
717
889
|
}
|
|
718
890
|
let c = 0;
|
|
719
891
|
for (let e of o) {
|
|
720
|
-
let t = await
|
|
892
|
+
let t = await J(n, b.SqlClient.pipe(g.flatMap((t) => t`
|
|
721
893
|
SELECT table_name FROM information_schema.tables
|
|
722
894
|
WHERE table_schema = 'public'
|
|
723
895
|
AND table_name LIKE ${e.table + "__dropped_%"}
|
|
@@ -728,57 +900,57 @@ var E = class extends Error {
|
|
|
728
900
|
continue;
|
|
729
901
|
}
|
|
730
902
|
let r = t[0].table_name;
|
|
731
|
-
await
|
|
903
|
+
await J(n, b.SqlClient.pipe(g.flatMap((t) => t.unsafe(`ALTER TABLE "${r}" RENAME TO "${e.table}"`)))), c++, console.log(` ↩ restored table ${e.table} (from ${r})`);
|
|
732
904
|
}
|
|
733
905
|
return console.log(`\n✓ restored ${s} column(s) + ${c} table(s) from plan '${t}'\n`), 0;
|
|
734
|
-
},
|
|
735
|
-
let t =
|
|
906
|
+
}, qe = async (e) => {
|
|
907
|
+
let t = l(e, "--before");
|
|
736
908
|
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;
|
|
737
909
|
let n = t.replace(/[^0-9]/g, "");
|
|
738
910
|
if (n.length < 8) return console.error(`gc-snapshots: --before must be at least a full date (YYYY-MM-DD), got "${t}"`), 2;
|
|
739
|
-
let r = (n + "00000000000000").slice(0, 14), i = e.includes("--dry-run"), a = await
|
|
911
|
+
let r = (n + "00000000000000").slice(0, 14), i = e.includes("--dry-run"), a = await W(), o = await J(a, b.SqlClient.pipe(g.flatMap((e) => e`
|
|
740
912
|
SELECT table_name, column_name FROM information_schema.columns
|
|
741
913
|
WHERE table_schema = 'public' AND column_name LIKE ${"%__dropped_%"}
|
|
742
|
-
`))), s = await
|
|
914
|
+
`))), s = await J(a, b.SqlClient.pipe(g.flatMap((e) => e`
|
|
743
915
|
SELECT table_name FROM information_schema.tables
|
|
744
916
|
WHERE table_schema = 'public' AND table_name LIKE ${"%__dropped_%"}
|
|
745
|
-
`))),
|
|
917
|
+
`))), c = (e) => {
|
|
746
918
|
let t = /__dropped_(\d{14})$/.exec(e);
|
|
747
919
|
return t ? t[1] : null;
|
|
748
920
|
}, u = 0;
|
|
749
921
|
for (let e of o) {
|
|
750
|
-
let t =
|
|
751
|
-
t === null || t >= r || (console.log(` ${i ? "[dry-run] would drop column" : "dropping column"} ${e.table_name}.${e.column_name}`), i || await
|
|
922
|
+
let t = c(e.column_name);
|
|
923
|
+
t === null || t >= r || (console.log(` ${i ? "[dry-run] would drop column" : "dropping column"} ${e.table_name}.${e.column_name}`), i || await J(a, b.SqlClient.pipe(g.flatMap((t) => t.unsafe(`ALTER TABLE "${e.table_name}" DROP COLUMN "${e.column_name}"`)))), u++);
|
|
752
924
|
}
|
|
753
925
|
for (let e of s) {
|
|
754
|
-
let t =
|
|
755
|
-
t === null || t >= r || (console.log(` ${i ? "[dry-run] would drop table" : "dropping table"} ${e.table_name}`), i || await
|
|
926
|
+
let t = c(e.table_name);
|
|
927
|
+
t === null || t >= r || (console.log(` ${i ? "[dry-run] would drop table" : "dropping table"} ${e.table_name}`), i || await J(a, b.SqlClient.pipe(g.flatMap((t) => t.unsafe(`DROP TABLE "${e.table_name}"`)))), u++);
|
|
756
928
|
}
|
|
757
929
|
return console.log(`\n${i ? `[dry-run] ${u} snapshot(s) would be dropped` : `✓ dropped ${u} snapshot(s)`} (older than ${r}).\n`), 0;
|
|
758
|
-
},
|
|
759
|
-
let t =
|
|
930
|
+
}, Je = async (e) => {
|
|
931
|
+
let t = l(e, "--before");
|
|
760
932
|
if (!t) return console.error("voltro db squash --before <iso-date> [--note \"<text>\"]"), 2;
|
|
761
933
|
let n = t;
|
|
762
934
|
if (Number.isNaN(new Date(n).getTime())) return console.error(`voltro db squash: --before '${n}' is not a valid ISO date`), 2;
|
|
763
|
-
let r =
|
|
764
|
-
if (a === void 0) return
|
|
765
|
-
let o =
|
|
935
|
+
let r = l(e, "--note"), i = z(e), a = await Q(i);
|
|
936
|
+
if (a === void 0) return L.warn("db squash: no schema files found", { root: i }), 1;
|
|
937
|
+
let o = C(ce(a)), s = await W();
|
|
766
938
|
try {
|
|
767
|
-
let e = await
|
|
939
|
+
let e = await J(s, ge({
|
|
768
940
|
before: n,
|
|
769
941
|
fingerprint: o,
|
|
770
942
|
appliedBy: process.env.USER ?? "cli",
|
|
771
943
|
environment: process.env.NODE_ENV === "production" ? "prod" : "dev",
|
|
772
944
|
...r === void 0 ? {} : { note: r }
|
|
773
945
|
}));
|
|
774
|
-
return console.log(`\n✓ squashed ${e.squashedCount} migration plan(s)\n snapshot id: ${e.snapshotId}\n fingerprint: ${
|
|
946
|
+
return console.log(`\n✓ squashed ${e.squashedCount} migration plan(s)\n snapshot id: ${e.snapshotId}\n fingerprint: ${D(e.snapshotFingerprint)}\n`), 0;
|
|
775
947
|
} catch (e) {
|
|
776
948
|
return console.error(`squash refused: ${e.message}`), 1;
|
|
777
949
|
}
|
|
778
|
-
},
|
|
950
|
+
}, Ye = async (e) => {
|
|
779
951
|
try {
|
|
780
|
-
let t =
|
|
781
|
-
await
|
|
952
|
+
let t = C(await Y(e));
|
|
953
|
+
await J(e, b.SqlClient.pipe(g.flatMap((e) => e`
|
|
782
954
|
UPDATE ${e("_voltro_migration_plans")}
|
|
783
955
|
SET ${e("liveFingerprint")} = ${t}
|
|
784
956
|
WHERE ${e("id")} = (
|
|
@@ -790,7 +962,7 @@ var E = class extends Error {
|
|
|
790
962
|
AND ${e("liveFingerprint")} IS NULL
|
|
791
963
|
`)));
|
|
792
964
|
} catch {}
|
|
793
|
-
},
|
|
965
|
+
}, Xe = (e) => typeof e == "string" ? e : e == null ? "" : JSON.stringify(e), $ = (e) => e instanceof Date ? e.toISOString() : String(e ?? ""), Ze = (e) => e === void 0 ? {
|
|
794
966
|
accept: !1,
|
|
795
967
|
reason: "no-declared-schema"
|
|
796
968
|
} : e > 0 ? {
|
|
@@ -799,38 +971,38 @@ var E = class extends Error {
|
|
|
799
971
|
} : {
|
|
800
972
|
accept: !0,
|
|
801
973
|
reason: "converged"
|
|
802
|
-
},
|
|
803
|
-
let t = e.includes("--accept"), n = await
|
|
974
|
+
}, Qe = async (e) => {
|
|
975
|
+
let t = e.includes("--accept"), n = await W(), r = await J(n, b.SqlClient.pipe(g.flatMap((e) => e`
|
|
804
976
|
SELECT * FROM ${e("_voltro_migration_plans")}
|
|
805
977
|
ORDER BY ${e("appliedAt")} DESC
|
|
806
978
|
LIMIT 1
|
|
807
|
-
`))), i = await
|
|
808
|
-
if (r.length === 0) return
|
|
979
|
+
`))), i = await Y(n), a = C(i);
|
|
980
|
+
if (r.length === 0) return L.info("db drift: no _voltro_migration_plans rows yet"), console.log(`live user-schema fingerprint: ${D(a)}`), console.log("No baseline to compare against. Run `voltro db apply` to lock in the current state."), 0;
|
|
809
981
|
let o = r[0];
|
|
810
|
-
if (o.liveFingerprint === null || o.liveFingerprint === void 0) return
|
|
811
|
-
if (o.liveFingerprint === a) return
|
|
982
|
+
if (o.liveFingerprint === null || o.liveFingerprint === void 0) return L.warn("db drift: no drift baseline recorded yet — cannot compare"), console.log(), console.log(` last applied: ${o.id} at ${$(o.appliedAt)}`), console.log(` live now: ${D(a)}`), console.log(), console.log("That row was written before the framework recorded a live-schema baseline,"), console.log("and the value it does carry is the DECLARED snapshot's hash — not comparable"), console.log("with a live one. Run `voltro db apply` once to establish the baseline (a no-op"), console.log("apply backfills it too); use `voltro db plan` meanwhile, which compares declared"), console.log("against live directly."), console.log(), console.log("Exit code 3 — NOT 0. \"Did not compare\" is not \"clean\", and a gate on the exit"), console.log("code has to be able to tell them apart."), 3;
|
|
983
|
+
if (o.liveFingerprint === a) return L.info(`db drift: live matches last applied (${D(a)})`), 0;
|
|
812
984
|
let s = await (async () => {
|
|
813
985
|
try {
|
|
814
|
-
let t = await
|
|
815
|
-
return t === void 0 ? void 0 :
|
|
986
|
+
let t = await Q(z(e));
|
|
987
|
+
return t === void 0 ? void 0 : E({
|
|
816
988
|
declared: t,
|
|
817
989
|
live: i,
|
|
818
|
-
dialect:
|
|
819
|
-
...
|
|
990
|
+
dialect: U(),
|
|
991
|
+
...w()
|
|
820
992
|
}).operations;
|
|
821
993
|
} catch {
|
|
822
994
|
return;
|
|
823
995
|
}
|
|
824
996
|
})();
|
|
825
997
|
if (t) {
|
|
826
|
-
let e =
|
|
827
|
-
if (e.reason === "no-declared-schema") return
|
|
998
|
+
let e = Ze(s?.length);
|
|
999
|
+
if (e.reason === "no-declared-schema") return L.error("db drift --accept: no schema files found — cannot verify the live schema is current."), 1;
|
|
828
1000
|
if (!e.accept && s !== void 0) {
|
|
829
|
-
|
|
1001
|
+
L.error(`db drift --accept: refusing — ${s.length} operation(s) still pending against your declared schema.`), console.log();
|
|
830
1002
|
for (let e of s.slice(0, 10)) console.log(` - ${e.op.kind} ${"table" in e.op ? `[${e.op.table}]` : ""}`);
|
|
831
1003
|
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;
|
|
832
1004
|
}
|
|
833
|
-
return await
|
|
1005
|
+
return await J(n, b.SqlClient.pipe(g.flatMap((e) => e`
|
|
834
1006
|
UPDATE ${e("_voltro_migration_plans")}
|
|
835
1007
|
SET ${e("liveFingerprint")} = ${a}
|
|
836
1008
|
WHERE ${e("id")} = (
|
|
@@ -839,31 +1011,43 @@ var E = class extends Error {
|
|
|
839
1011
|
ORDER BY ${e("appliedAt")} DESC LIMIT 1
|
|
840
1012
|
) AS ${e("newest")}
|
|
841
1013
|
)
|
|
842
|
-
`))),
|
|
1014
|
+
`))), L.info(`db drift: accepted the live schema as the baseline (${D(a)})`), console.log(), console.log("`voltro db plan` reports 0 operations against this schema, so the live state already"), console.log("matches your declaration — only the ledger did not know. It does now."), console.log(), console.log("No history entry was invented: the latest row's baseline was updated in place,"), console.log("because no migration ran."), 0;
|
|
1015
|
+
}
|
|
1016
|
+
return L.error("db drift: live schema DIVERGED from last applied state"), console.log(), console.log(` baseline: ${D(o.liveFingerprint)} at ${$(o.appliedAt)} (${o.id})`), console.log(` live now: ${D(a)}`), console.log(), console.log("Something changed the live schema after the last apply. This command can see"), console.log("THAT it changed, not what or who — the fingerprints are hashes, not a diff."), console.log(), s === void 0 ? (console.log("No schema files found here, so the declared side could not be compared."), console.log("Run `voltro db plan` from your project root for the actual difference."), 4) : s.length === 0 ? (console.log("Your DECLARED schema is already satisfied — `voltro db plan` reports 0 operations"), console.log("against this database. So the live schema is not wrong, only unrecorded: something"), console.log("applied a change without going through the planner (a hand-run ALTER, a DBA"), console.log("window, a restored dump), or it touched a table your code does not declare."), console.log(), console.log("If that was deliberate and the schema is right, record it:"), console.log(), console.log(" voltro db drift --accept"), console.log(), console.log("It updates the latest ledger row's baseline to the live schema and invents no"), console.log("history entry. Drift then measures from here."), 4) : (console.log(`\`voltro db plan\` reports ${s.length} operation(s) still pending against your`), console.log("declared schema, so this is not merely an unrecorded change — run it for the diff,"), console.log("then `voltro db apply`, which writes a fresh baseline of its own."), 4);
|
|
1017
|
+
}, $e = async (e, t) => {
|
|
1018
|
+
let n = (process.env.NODE_ENV ?? "").toLowerCase() === "production" ? "prod" : (process.env.NODE_ENV ?? "").toLowerCase() === "staging" ? "staging" : "dev";
|
|
1019
|
+
try {
|
|
1020
|
+
let r = await J(t, b.SqlClient.pipe(g.flatMap((t) => he(t, {
|
|
1021
|
+
projectRoot: e,
|
|
1022
|
+
env: n,
|
|
1023
|
+
appliedBy: process.env.USER ? `cli:${process.env.USER}` : "cli:user"
|
|
1024
|
+
}))));
|
|
1025
|
+
return r.applied.length > 0 && L.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 };
|
|
1026
|
+
} catch (e) {
|
|
1027
|
+
return { failed: e };
|
|
843
1028
|
}
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
await
|
|
848
|
-
let i = await G(n, _.SqlClient.pipe(h.flatMap((e) => ue(e, {
|
|
1029
|
+
}, et = async (e) => {
|
|
1030
|
+
let t = z(e), n = await W(), r = (process.env.NODE_ENV ?? "").toLowerCase() === "production" ? "prod" : (process.env.NODE_ENV ?? "").toLowerCase() === "staging" ? "staging" : "dev";
|
|
1031
|
+
await J(n, S(x, U()));
|
|
1032
|
+
let i = await J(n, b.SqlClient.pipe(g.flatMap((e) => he(e, {
|
|
849
1033
|
projectRoot: t,
|
|
850
1034
|
env: r,
|
|
851
1035
|
appliedBy: process.env.USER ? `cli:${process.env.USER}` : "cli:user"
|
|
852
1036
|
}))));
|
|
853
|
-
return i.applied.length === 0 ? (
|
|
1037
|
+
return i.applied.length === 0 ? (L.info(`db files: no pending file-based migrations (${i.skipped.length} already applied)`), 0) : (L.info(`db files: applied ${i.applied.length} migration(s)`, {
|
|
854
1038
|
applied: i.applied.map((e) => `${e.id} (${e.durationMs}ms)`),
|
|
855
1039
|
skipped: i.skipped.length
|
|
856
1040
|
}), 0);
|
|
857
|
-
},
|
|
858
|
-
let t =
|
|
859
|
-
if (!n) return
|
|
860
|
-
if ((process.env.NODE_ENV ?? "").toLowerCase() === "production") return
|
|
861
|
-
let r = await
|
|
1041
|
+
}, tt = async (e) => {
|
|
1042
|
+
let t = z(e), n = e.find((e) => !e.startsWith("--"));
|
|
1043
|
+
if (!n) return L.error("voltro db rollback-file <migration-id>"), 2;
|
|
1044
|
+
if ((process.env.NODE_ENV ?? "").toLowerCase() === "production") return L.error("voltro db rollback-file: refusing on prod — schema rollback runs as an explicit deploy step."), 3;
|
|
1045
|
+
let r = await J(await W(), b.SqlClient.pipe(g.flatMap((e) => pe(e, {
|
|
862
1046
|
projectRoot: t,
|
|
863
1047
|
id: n
|
|
864
1048
|
}))));
|
|
865
|
-
return
|
|
866
|
-
},
|
|
1049
|
+
return L.info(`db rollback-file: rolled back ${r.id} in ${r.durationMs}ms`), 0;
|
|
1050
|
+
}, nt = () => {
|
|
867
1051
|
let e = /* @__PURE__ */ new Date();
|
|
868
1052
|
return [
|
|
869
1053
|
e.getUTCFullYear(),
|
|
@@ -873,7 +1057,7 @@ var E = class extends Error {
|
|
|
873
1057
|
String(e.getUTCMinutes()).padStart(2, "0"),
|
|
874
1058
|
String(e.getUTCSeconds()).padStart(2, "0")
|
|
875
1059
|
].join("");
|
|
876
|
-
},
|
|
1060
|
+
}, rt = (e) => e.trim().toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_+|_+$/g, "") || "migration", it = (e, t) => `import { defineMigration, SqlClient } from '@voltro/database'
|
|
877
1061
|
import { Effect } from 'effect'
|
|
878
1062
|
|
|
879
1063
|
// A hand-written migration. Reach for this when the DECLARATIVE differ can't
|
|
@@ -905,36 +1089,63 @@ export default defineMigration({
|
|
|
905
1089
|
}),
|
|
906
1090
|
],
|
|
907
1091
|
})
|
|
908
|
-
`,
|
|
909
|
-
plan:
|
|
910
|
-
apply:
|
|
911
|
-
plans:
|
|
912
|
-
drift:
|
|
913
|
-
files:
|
|
914
|
-
"rollback-file":
|
|
915
|
-
migrate:
|
|
916
|
-
rollback:
|
|
917
|
-
status:
|
|
918
|
-
seed:
|
|
1092
|
+
`, at = {
|
|
1093
|
+
plan: He,
|
|
1094
|
+
apply: We,
|
|
1095
|
+
plans: Ge,
|
|
1096
|
+
drift: Qe,
|
|
1097
|
+
files: et,
|
|
1098
|
+
"rollback-file": tt,
|
|
1099
|
+
migrate: Ae,
|
|
1100
|
+
rollback: je,
|
|
1101
|
+
status: Me,
|
|
1102
|
+
seed: Pe,
|
|
919
1103
|
generate: async (e) => {
|
|
920
|
-
let { value: t, rest: n } =
|
|
921
|
-
return await
|
|
1104
|
+
let { value: t, rest: n } = c(e, "--root"), { value: r, rest: i } = c(n, "--name"), a = t ? m(t) : process.cwd(), o = rt(r ?? u(i) ?? "migration"), s = `${nt()}_${o}`, l = f(a, "migrations"), d = f(l, `${s}.migration.ts`);
|
|
1105
|
+
return await v.mkdir(l, { recursive: !0 }), await v.writeFile(d, it(s, o), "utf8"), L.info(`created ${p(a, d)}`), console.error("Fill in the apply/undo steps, then run `voltro db migrate` (rollback with `voltro db rollback`)."), console.error("For a plain schema shape change you may not need this — `voltro db apply` diffs declared vs live."), 0;
|
|
922
1106
|
},
|
|
923
|
-
squash:
|
|
924
|
-
"restore-snapshot":
|
|
925
|
-
"gc-snapshots":
|
|
926
|
-
|
|
1107
|
+
squash: Je,
|
|
1108
|
+
"restore-snapshot": Ke,
|
|
1109
|
+
"gc-snapshots": qe,
|
|
1110
|
+
adopt: async (e) => {
|
|
1111
|
+
let t = (t) => {
|
|
1112
|
+
let n = e.indexOf(`--${t}`);
|
|
1113
|
+
return n === -1 ? void 0 : e[n + 1];
|
|
1114
|
+
}, n = t("from"), r = t("into"), i = t("map");
|
|
1115
|
+
if (n === void 0 || r === void 0 || i === void 0) return console.error("voltro db adopt --from <table> --into <table> --map <file.ts> [--apply] [--keep-source]"), console.error(" Dry run unless --apply. The map file default-exports { map, leaveUnset? }."), 1;
|
|
1116
|
+
let o = (await a(m(process.cwd(), i))).default;
|
|
1117
|
+
if (o?.map === void 0) return console.error(`voltro db adopt: ${i} must default-export { map: { <targetColumn>: <source> } }`), 1;
|
|
1118
|
+
let s = {
|
|
1119
|
+
from: n,
|
|
1120
|
+
into: r,
|
|
1121
|
+
map: o.map,
|
|
1122
|
+
...o.leaveUnset ? { leaveUnset: o.leaveUnset } : {}
|
|
1123
|
+
}, c = U(), l = await W(), u = await J(l, b.SqlClient.pipe(g.flatMap(T))), d = (e) => u.tables.find((t) => t.name === e)?.columns.map((e) => ({
|
|
1124
|
+
name: e.name,
|
|
1125
|
+
nullable: e.nullable,
|
|
1126
|
+
hasDefault: e.defaultValue !== void 0 && e.defaultValue !== null
|
|
1127
|
+
})), f = d(n), p = d(r);
|
|
1128
|
+
if (f === void 0) return console.error(`voltro db adopt: '${n}' does not exist in the database`), 1;
|
|
1129
|
+
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;
|
|
1130
|
+
let h = async (e) => await J(l, b.SqlClient.pipe(g.flatMap((t) => t.unsafe(e)))), _ = async (e) => Number(Object.values((await h(`SELECT COUNT(*) AS n FROM "${e}"`))[0] ?? { n: 0 })[0] ?? 0), v = xe(s, f, p, await _(r));
|
|
1131
|
+
for (let e of Ce(s, v, await _(n))) console.log(e);
|
|
1132
|
+
if (v.fatal) return 1;
|
|
1133
|
+
if (!e.includes("--apply")) return console.log(""), console.log(" (dry run — nothing written. Re-run with --apply.)"), 0;
|
|
1134
|
+
let y = await Ee(s, v, c, "id", void 0, h, { keepSource: e.includes("--keep-source") });
|
|
1135
|
+
return console.log(""), console.log(` copied ${y.copied}/${y.sourceRows} row(s)`), console.log(` snapshot: ${y.snapshotTable} — restore from here, then drop it yourself`), y.verified ? (console.log(y.sourceDropped ? ` dropped ${n}` : ` ${n} left in place (--keep-source)`), 0) : (console.error(" ✗ counts DIFFER — the source was NOT dropped. Compare the two tables before doing anything else."), 1);
|
|
1136
|
+
}
|
|
1137
|
+
}, ot = async (e) => {
|
|
927
1138
|
let [t, ...n] = e;
|
|
928
1139
|
if (!t) {
|
|
929
1140
|
console.error("voltro db <subcommand> [args]"), console.error("subcommands:");
|
|
930
|
-
for (let e of Object.keys(
|
|
1141
|
+
for (let e of Object.keys(at)) console.error(` ${e}`);
|
|
931
1142
|
return 1;
|
|
932
1143
|
}
|
|
933
|
-
let r =
|
|
1144
|
+
let r = at[t];
|
|
934
1145
|
if (!r) return console.error(`voltro db: unknown subcommand '${t}'`), 2;
|
|
935
|
-
(process.env.DB_DIRECT_URL !== void 0 || process.env.DB_MIGRATE_URL !== void 0) &&
|
|
1146
|
+
(process.env.DB_DIRECT_URL !== void 0 || process.env.DB_MIGRATE_URL !== void 0) && L.info("using a DIRECT (non-pooler) connection for the db-command path (DB_DIRECT_URL/DB_MIGRATE_URL set)");
|
|
936
1147
|
let i = (e) => {
|
|
937
|
-
|
|
1148
|
+
V(e) ? L.error(H) : console.error(e), process.exit(1);
|
|
938
1149
|
};
|
|
939
1150
|
process.on("uncaughtException", i);
|
|
940
1151
|
try {
|
|
@@ -944,4 +1155,4 @@ export default defineMigration({
|
|
|
944
1155
|
}
|
|
945
1156
|
};
|
|
946
1157
|
//#endregion
|
|
947
|
-
export {
|
|
1158
|
+
export { K as a, ve as c, Ze as i, ye as l, B as n, V as o, G as r, ot as s, H as t };
|