@voltro/cli 0.54.0 → 0.55.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 +142 -2
- package/dist/{apiBuild-DTWp0S_q.js → apiBuild-CMvLJM_K.js} +2 -2
- package/dist/apiBuild-Cl0IDx8c.js +2 -0
- package/dist/bin.js +1 -1
- package/dist/{build-D4ygSbnV.js → build-S0QOzqPT.js} +10 -10
- package/dist/{checkCommand-L7DTlpIF.js → checkCommand-DNkY5kwF.js} +1 -1
- package/dist/{checkCommand-Dg1G7Gwd.js → checkCommand-fbj9GDjN.js} +5 -5
- package/dist/codegen-CN6vMM4J.js +2 -0
- package/dist/{codegen-DSLM8Su9.js → codegen-SIepQtUl.js} +74 -63
- package/dist/{codegenCommand-CG_Vx4lc.js → codegenCommand-3TDJezom.js} +10 -9
- package/dist/{codemodRunner-Cd4xkC6u.js → codemodRunner-C2zxZUIw.js} +55 -0
- package/dist/{commands-6Kzi92Np.js → commands-BBYJ7Q3B.js} +24 -24
- package/dist/{dashboardCommand-Cq1PWvI1.js → dashboardCommand-D2kmyCLL.js} +3 -3
- package/dist/{dataCommand-DYzW8vkv.js → dataCommand-BEPPQiTl.js} +266 -194
- package/dist/dbCommand-BTyBGhIA.js +2 -0
- package/dist/{dbCommand-B4NWZtGL.js → dbCommand-DZTmOFT4.js} +1 -1
- package/dist/{dev-CmuvUKRq.js → dev-Ca_A_S9v.js} +2182 -2157
- package/dist/{dev-cKUiZZsB.js → dev-DfVZaoys.js} +1 -1
- package/dist/{doctorCommand-DCiFVMtZ.js → doctorCommand-CGZJK_4o.js} +15 -15
- package/dist/doctorCommand-djmqEcDC.js +2 -0
- package/dist/{dormancyCommand-w1TrmgYP.js → dormancyCommand-DY2rYpTa.js} +1 -1
- package/dist/{embeddingsCommand-CMgPyRTr.js → embeddingsCommand-BoCqZsgp.js} +1 -1
- package/dist/{envCommand-Cyynmcfa.js → envCommand-Bxy2fOjc.js} +2 -2
- package/dist/{evolveCommand-BwvQ8dVH.js → evolveCommand-BsbZ-XDg.js} +2 -2
- package/dist/frameworkTableAssembly-Df2Ymp2f.js +2 -0
- package/dist/{frameworkTableAssembly-D7LJuALW.js → frameworkTableAssembly-Do-cf6RJ.js} +92 -98
- package/dist/index.js +1 -1
- package/dist/{infoCommand-DlYlUPqs.js → infoCommand-EmM3jPKD.js} +1 -1
- package/dist/{inspect-Bd8-9wsi.js → inspect-DCqILJ1G.js} +4 -0
- package/dist/inspect-DGJwpOAb.js +2 -0
- package/dist/interruptedReplace-CwnkBb2X.js +41 -0
- package/dist/interruptedReplace-qzmFI020.js +2 -0
- package/dist/manifestBuild-CJ2zvPvT.js +2 -0
- package/dist/{manifestBuild-Cqgsx2bM.js → manifestBuild-DjX5MoXy.js} +1 -1
- package/dist/{migrate-BK_Bbx-_.js → migrate-CGFZS-1a.js} +2 -2
- package/dist/{probeCommand-_C0YU207.js → probeCommand-Bs3iVBSL.js} +1 -1
- package/dist/{runtimeTrace-CGWx1Q6l.js → runtimeTrace-C1BTpHGQ.js} +1 -1
- package/dist/{sdkgen-CDGHQUFj.js → sdkgen-CXMwLg9n.js} +1 -1
- package/dist/{serveCommand-Bje09q1v.js → serveCommand-C7IrCD58.js} +840 -838
- package/dist/serveCommand-Cjt5S9hD.js +2 -0
- package/dist/serveEntry.js +1 -1
- package/dist/{start-B0bnJgxI.js → start-DH7cat4-.js} +1 -1
- package/dist/{start-Clz-1BHB.js → start-EOV7s1NZ.js} +486 -476
- package/dist/startEntry.js +1 -1
- package/dist/{test-D_kW4KMj.js → test-DO27-x2P.js} +1 -1
- package/dist/{updateCommand-CRJlAOaM.js → updateCommand-C_jN1w18.js} +1 -1
- package/dist/updateCommand-nnFjDbl4.js +2 -0
- package/dist/{webDev-DlvZO30c.js → webDev-1XpVnYkW.js} +1 -1
- package/dist/{webDev-DSI9SOhs.js → webDev-B7vNj4Bq.js} +502 -494
- package/dist/{webhooksCommand-BvzXNHji.js → webhooksCommand-B1LVcyO3.js} +1 -1
- package/package.json +31 -19
- package/templates/AGENTS.md +1 -1
- package/templates/agent-docs/_index.md +1 -1
- package/templates/agent-docs/authentication.md +49 -6
- package/templates/agent-docs/cli.md +94 -11
- package/templates/agent-docs/data.md +64 -13
- package/templates/agent-docs/plugins.md +46 -8
- package/templates/agent-docs/reference.md +5 -3
- package/templates/agent-docs/routing.md +18 -0
- package/templates/agent-docs/schema-driven-ui.md +125 -0
- package/templates/agent-docs/whats-new.md +76 -133
- package/templates/apps/api-ai/package.json +6 -6
- package/templates/apps/api-auth/package.json +8 -8
- package/templates/apps/api-backend/package.json +7 -7
- package/templates/apps/api-backend-deactivation/package.json +7 -7
- package/templates/apps/api-backend-mail/package.json +8 -8
- package/templates/apps/api-backend-mariadb/package.json +9 -9
- package/templates/apps/api-backend-sqlite/package.json +8 -8
- package/templates/apps/api-backend-storage/package.json +8 -8
- package/templates/apps/api-cms/package.json +9 -9
- package/templates/apps/api-collab/package.json +8 -8
- package/templates/apps/api-data-advanced/package.json +8 -8
- package/templates/apps/api-durable/package.json +8 -8
- package/templates/apps/api-feature-flags/package.json +9 -9
- package/templates/apps/api-governance/package.json +8 -8
- package/templates/apps/api-kv/package.json +8 -8
- package/templates/apps/api-moderation/package.json +8 -8
- package/templates/apps/api-observability/package.json +8 -8
- package/templates/apps/api-ratelimit/package.json +8 -8
- package/templates/apps/api-rbac/package.json +8 -8
- package/templates/apps/api-rest/package.json +7 -7
- package/templates/apps/api-row-history/package.json +8 -8
- package/templates/apps/api-saas/package.json +11 -11
- package/templates/apps/api-saas-starter/package.json +10 -10
- package/templates/apps/api-search/package.json +8 -8
- package/templates/apps/api-status/package.json +8 -8
- package/templates/apps/api-webhooks/package.json +9 -9
- package/templates/apps/changelog/package.json +7 -7
- package/templates/apps/edge-functions/package.json +2 -2
- package/templates/apps/frontend-admin/package.json +8 -8
- package/templates/apps/frontend-app/package.json +9 -9
- package/templates/apps/frontend-auth/package.json +8 -8
- package/templates/apps/frontend-blank/package.json +7 -7
- package/templates/apps/frontend-cms/package.json +9 -9
- package/templates/apps/frontend-collab/package.json +10 -10
- 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 +8 -8
- package/templates/apps/frontend-i18n/package.json +6 -6
- package/templates/apps/frontend-landing/package.json +7 -7
- package/templates/apps/frontend-portal/package.json +8 -8
- package/templates/apps/frontend-saas/package.json +8 -8
- package/templates/apps/frontend-spa/package.json +7 -7
- package/templates/apps/frontend-ssr/package.json +7 -7
- package/templates/apps/frontend-ssr-api/package.json +8 -8
- package/templates/apps/frontend-static-blog/package.json +8 -8
- package/templates/apps/frontend-status/package.json +8 -8
- package/templates/apps/mobile-app/package.json +4 -4
- package/dist/apiBuild-CeUN55uk.js +0 -2
- package/dist/codegen-DjgxEOnD.js +0 -2
- package/dist/dbCommand-CSFWs9ev.js +0 -2
- package/dist/doctorCommand-J3qu4E0Y.js +0 -2
- package/dist/frameworkTableAssembly-IPD1pUnZ.js +0 -2
- package/dist/inspect-CuoDInfZ.js +0 -2
- package/dist/interruptedReplace-C3O3M1MM.js +0 -28
- package/dist/interruptedReplace-CvmiAM9K.js +0 -2
- package/dist/manifestBuild-C4-J1-m_.js +0 -2
- package/dist/serveCommand-BiPe8BJm.js +0 -2
- package/dist/updateCommand-CIoVDKnj.js +0 -2
|
@@ -1,24 +1,24 @@
|
|
|
1
1
|
import { a as e, t } from "./subcommandNames-CDzfEtKV.js";
|
|
2
2
|
import { t as n } from "./cliError-BmdYnghb.js";
|
|
3
|
-
import { B as r, c as i, i as a, o } from "./dev-
|
|
3
|
+
import { B as r, c as i, i as a, o } from "./dev-Ca_A_S9v.js";
|
|
4
4
|
import { n as s, t as c } from "./cliArgs-D4p8n7EE.js";
|
|
5
5
|
import { r as l } from "./dialectDriver-czCHYpeH.js";
|
|
6
6
|
import { i as u, n as d, r as f, t as p } from "./dataProfile-Du3ztYxv.js";
|
|
7
7
|
import { o as m, t as h } from "./inspectFetch-EMuhTG_9.js";
|
|
8
|
-
import { r as g } from "./dbCommand-
|
|
8
|
+
import { r as g } from "./dbCommand-DZTmOFT4.js";
|
|
9
9
|
import { resolve as _ } from "node:path";
|
|
10
10
|
import { createLogger as v } from "@voltro/logger";
|
|
11
11
|
import { DATA_TRANSFERS_TABLE as y, REPLACE_IN_PROGRESS_TABLE as b, _voltroDataTransfersTable as x, _voltroReplaceInProgressTable as S, connectionIdentity as C, derivedRowId as w, ensureTableRegistered as T, normaliseTarget as ee, sameDatabase as te, streamTable as ne } from "@voltro/database";
|
|
12
12
|
import { Cause as E, Effect as D, Exit as re, Option as ie, Stream as ae } from "effect";
|
|
13
13
|
import { SqlClient as oe } from "@effect/sql";
|
|
14
|
-
import { declaredSnapshot as se, fingerprintSchema as
|
|
15
|
-
import { STORAGE_REFS_TABLE as
|
|
16
|
-
import { createHash as
|
|
17
|
-
import { ASSETS_DIR as
|
|
18
|
-
import { request as
|
|
19
|
-
import { request as
|
|
14
|
+
import { declaredSnapshot as se, fingerprintSchema as O, introspectSchema as ce } from "@voltro/database/sql";
|
|
15
|
+
import { STORAGE_REFS_TABLE as le, appStorageProvider as ue, configuredStorageProvider as k } from "@voltro/plugin-storage";
|
|
16
|
+
import { createHash as de } from "node:crypto";
|
|
17
|
+
import { ASSETS_DIR as A, ASSET_INDEX_FILE as j, BundleError as fe, asImportError as pe, assessLiveImport as me, backupArtifactName as M, backupCommand as he, captureAssetsToCas as ge, classificationFromSnapshot as _e, decryptStream as N, dumpExcludedTables as ve, encryptStream as ye, packBundle as be, peekBundle as P, previewMasking as xe, restoreAssetsFromCas as Se, restoreCommand as Ce, runExport as we, runImport as Te, runNativeStep as F, storageAssetSink as Ee, storageAssetSource as De, storageBlobSink as Oe, storageBlobSource as ke, unpackBundle as Ae, unpackBundleToDir as je } from "@voltro/data-transfer";
|
|
18
|
+
import { request as Me } from "node:http";
|
|
19
|
+
import { request as Ne } from "node:https";
|
|
20
20
|
//#region src/longRequest.ts
|
|
21
|
-
var
|
|
21
|
+
var Pe = /* @__PURE__ */ new Set([
|
|
22
22
|
"ECONNREFUSED",
|
|
23
23
|
"ETIMEDOUT",
|
|
24
24
|
"EHOSTUNREACH",
|
|
@@ -26,7 +26,7 @@ var Fe = /* @__PURE__ */ new Set([
|
|
|
26
26
|
"ENOTFOUND",
|
|
27
27
|
"ECONNRESET",
|
|
28
28
|
"EAI_AGAIN"
|
|
29
|
-
]),
|
|
29
|
+
]), I = class extends Error {
|
|
30
30
|
url;
|
|
31
31
|
cause;
|
|
32
32
|
constructor(e, t) {
|
|
@@ -39,14 +39,14 @@ var Fe = /* @__PURE__ */ new Set([
|
|
|
39
39
|
})();
|
|
40
40
|
super(`the instance at ${n} is not answering (${t.code ?? "connect failed"}).`), this.url = e, this.cause = t, this.name = "InstanceUnreachable";
|
|
41
41
|
}
|
|
42
|
-
},
|
|
42
|
+
}, Fe = class extends Error {
|
|
43
43
|
elapsedMs;
|
|
44
44
|
url;
|
|
45
45
|
constructor(e, t) {
|
|
46
46
|
super(`no response after ${Math.round(e / 1e3)}s`), this.elapsedMs = e, this.url = t, this.name = "LongRequestTimeout";
|
|
47
47
|
}
|
|
48
|
-
},
|
|
49
|
-
let i = new URL(e), a = i.protocol === "https:" ?
|
|
48
|
+
}, L = async (e, t, n, r) => {
|
|
49
|
+
let i = new URL(e), a = i.protocol === "https:" ? Ne : Me, o = Date.now();
|
|
50
50
|
return await new Promise((s, c) => {
|
|
51
51
|
let l = a({
|
|
52
52
|
protocol: i.protocol,
|
|
@@ -64,8 +64,8 @@ var Fe = /* @__PURE__ */ new Set([
|
|
|
64
64
|
})), e.on("error", c);
|
|
65
65
|
});
|
|
66
66
|
if (r?.timeoutMs !== void 0 && r.timeoutMs > 0 && l.setTimeout(r.timeoutMs, () => {
|
|
67
|
-
l.destroy(new
|
|
68
|
-
}), l.on("error", (t) => c(
|
|
67
|
+
l.destroy(new Fe(Date.now() - o, e));
|
|
68
|
+
}), l.on("error", (t) => c(Pe.has(t.code ?? "") ? new I(e, t) : t)), typeof t == "string" || Buffer.isBuffer(t)) {
|
|
69
69
|
l.end(t);
|
|
70
70
|
return;
|
|
71
71
|
}
|
|
@@ -78,7 +78,7 @@ var Fe = /* @__PURE__ */ new Set([
|
|
|
78
78
|
}
|
|
79
79
|
})();
|
|
80
80
|
});
|
|
81
|
-
},
|
|
81
|
+
}, Ie = (e) => `${e.message}\n That address came from --api-url (or VOLTRO_API_URL) — it is the running instance, not the database.\n Nothing was sent, so nothing was imported. Check the instance is up and reachable from here\n (a VPN, an ingress or a stopped deployment are the usual three), e.g. curl -sS ${e.url}/_voltro/inspect/app`, Le = (e, t) => `the instance did not answer within ${Math.round(e.elapsedMs / 1e3)}s. This is a client-side deadline, not a failure on the server: the upload itself finished long before, and the response arrives only when the import does. The instance is very probably STILL IMPORTING and will finish.\n` + (t === "replace" ? " Do NOT simply re-run: --mode replace empties the target first, so a second run started while the first is mid-flight would empty it under the first one. Check the instance's log for the import's completion, and check the target's row counts, before deciding." : ` Re-running the same bundle is safe once the first run has finished (--mode ${t} is idempotent), but check the instance's log first — the work may already be done.`) + "\n To keep a deadline anyway, pass --timeout <seconds>; the default is to wait.", Re = 16777216, ze = async function* (e, t) {
|
|
82
82
|
let n = [], r = 0, i = 0, a = function* (e) {
|
|
83
83
|
let t = Buffer.concat(n);
|
|
84
84
|
n = [], r = 0;
|
|
@@ -100,12 +100,12 @@ var Fe = /* @__PURE__ */ new Set([
|
|
|
100
100
|
i += a.byteLength, yield s;
|
|
101
101
|
}
|
|
102
102
|
yield* a(!0);
|
|
103
|
-
},
|
|
103
|
+
}, Be = (e) => de("sha256").update(Buffer.from(e)).digest("hex"), Ve = async (e) => {
|
|
104
104
|
let t = 0, n = 0, r = {
|
|
105
105
|
status: 0,
|
|
106
106
|
body: {}
|
|
107
107
|
};
|
|
108
|
-
for await (let i of
|
|
108
|
+
for await (let i of ze(e.stream, e.chunkBytes)) {
|
|
109
109
|
if (i.offset + i.bytes.byteLength <= e.alreadyHeld && !i.final) {
|
|
110
110
|
n++;
|
|
111
111
|
continue;
|
|
@@ -131,14 +131,15 @@ var Fe = /* @__PURE__ */ new Set([
|
|
|
131
131
|
bytes: t,
|
|
132
132
|
resumedFrom: e.alreadyHeld
|
|
133
133
|
};
|
|
134
|
-
}, R = "voltro-backup-stamp.json",
|
|
134
|
+
}, R = "voltro-backup-stamp.json", He = (e) => ({
|
|
135
135
|
stampVersion: 1,
|
|
136
136
|
dialect: e.dialect,
|
|
137
137
|
schemaFingerprint: e.schemaFingerprint,
|
|
138
|
+
dumpFingerprint: e.dumpFingerprint ?? null,
|
|
138
139
|
voltroVersion: e.voltroVersion,
|
|
139
140
|
createdAt: e.createdAt,
|
|
140
141
|
assets: e.assets ?? null
|
|
141
|
-
}),
|
|
142
|
+
}), Ue = (e) => {
|
|
142
143
|
let t;
|
|
143
144
|
try {
|
|
144
145
|
t = JSON.parse(e);
|
|
@@ -150,25 +151,26 @@ var Fe = /* @__PURE__ */ new Set([
|
|
|
150
151
|
if (n.stampVersion !== 1 || typeof n.dialect != "string" || typeof n.voltroVersion != "string" || typeof n.createdAt != "string") return null;
|
|
151
152
|
let r = n.schemaFingerprint;
|
|
152
153
|
if (r !== null && typeof r != "string") return null;
|
|
153
|
-
let i = n.
|
|
154
|
-
let t =
|
|
154
|
+
let i = n.dumpFingerprint, a = n.assets, o = (e) => {
|
|
155
|
+
let t = a?.[e];
|
|
155
156
|
return typeof t == "number" ? t : void 0;
|
|
156
|
-
},
|
|
157
|
-
count:
|
|
158
|
-
totalBytes:
|
|
159
|
-
...
|
|
160
|
-
...
|
|
161
|
-
...
|
|
157
|
+
}, s = typeof a == "object" && a && typeof a.count == "number" && typeof a.totalBytes == "number" ? {
|
|
158
|
+
count: a.count,
|
|
159
|
+
totalBytes: a.totalBytes,
|
|
160
|
+
...o("references") === void 0 ? {} : { references: o("references") },
|
|
161
|
+
...o("objects") === void 0 ? {} : { objects: o("objects") },
|
|
162
|
+
...o("objectBytes") === void 0 ? {} : { objectBytes: o("objectBytes") }
|
|
162
163
|
} : null;
|
|
163
164
|
return {
|
|
164
165
|
stampVersion: 1,
|
|
165
166
|
dialect: n.dialect,
|
|
166
167
|
schemaFingerprint: r,
|
|
168
|
+
dumpFingerprint: typeof i == "string" ? i : null,
|
|
167
169
|
voltroVersion: n.voltroVersion,
|
|
168
170
|
createdAt: n.createdAt,
|
|
169
|
-
assets:
|
|
171
|
+
assets: s
|
|
170
172
|
};
|
|
171
|
-
},
|
|
173
|
+
}, We = (e) => {
|
|
172
174
|
let { stamp: t, currentDialect: n, targetFingerprint: r } = e;
|
|
173
175
|
if (t === null) return {
|
|
174
176
|
warn: `no ${R} beside the dump — an older/handmade backup. Cannot verify dialect or schema version; make sure this dump is a ${n} dump.`,
|
|
@@ -182,7 +184,7 @@ var Fe = /* @__PURE__ */ new Set([
|
|
|
182
184
|
refuse: `dialect mismatch — backup is a ${t.dialect} dump, but DB_DIALECT is ${n}. A cross-dialect restore is never valid.`,
|
|
183
185
|
info: i
|
|
184
186
|
};
|
|
185
|
-
},
|
|
187
|
+
}, Ge = async (e, t) => {
|
|
186
188
|
let n = w("dimp", `${t.direction}-${t.artifact}-${Date.now()}-${Math.random()}`), r = {
|
|
187
189
|
id: n,
|
|
188
190
|
direction: t.direction,
|
|
@@ -230,7 +232,7 @@ var Fe = /* @__PURE__ */ new Set([
|
|
|
230
232
|
...r
|
|
231
233
|
});
|
|
232
234
|
} catch {}
|
|
233
|
-
},
|
|
235
|
+
}, Ke = async (e, t) => {
|
|
234
236
|
let n = w("repl", `restore-${t.artifact}-${Date.now()}-${Math.random()}`), r = {
|
|
235
237
|
id: n,
|
|
236
238
|
startedAt: /* @__PURE__ */ new Date(),
|
|
@@ -271,7 +273,7 @@ var Fe = /* @__PURE__ */ new Set([
|
|
|
271
273
|
} }).then(() => void 0, () => void 0);
|
|
272
274
|
}
|
|
273
275
|
};
|
|
274
|
-
},
|
|
276
|
+
}, qe = async (e) => {
|
|
275
277
|
let t = await D.runPromiseExit(e);
|
|
276
278
|
if (re.isSuccess(t)) return;
|
|
277
279
|
let n = E.failureOption(t.cause);
|
|
@@ -287,7 +289,7 @@ var Fe = /* @__PURE__ */ new Set([
|
|
|
287
289
|
} catch {
|
|
288
290
|
return e.trim();
|
|
289
291
|
}
|
|
290
|
-
},
|
|
292
|
+
}, Je = (e) => {
|
|
291
293
|
let t = e.flagUrl ?? e.envUrl;
|
|
292
294
|
if (t === void 0 || t.trim() === "") return {
|
|
293
295
|
ok: !1,
|
|
@@ -301,39 +303,67 @@ var Fe = /* @__PURE__ */ new Set([
|
|
|
301
303
|
ok: !0,
|
|
302
304
|
url: t
|
|
303
305
|
};
|
|
304
|
-
},
|
|
305
|
-
let {
|
|
306
|
-
|
|
306
|
+
}, Ye = (e) => {
|
|
307
|
+
let { dumpFingerprint: t, liveFingerprintAtBackup: n, restoredFingerprint: r, restoredTableCount: i, ledger: a } = e;
|
|
308
|
+
if (i === 0) return {
|
|
307
309
|
pass: !1,
|
|
308
310
|
lines: ["FAIL — the restore produced ZERO tables. The dump is empty or unreadable; this backup would NOT recover your app."]
|
|
309
|
-
}
|
|
311
|
+
};
|
|
312
|
+
if (t !== null && r !== null && t !== r) return {
|
|
313
|
+
pass: !1,
|
|
314
|
+
lines: [`FAIL — restored ${i} table(s), but the schema fingerprint (${r.slice(0, 12)}) does NOT match what the backup's artifact should carry (${t.slice(0, 12)}).`, "The restore did not reproduce the schema that was backed up — the artifact is inconsistent."]
|
|
315
|
+
};
|
|
316
|
+
if (a.kind === "empty") return {
|
|
310
317
|
pass: !1,
|
|
311
|
-
lines: [
|
|
312
|
-
|
|
318
|
+
lines: [
|
|
319
|
+
`FAIL — restored ${i} table(s) with the right shape, but \`_voltro_migration_plans\` came back EMPTY.`,
|
|
320
|
+
"`voltro serve` reads the newest row of that table as its boot gate and refuses with `prod-mismatch` when there is none — no source tree makes an empty ledger boot. Restoring this artifact would give you a database the app will not serve.",
|
|
321
|
+
"Run `voltro db apply` after a real restore to record a baseline, or find out why the ledger rows did not cross."
|
|
322
|
+
]
|
|
323
|
+
};
|
|
324
|
+
let o = a.kind === "recorded" ? `Migration ledger restored — newest recorded schema ${a.fingerprint.slice(0, 12)}; \`voltro serve\` boots against this database when your code declares that same schema.` : "No `_voltro_migration_plans` in the restored DB — not a voltro-managed schema, so nothing is claimed about booting against it.";
|
|
325
|
+
return t === null || r === null ? {
|
|
313
326
|
pass: !0,
|
|
314
|
-
lines: [
|
|
327
|
+
lines: [
|
|
328
|
+
`PASS (partial) — restored ${i} table(s) into the throwaway DB.`,
|
|
329
|
+
n === null ? "No stamped fingerprint to cross-check the schema shape (older/hand-made backup) — the bytes restore, but the shape was not verified." : "The stamp predates the dump-scoped fingerprint, and its `schemaFingerprint` covers the SOURCE database rather than the artifact — comparing against it would fail a healthy backup. Re-take the backup to cross-check the shape.",
|
|
330
|
+
o
|
|
331
|
+
]
|
|
315
332
|
} : {
|
|
316
333
|
pass: !0,
|
|
317
|
-
lines: [`PASS — restored ${
|
|
334
|
+
lines: [`PASS — restored ${i} table(s); schema fingerprint matches the backup stamp (${r.slice(0, 12)}).`, o]
|
|
318
335
|
};
|
|
319
|
-
}, V = v({ scope: "voltro:data" }),
|
|
336
|
+
}, V = v({ scope: "voltro:data" }), Xe = async () => {
|
|
320
337
|
try {
|
|
321
338
|
let { createRequire: e } = await import("node:module"), { join: t } = await import("node:path"), { readFileSync: n } = await import("node:fs"), r = e(t(process.cwd(), "package.json")).resolve("@voltro/cli/package.json");
|
|
322
339
|
return JSON.parse(n(r, "utf8")).version ?? "unknown";
|
|
323
340
|
} catch {
|
|
324
341
|
return "unknown";
|
|
325
342
|
}
|
|
326
|
-
},
|
|
343
|
+
}, Ze = async (e) => {
|
|
327
344
|
try {
|
|
328
|
-
return
|
|
345
|
+
return O(await K(e));
|
|
329
346
|
} catch {
|
|
330
347
|
return null;
|
|
331
348
|
}
|
|
349
|
+
}, Qe = async (e) => {
|
|
350
|
+
try {
|
|
351
|
+
let t = await K(e), n = new Set(ve(e));
|
|
352
|
+
return {
|
|
353
|
+
live: O(t),
|
|
354
|
+
dump: O({ tables: t.tables.filter((e) => !n.has(e.name)) })
|
|
355
|
+
};
|
|
356
|
+
} catch {
|
|
357
|
+
return {
|
|
358
|
+
live: null,
|
|
359
|
+
dump: null
|
|
360
|
+
};
|
|
361
|
+
}
|
|
332
362
|
}, H = (e) => {
|
|
333
363
|
if (e.event !== "done") return;
|
|
334
364
|
let t = e.total === void 0 ? "" : ` / ${e.total}`;
|
|
335
365
|
V.info(`${e.phase} [${e.index + 1}/${e.tableCount}] ${e.table}`, { rows: `${e.rowsDone}${t}` });
|
|
336
|
-
},
|
|
366
|
+
}, $e = async (e) => {
|
|
337
367
|
let t = (e) => e?.meta?.databaseId;
|
|
338
368
|
if (e) {
|
|
339
369
|
let n = await h({
|
|
@@ -353,21 +383,21 @@ var Fe = /* @__PURE__ */ new Set([
|
|
|
353
383
|
};
|
|
354
384
|
}
|
|
355
385
|
return null;
|
|
356
|
-
},
|
|
357
|
-
let t = e.includes("--allow-live"), n = t ? null : await
|
|
386
|
+
}, et = async (e) => {
|
|
387
|
+
let t = e.includes("--allow-live"), n = t ? null : await $e(U(e, "--api-url")), r = C({
|
|
358
388
|
dialect: G(),
|
|
359
389
|
...o()
|
|
360
390
|
}), i = ee({
|
|
361
391
|
dialect: G(),
|
|
362
392
|
...o()
|
|
363
|
-
}), { refuse: a, message: s } =
|
|
393
|
+
}), { refuse: a, message: s } = me({
|
|
364
394
|
liveInstance: n?.label ?? null,
|
|
365
395
|
allowLive: t,
|
|
366
396
|
sameTarget: te(r, n?.databaseId),
|
|
367
397
|
...i === void 0 ? {} : { targetLabel: `${i.host}:${i.port}/${i.database}` }
|
|
368
398
|
});
|
|
369
399
|
return !a || (V.error(s ?? "refusing: live instance detected"), !1);
|
|
370
|
-
}, U = s,
|
|
400
|
+
}, U = s, tt = [
|
|
371
401
|
"--api-url",
|
|
372
402
|
"--bundle-key",
|
|
373
403
|
"--compression",
|
|
@@ -387,42 +417,42 @@ var Fe = /* @__PURE__ */ new Set([
|
|
|
387
417
|
"--timeout",
|
|
388
418
|
"--tenant",
|
|
389
419
|
"--token"
|
|
390
|
-
], W = (e) => c(e,
|
|
420
|
+
], W = (e) => c(e, tt), G = () => {
|
|
391
421
|
let e = (process.env.DB_DIALECT ?? "postgres").toLowerCase();
|
|
392
422
|
if (e === "memory") throw new n("voltro data: requires a real dialect, not DB_DIALECT=memory.");
|
|
393
423
|
return e;
|
|
394
|
-
},
|
|
424
|
+
}, nt = async (e, t) => {
|
|
395
425
|
let n = (await l(e)).makeSqlLayer(t);
|
|
396
|
-
return D.runPromise(oe.SqlClient.pipe(D.flatMap(
|
|
397
|
-
},
|
|
426
|
+
return D.runPromise(oe.SqlClient.pipe(D.flatMap(ce), D.provide(n)));
|
|
427
|
+
}, K = (e) => nt(e, o()), rt = () => {
|
|
398
428
|
let e = o(), t = [];
|
|
399
429
|
return e.url && t.push(B(e.url)), (e.host || e.database) && t.push(`${(e.host ?? "localhost").toLowerCase()}:${e.port ?? "(default)"}/${e.database ?? ""}`), t;
|
|
400
|
-
},
|
|
430
|
+
}, q = async () => {
|
|
401
431
|
let e = process.cwd(), t = await i(e);
|
|
402
432
|
if (!t) throw new n("voltro data: no app.config.ts found in the current directory.");
|
|
403
433
|
let r = await a(t, void 0), o = G();
|
|
404
434
|
return {
|
|
405
435
|
store: r,
|
|
406
|
-
snapshot: await
|
|
436
|
+
snapshot: await K(o),
|
|
407
437
|
dialect: o
|
|
408
438
|
};
|
|
409
|
-
},
|
|
410
|
-
table:
|
|
439
|
+
}, J = (e) => ne(e, {
|
|
440
|
+
table: le,
|
|
411
441
|
chunkSize: 500
|
|
412
442
|
}).pipe(ae.map((e) => ({
|
|
413
443
|
key: String(e.key),
|
|
414
444
|
contentType: String(e.contentType ?? "application/octet-stream"),
|
|
415
445
|
size: Number(e.size ?? 0)
|
|
416
|
-
}))),
|
|
446
|
+
}))), Y = (e) => !!e && e.endsWith(".vbundle"), X = (e) => U(e, "--passphrase") ?? process.env.VOLTRO_BUNDLE_KEY, it = (e) => e.includes("--encrypt") || U(e, "--passphrase") !== void 0 || process.env.VOLTRO_BUNDLE_KEY !== void 0, at = async (e, t) => {
|
|
417
447
|
let { createWriteStream: n } = await import("node:fs"), { pipeline: r } = await import("node:stream/promises"), { Readable: i } = await import("node:stream");
|
|
418
448
|
await r(i.from(e), n(t));
|
|
419
|
-
},
|
|
449
|
+
}, Z = (e) => ({ async *[Symbol.asyncIterator]() {
|
|
420
450
|
let { createReadStream: t } = await import("node:fs");
|
|
421
451
|
for await (let n of t(e)) yield n instanceof Uint8Array ? n : new Uint8Array(n);
|
|
422
452
|
} }), ot = async (e, t) => {
|
|
423
453
|
if (t !== void 0 || e === void 0) return null;
|
|
424
454
|
try {
|
|
425
|
-
let { resolve: t, join: n } = await import("node:path"), r = await import("node:fs/promises"), i = t(process.cwd(), e), a =
|
|
455
|
+
let { resolve: t, join: n } = await import("node:path"), r = await import("node:fs/promises"), i = t(process.cwd(), e), a = Y(e) ? (await P(Z(i), ["manifest.json"])).get("manifest.json") : await r.readFile(n(i, "manifest.json"));
|
|
426
456
|
if (a === void 0) return null;
|
|
427
457
|
let o = (JSON.parse(Buffer.from(a).toString("utf8")).tables ?? []).map((e) => e.name).filter((e) => typeof e == "string");
|
|
428
458
|
return o.length > 0 ? o : null;
|
|
@@ -491,7 +521,7 @@ var Fe = /* @__PURE__ */ new Set([
|
|
|
491
521
|
} : { kind: "all" };
|
|
492
522
|
}, pt = async () => {
|
|
493
523
|
let e = await g(process.cwd());
|
|
494
|
-
return
|
|
524
|
+
return _e(se(e));
|
|
495
525
|
}, mt = "voltro/tenant", ht = async () => {
|
|
496
526
|
let e = (await g(process.cwd())).filter((e) => (e.appliedMixins ?? []).some((e) => e.id === mt)).map((e) => e.tableName);
|
|
497
527
|
return e.length > 0 ? e : void 0;
|
|
@@ -509,7 +539,7 @@ var Fe = /* @__PURE__ */ new Set([
|
|
|
509
539
|
for (let t of e.unclassified) console.log(` ${t}`);
|
|
510
540
|
} else console.log("\n ✓ every exported column is classified — safe to run.");
|
|
511
541
|
}, _t = async (e) => {
|
|
512
|
-
let { resolve: t, join: r } = await import("node:path"), i = await import("node:fs/promises"), a = U(e.args, "--chunk-size"), o = a === void 0 ?
|
|
542
|
+
let { resolve: t, join: r } = await import("node:path"), i = await import("node:fs/promises"), a = U(e.args, "--chunk-size"), o = a === void 0 ? Re : Math.round(Number(a) * 1024 * 1024);
|
|
513
543
|
if (!Number.isFinite(o) || o <= 0) throw new n(`--chunk-size expects a positive number of megabytes, got '${String(a)}'`);
|
|
514
544
|
let s = e.stream[Symbol.asyncIterator](), c = [], l = 0, u = !1;
|
|
515
545
|
for (; l <= o;) {
|
|
@@ -524,11 +554,11 @@ var Fe = /* @__PURE__ */ new Set([
|
|
|
524
554
|
kind: "small",
|
|
525
555
|
body: Buffer.concat(c)
|
|
526
556
|
};
|
|
527
|
-
let d = t(process.cwd(), e.bundleDir), f =
|
|
557
|
+
let d = t(process.cwd(), e.bundleDir), f = Be(Y(e.bundleDir) ? await i.readFile(d) : await i.readFile(r(d, "manifest.json"))), p = `up-${f.slice(0, 32)}`, m = await L(e.url, Buffer.alloc(0), {
|
|
528
558
|
...e.headers,
|
|
529
559
|
"x-import-upload-id": p,
|
|
530
560
|
"x-import-probe": "1"
|
|
531
|
-
},
|
|
561
|
+
}, $(e.args)), h = m.status === 200 ? Number(JSON.parse(m.body.toString("utf8")).bytes ?? 0) : 0;
|
|
532
562
|
h > 0 && e.log.info("api import: resuming a chunked upload the instance already holds", { bytes: h });
|
|
533
563
|
let g = (async function* () {
|
|
534
564
|
for (let e of c) yield e;
|
|
@@ -537,7 +567,7 @@ var Fe = /* @__PURE__ */ new Set([
|
|
|
537
567
|
if (e.done === !0) return;
|
|
538
568
|
yield e.value;
|
|
539
569
|
}
|
|
540
|
-
})(), _ = 0, v = await
|
|
570
|
+
})(), _ = 0, v = await Ve({
|
|
541
571
|
stream: g,
|
|
542
572
|
chunkBytes: o,
|
|
543
573
|
uploadId: p,
|
|
@@ -551,7 +581,7 @@ var Fe = /* @__PURE__ */ new Set([
|
|
|
551
581
|
e.log.info("api import: upload done — the instance is importing now. This request stays open until it finishes, which on a full bundle is minutes, and nothing more is printed until then. It is not a hang; the instance logs the import itself.");
|
|
552
582
|
},
|
|
553
583
|
target: { send: async (t, n) => {
|
|
554
|
-
let r = await
|
|
584
|
+
let r = await L(e.url, t, n, $(e.args)), i = {};
|
|
555
585
|
try {
|
|
556
586
|
i = JSON.parse(r.body.toString("utf8") || "{}");
|
|
557
587
|
} catch {}
|
|
@@ -573,7 +603,7 @@ var Fe = /* @__PURE__ */ new Set([
|
|
|
573
603
|
body: Buffer.from(JSON.stringify(v.body))
|
|
574
604
|
}
|
|
575
605
|
};
|
|
576
|
-
}, vt = (e) => `--mode replace with --no-atomic against a ${e ? "RUNNING instance" : "live database"}: the target is empty between the delete and the load, and anything the application writes in that window can collide with a row from the bundle — or survive the import as a row the bundle never carried. Drop --no-atomic to run the whole load in one transaction (concurrent writers then wait instead of racing), or stop the traffic for the duration.`,
|
|
606
|
+
}, vt = (e) => `--mode replace with --no-atomic against a ${e ? "RUNNING instance" : "live database"}: the target is empty between the delete and the load, and anything the application writes in that window can collide with a row from the bundle — or survive the import as a row the bundle never carried. Drop --no-atomic to run the whole load in one transaction (concurrent writers then wait instead of racing), or stop the traffic for the duration.`, Q = (e, t) => e.includes("--no-atomic") ? !1 : e.includes("--atomic") || t === "replace", yt = (e, t, n) => {
|
|
577
607
|
if (t !== "replace" || e.includes("--no-rollback")) return;
|
|
578
608
|
let i = U(e, "--rollback-dir");
|
|
579
609
|
return i === void 0 ? r(n) : _(i);
|
|
@@ -584,7 +614,7 @@ var Fe = /* @__PURE__ */ new Set([
|
|
|
584
614
|
recordInterruption: !0,
|
|
585
615
|
via: "direct"
|
|
586
616
|
};
|
|
587
|
-
},
|
|
617
|
+
}, $ = (e) => {
|
|
588
618
|
let t = U(e, "--timeout");
|
|
589
619
|
if (t === void 0) return {};
|
|
590
620
|
let r = Number(t);
|
|
@@ -611,15 +641,15 @@ var Fe = /* @__PURE__ */ new Set([
|
|
|
611
641
|
...n ? { bundleKey: n } : {}
|
|
612
642
|
}, c = st(e), l;
|
|
613
643
|
try {
|
|
614
|
-
let n = await
|
|
644
|
+
let n = await L(`${t.replace(/\/+$/, "")}/_voltro/admin/export`, JSON.stringify(s), {
|
|
615
645
|
"content-type": "application/json",
|
|
616
646
|
authorization: `Bearer ${i}`
|
|
617
|
-
}, c.perRequest ??
|
|
647
|
+
}, c.perRequest ?? $(e));
|
|
618
648
|
if (n.status < 200 || n.status >= 300) return V.error(`api export failed: HTTP ${n.status} — ${n.body.toString("utf8")}`), 1;
|
|
619
649
|
l = JSON.parse(n.body.toString("utf8"));
|
|
620
650
|
} catch (e) {
|
|
621
|
-
if (e instanceof
|
|
622
|
-
if (e instanceof
|
|
651
|
+
if (e instanceof Fe) return V.error(`api export: the instance did not answer within ${Math.round(e.elapsedMs / 1e3)}s. That is a client-side deadline. Nothing was written locally; re-run, or raise --timeout.`), 1;
|
|
652
|
+
if (e instanceof I) return V.error(`api export: ${Ie(e)}`), 1;
|
|
623
653
|
throw e;
|
|
624
654
|
}
|
|
625
655
|
if (e.includes("--detach")) return V.info(`api export: started as ${l.runId}. NOT waiting for it.\n The outcome is not known yet — read it with: voltro data ${ut} --target api --api-url ${t}`), 0;
|
|
@@ -647,7 +677,7 @@ var Fe = /* @__PURE__ */ new Set([
|
|
|
647
677
|
log: V,
|
|
648
678
|
...c.perRequest ? { perRequest: c.perRequest } : {}
|
|
649
679
|
});
|
|
650
|
-
return g === null ? 1 : (await
|
|
680
|
+
return g === null ? 1 : (await je((async function* () {
|
|
651
681
|
yield g;
|
|
652
682
|
})(), h(process.cwd(), r)), V.info("api export complete (downloaded from the running instance)", {
|
|
653
683
|
outDir: r,
|
|
@@ -658,7 +688,7 @@ var Fe = /* @__PURE__ */ new Set([
|
|
|
658
688
|
}), 0);
|
|
659
689
|
}
|
|
660
690
|
if (!r) return V.error("a <outDir> is required for a direct export"), 1;
|
|
661
|
-
let i = U(e, "--profile"), a = i ? await p(i, { allowPath: !0 }) : {}, { store: o, snapshot: s, dialect: c } = await
|
|
691
|
+
let i = U(e, "--profile"), a = i ? await p(i, { allowPath: !0 }) : {}, { store: o, snapshot: s, dialect: c } = await q(), l = e.includes("--assets") || a.assets === !0, m = l ? k() : void 0;
|
|
662
692
|
if (l && !m) return V.error("--assets: this app has no storage provider configured, so there are no blobs to export.\n Install `storagePlugin(...)` in app.config.ts or set STORAGE_PROVIDER, or drop the flag."), 1;
|
|
663
693
|
let h = U(e, "--compression") ?? a.compression, g = U(e, "--consistency") === "snapshot" ? "snapshot" : a.consistency ?? "live", _ = a.masking, v = a.subset, y = a.scope ?? ft(e, s.tables.map((e) => e.name)), b = _ ? await pt() : void 0, x = y.kind === "tenant" ? await ht() : void 0, S = x ? { tenantTables: x } : {};
|
|
664
694
|
if (e.includes("--dry-run")) {
|
|
@@ -684,7 +714,7 @@ var Fe = /* @__PURE__ */ new Set([
|
|
|
684
714
|
}));
|
|
685
715
|
return gt(t), +(t.unclassified.length > 0);
|
|
686
716
|
}
|
|
687
|
-
if (
|
|
717
|
+
if (Y(r)) {
|
|
688
718
|
let t = await import("node:os"), n = await import("node:fs/promises"), { resolve: i, join: a } = await import("node:path"), u = await n.mkdtemp(a(t.tmpdir(), "voltro-export-"));
|
|
689
719
|
try {
|
|
690
720
|
let t = await D.runPromise(we({
|
|
@@ -703,10 +733,10 @@ var Fe = /* @__PURE__ */ new Set([
|
|
|
703
733
|
onProgress: H
|
|
704
734
|
})), n = be({
|
|
705
735
|
dir: u,
|
|
706
|
-
...m ? { blobs:
|
|
736
|
+
...m ? { blobs: ke(m, () => J(o)) } : {}
|
|
707
737
|
}), a = it(e);
|
|
708
738
|
if (a) {
|
|
709
|
-
let t =
|
|
739
|
+
let t = X(e);
|
|
710
740
|
if (!t) return V.error("--encrypt requires --passphrase <p> or VOLTRO_BUNDLE_KEY (a dedicated key, NOT the transfer secret)"), 1;
|
|
711
741
|
n = ye(n, t);
|
|
712
742
|
}
|
|
@@ -737,7 +767,7 @@ var Fe = /* @__PURE__ */ new Set([
|
|
|
737
767
|
...h ? { compression: h } : {},
|
|
738
768
|
..._ ? { masking: _ } : {},
|
|
739
769
|
...b ? { classification: b } : {},
|
|
740
|
-
...m ? { assets:
|
|
770
|
+
...m ? { assets: De(m, () => J(o)) } : {},
|
|
741
771
|
onProgress: H
|
|
742
772
|
}));
|
|
743
773
|
return V.info("export complete", {
|
|
@@ -772,8 +802,8 @@ var Fe = /* @__PURE__ */ new Set([
|
|
|
772
802
|
return;
|
|
773
803
|
}
|
|
774
804
|
V.info("import complete", t);
|
|
775
|
-
}, Et = Symbol("refused"),
|
|
776
|
-
let t =
|
|
805
|
+
}, Et = Symbol("refused"), Dt = (e) => {
|
|
806
|
+
let t = pe(e) ?? e;
|
|
777
807
|
if (t?._tag !== "RowsRefusedError" || !Array.isArray(t.rows)) return !1;
|
|
778
808
|
let n = t.rows, r = n.filter((e) => !e.derived), i = Number(t.totalCount ?? n.length), a = Number(t.primaryCount ?? r.length);
|
|
779
809
|
V.error(`import refused ${i} row(s), of which ${a} are the actual failures — the rest could not land because a row they reference did not.`);
|
|
@@ -783,7 +813,7 @@ var Fe = /* @__PURE__ */ new Set([
|
|
|
783
813
|
n.length < i && V.error(` (the list above is capped at ${n.length} of ${i} — the counts by table are complete)`);
|
|
784
814
|
let s = i - a;
|
|
785
815
|
return s > 0 && V.error(` … and ${s} row(s) behind them. Fix the ${a} above and re-run; they resolve with their parents.`), !0;
|
|
786
|
-
},
|
|
816
|
+
}, Ot = (e) => {
|
|
787
817
|
let t = typeof e == "string" ? e : e.toString("utf8"), n = t;
|
|
788
818
|
try {
|
|
789
819
|
let e = JSON.parse(t), r = e.error ?? e.message;
|
|
@@ -823,7 +853,7 @@ var Fe = /* @__PURE__ */ new Set([
|
|
|
823
853
|
return;
|
|
824
854
|
}
|
|
825
855
|
}
|
|
826
|
-
},
|
|
856
|
+
}, kt = async (e) => {
|
|
827
857
|
let t = U(e, "--target") === "api", n = U(e, "--bundle-key"), r = W(e);
|
|
828
858
|
if (!r && !(t && n)) return V.error("usage: voltro data import <bundleDir> [--target direct|api] [--api-url <url>] [--token <secret>] [--timeout <seconds>] [--bundle-key <storage-key>] [--tables a,b] [--mode upsert|append|replace] [--on-conflict skip|fail] [--atomic] [--assets] [--no-verify] [--force] [--allow-live] [--dry-run] (--force: import despite schema drift AND cross-dialect warnings; --dry-run: report what would move, write nothing)"), 1;
|
|
829
859
|
let i = U(e, "--mode") ?? "upsert", a = U(e, "--on-conflict") ?? "skip";
|
|
@@ -838,7 +868,7 @@ var Fe = /* @__PURE__ */ new Set([
|
|
|
838
868
|
authorization: `Bearer ${o}`,
|
|
839
869
|
"x-import-mode": i,
|
|
840
870
|
"x-import-on-conflict": a,
|
|
841
|
-
...
|
|
871
|
+
...Q(e, i) ? { "x-import-atomic": "1" } : {},
|
|
842
872
|
...e.includes("--no-atomic") ? { "x-import-atomic": "0" } : {},
|
|
843
873
|
...c === void 0 ? {} : { "x-import-rollback-key": c },
|
|
844
874
|
...e.includes("--force") ? { "x-import-force": "1" } : {},
|
|
@@ -846,23 +876,23 @@ var Fe = /* @__PURE__ */ new Set([
|
|
|
846
876
|
...s ? { "x-import-tables": s } : {}
|
|
847
877
|
}, { resolve: p } = await import("node:path"), m;
|
|
848
878
|
if (n) l["content-type"] = "application/json", m = JSON.stringify({ bundleKey: n });
|
|
849
|
-
else if (
|
|
850
|
-
let t =
|
|
851
|
-
n && (t =
|
|
879
|
+
else if (Y(r)) {
|
|
880
|
+
let t = Z(p(process.cwd(), r)), n = X(e);
|
|
881
|
+
n && (t = N(t, n)), l["content-type"] = "application/octet-stream", m = t;
|
|
852
882
|
} else await (await import("node:fs/promises")).access(p(process.cwd(), r, "assets", "index.ndjson")).then(() => !0, () => !1) && V.warn("this directory bundle has assets; --target api uploads DATA ONLY. Pack a single-file `.vbundle` (voltro data export <name>.vbundle --assets) to transfer assets over the API."), l["content-type"] = "application/octet-stream", m = be({ dir: p(process.cwd(), r) });
|
|
853
883
|
if (i === "replace") {
|
|
854
884
|
let i = await ot(r, n);
|
|
855
885
|
{
|
|
856
|
-
let r = i === null ? { bundleKey: n } : { tables: i }, a = await
|
|
886
|
+
let r = i === null ? { bundleKey: n } : { tables: i }, a = await L(`${t.replace(/\/+$/, "")}/_voltro/admin/import`, JSON.stringify(r), {
|
|
857
887
|
...l,
|
|
858
888
|
"content-type": "application/json",
|
|
859
889
|
"x-import-preflight": "1"
|
|
860
|
-
},
|
|
890
|
+
}, $(e)).catch(() => null), o = a !== null && a.status === 200 ? JSON.parse(a.body.toString("utf8")) : null;
|
|
861
891
|
o === null ? V.warn("api replace: the instance did not answer the preflight (an older build does not have it). The run still works; the decision will only be visible in the pod log.") : o.staged === !0 ? V.info(`api replace: the instance WILL stage — ${o.stagedTables ?? 0} table(s) load into copies and the\n target keeps its rows until one short swap at the end. Interrupting the load leaves the target intact.`) : V.warn(`api replace: the instance will NOT stage — it empties the target first and refills it, so an
|
|
862
892
|
interrupted run leaves the target empty.\n Reason: ${o.reason ?? "not stated"}`);
|
|
863
893
|
}
|
|
864
894
|
}
|
|
865
|
-
i === "replace" && !
|
|
895
|
+
i === "replace" && !Q(e, i) && V.warn(vt(!0));
|
|
866
896
|
let h = `${t.replace(/\/+$/, "")}/_voltro/admin/import`, g = st(e), _;
|
|
867
897
|
try {
|
|
868
898
|
let t = typeof m == "string" ? null : await _t({
|
|
@@ -872,23 +902,23 @@ var Fe = /* @__PURE__ */ new Set([
|
|
|
872
902
|
bundleDir: r,
|
|
873
903
|
args: e,
|
|
874
904
|
log: V
|
|
875
|
-
}), n = t === null ? await
|
|
876
|
-
if (n.status < 200 || n.status >= 300) return
|
|
905
|
+
}), n = t === null ? await L(h, m, l, g.perRequest ?? $(e)) : t.kind === "sent" ? t.res : await L(h, t.body, l, g.perRequest ?? $(e));
|
|
906
|
+
if (n.status < 200 || n.status >= 300) return Dt(Ot(n.body)) || V.error(`api import failed: HTTP ${n.status} — ${n.body.toString("utf8")}`), 1;
|
|
877
907
|
_ = JSON.parse(n.body.toString("utf8"));
|
|
878
908
|
} catch (e) {
|
|
879
|
-
if (e instanceof
|
|
880
|
-
if (e instanceof
|
|
909
|
+
if (e instanceof Fe) return V.error(`api import: ${Le(e, i)}`), 1;
|
|
910
|
+
if (e instanceof I) return V.error(`api import: ${Ie(e)}`), 1;
|
|
881
911
|
throw e;
|
|
882
912
|
}
|
|
883
|
-
let v = await
|
|
913
|
+
let v = await L(`${t.replace(/\/+$/, "")}/_voltro/admin/import/start`, JSON.stringify(_.bundleKey === void 0 ? { uploadId: _.uploadId } : { bundleKey: _.bundleKey }), {
|
|
884
914
|
...l,
|
|
885
915
|
"content-type": "application/json"
|
|
886
|
-
}, g.perRequest ??
|
|
887
|
-
if (e instanceof
|
|
916
|
+
}, g.perRequest ?? $(e)).catch((e) => {
|
|
917
|
+
if (e instanceof I) return V.error(`api import: ${Ie(e)}`), null;
|
|
888
918
|
throw e;
|
|
889
919
|
});
|
|
890
920
|
if (v === null) return 1;
|
|
891
|
-
if (v.status < 200 || v.status >= 300) return
|
|
921
|
+
if (v.status < 200 || v.status >= 300) return Dt(Ot(v.body)) || V.error(`api import refused: HTTP ${v.status} — ${v.body.toString("utf8")}`), 1;
|
|
892
922
|
let y = JSON.parse(v.body.toString("utf8"));
|
|
893
923
|
if (y.alreadyStarted === !0 && V.info(`api import: this bundle is already being imported as ${y.runId} — watching that run instead of starting a second one`), e.includes("--detach")) return V.info(`api import: started as ${y.runId}. NOT waiting for it.\n The outcome is not known yet — read it with: voltro data ${ut} --target api --api-url ${t}`), 0;
|
|
894
924
|
if (y.recorded === !1) return V.warn(`api import: started as ${y.runId}, but this instance could not record the run — so its outcome\n cannot be read back. Run \`voltro db apply\` against it so the next import is watchable.`), 0;
|
|
@@ -914,20 +944,20 @@ var Fe = /* @__PURE__ */ new Set([
|
|
|
914
944
|
}), 0);
|
|
915
945
|
}
|
|
916
946
|
if (!r) return V.error("a <bundleDir> is required for a direct import"), 1;
|
|
917
|
-
if (!await
|
|
918
|
-
i === "replace" && e.includes("--allow-live") && !
|
|
919
|
-
let { store: s, snapshot: c } = await
|
|
947
|
+
if (!await et(e)) return 1;
|
|
948
|
+
i === "replace" && e.includes("--allow-live") && !Q(e, i) && V.warn(vt(!1));
|
|
949
|
+
let { store: s, snapshot: c } = await q();
|
|
920
950
|
c.tables.some((e) => e.name === y) || V.warn(`this target has no \`${y}\` table, so this import will not be recorded.\n The import itself is unaffected. \`voltro db apply\` creates it.`);
|
|
921
|
-
let l = e.includes("--force"), p = (e) => V.warn(e), m = e.includes("--assets"), h = m ?
|
|
951
|
+
let l = e.includes("--force"), p = (e) => V.warn(e), m = e.includes("--assets"), h = m ? k() : void 0;
|
|
922
952
|
if (m && !h) return V.error("--assets: this app has no storage provider configured, so there is nowhere to put the blobs.\n Install `storagePlugin(...)` in app.config.ts or set STORAGE_PROVIDER, or drop the flag."), 1;
|
|
923
953
|
let g = G() === "postgres" ? (await import("@voltro/sql-postgres")).makePgCopySession(o()) : void 0;
|
|
924
|
-
if (
|
|
954
|
+
if (Y(r)) {
|
|
925
955
|
let t = await import("node:os"), n = await import("node:fs/promises"), { resolve: o, join: u } = await import("node:path"), d = await n.mkdtemp(u(t.tmpdir(), "voltro-import-"));
|
|
926
956
|
try {
|
|
927
|
-
let t =
|
|
928
|
-
return n && (t =
|
|
957
|
+
let t = Z(o(process.cwd(), r)), n = X(e);
|
|
958
|
+
return n && (t = N(t, n)), await Ae(t, {
|
|
929
959
|
dir: d,
|
|
930
|
-
blobs:
|
|
960
|
+
blobs: Oe(h ?? ue())
|
|
931
961
|
}), Tt(e, {
|
|
932
962
|
file: r,
|
|
933
963
|
...wt(await D.runPromise(Te({
|
|
@@ -935,7 +965,7 @@ var Fe = /* @__PURE__ */ new Set([
|
|
|
935
965
|
bundleDir: d,
|
|
936
966
|
mode: i,
|
|
937
967
|
onConflict: a,
|
|
938
|
-
atomic:
|
|
968
|
+
atomic: Q(e, i),
|
|
939
969
|
...bt(e, i, r),
|
|
940
970
|
verify: !e.includes("--no-verify"),
|
|
941
971
|
targetDialect: G(),
|
|
@@ -962,7 +992,7 @@ var Fe = /* @__PURE__ */ new Set([
|
|
|
962
992
|
bundleDir: r,
|
|
963
993
|
mode: i,
|
|
964
994
|
onConflict: a,
|
|
965
|
-
atomic:
|
|
995
|
+
atomic: Q(e, i),
|
|
966
996
|
verify: !e.includes("--no-verify"),
|
|
967
997
|
targetDialect: G(),
|
|
968
998
|
allowIncompatible: l,
|
|
@@ -971,10 +1001,10 @@ var Fe = /* @__PURE__ */ new Set([
|
|
|
971
1001
|
onWarn: p,
|
|
972
1002
|
onProgress: H,
|
|
973
1003
|
...Ct(e),
|
|
974
|
-
...h ? { assets:
|
|
1004
|
+
...h ? { assets: Ee(h) } : {},
|
|
975
1005
|
...g ? { copyLoader: g } : {}
|
|
976
1006
|
})).catch((e) => {
|
|
977
|
-
if (
|
|
1007
|
+
if (Dt(e)) return Et;
|
|
978
1008
|
throw e;
|
|
979
1009
|
}).finally(() => g?.close());
|
|
980
1010
|
return typeof _ == "symbol" ? 1 : (Tt(e, {
|
|
@@ -982,7 +1012,7 @@ var Fe = /* @__PURE__ */ new Set([
|
|
|
982
1012
|
...wt(_),
|
|
983
1013
|
assets: _.manifest.assets.count
|
|
984
1014
|
}), 0);
|
|
985
|
-
},
|
|
1015
|
+
}, At = () => {
|
|
986
1016
|
let e = o();
|
|
987
1017
|
return {
|
|
988
1018
|
...e.url ? { url: e.url } : {},
|
|
@@ -992,23 +1022,24 @@ var Fe = /* @__PURE__ */ new Set([
|
|
|
992
1022
|
...e.password ? { password: e.password } : {},
|
|
993
1023
|
...e.database ? { database: e.database } : {}
|
|
994
1024
|
};
|
|
995
|
-
},
|
|
1025
|
+
}, jt = async (e) => {
|
|
996
1026
|
let t = W(e);
|
|
997
1027
|
if (!t) return V.error("usage: voltro data backup <outDir> [--assets] (native pg_dump/mariadb-dump/… + consistent artifact)"), 1;
|
|
998
|
-
let n = G(), r = e.includes("--assets"), { mkdir: i, writeFile: a } = await import("node:fs/promises"), { join: o } = await import("node:path"), s = await
|
|
1028
|
+
let n = G(), r = e.includes("--assets"), { mkdir: i, writeFile: a } = await import("node:fs/promises"), { join: o } = await import("node:path"), s = await Nt(), c = r ? k() : void 0;
|
|
999
1029
|
if (r && !c) return V.error("--assets: this app has no storage provider configured, so there are no blobs to capture.\n Install `storagePlugin(...)` in app.config.ts or set STORAGE_PROVIDER, or drop the\n flag to take the rows-only dump."), 1;
|
|
1000
1030
|
await i(t, { recursive: !0 });
|
|
1001
|
-
let l = o(t,
|
|
1031
|
+
let l = o(t, M(n)), u = Ft(s), d = It(s), f = u ? await Ge(u, {
|
|
1002
1032
|
direction: "backup",
|
|
1003
1033
|
dialect: n,
|
|
1004
1034
|
artifact: l,
|
|
1005
1035
|
assets: r
|
|
1006
|
-
}) : void 0, p = await
|
|
1036
|
+
}) : void 0, p = await qe(F(he(n, At(), l)));
|
|
1007
1037
|
if (p !== void 0) return u && f && await z(u, f, { failure: p }), V.error(`native backup failed: ${p}`), 1;
|
|
1008
|
-
let m = await
|
|
1009
|
-
let r =
|
|
1038
|
+
let m = await Qe(n), h = await Xe(), g = (/* @__PURE__ */ new Date()).toISOString(), _ = async (e) => {
|
|
1039
|
+
let r = He({
|
|
1010
1040
|
dialect: n,
|
|
1011
|
-
schemaFingerprint: m,
|
|
1041
|
+
schemaFingerprint: m.live,
|
|
1042
|
+
dumpFingerprint: m.dump,
|
|
1012
1043
|
voltroVersion: h,
|
|
1013
1044
|
createdAt: g,
|
|
1014
1045
|
assets: e
|
|
@@ -1016,10 +1047,10 @@ var Fe = /* @__PURE__ */ new Set([
|
|
|
1016
1047
|
return await a(o(t, R), `${JSON.stringify(r, null, 2)}\n`, "utf8"), r;
|
|
1017
1048
|
}, v = 0, y = 0, b = [], x = 0, S = 0, C = 0;
|
|
1018
1049
|
if (c && u) {
|
|
1019
|
-
let e = o(t,
|
|
1050
|
+
let e = o(t, A);
|
|
1020
1051
|
await i(e, { recursive: !0 });
|
|
1021
|
-
let n = o(t,
|
|
1022
|
-
source:
|
|
1052
|
+
let n = o(t, j), r = u, a = await D.runPromiseExit(ge({
|
|
1053
|
+
source: De(c, () => J(r)),
|
|
1023
1054
|
assetsDir: e,
|
|
1024
1055
|
progress: {
|
|
1025
1056
|
done: () => !1,
|
|
@@ -1027,7 +1058,7 @@ var Fe = /* @__PURE__ */ new Set([
|
|
|
1027
1058
|
try: async () => {
|
|
1028
1059
|
await (await import("node:fs/promises")).appendFile(n, `${JSON.stringify(t)}\n`);
|
|
1029
1060
|
},
|
|
1030
|
-
catch: (e) => new
|
|
1061
|
+
catch: (e) => new fe({
|
|
1031
1062
|
reason: `asset index append failed: ${String(e)}`,
|
|
1032
1063
|
path: n
|
|
1033
1064
|
})
|
|
@@ -1054,17 +1085,17 @@ var Fe = /* @__PURE__ */ new Set([
|
|
|
1054
1085
|
artifact: l,
|
|
1055
1086
|
stamp: R,
|
|
1056
1087
|
schemaFingerprint: w.schemaFingerprint ?? "<none>",
|
|
1057
|
-
assets: r ? `${v} key(s) → ${x} object(s), ${S} byte(s) under ${
|
|
1058
|
-
recorded:
|
|
1088
|
+
assets: r ? `${v} key(s) → ${x} object(s), ${S} byte(s) under ${A}/ (from ${C} reference(s))` + (b.length > 0 ? ` — ${b.length} reference(s) had no object and are NOT in here` : "") : "NOT included — re-run with --assets, or use `voltro data export --assets`",
|
|
1089
|
+
recorded: Mt(f, d)
|
|
1059
1090
|
}), 0;
|
|
1060
|
-
},
|
|
1091
|
+
}, Mt = (e, t) => e?.recorded === !0 ? "voltro data transfers" : t === void 0 ? "NOT recorded (this target keeps no transfer history)" : `NOT recorded — bookkeeping was unavailable HERE, not at the target: ${t}`, Nt = async () => {
|
|
1061
1092
|
let e;
|
|
1062
1093
|
try {
|
|
1063
1094
|
e = await i(process.cwd());
|
|
1064
1095
|
} catch (e) {
|
|
1065
1096
|
return {
|
|
1066
1097
|
unavailable: "store-unopenable",
|
|
1067
|
-
reason: `app.config.ts could not be loaded: ${
|
|
1098
|
+
reason: `app.config.ts could not be loaded: ${Pt(e)}`
|
|
1068
1099
|
};
|
|
1069
1100
|
}
|
|
1070
1101
|
if (!e) return { unavailable: "no-app-config" };
|
|
@@ -1073,61 +1104,77 @@ var Fe = /* @__PURE__ */ new Set([
|
|
|
1073
1104
|
} catch (e) {
|
|
1074
1105
|
return {
|
|
1075
1106
|
unavailable: "store-unopenable",
|
|
1076
|
-
reason:
|
|
1107
|
+
reason: Pt(e)
|
|
1077
1108
|
};
|
|
1078
1109
|
}
|
|
1079
|
-
},
|
|
1110
|
+
}, Pt = (e) => {
|
|
1080
1111
|
let t = e?.message, n = typeof t == "string" && t.trim() !== "" ? t : String(e);
|
|
1081
1112
|
return n.split("\n")[0] ?? n;
|
|
1082
|
-
},
|
|
1083
|
-
|
|
1113
|
+
}, Ft = (e) => "store" in e ? e.store : void 0, It = (e) => "store" in e ? void 0 : e.unavailable === "no-app-config" ? "no app.config.ts was found from this directory, so there is no store to record through" : e.reason, Lt = "_voltro_migration_plans", Rt = async (e, t, n) => {
|
|
1114
|
+
if (!n.has(Lt)) return { kind: "no-table" };
|
|
1115
|
+
try {
|
|
1116
|
+
let n = (await l(e)).makeSqlLayer({ url: t }), r = (await D.runPromise(oe.SqlClient.pipe(D.flatMap((e) => e`
|
|
1117
|
+
SELECT ${e("fingerprint")} FROM ${e(Lt)}
|
|
1118
|
+
ORDER BY ${e("appliedAt")} DESC
|
|
1119
|
+
`), D.provide(n))))[0];
|
|
1120
|
+
return r === void 0 ? { kind: "empty" } : {
|
|
1121
|
+
kind: "recorded",
|
|
1122
|
+
fingerprint: r.fingerprint
|
|
1123
|
+
};
|
|
1124
|
+
} catch {
|
|
1125
|
+
return { kind: "no-table" };
|
|
1126
|
+
}
|
|
1127
|
+
}, zt = async (e, t) => {
|
|
1128
|
+
let n = G(), { join: r } = await import("node:path"), { readFile: i } = await import("node:fs/promises"), a = r(e, M(n)), o = Je({
|
|
1084
1129
|
flagUrl: U(t, "--drill-url"),
|
|
1085
1130
|
envUrl: process.env.DRILL_DB_URL,
|
|
1086
|
-
liveKeys:
|
|
1131
|
+
liveKeys: rt()
|
|
1087
1132
|
});
|
|
1088
1133
|
if (!o.ok) return V.error(`restore --drill: ${o.error}`), 1;
|
|
1089
|
-
let s = await i(r(e, R), "utf8").then(
|
|
1134
|
+
let s = await i(r(e, R), "utf8").then(Ue, () => null);
|
|
1090
1135
|
if (s && s.dialect !== n) return V.error(`restore --drill: backup is a ${s.dialect} dump but DB_DIALECT is ${n} — cross-dialect restore is never valid.`), 1;
|
|
1091
1136
|
V.info("restore drill: restoring into throwaway DB", {
|
|
1092
1137
|
dialect: n,
|
|
1093
1138
|
target: B(o.url)
|
|
1094
|
-
}), await D.runPromise(
|
|
1095
|
-
let c = null, l = 0;
|
|
1139
|
+
}), await D.runPromise(F(Ce(n, { url: o.url }, a)));
|
|
1140
|
+
let c = null, l = 0, u = /* @__PURE__ */ new Set();
|
|
1096
1141
|
try {
|
|
1097
|
-
let e = await
|
|
1098
|
-
c =
|
|
1142
|
+
let e = await nt(n, { url: o.url });
|
|
1143
|
+
c = O(e), l = e.tables.length, u = new Set(e.tables.map((e) => e.name));
|
|
1099
1144
|
} catch (e) {
|
|
1100
1145
|
return V.error(`restore --drill: could not introspect the restored DB — ${String(e)}`), 1;
|
|
1101
1146
|
}
|
|
1102
|
-
let
|
|
1103
|
-
|
|
1147
|
+
let d = Ye({
|
|
1148
|
+
dumpFingerprint: s?.dumpFingerprint ?? null,
|
|
1149
|
+
liveFingerprintAtBackup: s?.schemaFingerprint ?? null,
|
|
1104
1150
|
restoredFingerprint: c,
|
|
1105
|
-
restoredTableCount: l
|
|
1151
|
+
restoredTableCount: l,
|
|
1152
|
+
ledger: await Rt(n, o.url, u)
|
|
1106
1153
|
});
|
|
1107
|
-
for (let e of
|
|
1108
|
-
return +!
|
|
1109
|
-
},
|
|
1154
|
+
for (let e of d.lines) d.pass ? V.info(`restore drill: ${e}`) : V.error(`restore drill: ${e}`);
|
|
1155
|
+
return +!d.pass;
|
|
1156
|
+
}, Bt = async (e) => {
|
|
1110
1157
|
let t = W(e);
|
|
1111
1158
|
if (!t) return V.error("usage: voltro data restore <backupDir> [--assets] [--allow-live] [--api-url <url>] [--force] [--no-marker] [--drill [--drill-url <url>]] (native pg_restore/mariadb/…)"), 1;
|
|
1112
|
-
if (e.includes("--drill")) return
|
|
1113
|
-
if (!await
|
|
1114
|
-
let n = G(), { join: r } = await import("node:path"), { readFile: i } = await import("node:fs/promises"), a = r(t,
|
|
1115
|
-
stamp: await i(r(t, R), "utf8").then(
|
|
1159
|
+
if (e.includes("--drill")) return zt(t, e);
|
|
1160
|
+
if (!await et(e)) return 1;
|
|
1161
|
+
let n = G(), { join: r } = await import("node:path"), { readFile: i } = await import("node:fs/promises"), a = r(t, M(n)), o = e.includes("--assets"), s = We({
|
|
1162
|
+
stamp: await i(r(t, R), "utf8").then(Ue, () => null),
|
|
1116
1163
|
currentDialect: n,
|
|
1117
|
-
targetFingerprint: await
|
|
1164
|
+
targetFingerprint: await Ze(n)
|
|
1118
1165
|
});
|
|
1119
1166
|
for (let e of s.info) V.info(`backup stamp: ${e}`);
|
|
1120
1167
|
if (s.warn && V.warn(s.warn), s.refuse) {
|
|
1121
1168
|
if (!e.includes("--force")) return V.error(`restore refused: ${s.refuse} (pass --force to override)`), 1;
|
|
1122
1169
|
V.warn(`restore proceeding despite: ${s.refuse} (--force)`);
|
|
1123
1170
|
}
|
|
1124
|
-
let c = r(t,
|
|
1125
|
-
if (o && !l) return V.error(`--assets: this backup carries no assets — there is no ${
|
|
1171
|
+
let c = r(t, j), l = await i(c, "utf8").then(() => !0, () => !1);
|
|
1172
|
+
if (o && !l) return V.error(`--assets: this backup carries no assets — there is no ${j} in ${t}.\n It was taken without --assets, so its rows will reference blobs this restore cannot
|
|
1126
1173
|
put back. Restore from a backup taken with --assets, or drop the flag to accept that.`), 1;
|
|
1127
1174
|
!o && l && V.warn("this backup CARRIES assets and --assets was not passed: the rows will be restored and the blobs will not. Pass --assets to restore both.");
|
|
1128
|
-
let u = await
|
|
1175
|
+
let u = await Nt(), d = o ? k() : void 0;
|
|
1129
1176
|
if (o && !d) return V.error("--assets: this app has no storage provider configured, so there is nowhere to put the blobs.\n Install `storagePlugin(...)` in app.config.ts or set STORAGE_PROVIDER, or drop the flag."), 1;
|
|
1130
|
-
let f =
|
|
1177
|
+
let f = It(u), p = e.includes("--no-marker"), m = Ft(u), h = m ? await Ke(m, { artifact: a }) : void 0, g = f === void 0 ? h === void 0 ? `the marker row could not be written to \`${b}\` (run \`voltro db apply\` so the table exists)` : void 0 : `bookkeeping is unavailable: ${f}`;
|
|
1131
1178
|
if (g !== void 0 && !p) return V.error(`refusing to restore: the in-progress marker cannot be written.
|
|
1132
1179
|
reason: ${g}\n The marker is one row in \`${b}\`, written before the first\n destructive statement so a killed restore refuses the next boot instead of serving a
|
|
1133
1180
|
half-loaded database. Without it this restore has no guard at all.
|
|
@@ -1136,20 +1183,20 @@ var Fe = /* @__PURE__ */ new Set([
|
|
|
1136
1183
|
g !== void 0 && V.warn(`--no-marker: restoring with NO in-progress marker. If this run does not finish, nothing
|
|
1137
1184
|
will stop the next boot from serving a half-loaded database.
|
|
1138
1185
|
${g}`);
|
|
1139
|
-
let _ = m ? await
|
|
1186
|
+
let _ = m ? await Ge(m, {
|
|
1140
1187
|
direction: "restore",
|
|
1141
1188
|
dialect: n,
|
|
1142
1189
|
artifact: a,
|
|
1143
1190
|
assets: o
|
|
1144
|
-
}) : void 0, v = await
|
|
1191
|
+
}) : void 0, v = await qe(F(Ce(n, At(), a))), y = h === void 0 ? "lost" : await h.reassert();
|
|
1145
1192
|
if (y === "rewritten" ? V.warn(`the restore artefact removed the in-progress marker (it carries \`${b}\`) — it has been written back.\n Backups taken from this version on exclude that table, so this only affects older
|
|
1146
1193
|
and hand-made dumps.`) : y === "lost" && V.error(`the restore artefact removed the in-progress marker and it could NOT be written back.\n \`${b}\` is unwritable, so if this restore does not finish,\n nothing will stop the next boot from serving a half-loaded database. Check the
|
|
1147
1194
|
result of this command before you start the app.`), v !== void 0) return m && _ && await z(m, _, { failure: v }), V.error(`native restore failed: ${v}\n` + (y === "lost" ? " This database is now in an unknown state and NOTHING WILL STOP THE NEXT BOOT — see above.\n" : " This database is now in an unknown state and the next boot will REFUSE, by design.\n") + " Re-run the restore to completion, or clear the marker deliberately with\n `voltro data clear-replace-marker --yes` once you have decided the state is correct."), 1;
|
|
1148
1195
|
let x = 0, S = 0;
|
|
1149
1196
|
if (d) {
|
|
1150
1197
|
let e = await D.runPromiseExit(Se({
|
|
1151
|
-
sink:
|
|
1152
|
-
assetsDir: r(t,
|
|
1198
|
+
sink: Ee(d),
|
|
1199
|
+
assetsDir: r(t, A),
|
|
1153
1200
|
indexPath: c
|
|
1154
1201
|
}));
|
|
1155
1202
|
if (e._tag === "Failure") {
|
|
@@ -1163,9 +1210,9 @@ var Fe = /* @__PURE__ */ new Set([
|
|
|
1163
1210
|
dialect: n,
|
|
1164
1211
|
artifact: a,
|
|
1165
1212
|
assets: o ? `${x} key(s) restored from ${S} object(s)` : "not restored (no --assets)",
|
|
1166
|
-
recorded:
|
|
1213
|
+
recorded: Mt(_, f)
|
|
1167
1214
|
}), 0;
|
|
1168
|
-
},
|
|
1215
|
+
}, Vt = async (e) => {
|
|
1169
1216
|
let t = W(e);
|
|
1170
1217
|
if (!t) return V.error("usage: voltro data inspect <file.vbundle|dir> [--json] [--passphrase <p>]"), 1;
|
|
1171
1218
|
let { resolve: n, join: r } = await import("node:path"), i = await import("node:fs/promises"), a = n(process.cwd(), t);
|
|
@@ -1178,38 +1225,63 @@ var Fe = /* @__PURE__ */ new Set([
|
|
|
1178
1225
|
let s = await i.readFile(n, "utf8").catch(() => void 0);
|
|
1179
1226
|
return s === void 0 ? (V.error(`'${t}' is a directory but holds no manifest.json — is it an unpacked bundle? (a bundle exported with --target api is always unpacked into a directory, even when the path ends in .vbundle)`), 1) : (console.log(s), 0);
|
|
1180
1227
|
}
|
|
1181
|
-
let o =
|
|
1182
|
-
let e =
|
|
1183
|
-
return o ?
|
|
1228
|
+
let o = X(e), s = () => {
|
|
1229
|
+
let e = Z(a);
|
|
1230
|
+
return o ? N(e, o) : e;
|
|
1184
1231
|
};
|
|
1185
1232
|
if (e.includes("--json")) {
|
|
1186
|
-
let e = (await
|
|
1233
|
+
let e = (await P(s(), ["manifest.json"])).get("manifest.json");
|
|
1187
1234
|
return e ? (console.log(e.toString("utf8")), 0) : (V.error("no manifest.json found in the bundle"), 1);
|
|
1188
1235
|
}
|
|
1189
|
-
let c = (await
|
|
1236
|
+
let c = (await P(s(), ["README.md"])).get("README.md");
|
|
1190
1237
|
if (c) return console.log(c.toString("utf8")), 0;
|
|
1191
|
-
let l = (await
|
|
1238
|
+
let l = (await P(s(), ["manifest.json"])).get("manifest.json");
|
|
1192
1239
|
return l ? (console.log(l.toString("utf8")), 0) : (V.error("bundle has no README.md or manifest.json"), 1);
|
|
1193
|
-
},
|
|
1194
|
-
let { REPLACE_IN_PROGRESS_TABLE: t } = await import("@voltro/database"), { interruptedReplaceAtBoot: n } = await import("./interruptedReplace-
|
|
1240
|
+
}, Ht = async (e) => {
|
|
1241
|
+
let { REPLACE_IN_PROGRESS_TABLE: t } = await import("@voltro/database"), { interruptedReplaceAtBoot: n } = await import("./interruptedReplace-qzmFI020.js"), { store: r } = await q(), i = await n(r);
|
|
1195
1242
|
if (i === void 0) return V.info("no interrupted replace is recorded — nothing to clear"), 0;
|
|
1196
1243
|
if (!e.includes("--yes")) return V.error(`${i}\n\n Re-run with --yes to clear it.`), 1;
|
|
1197
|
-
let a = await r.query({
|
|
1244
|
+
let a = (await r.query({
|
|
1198
1245
|
table: t,
|
|
1199
1246
|
predicate: void 0,
|
|
1200
1247
|
order: [],
|
|
1201
1248
|
take: void 0,
|
|
1202
1249
|
skip: void 0,
|
|
1203
1250
|
projection: void 0
|
|
1204
|
-
});
|
|
1251
|
+
})).filter((e) => String(e.kind ?? "replace") !== "staging");
|
|
1205
1252
|
for (let e of a) await r.delete(t, String(e.id));
|
|
1206
1253
|
return V.info(`cleared ${a.length} interrupted-replace marker(s) — the next boot will start`), 0;
|
|
1207
|
-
},
|
|
1208
|
-
let { STAGING_PREFIX: t, dropStagingSql: n, execStatements: r } = await import("@voltro/data-transfer"), { store: i } = await
|
|
1254
|
+
}, Ut = async (e) => {
|
|
1255
|
+
let { STAGING_PREFIX: t, dropStagingSql: n, execStatements: r } = await import("@voltro/data-transfer"), { store: i } = await q(), a = G();
|
|
1209
1256
|
if (a !== "postgres" && a !== "mysql" && a !== "mariadb" && a !== "sqlite" && a !== "mssql") return V.error(`this store has no staging tables to clear (dialect: ${a})`), 1;
|
|
1210
1257
|
let { introspectSchema: o } = await import("@voltro/database/sql"), s = (await i.run((await import("@effect/sql")).SqlClient.SqlClient.pipe((await import("effect")).Effect.flatMap(o)))).tables.map((e) => e.name).filter((e) => e.startsWith(t));
|
|
1211
|
-
|
|
1212
|
-
},
|
|
1258
|
+
if (s.length === 0) return V.info("no staging tables are present — nothing to clear"), 0;
|
|
1259
|
+
let { REPLACE_IN_PROGRESS_TABLE: c, stagingNamesOf: l } = await import("@voltro/database"), u = await (async () => {
|
|
1260
|
+
try {
|
|
1261
|
+
return (await i.query({
|
|
1262
|
+
table: c,
|
|
1263
|
+
predicate: void 0,
|
|
1264
|
+
order: [],
|
|
1265
|
+
take: void 0,
|
|
1266
|
+
skip: void 0,
|
|
1267
|
+
projection: void 0
|
|
1268
|
+
})).filter((e) => String(e.kind ?? "") === "staging");
|
|
1269
|
+
} catch {
|
|
1270
|
+
return [];
|
|
1271
|
+
}
|
|
1272
|
+
})(), d = /* @__PURE__ */ new Map();
|
|
1273
|
+
for (let e of u) for (let t of l(e.staging)) d.set(t, e);
|
|
1274
|
+
let f = (e) => {
|
|
1275
|
+
let t = d.get(e);
|
|
1276
|
+
if (t === void 0) return `${e} — no run claims it (an orphan, or from before the marker)`;
|
|
1277
|
+
let n = t.heartbeatAt, r = n == null ? "never" : `${Math.max(0, Math.round((Date.now() - new Date(String(n)).getTime()) / 6e4))} min ago`;
|
|
1278
|
+
return `${e} — ${t.resumable === !0 || t.resumable === 1 ? "RESUMABLE: a `--no-atomic` re-run continues from it" : "from an interrupted run"}, last active ${r}`;
|
|
1279
|
+
};
|
|
1280
|
+
if (!e.includes("--yes")) return V.error(`${s.length} staging table(s) from an earlier \`--mode replace\`:\n` + s.map((e) => ` ${f(e)}`).join("\n") + "\n\n Each holds a copy of what a run was loading. Nothing else removes them.\n A RESUMABLE one is a resume point — dropping it makes the next run reload the whole bundle.\n Re-run with --yes to drop them all."), 1;
|
|
1281
|
+
await r(i, s.map((e) => n(e.slice(t.length), a)));
|
|
1282
|
+
for (let e of u) l(e.staging).some((e) => s.includes(e)) && await i.delete(c, String(e.id)).catch(() => void 0);
|
|
1283
|
+
return V.info(`dropped ${s.length} staging table(s)`), 0;
|
|
1284
|
+
}, Wt = async (e) => {
|
|
1213
1285
|
let { describeTransferRun: t, DATA_TRANSFERS_TABLE: n } = await import("@voltro/database"), r = Number(U(e, "--limit") ?? "20"), i = Number.isFinite(r) ? Math.max(1, Math.min(200, Math.trunc(r))) : 20;
|
|
1214
1286
|
if (U(e, "--target") === "api") {
|
|
1215
1287
|
let t = U(e, "--api-url");
|
|
@@ -1226,7 +1298,7 @@ var Fe = /* @__PURE__ */ new Set([
|
|
|
1226
1298
|
for (let e of s) V.info(` ${e.line ?? ""}`);
|
|
1227
1299
|
return 0;
|
|
1228
1300
|
}
|
|
1229
|
-
let { store: a } = await
|
|
1301
|
+
let { store: a } = await q(), o;
|
|
1230
1302
|
try {
|
|
1231
1303
|
o = await a.query({
|
|
1232
1304
|
table: n,
|
|
@@ -1252,15 +1324,15 @@ var Fe = /* @__PURE__ */ new Set([
|
|
|
1252
1324
|
})}`);
|
|
1253
1325
|
}
|
|
1254
1326
|
return 0;
|
|
1255
|
-
},
|
|
1327
|
+
}, Gt = async (e) => {
|
|
1256
1328
|
let t = W(e), n = W(e.filter((e) => e !== t));
|
|
1257
1329
|
if (!t || !n) return V.error("usage: voltro data unpack <file.vbundle> <dir> [--passphrase <p>]"), 1;
|
|
1258
|
-
let { resolve: r } = await import("node:path"), i =
|
|
1259
|
-
return a && (i =
|
|
1330
|
+
let { resolve: r } = await import("node:path"), i = Z(r(process.cwd(), t)), a = X(e);
|
|
1331
|
+
return a && (i = N(i, a)), await je(i, r(process.cwd(), n)), V.info("unpack complete", {
|
|
1260
1332
|
file: t,
|
|
1261
1333
|
dir: n
|
|
1262
1334
|
}), 0;
|
|
1263
|
-
},
|
|
1335
|
+
}, Kt = {
|
|
1264
1336
|
export: {
|
|
1265
1337
|
common: [
|
|
1266
1338
|
"--target",
|
|
@@ -1341,40 +1413,40 @@ var Fe = /* @__PURE__ */ new Set([
|
|
|
1341
1413
|
"--drill-url",
|
|
1342
1414
|
"--api-url"
|
|
1343
1415
|
] }
|
|
1344
|
-
},
|
|
1345
|
-
let n =
|
|
1416
|
+
}, qt = (e) => e.split("=")[0], Jt = (e, t) => {
|
|
1417
|
+
let n = Kt[e];
|
|
1346
1418
|
if (!n) return;
|
|
1347
1419
|
let r = U(t, "--target") === "api" ? "api" : "direct", i = /* @__PURE__ */ new Set([...n.common, ...r === "api" ? n.apiOnly ?? [] : n.directOnly ?? []]), a = new Set(r === "api" ? n.directOnly ?? [] : n.apiOnly ?? []);
|
|
1348
1420
|
for (let n of t) {
|
|
1349
1421
|
if (!n.startsWith("--")) continue;
|
|
1350
|
-
let t =
|
|
1422
|
+
let t = qt(n);
|
|
1351
1423
|
if (i.has(t)) continue;
|
|
1352
1424
|
if (a.has(t)) return `voltro data ${e}: ${t} is not read with --target ${r} (it is a --target ${r === "api" ? "direct" : "api"} flag). Remove it, or change --target.`;
|
|
1353
|
-
let o = Object.entries(
|
|
1425
|
+
let o = Object.entries(Kt).filter(([n, r]) => n !== e && [
|
|
1354
1426
|
...r.common,
|
|
1355
1427
|
...r.directOnly ?? [],
|
|
1356
1428
|
...r.apiOnly ?? []
|
|
1357
1429
|
].includes(t)).map(([e]) => e);
|
|
1358
1430
|
return o.length > 0 ? `voltro data ${e}: ${t} is not a \`${e}\` flag (it belongs to \`voltro data ${o.join("` / `")}\`). Remove it.` : `voltro data ${e}: unknown flag ${t}. Run \`voltro data --help\` for the flags this command reads.`;
|
|
1359
1431
|
}
|
|
1360
|
-
},
|
|
1432
|
+
}, Yt = {
|
|
1361
1433
|
export: xt,
|
|
1362
|
-
import:
|
|
1363
|
-
transfers:
|
|
1364
|
-
inspect:
|
|
1365
|
-
unpack:
|
|
1366
|
-
backup:
|
|
1367
|
-
restore:
|
|
1368
|
-
"clear-replace-marker":
|
|
1369
|
-
"clear-staging":
|
|
1370
|
-
},
|
|
1434
|
+
import: kt,
|
|
1435
|
+
transfers: Wt,
|
|
1436
|
+
inspect: Vt,
|
|
1437
|
+
unpack: Gt,
|
|
1438
|
+
backup: jt,
|
|
1439
|
+
restore: Bt,
|
|
1440
|
+
"clear-replace-marker": Ht,
|
|
1441
|
+
"clear-staging": Ut
|
|
1442
|
+
}, Xt = async (n) => {
|
|
1371
1443
|
let [r, ...i] = n;
|
|
1372
|
-
if (r !== void 0 &&
|
|
1373
|
-
let e =
|
|
1444
|
+
if (r !== void 0 && Kt[r] !== void 0) {
|
|
1445
|
+
let e = Jt(r, i);
|
|
1374
1446
|
if (e) return V.error(e), 2;
|
|
1375
1447
|
}
|
|
1376
|
-
let a = r === void 0 ? void 0 :
|
|
1448
|
+
let a = r === void 0 ? void 0 : Yt[r];
|
|
1377
1449
|
return a === void 0 ? (V.error(`usage: voltro data ${e(t)} … (logical: export/import; native: backup/restore; \`imports\` reads the history, incl. a run in flight)`), r ? 2 : 1) : a(i);
|
|
1378
1450
|
};
|
|
1379
1451
|
//#endregion
|
|
1380
|
-
export {
|
|
1452
|
+
export { Xt as runDataCommand };
|