@voltro/cli 0.22.1 → 0.23.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 +316 -0
- package/dist/apiBuild-CD-4JLLA.js +2 -0
- package/dist/{apiBuild-DiWxVz-M.js → apiBuild-JQtIhZPy.js} +2 -2
- package/dist/bin.js +3 -3
- package/dist/{commands-BxRaIOBG.js → commands-Bsu9Buln.js} +1767 -1501
- package/dist/dbCommand-CSguuUQn.js +2 -0
- package/dist/{dbCommand-DVASmZj2.js → dbCommand-GI7-BVZt.js} +440 -259
- package/dist/{dev-Dd3EZzj5.js → dev-DRxF_qOB.js} +1 -1
- package/dist/{dev-BRiPgbKw.js → dev-jM07kq_D.js} +1479 -1479
- package/dist/index.js +1 -1
- package/dist/{inspectMetrics-9ZSuDeqD.js → inspectMetrics-dcC7gnxu.js} +566 -552
- package/dist/{serveCommand-DXJOARkO.js → serveCommand-Cl8sicD5.js} +280 -279
- 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/caching.md +21 -5
- package/templates/agent-docs/cli.md +44 -0
- package/templates/agent-docs/data.md +23 -5
- package/templates/agent-docs/database/migrations.md +94 -0
- 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/apps/api-ai/package.json +7 -7
- package/templates/apps/api-auth/package.json +8 -8
- package/templates/apps/api-backend/package.json +7 -7
- package/templates/apps/api-backend-deactivation/package.json +7 -7
- package/templates/apps/api-backend-mail/package.json +8 -8
- package/templates/apps/api-backend-mariadb/package.json +9 -9
- package/templates/apps/api-backend-storage/package.json +8 -8
- package/templates/apps/api-data-advanced/package.json +8 -8
- package/templates/apps/api-durable/package.json +8 -8
- package/templates/apps/api-feature-flags/package.json +9 -9
- package/templates/apps/api-governance/package.json +8 -8
- package/templates/apps/api-kv/package.json +8 -8
- package/templates/apps/api-moderation/package.json +8 -8
- package/templates/apps/api-observability/package.json +8 -8
- package/templates/apps/api-ratelimit/package.json +8 -8
- package/templates/apps/api-rbac/package.json +8 -8
- package/templates/apps/api-rest/package.json +7 -7
- package/templates/apps/api-saas/package.json +11 -11
- package/templates/apps/api-search/package.json +8 -8
- package/templates/apps/api-versioning/app.config.ts +6 -4
- package/templates/apps/api-versioning/package.json +8 -8
- package/templates/apps/api-webhooks/package.json +9 -9
- package/templates/apps/changelog/package.json +6 -6
- package/templates/apps/edge-functions/package.json +2 -2
- package/templates/apps/frontend-admin/package.json +8 -8
- package/templates/apps/frontend-app/package.json +8 -8
- package/templates/apps/frontend-blank/package.json +7 -7
- package/templates/apps/frontend-contact/package.json +7 -7
- package/templates/apps/frontend-dashboard/package.json +7 -7
- package/templates/apps/frontend-docs/package.json +7 -7
- package/templates/apps/frontend-i18n/package.json +6 -6
- package/templates/apps/frontend-landing/package.json +7 -7
- package/templates/apps/frontend-spa/package.json +7 -7
- package/templates/apps/frontend-ssr/package.json +7 -7
- package/templates/apps/frontend-ssr-api/package.json +8 -8
- package/templates/apps/frontend-static-blog/package.json +6 -6
- package/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 { applyPlan as oe, applySchema as S, declaredSnapshot as se, destructiveScope as ce, fingerprintSchema as C, ignoreTablesFromEnv as w, introspectSchema as T, pendingFileMigrationIds as le, planMigrations as E, rollbackFileBasedMigration as ue, runFileBasedMigrations as de, shortFingerprint as D, squashMigrationPlans as fe, unblockLossy as pe } from "@voltro/database/sql";
|
|
15
16
|
//#region src/cliError.ts
|
|
16
|
-
var
|
|
17
|
+
var O = 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
|
+
}, me = (e) => typeof e == "object" && !!e && e.isCliError === !0, he = (e) => typeof e != "string", ge = (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 (he(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
|
+
}, _e = (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
|
+
], ve = (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 _e(e, t).entries()) r.push(` ${n + 1}. ${i}`);
|
|
97
|
+
return r;
|
|
98
|
+
}, ye = (e) => e === "mysql" || e === "mariadb" ? (e) => `\`${e}\`` : e === "mssql" ? (e) => `[${e}]` : (e) => `"${e}"`, k = (e) => `${e}__adopt_snapshot`, be = (e, t, n, r, i) => {
|
|
99
|
+
let a = ye(n), o = k(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
|
+
}, A = (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
|
+
}, xe = 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 = be(e, t, n, r, i), c = k(e.from), l = A(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 = A(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
|
+
}, j = /\.migration\.ts$/, M = 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
|
+
}, N = (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
|
+
}, P = (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))), F = (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))), Se = 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 N(t);
|
|
197
|
+
let r = await P(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 F(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 F(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 F(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
|
+
}, Ce = async (e, t, n, r = {}) => {
|
|
299
|
+
await N(t);
|
|
300
|
+
let i = await P(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 F(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
|
+
}, I = async (e, t) => {
|
|
370
|
+
await N(t);
|
|
371
|
+
let n = await P(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
|
+
}, L = (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
|
+
}, R = _({ scope: "voltro:db" }), we = /* @__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
|
+
]), z = 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 (we.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
|
+
}, B = (e) => m(e.find((e) => !e.startsWith("-")) ?? process.cwd()), V = () => {
|
|
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
|
+
}, H = (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
|
+
}, U = "Live-DB introspection got a corrupt / misaligned response from the database. This happens when `voltro db plan` / `apply` / `drift` / `migrate` runs through a connection POOLER (Supabase Supavisor / PgBouncer) on a LARGE schema — the pooler mis-frames a big catalog response. The introspection already batches by table to keep responses small; if a batch still trips it:\n 1. Lower the batch: set `VOLTRO_INTROSPECT_BATCH` to a smaller number of tables (default 20 — try 5 or 1). This keeps `db plan` on the normal pooler, no direct connection needed.\n 2. Or use a DIRECT (non-pooler) connection: set `DB_DIRECT_URL` (or `DB_MIGRATE_URL`) to the direct database URL — the runtime keeps `DB_URL`, so only the migration path is affected. (Supabase: direct = `db.<project-ref>.supabase.co` [IPv6-only on newer projects]; pooler = `aws-0-<region>.pooler.supabase.com`.)", W = () => {
|
|
323
441
|
let e = (process.env.DB_DIALECT ?? "postgres").toLowerCase();
|
|
324
|
-
if (e === "memory") throw new
|
|
442
|
+
if (e === "memory") throw new O("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
|
+
}, G = async () => (await r(W())).makeSqlLayer(V()), Te = async (e) => {
|
|
445
|
+
let t = B(e), n = e.includes("--dry-run"), r = await M(await z(t, j), R);
|
|
446
|
+
if (r.length === 0) return R.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 I(r, await G());
|
|
333
451
|
e = new Set(t.filter((e) => e.status === "applied").map((e) => e.id));
|
|
334
452
|
} catch {
|
|
335
|
-
|
|
453
|
+
R.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 R.info("migrate --dry-run: nothing pending — every migration is applied"), 0;
|
|
457
|
+
R.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 Se(r, await G(), R);
|
|
462
|
+
return i.failed ? (process.stderr.write(L(i) + "\n"), 1) : (R.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
|
+
}, Ee = async (e) => {
|
|
467
|
+
let t = B(e), n = l(e, "--to"), r = await M(await z(t, j), R);
|
|
468
|
+
if (r.length === 0) return R.info("no migrations found", { root: t }), 0;
|
|
469
|
+
let i = await Ce(r, await G(), R, n ? { to: n } : {});
|
|
470
|
+
return i.failed ? (process.stderr.write(L(i) + "\n"), 1) : (R.info("rollback complete", { reverted: i.applied.length }), 0);
|
|
471
|
+
}, De = async (e) => {
|
|
472
|
+
let t = await M(await z(B(e), j), R);
|
|
355
473
|
if (t.length === 0) return console.log("no migrations found"), 0;
|
|
356
|
-
let n = await
|
|
474
|
+
let n = await I(t, await G());
|
|
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
|
+
}, Oe = async (e) => {
|
|
481
|
+
let t = (l(e, "--store") ?? process.env.DB_DIALECT ?? "postgres").toLowerCase();
|
|
482
|
+
if (t === "memory") return R.warn("voltro db seed: using in-memory store (explicit opt-in via --store memory / DB_DIALECT=memory)"), R.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
|
+
R.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(V()),
|
|
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
|
+
}, ke = async (r) => {
|
|
501
|
+
let a = B(r), o = l(r, "--id"), s = l(r, "--lifecycle"), c = await t(await z(a, i), R);
|
|
502
|
+
if (c.length === 0) return R.info("no seeds found", { root: a }), 0;
|
|
503
|
+
let { store: u, dispose: d, dialectId: f } = await Oe(r);
|
|
386
504
|
try {
|
|
387
|
-
return o ? +((await e(
|
|
505
|
+
return o ? +((await e(c, o, u, R)).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, R, { force: !0 })).filter((e) => e.status === "failed").length > 0);
|
|
394
512
|
} finally {
|
|
395
513
|
await d().catch((e) => {
|
|
396
|
-
|
|
514
|
+
R.warn("voltro db seed: store dispose failed", { error: String(e) });
|
|
397
515
|
});
|
|
398
516
|
}
|
|
399
|
-
},
|
|
400
|
-
let t = await
|
|
517
|
+
}, Ae = (e) => typeof e == "object" && !!e && "tableName" in e && "fields" in e && typeof e.tableName == "string", K = async (e) => {
|
|
518
|
+
let t = await z(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
|
+
R.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)) Ae(e) && n.push(e);
|
|
413
531
|
}
|
|
414
532
|
return n;
|
|
415
|
-
},
|
|
533
|
+
}, q = (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
|
+
}, je = (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
|
+
}, J = (e) => {
|
|
549
|
+
let t = ce(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 = pe(e, t);
|
|
437
555
|
return {
|
|
438
556
|
plan: n,
|
|
439
557
|
acknowledged: !n.operations.some((e) => e.blocked)
|
|
440
558
|
};
|
|
441
|
-
},
|
|
559
|
+
}, Y = async (e, t) => g.runPromise(t.pipe(g.provide(e))), X = async (e) => {
|
|
442
560
|
try {
|
|
443
|
-
return await
|
|
561
|
+
return await Y(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 (H(e) || /connection error/i.test(t)) {
|
|
565
|
+
let n = /* @__PURE__ */ Error(`${U}\n\nOriginal error: ${t}`);
|
|
448
566
|
throw n.cause = e, n;
|
|
449
567
|
}
|
|
450
568
|
throw e;
|
|
451
569
|
}
|
|
452
|
-
},
|
|
570
|
+
}, Me = {
|
|
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
|
+
}, Ne = (e) => {
|
|
461
579
|
let t = e.op, n;
|
|
462
580
|
switch (t.kind) {
|
|
463
581
|
case "create-table":
|
|
@@ -521,147 +639,171 @@ 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 = Me[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
|
+
}, Pe = 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 Y(t, b.SqlClient.pipe(g.flatMap((e) => e.unsafe(`SELECT COUNT(*) AS n FROM ${re(i, W())}`))));
|
|
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
|
+
}, Z = (e) => {
|
|
527
666
|
let t = e.summary, n = e.operations.length;
|
|
528
667
|
if (console.log(`\nschema diff: ${n} operation${n === 1 ? "" : "s"}, ${t.blocked} blocked\n`), n === 0) {
|
|
529
668
|
console.log(" (schema is up to date)\n");
|
|
530
669
|
return;
|
|
531
670
|
}
|
|
532
|
-
for (let t of e.operations) console.log(
|
|
671
|
+
for (let t of e.operations) console.log(Ne(t));
|
|
533
672
|
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 =
|
|
673
|
+
}, Fe = (e) => {
|
|
674
|
+
let t = l(e, "--online-after") ?? process.env.VOLTRO_ONLINE_THRESHOLD;
|
|
536
675
|
if (t === void 0) return;
|
|
537
676
|
let n = Number(t);
|
|
538
677
|
if (!Number.isInteger(n) || n < 0) throw Error(`--online-after / VOLTRO_ONLINE_THRESHOLD must be a non-negative integer, got "${t}"`);
|
|
539
678
|
return n;
|
|
540
|
-
},
|
|
541
|
-
let t = await
|
|
679
|
+
}, Q = async (e) => {
|
|
680
|
+
let t = await K(e);
|
|
542
681
|
if (t.length === 0) return;
|
|
543
|
-
let n = await
|
|
544
|
-
return
|
|
682
|
+
let n = await o({ root: e });
|
|
683
|
+
return q(je([
|
|
545
684
|
...t,
|
|
546
|
-
...
|
|
685
|
+
...x,
|
|
547
686
|
...n
|
|
548
687
|
]));
|
|
549
|
-
},
|
|
550
|
-
let t =
|
|
551
|
-
if (r === void 0) return
|
|
688
|
+
}, Ie = async (e) => {
|
|
689
|
+
let t = Fe(e), n = B(e), r = await Q(n);
|
|
690
|
+
if (r === void 0) return R.warn("db plan: no schema files found", {
|
|
552
691
|
root: n,
|
|
553
692
|
hint: "looked for *.entity.ts / *.schema.ts / schema.ts"
|
|
554
693
|
}), 1;
|
|
555
|
-
let i =
|
|
694
|
+
let i = l(e, "--against");
|
|
556
695
|
if (i) {
|
|
557
|
-
let n = i, a =
|
|
696
|
+
let n = i, a = l(e, "--token") ?? process.env.VOLTRO_INSPECT_TOKEN, o = { accept: "application/json" };
|
|
558
697
|
a && (o.authorization = `Bearer ${a}`);
|
|
559
698
|
try {
|
|
560
699
|
let e = n.endsWith("/_voltro/inspect/migrations") ? n : `${n.replace(/\/+$/, "")}/_voltro/inspect/migrations`, i = await fetch(e, { headers: o });
|
|
561
|
-
if (!i.ok) return
|
|
700
|
+
if (!i.ok) return R.error(`db plan --against: remote returned ${i.status}`, { url: e }), 1;
|
|
562
701
|
let a = (await i.json()).drift?.liveSnapshot;
|
|
563
|
-
if (!a) return
|
|
564
|
-
let s =
|
|
702
|
+
if (!a) return R.error("db plan --against: remote inspect payload missing drift.liveSnapshot", { url: e }), 1;
|
|
703
|
+
let s = E({
|
|
565
704
|
declared: r,
|
|
566
705
|
live: a,
|
|
567
|
-
dialect:
|
|
568
|
-
...
|
|
706
|
+
dialect: W(),
|
|
707
|
+
...w(),
|
|
569
708
|
...t === void 0 ? {} : { onlineAfter: t }
|
|
570
709
|
});
|
|
571
|
-
return console.log(`\n[--against ${e}] — diff vs remote live schema:\n`),
|
|
710
|
+
return console.log(`\n[--against ${e}] — diff vs remote live schema:\n`), Z(s), s.summary.blocked > 0 ? 2 : 0;
|
|
572
711
|
} catch (e) {
|
|
573
|
-
return
|
|
712
|
+
return R.error(`db plan --against: ${e.message}`), 1;
|
|
574
713
|
}
|
|
575
714
|
}
|
|
576
|
-
let a =
|
|
715
|
+
let a = E({
|
|
577
716
|
declared: r,
|
|
578
|
-
live: await
|
|
579
|
-
dialect:
|
|
580
|
-
...
|
|
717
|
+
live: await X(await G()),
|
|
718
|
+
dialect: W(),
|
|
719
|
+
...w(),
|
|
581
720
|
...t === void 0 ? {} : { onlineAfter: t }
|
|
582
721
|
});
|
|
583
|
-
return e.includes("--json") ? (
|
|
584
|
-
},
|
|
722
|
+
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);
|
|
723
|
+
}, Le = async (e, t, n) => {
|
|
585
724
|
let r;
|
|
586
725
|
try {
|
|
587
|
-
let t = await
|
|
726
|
+
let t = await v.readFile(m(e), "utf8");
|
|
588
727
|
r = JSON.parse(t);
|
|
589
728
|
} catch (t) {
|
|
590
|
-
return
|
|
729
|
+
return R.error(`db apply --plan: cannot read/parse plan file '${e}': ${t.message}`), 1;
|
|
591
730
|
}
|
|
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 =
|
|
731
|
+
if (!Array.isArray(r?.operations) || typeof r?.fromFingerprint != "string" || typeof r?.toFingerprint != "string") return R.error(`db apply --plan: '${e}' is not a valid plan file (expected the output of \`voltro db plan --json\`).`), 1;
|
|
732
|
+
let i = await Q(n);
|
|
733
|
+
if (i === void 0) return R.warn("db apply --plan: no schema files found", { root: n }), 1;
|
|
734
|
+
let a = await G();
|
|
735
|
+
await Y(a, S(x, W()));
|
|
736
|
+
let o = await Y(a, b.SqlClient.pipe(g.flatMap((e) => le(e, n)))).catch(() => []);
|
|
737
|
+
if (o.length > 0) return R.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;
|
|
738
|
+
let s = E({
|
|
598
739
|
declared: i,
|
|
599
|
-
live: await
|
|
600
|
-
dialect:
|
|
601
|
-
...
|
|
740
|
+
live: await X(a),
|
|
741
|
+
dialect: W(),
|
|
742
|
+
...w()
|
|
602
743
|
});
|
|
603
|
-
if (
|
|
604
|
-
if (
|
|
605
|
-
let
|
|
606
|
-
if (
|
|
607
|
-
if (
|
|
608
|
-
|
|
609
|
-
let
|
|
744
|
+
if (s.fromFingerprint !== r.fromFingerprint) return R.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;
|
|
745
|
+
if (s.toFingerprint !== r.toFingerprint) return R.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;
|
|
746
|
+
let c = J(s), u = c.plan;
|
|
747
|
+
if (u.summary.blocked > 0 && !c.acknowledged) return Z(u), R.error(`db apply --plan: refusing — ${s.summary.blocked} blocked operation(s). See the fix hints above.`), process.env.VOLTRO_DESTRUCTIVE_OK !== void 0 && (R.error(" VOLTRO_DESTRUCTIVE_OK is set but did not cover this plan — it only relaxes"), R.error(" `lossy` operations, and only the tables it names. Anything else stays refused.")), 2;
|
|
748
|
+
if (c.acknowledged && R.warn("db apply --plan: proceeding with acknowledged lossy operation(s) (VOLTRO_DESTRUCTIVE_OK)"), u.operations.length === 0) return R.info("db apply --plan: schema is up to date — nothing to apply"), 0;
|
|
749
|
+
Z(u);
|
|
750
|
+
let d = (process.env.NODE_ENV ?? "dev").toLowerCase(), f = l(t, "--note"), p = await Y(a, b.SqlClient.pipe(g.flatMap((e) => oe(e, u, {
|
|
610
751
|
declared: i,
|
|
611
752
|
appliedBy: process.env.USER ? `cli:${process.env.USER}` : "cli:plan",
|
|
612
|
-
environment:
|
|
753
|
+
environment: d === "production" ? "prod" : d === "staging" ? "staging" : "dev",
|
|
613
754
|
source: "file",
|
|
614
|
-
...
|
|
615
|
-
replan: () =>
|
|
755
|
+
...f ? { notes: f } : {},
|
|
756
|
+
replan: () => g.promise(() => X(a)).pipe(g.map((e) => E({
|
|
616
757
|
declared: i,
|
|
617
758
|
live: e,
|
|
618
|
-
dialect:
|
|
619
|
-
...
|
|
759
|
+
dialect: W(),
|
|
760
|
+
...w()
|
|
620
761
|
})))
|
|
621
762
|
}))));
|
|
622
|
-
return
|
|
623
|
-
id:
|
|
624
|
-
fingerprint:
|
|
763
|
+
return R.info(`db apply --plan: applied ${p.appliedOps} op(s) in ${p.durationMs}ms`, {
|
|
764
|
+
id: p.id,
|
|
765
|
+
fingerprint: p.fingerprint.slice(0, 16)
|
|
625
766
|
}), 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
|
-
|
|
767
|
+
}, Re = async (e) => {
|
|
768
|
+
let t = B(e), n = Fe(e), r = l(e, "--plan");
|
|
769
|
+
if (r) return Le(r, e, t);
|
|
770
|
+
if ((process.env.NODE_ENV ?? "").toLowerCase() === "production") return R.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;
|
|
771
|
+
let i = await Q(t);
|
|
772
|
+
if (i === void 0) return R.warn("db apply: no schema files found", { root: t }), 1;
|
|
773
|
+
let a = await G(), o = await qe(t, a);
|
|
774
|
+
if ("failed" in o) return R.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;
|
|
775
|
+
await Y(a, S(x, W()));
|
|
776
|
+
let s = E({
|
|
635
777
|
declared: i,
|
|
636
|
-
live: await
|
|
637
|
-
dialect:
|
|
638
|
-
...
|
|
778
|
+
live: await X(a),
|
|
779
|
+
dialect: W(),
|
|
780
|
+
...w(),
|
|
639
781
|
...n === void 0 ? {} : { onlineAfter: n }
|
|
640
782
|
});
|
|
641
|
-
|
|
642
|
-
let
|
|
643
|
-
if (
|
|
644
|
-
if (
|
|
645
|
-
let
|
|
783
|
+
Z(s), await Pe(s, a);
|
|
784
|
+
let c = J(s), u = c.plan;
|
|
785
|
+
if (u.summary.blocked > 0 && !c.acknowledged) return R.error(`db apply: refusing — ${u.summary.blocked} blocked operation(s). See the fix hints above.`), process.env.VOLTRO_DESTRUCTIVE_OK !== void 0 && (R.error(" VOLTRO_DESTRUCTIVE_OK is set but did not cover this plan — it only relaxes"), R.error(" `lossy` operations, and only the tables it names. Anything else stays refused.")), 2;
|
|
786
|
+
if (c.acknowledged && R.warn(`db apply: proceeding with ${s.summary.blocked} acknowledged lossy operation(s) (VOLTRO_DESTRUCTIVE_OK)`), u.operations.length === 0) return R.info("db apply: schema is up to date — nothing to apply"), await Ue(a), l(e, "--note") !== void 0 && (R.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`.")), 0;
|
|
787
|
+
let d = l(e, "--note"), f = await Y(a, b.SqlClient.pipe(g.flatMap((e) => oe(e, u, {
|
|
646
788
|
declared: i,
|
|
647
789
|
appliedBy: process.env.USER ? `cli:${process.env.USER}` : "cli:user",
|
|
648
790
|
environment: (process.env.NODE_ENV ?? "dev").toLowerCase() === "staging" ? "staging" : "dev",
|
|
649
791
|
source: "auto-diff",
|
|
650
|
-
...
|
|
651
|
-
replan: () =>
|
|
792
|
+
...d ? { notes: d } : {},
|
|
793
|
+
replan: () => g.promise(() => X(a)).pipe(g.map((e) => E({
|
|
652
794
|
declared: i,
|
|
653
795
|
live: e,
|
|
654
|
-
dialect:
|
|
655
|
-
...
|
|
796
|
+
dialect: W(),
|
|
797
|
+
...w(),
|
|
656
798
|
...n === void 0 ? {} : { onlineAfter: n }
|
|
657
799
|
})))
|
|
658
800
|
}))));
|
|
659
|
-
return
|
|
660
|
-
id:
|
|
661
|
-
fingerprint:
|
|
801
|
+
return R.info(`db apply: applied ${f.appliedOps} op(s) in ${f.durationMs}ms`, {
|
|
802
|
+
id: f.id,
|
|
803
|
+
fingerprint: f.fingerprint.slice(0, 16)
|
|
662
804
|
}), 0;
|
|
663
|
-
},
|
|
664
|
-
let t = Math.max(1, Number(
|
|
805
|
+
}, ze = async (e) => {
|
|
806
|
+
let t = Math.max(1, Number(l(e, "--limit") ?? "20")), n = await Y(await G(), b.SqlClient.pipe(g.flatMap((e) => e`
|
|
665
807
|
SELECT ${e("id")}, ${e("fingerprint")}, ${e("operations")},
|
|
666
808
|
${e("appliedBy")}, ${e("environment")}, ${e("source")},
|
|
667
809
|
${e("durationMs")}, ${e("notes")}, ${e("appliedAt")}
|
|
@@ -669,25 +811,25 @@ var E = class extends Error {
|
|
|
669
811
|
ORDER BY ${e("appliedAt")} DESC
|
|
670
812
|
LIMIT ${t}
|
|
671
813
|
`)));
|
|
672
|
-
if (n.length === 0) return
|
|
814
|
+
if (n.length === 0) return R.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
815
|
console.log(`\n${n.length} plan${n.length === 1 ? "" : "s"} (newest first):\n`);
|
|
674
816
|
for (let e of n) {
|
|
675
817
|
let t = 0;
|
|
676
818
|
try {
|
|
677
|
-
t = JSON.parse(
|
|
819
|
+
t = JSON.parse(We(e.operations)).length;
|
|
678
820
|
} catch {}
|
|
679
|
-
console.log(` ${e.id} fp=${
|
|
821
|
+
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
822
|
}
|
|
681
823
|
return console.log(), 0;
|
|
682
|
-
},
|
|
824
|
+
}, Be = async (e) => {
|
|
683
825
|
let t = e[0];
|
|
684
826
|
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
|
|
827
|
+
let n = await G(), r = await Y(n, b.SqlClient.pipe(g.flatMap((e) => e`SELECT ${e("id")}, ${e("operations")}
|
|
686
828
|
FROM ${e("_voltro_migration_plans")} WHERE ${e("id")} = ${t}`)));
|
|
687
829
|
if (r.length === 0) return console.error(`restore-snapshot: plan '${t}' not found`), 1;
|
|
688
830
|
let i;
|
|
689
831
|
try {
|
|
690
|
-
i = JSON.parse(
|
|
832
|
+
i = JSON.parse(We(r[0].operations));
|
|
691
833
|
} catch {
|
|
692
834
|
return console.error(`restore-snapshot: plan '${t}' has corrupted operations JSON`), 1;
|
|
693
835
|
}
|
|
@@ -701,7 +843,7 @@ var E = class extends Error {
|
|
|
701
843
|
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
844
|
let s = 0;
|
|
703
845
|
for (let e of a) {
|
|
704
|
-
let t = await
|
|
846
|
+
let t = await Y(n, b.SqlClient.pipe(g.flatMap((t) => t`
|
|
705
847
|
SELECT column_name FROM information_schema.columns
|
|
706
848
|
WHERE table_schema = 'public'
|
|
707
849
|
AND table_name = ${e.table}
|
|
@@ -713,11 +855,11 @@ var E = class extends Error {
|
|
|
713
855
|
continue;
|
|
714
856
|
}
|
|
715
857
|
let r = t[0].column_name;
|
|
716
|
-
await
|
|
858
|
+
await Y(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
859
|
}
|
|
718
860
|
let c = 0;
|
|
719
861
|
for (let e of o) {
|
|
720
|
-
let t = await
|
|
862
|
+
let t = await Y(n, b.SqlClient.pipe(g.flatMap((t) => t`
|
|
721
863
|
SELECT table_name FROM information_schema.tables
|
|
722
864
|
WHERE table_schema = 'public'
|
|
723
865
|
AND table_name LIKE ${e.table + "__dropped_%"}
|
|
@@ -728,57 +870,57 @@ var E = class extends Error {
|
|
|
728
870
|
continue;
|
|
729
871
|
}
|
|
730
872
|
let r = t[0].table_name;
|
|
731
|
-
await
|
|
873
|
+
await Y(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
874
|
}
|
|
733
875
|
return console.log(`\n✓ restored ${s} column(s) + ${c} table(s) from plan '${t}'\n`), 0;
|
|
734
|
-
},
|
|
735
|
-
let t =
|
|
876
|
+
}, Ve = async (e) => {
|
|
877
|
+
let t = l(e, "--before");
|
|
736
878
|
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
879
|
let n = t.replace(/[^0-9]/g, "");
|
|
738
880
|
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
|
|
881
|
+
let r = (n + "00000000000000").slice(0, 14), i = e.includes("--dry-run"), a = await G(), o = await Y(a, b.SqlClient.pipe(g.flatMap((e) => e`
|
|
740
882
|
SELECT table_name, column_name FROM information_schema.columns
|
|
741
883
|
WHERE table_schema = 'public' AND column_name LIKE ${"%__dropped_%"}
|
|
742
|
-
`))), s = await
|
|
884
|
+
`))), s = await Y(a, b.SqlClient.pipe(g.flatMap((e) => e`
|
|
743
885
|
SELECT table_name FROM information_schema.tables
|
|
744
886
|
WHERE table_schema = 'public' AND table_name LIKE ${"%__dropped_%"}
|
|
745
|
-
`))),
|
|
887
|
+
`))), c = (e) => {
|
|
746
888
|
let t = /__dropped_(\d{14})$/.exec(e);
|
|
747
889
|
return t ? t[1] : null;
|
|
748
890
|
}, u = 0;
|
|
749
891
|
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
|
|
892
|
+
let t = c(e.column_name);
|
|
893
|
+
t === null || t >= r || (console.log(` ${i ? "[dry-run] would drop column" : "dropping column"} ${e.table_name}.${e.column_name}`), i || await Y(a, b.SqlClient.pipe(g.flatMap((t) => t.unsafe(`ALTER TABLE "${e.table_name}" DROP COLUMN "${e.column_name}"`)))), u++);
|
|
752
894
|
}
|
|
753
895
|
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
|
|
896
|
+
let t = c(e.table_name);
|
|
897
|
+
t === null || t >= r || (console.log(` ${i ? "[dry-run] would drop table" : "dropping table"} ${e.table_name}`), i || await Y(a, b.SqlClient.pipe(g.flatMap((t) => t.unsafe(`DROP TABLE "${e.table_name}"`)))), u++);
|
|
756
898
|
}
|
|
757
899
|
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 =
|
|
900
|
+
}, He = async (e) => {
|
|
901
|
+
let t = l(e, "--before");
|
|
760
902
|
if (!t) return console.error("voltro db squash --before <iso-date> [--note \"<text>\"]"), 2;
|
|
761
903
|
let n = t;
|
|
762
904
|
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 =
|
|
905
|
+
let r = l(e, "--note"), i = B(e), a = await Q(i);
|
|
906
|
+
if (a === void 0) return R.warn("db squash: no schema files found", { root: i }), 1;
|
|
907
|
+
let o = C(se(a)), s = await G();
|
|
766
908
|
try {
|
|
767
|
-
let e = await
|
|
909
|
+
let e = await Y(s, fe({
|
|
768
910
|
before: n,
|
|
769
911
|
fingerprint: o,
|
|
770
912
|
appliedBy: process.env.USER ?? "cli",
|
|
771
913
|
environment: process.env.NODE_ENV === "production" ? "prod" : "dev",
|
|
772
914
|
...r === void 0 ? {} : { note: r }
|
|
773
915
|
}));
|
|
774
|
-
return console.log(`\n✓ squashed ${e.squashedCount} migration plan(s)\n snapshot id: ${e.snapshotId}\n fingerprint: ${
|
|
916
|
+
return console.log(`\n✓ squashed ${e.squashedCount} migration plan(s)\n snapshot id: ${e.snapshotId}\n fingerprint: ${D(e.snapshotFingerprint)}\n`), 0;
|
|
775
917
|
} catch (e) {
|
|
776
918
|
return console.error(`squash refused: ${e.message}`), 1;
|
|
777
919
|
}
|
|
778
|
-
},
|
|
920
|
+
}, Ue = async (e) => {
|
|
779
921
|
try {
|
|
780
|
-
let t =
|
|
781
|
-
await
|
|
922
|
+
let t = C(await X(e));
|
|
923
|
+
await Y(e, b.SqlClient.pipe(g.flatMap((e) => e`
|
|
782
924
|
UPDATE ${e("_voltro_migration_plans")}
|
|
783
925
|
SET ${e("liveFingerprint")} = ${t}
|
|
784
926
|
WHERE ${e("id")} = (
|
|
@@ -790,7 +932,7 @@ var E = class extends Error {
|
|
|
790
932
|
AND ${e("liveFingerprint")} IS NULL
|
|
791
933
|
`)));
|
|
792
934
|
} catch {}
|
|
793
|
-
},
|
|
935
|
+
}, We = (e) => typeof e == "string" ? e : e == null ? "" : JSON.stringify(e), $ = (e) => e instanceof Date ? e.toISOString() : String(e ?? ""), Ge = (e) => e === void 0 ? {
|
|
794
936
|
accept: !1,
|
|
795
937
|
reason: "no-declared-schema"
|
|
796
938
|
} : e > 0 ? {
|
|
@@ -799,38 +941,38 @@ var E = class extends Error {
|
|
|
799
941
|
} : {
|
|
800
942
|
accept: !0,
|
|
801
943
|
reason: "converged"
|
|
802
|
-
},
|
|
803
|
-
let t = e.includes("--accept"), n = await
|
|
944
|
+
}, Ke = async (e) => {
|
|
945
|
+
let t = e.includes("--accept"), n = await G(), r = await Y(n, b.SqlClient.pipe(g.flatMap((e) => e`
|
|
804
946
|
SELECT * FROM ${e("_voltro_migration_plans")}
|
|
805
947
|
ORDER BY ${e("appliedAt")} DESC
|
|
806
948
|
LIMIT 1
|
|
807
|
-
`))), i = await
|
|
808
|
-
if (r.length === 0) return
|
|
949
|
+
`))), i = await X(n), a = C(i);
|
|
950
|
+
if (r.length === 0) return R.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
951
|
let o = r[0];
|
|
810
|
-
if (o.liveFingerprint === null || o.liveFingerprint === void 0) return
|
|
811
|
-
if (o.liveFingerprint === a) return
|
|
952
|
+
if (o.liveFingerprint === null || o.liveFingerprint === void 0) return R.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;
|
|
953
|
+
if (o.liveFingerprint === a) return R.info(`db drift: live matches last applied (${D(a)})`), 0;
|
|
812
954
|
let s = await (async () => {
|
|
813
955
|
try {
|
|
814
|
-
let t = await
|
|
815
|
-
return t === void 0 ? void 0 :
|
|
956
|
+
let t = await Q(B(e));
|
|
957
|
+
return t === void 0 ? void 0 : E({
|
|
816
958
|
declared: t,
|
|
817
959
|
live: i,
|
|
818
|
-
dialect:
|
|
819
|
-
...
|
|
960
|
+
dialect: W(),
|
|
961
|
+
...w()
|
|
820
962
|
}).operations;
|
|
821
963
|
} catch {
|
|
822
964
|
return;
|
|
823
965
|
}
|
|
824
966
|
})();
|
|
825
967
|
if (t) {
|
|
826
|
-
let e =
|
|
827
|
-
if (e.reason === "no-declared-schema") return
|
|
968
|
+
let e = Ge(s?.length);
|
|
969
|
+
if (e.reason === "no-declared-schema") return R.error("db drift --accept: no schema files found — cannot verify the live schema is current."), 1;
|
|
828
970
|
if (!e.accept && s !== void 0) {
|
|
829
|
-
|
|
971
|
+
R.error(`db drift --accept: refusing — ${s.length} operation(s) still pending against your declared schema.`), console.log();
|
|
830
972
|
for (let e of s.slice(0, 10)) console.log(` - ${e.op.kind} ${"table" in e.op ? `[${e.op.table}]` : ""}`);
|
|
831
973
|
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
974
|
}
|
|
833
|
-
return await
|
|
975
|
+
return await Y(n, b.SqlClient.pipe(g.flatMap((e) => e`
|
|
834
976
|
UPDATE ${e("_voltro_migration_plans")}
|
|
835
977
|
SET ${e("liveFingerprint")} = ${a}
|
|
836
978
|
WHERE ${e("id")} = (
|
|
@@ -839,31 +981,43 @@ var E = class extends Error {
|
|
|
839
981
|
ORDER BY ${e("appliedAt")} DESC LIMIT 1
|
|
840
982
|
) AS ${e("newest")}
|
|
841
983
|
)
|
|
842
|
-
`))),
|
|
984
|
+
`))), R.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;
|
|
843
985
|
}
|
|
844
|
-
return
|
|
845
|
-
},
|
|
846
|
-
let
|
|
847
|
-
|
|
848
|
-
|
|
986
|
+
return R.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);
|
|
987
|
+
}, qe = async (e, t) => {
|
|
988
|
+
let n = (process.env.NODE_ENV ?? "").toLowerCase() === "production" ? "prod" : (process.env.NODE_ENV ?? "").toLowerCase() === "staging" ? "staging" : "dev";
|
|
989
|
+
try {
|
|
990
|
+
let r = await Y(t, b.SqlClient.pipe(g.flatMap((t) => de(t, {
|
|
991
|
+
projectRoot: e,
|
|
992
|
+
env: n,
|
|
993
|
+
appliedBy: process.env.USER ? `cli:${process.env.USER}` : "cli:user"
|
|
994
|
+
}))));
|
|
995
|
+
return r.applied.length > 0 && R.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 };
|
|
996
|
+
} catch (e) {
|
|
997
|
+
return { failed: e };
|
|
998
|
+
}
|
|
999
|
+
}, Je = async (e) => {
|
|
1000
|
+
let t = B(e), n = await G(), r = (process.env.NODE_ENV ?? "").toLowerCase() === "production" ? "prod" : (process.env.NODE_ENV ?? "").toLowerCase() === "staging" ? "staging" : "dev";
|
|
1001
|
+
await Y(n, S(x, W()));
|
|
1002
|
+
let i = await Y(n, b.SqlClient.pipe(g.flatMap((e) => de(e, {
|
|
849
1003
|
projectRoot: t,
|
|
850
1004
|
env: r,
|
|
851
1005
|
appliedBy: process.env.USER ? `cli:${process.env.USER}` : "cli:user"
|
|
852
1006
|
}))));
|
|
853
|
-
return i.applied.length === 0 ? (
|
|
1007
|
+
return i.applied.length === 0 ? (R.info(`db files: no pending file-based migrations (${i.skipped.length} already applied)`), 0) : (R.info(`db files: applied ${i.applied.length} migration(s)`, {
|
|
854
1008
|
applied: i.applied.map((e) => `${e.id} (${e.durationMs}ms)`),
|
|
855
1009
|
skipped: i.skipped.length
|
|
856
1010
|
}), 0);
|
|
857
|
-
},
|
|
858
|
-
let t =
|
|
859
|
-
if (!n) return
|
|
860
|
-
if ((process.env.NODE_ENV ?? "").toLowerCase() === "production") return
|
|
861
|
-
let r = await
|
|
1011
|
+
}, Ye = async (e) => {
|
|
1012
|
+
let t = B(e), n = e.find((e) => !e.startsWith("--"));
|
|
1013
|
+
if (!n) return R.error("voltro db rollback-file <migration-id>"), 2;
|
|
1014
|
+
if ((process.env.NODE_ENV ?? "").toLowerCase() === "production") return R.error("voltro db rollback-file: refusing on prod — schema rollback runs as an explicit deploy step."), 3;
|
|
1015
|
+
let r = await Y(await G(), b.SqlClient.pipe(g.flatMap((e) => ue(e, {
|
|
862
1016
|
projectRoot: t,
|
|
863
1017
|
id: n
|
|
864
1018
|
}))));
|
|
865
|
-
return
|
|
866
|
-
},
|
|
1019
|
+
return R.info(`db rollback-file: rolled back ${r.id} in ${r.durationMs}ms`), 0;
|
|
1020
|
+
}, Xe = () => {
|
|
867
1021
|
let e = /* @__PURE__ */ new Date();
|
|
868
1022
|
return [
|
|
869
1023
|
e.getUTCFullYear(),
|
|
@@ -873,7 +1027,7 @@ var E = class extends Error {
|
|
|
873
1027
|
String(e.getUTCMinutes()).padStart(2, "0"),
|
|
874
1028
|
String(e.getUTCSeconds()).padStart(2, "0")
|
|
875
1029
|
].join("");
|
|
876
|
-
},
|
|
1030
|
+
}, Ze = (e) => e.trim().toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_+|_+$/g, "") || "migration", Qe = (e, t) => `import { defineMigration, SqlClient } from '@voltro/database'
|
|
877
1031
|
import { Effect } from 'effect'
|
|
878
1032
|
|
|
879
1033
|
// A hand-written migration. Reach for this when the DECLARATIVE differ can't
|
|
@@ -905,36 +1059,63 @@ export default defineMigration({
|
|
|
905
1059
|
}),
|
|
906
1060
|
],
|
|
907
1061
|
})
|
|
908
|
-
`, $ = {
|
|
909
|
-
plan:
|
|
910
|
-
apply:
|
|
911
|
-
plans:
|
|
912
|
-
drift:
|
|
913
|
-
files:
|
|
914
|
-
"rollback-file":
|
|
915
|
-
migrate:
|
|
916
|
-
rollback:
|
|
917
|
-
status:
|
|
918
|
-
seed:
|
|
1062
|
+
`, $e = {
|
|
1063
|
+
plan: Ie,
|
|
1064
|
+
apply: Re,
|
|
1065
|
+
plans: ze,
|
|
1066
|
+
drift: Ke,
|
|
1067
|
+
files: Je,
|
|
1068
|
+
"rollback-file": Ye,
|
|
1069
|
+
migrate: Te,
|
|
1070
|
+
rollback: Ee,
|
|
1071
|
+
status: De,
|
|
1072
|
+
seed: ke,
|
|
919
1073
|
generate: async (e) => {
|
|
920
|
-
let { value: t, rest: n } =
|
|
921
|
-
return await
|
|
1074
|
+
let { value: t, rest: n } = c(e, "--root"), { value: r, rest: i } = c(n, "--name"), a = t ? m(t) : process.cwd(), o = Ze(r ?? u(i) ?? "migration"), s = `${Xe()}_${o}`, l = f(a, "migrations"), d = f(l, `${s}.migration.ts`);
|
|
1075
|
+
return await v.mkdir(l, { recursive: !0 }), await v.writeFile(d, Qe(s, o), "utf8"), R.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
1076
|
},
|
|
923
|
-
squash:
|
|
924
|
-
"restore-snapshot":
|
|
925
|
-
"gc-snapshots":
|
|
926
|
-
|
|
1077
|
+
squash: He,
|
|
1078
|
+
"restore-snapshot": Be,
|
|
1079
|
+
"gc-snapshots": Ve,
|
|
1080
|
+
adopt: async (e) => {
|
|
1081
|
+
let t = (t) => {
|
|
1082
|
+
let n = e.indexOf(`--${t}`);
|
|
1083
|
+
return n === -1 ? void 0 : e[n + 1];
|
|
1084
|
+
}, n = t("from"), r = t("into"), i = t("map");
|
|
1085
|
+
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;
|
|
1086
|
+
let o = (await a(m(process.cwd(), i))).default;
|
|
1087
|
+
if (o?.map === void 0) return console.error(`voltro db adopt: ${i} must default-export { map: { <targetColumn>: <source> } }`), 1;
|
|
1088
|
+
let s = {
|
|
1089
|
+
from: n,
|
|
1090
|
+
into: r,
|
|
1091
|
+
map: o.map,
|
|
1092
|
+
...o.leaveUnset ? { leaveUnset: o.leaveUnset } : {}
|
|
1093
|
+
}, c = W(), l = await G(), u = await Y(l, b.SqlClient.pipe(g.flatMap(T))), d = (e) => u.tables.find((t) => t.name === e)?.columns.map((e) => ({
|
|
1094
|
+
name: e.name,
|
|
1095
|
+
nullable: e.nullable,
|
|
1096
|
+
hasDefault: e.defaultValue !== void 0 && e.defaultValue !== null
|
|
1097
|
+
})), f = d(n), p = d(r);
|
|
1098
|
+
if (f === void 0) return console.error(`voltro db adopt: '${n}' does not exist in the database`), 1;
|
|
1099
|
+
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;
|
|
1100
|
+
let h = async (e) => await Y(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 = ge(s, f, p, await _(r));
|
|
1101
|
+
for (let e of ve(s, v, await _(n))) console.log(e);
|
|
1102
|
+
if (v.fatal) return 1;
|
|
1103
|
+
if (!e.includes("--apply")) return console.log(""), console.log(" (dry run — nothing written. Re-run with --apply.)"), 0;
|
|
1104
|
+
let y = await xe(s, v, c, "id", void 0, h, { keepSource: e.includes("--keep-source") });
|
|
1105
|
+
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);
|
|
1106
|
+
}
|
|
1107
|
+
}, et = async (e) => {
|
|
927
1108
|
let [t, ...n] = e;
|
|
928
1109
|
if (!t) {
|
|
929
1110
|
console.error("voltro db <subcommand> [args]"), console.error("subcommands:");
|
|
930
|
-
for (let e of Object.keys($)) console.error(` ${e}`);
|
|
1111
|
+
for (let e of Object.keys($e)) console.error(` ${e}`);
|
|
931
1112
|
return 1;
|
|
932
1113
|
}
|
|
933
|
-
let r = $[t];
|
|
1114
|
+
let r = $e[t];
|
|
934
1115
|
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) &&
|
|
1116
|
+
(process.env.DB_DIRECT_URL !== void 0 || process.env.DB_MIGRATE_URL !== void 0) && R.info("using a DIRECT (non-pooler) connection for the db-command path (DB_DIRECT_URL/DB_MIGRATE_URL set)");
|
|
936
1117
|
let i = (e) => {
|
|
937
|
-
|
|
1118
|
+
H(e) ? R.error(U) : console.error(e), process.exit(1);
|
|
938
1119
|
};
|
|
939
1120
|
process.on("uncaughtException", i);
|
|
940
1121
|
try {
|
|
@@ -944,4 +1125,4 @@ export default defineMigration({
|
|
|
944
1125
|
}
|
|
945
1126
|
};
|
|
946
1127
|
//#endregion
|
|
947
|
-
export {
|
|
1128
|
+
export { q as a, O as c, Ge as i, me as l, V as n, H as o, K as r, et as s, U as t };
|