@voltro/cli 0.50.0 → 0.50.1
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 +56 -0
- package/dist/apiBuild-BZ-plaQN.js +2 -0
- package/dist/{apiBuild-CFYXo06U.js → apiBuild-D3s7lx57.js} +2 -2
- package/dist/appGraph-CM9wvUmD.js +2 -0
- package/dist/appGraph-Cv2W_wO5.js +111 -0
- package/dist/bin.js +1 -1
- package/dist/{build-CnSH-zru.js → build-CFmqF39c.js} +7 -7
- package/dist/{checkCommand-OL41BMIb.js → checkCommand-DomvYDi1.js} +1 -1
- package/dist/{checkCommand-Do3SQbLx.js → checkCommand-DsKIjL6k.js} +54 -45
- package/dist/{codegenCommand-BJzaRz6q.js → codegenCommand-C0S27nbB.js} +2 -2
- package/dist/{codemodRunner-De3k0pRd.js → codemodRunner-DwY8Rjbl.js} +55 -0
- package/dist/{commands-By23Ljny.js → commands-Brp4DI8u.js} +21 -21
- package/dist/{dashboardCommand-DN1rAABE.js → dashboardCommand-bbLfc9FW.js} +3 -3
- package/dist/{dataCommand-B93TUBIx.js → dataCommand-k9mXTXin.js} +331 -303
- package/dist/{dev-1yu42ubu.js → dev-C0cbqysu.js} +3 -3
- package/dist/{dev-BWwE0QjK.js → dev-Dz9GqA6P.js} +1 -1
- package/dist/doctorCommand-BlciWUJb.js +2 -0
- package/dist/{doctorCommand-y-EbP8st.js → doctorCommand-DyViaaCP.js} +38 -22
- package/dist/{doctorSourceRelations-Dd48LPvO.js → doctorSourceRelations-c5mlN5HI.js} +66 -34
- package/dist/{dormancyCommand-Dc7KRNCG.js → dormancyCommand-DMZPA_HW.js} +1 -1
- package/dist/{embeddingsCommand-kPs132bB.js → embeddingsCommand-CMxRPkg6.js} +1 -1
- package/dist/{envCommand-E12zSt51.js → envCommand-CDLr-yD4.js} +2 -2
- package/dist/{evolveCommand-B3nagPVc.js → evolveCommand-Ct4NesL3.js} +2 -2
- package/dist/{generateCommand-CcyvH2ve.js → generateCommand-BDdOxhnv.js} +1 -1
- package/dist/index.js +1 -1
- package/dist/{infoCommand-Cq0Yz98u.js → infoCommand-orsKJKUy.js} +1 -1
- package/dist/{inspect-B7U7Cl_Z.js → inspect-CtL_xTbu.js} +1 -0
- package/dist/inspect-CuGDYES0.js +2 -0
- package/dist/manifestBuild-CPjhvM62.js +2 -0
- package/dist/{manifestBuild-BK42hu0k.js → manifestBuild-CuU1VrSm.js} +2 -2
- package/dist/{runtimeTrace-BpLUcrfy.js → runtimeTrace-Bo8jWsNe.js} +1 -1
- package/dist/{sdkgen-dl4-WKF6.js → sdkgen-OZhiT2vz.js} +1 -1
- package/dist/serveCommand-Bbgg7Paq.js +2 -0
- package/dist/{serveCommand-8CTzQCY0.js → serveCommand-YbD5VgfV.js} +4 -4
- package/dist/serveEntry.js +1 -1
- package/dist/{start-DvlYDyAr.js → start-7b_bL8Uc.js} +1 -1
- package/dist/{start-7HcSIKEp.js → start-B1nH9ahf.js} +2 -2
- package/dist/startEntry.js +1 -1
- package/dist/{updateCommand-i8JbrLAA.js → updateCommand-BgH6vTtq.js} +1 -1
- package/dist/updateCommand-BweY17z-.js +2 -0
- package/dist/{webDev-B4IEMB0n.js → webDev-BbdkzMbd.js} +1 -1
- package/dist/webDev-BcRFTsMA.js +2 -0
- package/dist/{webhooksCommand-B0bdyuQ4.js → webhooksCommand-DjE6u0r4.js} +1 -1
- package/package.json +23 -17
- package/templates/AGENTS.md +1 -1
- package/templates/agent-docs/_index.md +1 -1
- package/templates/agent-docs/cli.md +63 -3
- package/templates/agent-docs/data.md +36 -0
- package/templates/agent-docs/whats-new.md +30 -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-sqlite/package.json +8 -8
- package/templates/apps/api-backend-storage/package.json +8 -8
- package/templates/apps/api-cms/package.json +10 -10
- package/templates/apps/api-collab/package.json +8 -8
- package/templates/apps/api-data-advanced/package.json +8 -8
- package/templates/apps/api-durable/package.json +8 -8
- package/templates/apps/api-feature-flags/package.json +9 -9
- package/templates/apps/api-governance/package.json +8 -8
- package/templates/apps/api-kv/package.json +8 -8
- package/templates/apps/api-moderation/package.json +8 -8
- package/templates/apps/api-observability/package.json +8 -8
- package/templates/apps/api-ratelimit/package.json +8 -8
- package/templates/apps/api-rbac/package.json +8 -8
- package/templates/apps/api-rest/package.json +7 -7
- package/templates/apps/api-saas/package.json +11 -11
- package/templates/apps/api-saas-starter/package.json +10 -10
- package/templates/apps/api-search/package.json +8 -8
- package/templates/apps/api-status/package.json +8 -8
- package/templates/apps/api-versioning/package.json +8 -8
- package/templates/apps/api-webhooks/package.json +9 -9
- package/templates/apps/changelog/package.json +6 -6
- package/templates/apps/edge-functions/package.json +2 -2
- package/templates/apps/frontend-admin/package.json +8 -8
- package/templates/apps/frontend-app/package.json +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 +7 -7
- package/templates/apps/frontend-i18n/package.json +6 -6
- package/templates/apps/frontend-landing/package.json +7 -7
- package/templates/apps/frontend-portal/package.json +8 -8
- package/templates/apps/frontend-saas/package.json +8 -8
- package/templates/apps/frontend-spa/package.json +7 -7
- package/templates/apps/frontend-ssr/package.json +7 -7
- package/templates/apps/frontend-ssr-api/package.json +8 -8
- package/templates/apps/frontend-static-blog/package.json +6 -6
- package/templates/apps/frontend-status/package.json +8 -8
- package/templates/apps/mobile-app/package.json +4 -4
- package/dist/apiBuild-BWo8KI6n.js +0 -2
- package/dist/appGraph-KGDPTuTy.js +0 -110
- package/dist/appGraph-zuMGKVYX.js +0 -2
- package/dist/doctorCommand-BmovAwb7.js +0 -2
- package/dist/inspect-DUze25t0.js +0 -2
- package/dist/manifestBuild-j0n109tt.js +0 -2
- package/dist/serveCommand-C_WI4TTG.js +0 -2
- package/dist/updateCommand-D91-TZ7E.js +0 -2
- package/dist/webDev-B79ji8M5.js +0 -2
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { i as e, t } from "./subcommandNames-DpYs3DXr.js";
|
|
2
2
|
import { t as n } from "./cliError-BmdYnghb.js";
|
|
3
|
-
import { c as r, i, o as a, z as o } from "./dev-
|
|
3
|
+
import { c as r, i, o as a, z as o } from "./dev-C0cbqysu.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";
|
|
@@ -8,13 +8,13 @@ import { o as m, t as h } from "./inspectFetch-EMuhTG_9.js";
|
|
|
8
8
|
import { r as g } from "./dbCommand-Bf7Cy0mn.js";
|
|
9
9
|
import { resolve as _ } from "node:path";
|
|
10
10
|
import { createLogger as v } from "@voltro/logger";
|
|
11
|
-
import { DATA_TRANSFERS_TABLE as y, REPLACE_IN_PROGRESS_TABLE as b, _voltroDataTransfersTable as x, _voltroReplaceInProgressTable as S, connectionIdentity as C, derivedRowId as ee, ensureTableRegistered as
|
|
12
|
-
import { Cause as
|
|
13
|
-
import { SqlClient as
|
|
14
|
-
import { declaredSnapshot as
|
|
15
|
-
import { STORAGE_REFS_TABLE as fe,
|
|
11
|
+
import { DATA_TRANSFERS_TABLE as y, REPLACE_IN_PROGRESS_TABLE as b, _voltroDataTransfersTable as x, _voltroReplaceInProgressTable as S, connectionIdentity as C, derivedRowId as ee, ensureTableRegistered as w, normaliseTarget as te, sameDatabase as ne, streamTable as re } from "@voltro/database";
|
|
12
|
+
import { Cause as T, Effect as E, Exit as ie, Option as ae, Stream as oe } from "effect";
|
|
13
|
+
import { SqlClient as se } from "@effect/sql";
|
|
14
|
+
import { declaredSnapshot as ce, fingerprintSchema as le, introspectSchema as ue } from "@voltro/database/sql";
|
|
15
|
+
import { STORAGE_REFS_TABLE as de, appStorageProvider as fe, configuredStorageProvider as D } from "@voltro/plugin-storage";
|
|
16
16
|
import { createHash as pe } from "node:crypto";
|
|
17
|
-
import { ASSETS_DIR as
|
|
17
|
+
import { ASSETS_DIR as O, ASSET_INDEX_FILE as k, BundleError as me, asImportError as he, assessLiveImport as ge, backupArtifactName as A, backupCommand as _e, captureAssetsToCas as ve, classificationFromSnapshot as ye, decryptStream as j, encryptStream as be, packBundle as xe, peekBundle as M, previewMasking as Se, restoreAssetsFromCas as Ce, restoreCommand as we, runExport as Te, runImport as Ee, runNativeStep as N, storageAssetSink as De, storageAssetSource as Oe, storageBlobSink as ke, storageBlobSource as Ae, unpackBundle as je, unpackBundleToDir as Me } from "@voltro/data-transfer";
|
|
18
18
|
import { request as Ne } from "node:http";
|
|
19
19
|
import { request as Pe } from "node:https";
|
|
20
20
|
//#region src/longRequest.ts
|
|
@@ -26,7 +26,7 @@ var Fe = /* @__PURE__ */ new Set([
|
|
|
26
26
|
"ENOTFOUND",
|
|
27
27
|
"ECONNRESET",
|
|
28
28
|
"EAI_AGAIN"
|
|
29
|
-
]),
|
|
29
|
+
]), P = class extends Error {
|
|
30
30
|
url;
|
|
31
31
|
cause;
|
|
32
32
|
constructor(e, t) {
|
|
@@ -39,13 +39,13 @@ 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
|
+
}, F = 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
|
-
},
|
|
48
|
+
}, I = async (e, t, n, r) => {
|
|
49
49
|
let i = new URL(e), a = i.protocol === "https:" ? Pe : Ne, o = Date.now();
|
|
50
50
|
return await new Promise((s, c) => {
|
|
51
51
|
let l = a({
|
|
@@ -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(Fe.has(t.code ?? "") ? new
|
|
67
|
+
l.destroy(new F(Date.now() - o, e));
|
|
68
|
+
}), l.on("error", (t) => c(Fe.has(t.code ?? "") ? new P(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
|
+
}, L = (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`, Ie = (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.", Le = 16777216, Re = 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;
|
|
@@ -131,7 +131,7 @@ var Fe = /* @__PURE__ */ new Set([
|
|
|
131
131
|
bytes: t,
|
|
132
132
|
resumedFrom: e.alreadyHeld
|
|
133
133
|
};
|
|
134
|
-
},
|
|
134
|
+
}, R = "voltro-backup-stamp.json", Ve = (e) => ({
|
|
135
135
|
stampVersion: 1,
|
|
136
136
|
dialect: e.dialect,
|
|
137
137
|
schemaFingerprint: e.schemaFingerprint,
|
|
@@ -165,7 +165,7 @@ var Fe = /* @__PURE__ */ new Set([
|
|
|
165
165
|
}, Ue = (e) => {
|
|
166
166
|
let { stamp: t, currentDialect: n, currentFingerprint: r } = e;
|
|
167
167
|
if (t === null) return {
|
|
168
|
-
warn: `no ${
|
|
168
|
+
warn: `no ${R} beside the dump — an older/handmade backup. Cannot verify dialect or schema version; make sure this dump is a ${n} dump.`,
|
|
169
169
|
info: []
|
|
170
170
|
};
|
|
171
171
|
let i = [`backup produced by @voltro/cli ${t.voltroVersion} at ${t.createdAt}`, `schema fingerprint: ${t.schemaFingerprint ?? "<none recorded>"}`];
|
|
@@ -179,7 +179,7 @@ var Fe = /* @__PURE__ */ new Set([
|
|
|
179
179
|
}, We = async (e, t) => {
|
|
180
180
|
let n = ee("dimp", `${t.direction}-${t.artifact}-${Date.now()}-${Math.random()}`);
|
|
181
181
|
try {
|
|
182
|
-
return
|
|
182
|
+
return w(x), await e.insert(y, {
|
|
183
183
|
id: n,
|
|
184
184
|
direction: t.direction,
|
|
185
185
|
startedAt: /* @__PURE__ */ new Date(),
|
|
@@ -202,43 +202,63 @@ var Fe = /* @__PURE__ */ new Set([
|
|
|
202
202
|
recorded: !1
|
|
203
203
|
};
|
|
204
204
|
}
|
|
205
|
-
},
|
|
205
|
+
}, z = async (e, t, n) => {
|
|
206
206
|
t.recorded && await e.update(y, t.runId, {
|
|
207
207
|
finishedAt: /* @__PURE__ */ new Date(),
|
|
208
208
|
failure: n.failure ?? null,
|
|
209
209
|
...n.assets === void 0 ? {} : { rowsWritten: n.assets }
|
|
210
210
|
}).then(() => void 0, () => void 0);
|
|
211
211
|
}, Ge = async (e, t) => {
|
|
212
|
-
let n = ee("repl", `restore-${t.artifact}-${Date.now()}-${Math.random()}`)
|
|
212
|
+
let n = ee("repl", `restore-${t.artifact}-${Date.now()}-${Math.random()}`), r = {
|
|
213
|
+
id: n,
|
|
214
|
+
startedAt: /* @__PURE__ */ new Date(),
|
|
215
|
+
kind: "restore",
|
|
216
|
+
tables: null,
|
|
217
|
+
capture: t.artifact,
|
|
218
|
+
via: "cli"
|
|
219
|
+
};
|
|
213
220
|
try {
|
|
214
|
-
|
|
215
|
-
id: n,
|
|
216
|
-
startedAt: /* @__PURE__ */ new Date(),
|
|
217
|
-
kind: "restore",
|
|
218
|
-
tables: null,
|
|
219
|
-
capture: t.artifact,
|
|
220
|
-
via: "cli"
|
|
221
|
-
});
|
|
221
|
+
w(S), await e.insert(b, r);
|
|
222
222
|
} catch {
|
|
223
223
|
return;
|
|
224
224
|
}
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
225
|
+
let i = async () => (await e.query({
|
|
226
|
+
table: b,
|
|
227
|
+
predicate: void 0,
|
|
228
|
+
order: [],
|
|
229
|
+
take: void 0,
|
|
230
|
+
skip: void 0,
|
|
231
|
+
projection: void 0
|
|
232
|
+
})).some((e) => String(e.id ?? "") === n);
|
|
233
|
+
return {
|
|
234
|
+
reassert: async () => {
|
|
235
|
+
try {
|
|
236
|
+
if (await i()) return "held";
|
|
237
|
+
} catch {}
|
|
238
|
+
try {
|
|
239
|
+
return w(S), await e.insert(b, r), "rewritten";
|
|
240
|
+
} catch {
|
|
241
|
+
return "lost";
|
|
242
|
+
}
|
|
243
|
+
},
|
|
244
|
+
clear: async () => {
|
|
245
|
+
await e.deleteMany(b, { where: {
|
|
246
|
+
column: "id",
|
|
247
|
+
op: "eq",
|
|
248
|
+
value: n
|
|
249
|
+
} }).then(() => void 0, () => void 0);
|
|
250
|
+
}
|
|
231
251
|
};
|
|
232
252
|
}, Ke = async (e) => {
|
|
233
|
-
let t = await
|
|
234
|
-
if (
|
|
235
|
-
let n =
|
|
236
|
-
if (
|
|
253
|
+
let t = await E.runPromiseExit(e);
|
|
254
|
+
if (ie.isSuccess(t)) return;
|
|
255
|
+
let n = T.failureOption(t.cause);
|
|
256
|
+
if (ae.isSome(n)) {
|
|
237
257
|
let e = n.value;
|
|
238
258
|
return String(e?.message ?? e);
|
|
239
259
|
}
|
|
240
|
-
return
|
|
241
|
-
},
|
|
260
|
+
return T.pretty(t.cause);
|
|
261
|
+
}, B = (e) => {
|
|
242
262
|
try {
|
|
243
263
|
let t = new URL(e), n = t.pathname.replace(/^\//, "");
|
|
244
264
|
return `${t.hostname.toLowerCase()}:${t.port || "(default)"}/${n}`;
|
|
@@ -251,7 +271,7 @@ var Fe = /* @__PURE__ */ new Set([
|
|
|
251
271
|
ok: !1,
|
|
252
272
|
error: "no drill target — pass `--drill-url <url>` or set `DRILL_DB_URL`. The drill restores into a THROWAWAY database (never the live one), so it needs its own connection."
|
|
253
273
|
};
|
|
254
|
-
let n =
|
|
274
|
+
let n = B(t);
|
|
255
275
|
return e.liveKeys.some((e) => e === n) ? {
|
|
256
276
|
ok: !1,
|
|
257
277
|
error: `drill target (${n}) is the LIVE database — a restore drill must target a throwaway DB, never production. It would --clean and overwrite it.`
|
|
@@ -274,7 +294,7 @@ var Fe = /* @__PURE__ */ new Set([
|
|
|
274
294
|
pass: !0,
|
|
275
295
|
lines: [`PASS — restored ${r} table(s); schema fingerprint matches the backup stamp (${n.slice(0, 12)}).`]
|
|
276
296
|
};
|
|
277
|
-
},
|
|
297
|
+
}, V = v({ scope: "voltro:data" }), Ye = async () => {
|
|
278
298
|
try {
|
|
279
299
|
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");
|
|
280
300
|
return JSON.parse(n(r, "utf8")).version ?? "unknown";
|
|
@@ -283,14 +303,14 @@ var Fe = /* @__PURE__ */ new Set([
|
|
|
283
303
|
}
|
|
284
304
|
}, Xe = async (e) => {
|
|
285
305
|
try {
|
|
286
|
-
return
|
|
306
|
+
return le(await tt(e));
|
|
287
307
|
} catch {
|
|
288
308
|
return null;
|
|
289
309
|
}
|
|
290
|
-
},
|
|
310
|
+
}, H = (e) => {
|
|
291
311
|
if (e.event !== "done") return;
|
|
292
312
|
let t = e.total === void 0 ? "" : ` / ${e.total}`;
|
|
293
|
-
|
|
313
|
+
V.info(`${e.phase} [${e.index + 1}/${e.tableCount}] ${e.table}`, { rows: `${e.rowsDone}${t}` });
|
|
294
314
|
}, Ze = async (e) => {
|
|
295
315
|
let t = (e) => e?.meta?.databaseId;
|
|
296
316
|
if (e) {
|
|
@@ -312,20 +332,20 @@ var Fe = /* @__PURE__ */ new Set([
|
|
|
312
332
|
}
|
|
313
333
|
return null;
|
|
314
334
|
}, Qe = async (e) => {
|
|
315
|
-
let t = e.includes("--allow-live"), n = t ? null : await Ze(
|
|
316
|
-
dialect:
|
|
335
|
+
let t = e.includes("--allow-live"), n = t ? null : await Ze(U(e, "--api-url")), r = C({
|
|
336
|
+
dialect: G(),
|
|
317
337
|
...a()
|
|
318
|
-
}), i =
|
|
319
|
-
dialect:
|
|
338
|
+
}), i = te({
|
|
339
|
+
dialect: G(),
|
|
320
340
|
...a()
|
|
321
341
|
}), { refuse: o, message: s } = ge({
|
|
322
342
|
liveInstance: n?.label ?? null,
|
|
323
343
|
allowLive: t,
|
|
324
|
-
sameTarget:
|
|
344
|
+
sameTarget: ne(r, n?.databaseId),
|
|
325
345
|
...i === void 0 ? {} : { targetLabel: `${i.host}:${i.port}/${i.database}` }
|
|
326
346
|
});
|
|
327
|
-
return !o || (
|
|
328
|
-
},
|
|
347
|
+
return !o || (V.error(s ?? "refusing: live instance detected"), !1);
|
|
348
|
+
}, U = s, $e = [
|
|
329
349
|
"--api-url",
|
|
330
350
|
"--bundle-key",
|
|
331
351
|
"--compression",
|
|
@@ -345,42 +365,42 @@ var Fe = /* @__PURE__ */ new Set([
|
|
|
345
365
|
"--timeout",
|
|
346
366
|
"--tenant",
|
|
347
367
|
"--token"
|
|
348
|
-
],
|
|
368
|
+
], W = (e) => c(e, $e), G = () => {
|
|
349
369
|
let e = (process.env.DB_DIALECT ?? "postgres").toLowerCase();
|
|
350
370
|
if (e === "memory") throw new n("voltro data: requires a real dialect, not DB_DIALECT=memory.");
|
|
351
371
|
return e;
|
|
352
372
|
}, et = async (e, t) => {
|
|
353
373
|
let n = (await l(e)).makeSqlLayer(t);
|
|
354
|
-
return
|
|
374
|
+
return E.runPromise(se.SqlClient.pipe(E.flatMap(ue), E.provide(n)));
|
|
355
375
|
}, tt = (e) => et(e, a()), nt = () => {
|
|
356
376
|
let e = a(), t = [];
|
|
357
|
-
return e.url && t.push(
|
|
358
|
-
},
|
|
377
|
+
return e.url && t.push(B(e.url)), (e.host || e.database) && t.push(`${(e.host ?? "localhost").toLowerCase()}:${e.port ?? "(default)"}/${e.database ?? ""}`), t;
|
|
378
|
+
}, K = async () => {
|
|
359
379
|
let e = process.cwd(), t = await r(e);
|
|
360
380
|
if (!t) throw new n("voltro data: no app.config.ts found in the current directory.");
|
|
361
|
-
let a = await i(t, void 0), o =
|
|
381
|
+
let a = await i(t, void 0), o = G();
|
|
362
382
|
return {
|
|
363
383
|
store: a,
|
|
364
384
|
snapshot: await tt(o),
|
|
365
385
|
dialect: o
|
|
366
386
|
};
|
|
367
|
-
},
|
|
368
|
-
table:
|
|
387
|
+
}, q = (e) => re(e, {
|
|
388
|
+
table: de,
|
|
369
389
|
chunkSize: 500
|
|
370
|
-
}).pipe(
|
|
390
|
+
}).pipe(oe.map((e) => ({
|
|
371
391
|
key: String(e.key),
|
|
372
392
|
contentType: String(e.contentType ?? "application/octet-stream"),
|
|
373
393
|
size: Number(e.size ?? 0)
|
|
374
|
-
}))),
|
|
394
|
+
}))), J = (e) => !!e && e.endsWith(".vbundle"), Y = (e) => U(e, "--passphrase") ?? process.env.VOLTRO_BUNDLE_KEY, rt = (e) => e.includes("--encrypt") || U(e, "--passphrase") !== void 0 || process.env.VOLTRO_BUNDLE_KEY !== void 0, it = async (e, t) => {
|
|
375
395
|
let { createWriteStream: n } = await import("node:fs"), { pipeline: r } = await import("node:stream/promises"), { Readable: i } = await import("node:stream");
|
|
376
396
|
await r(i.from(e), n(t));
|
|
377
|
-
},
|
|
397
|
+
}, X = (e) => ({ async *[Symbol.asyncIterator]() {
|
|
378
398
|
let { createReadStream: t } = await import("node:fs");
|
|
379
399
|
for await (let n of t(e)) yield n instanceof Uint8Array ? n : new Uint8Array(n);
|
|
380
400
|
} }), at = async (e, t) => {
|
|
381
401
|
if (t !== void 0 || e === void 0) return null;
|
|
382
402
|
try {
|
|
383
|
-
let { resolve: t, join: n } = await import("node:path"), r = await import("node:fs/promises"), i = t(process.cwd(), e), a =
|
|
403
|
+
let { resolve: t, join: n } = await import("node:path"), r = await import("node:fs/promises"), i = t(process.cwd(), e), a = J(e) ? (await M(X(i), ["manifest.json"])).get("manifest.json") : await r.readFile(n(i, "manifest.json"));
|
|
384
404
|
if (a === void 0) return null;
|
|
385
405
|
let o = (JSON.parse(Buffer.from(a).toString("utf8")).tables ?? []).map((e) => e.name).filter((e) => typeof e == "string");
|
|
386
406
|
return o.length > 0 ? o : null;
|
|
@@ -388,7 +408,7 @@ var Fe = /* @__PURE__ */ new Set([
|
|
|
388
408
|
return null;
|
|
389
409
|
}
|
|
390
410
|
}, ot = (e) => {
|
|
391
|
-
let t =
|
|
411
|
+
let t = U(e, "--max-request-seconds") ?? process.env.VOLTRO_MAX_REQUEST_SECONDS;
|
|
392
412
|
if (t === void 0) return { pollMs: 2e3 };
|
|
393
413
|
let r = Number(t);
|
|
394
414
|
if (!Number.isFinite(r) || r <= 0) throw new n(`--max-request-seconds expects a positive number of seconds, got '${t}'`);
|
|
@@ -428,8 +448,8 @@ var Fe = /* @__PURE__ */ new Set([
|
|
|
428
448
|
e.log.info(` downloading — ${r}/${i ?? "?"} bytes`);
|
|
429
449
|
}
|
|
430
450
|
return new Uint8Array(Buffer.concat(n));
|
|
431
|
-
},
|
|
432
|
-
let r =
|
|
451
|
+
}, Z = "transfers", lt = (e) => (e ?? "").split(",").map((e) => e.trim()).filter(Boolean), ut = (e, t) => {
|
|
452
|
+
let r = U(e, "--tenant"), i = U(e, "--tables"), a = U(e, "--exclude");
|
|
433
453
|
if (a !== void 0) {
|
|
434
454
|
if (i !== void 0) throw new n("voltro data export: --exclude and --tables are two ways to say the same thing; use one.");
|
|
435
455
|
if (r !== void 0) throw new n("voltro data export: --exclude does not apply to --tenant, whose table set is derived from the tenant closure.");
|
|
@@ -449,7 +469,7 @@ var Fe = /* @__PURE__ */ new Set([
|
|
|
449
469
|
} : { kind: "all" };
|
|
450
470
|
}, dt = async () => {
|
|
451
471
|
let e = await g(process.cwd());
|
|
452
|
-
return ye(
|
|
472
|
+
return ye(ce(e));
|
|
453
473
|
}, ft = "voltro/tenant", pt = async () => {
|
|
454
474
|
let e = (await g(process.cwd())).filter((e) => (e.appliedMixins ?? []).some((e) => e.id === ft)).map((e) => e.tableName);
|
|
455
475
|
return e.length > 0 ? e : void 0;
|
|
@@ -467,7 +487,7 @@ var Fe = /* @__PURE__ */ new Set([
|
|
|
467
487
|
for (let t of e.unclassified) console.log(` ${t}`);
|
|
468
488
|
} else console.log("\n ✓ every exported column is classified — safe to run.");
|
|
469
489
|
}, ht = async (e) => {
|
|
470
|
-
let { resolve: t, join: r } = await import("node:path"), i = await import("node:fs/promises"), a =
|
|
490
|
+
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 ? Le : Math.round(Number(a) * 1024 * 1024);
|
|
471
491
|
if (!Number.isFinite(o) || o <= 0) throw new n(`--chunk-size expects a positive number of megabytes, got '${String(a)}'`);
|
|
472
492
|
let s = e.stream[Symbol.asyncIterator](), c = [], l = 0, u = !1;
|
|
473
493
|
for (; l <= o;) {
|
|
@@ -482,11 +502,11 @@ var Fe = /* @__PURE__ */ new Set([
|
|
|
482
502
|
kind: "small",
|
|
483
503
|
body: Buffer.concat(c)
|
|
484
504
|
};
|
|
485
|
-
let d = t(process.cwd(), e.bundleDir), f = ze(
|
|
505
|
+
let d = t(process.cwd(), e.bundleDir), f = ze(J(e.bundleDir) ? await i.readFile(d) : await i.readFile(r(d, "manifest.json"))), p = `up-${f.slice(0, 32)}`, m = await I(e.url, Buffer.alloc(0), {
|
|
486
506
|
...e.headers,
|
|
487
507
|
"x-import-upload-id": p,
|
|
488
508
|
"x-import-probe": "1"
|
|
489
|
-
},
|
|
509
|
+
}, $(e.args)), h = m.status === 200 ? Number(JSON.parse(m.body.toString("utf8")).bytes ?? 0) : 0;
|
|
490
510
|
h > 0 && e.log.info("api import: resuming a chunked upload the instance already holds", { bytes: h });
|
|
491
511
|
let g = (async function* () {
|
|
492
512
|
for (let e of c) yield e;
|
|
@@ -509,7 +529,7 @@ var Fe = /* @__PURE__ */ new Set([
|
|
|
509
529
|
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.");
|
|
510
530
|
},
|
|
511
531
|
target: { send: async (t, n) => {
|
|
512
|
-
let r = await
|
|
532
|
+
let r = await I(e.url, t, n, $(e.args)), i = {};
|
|
513
533
|
try {
|
|
514
534
|
i = JSON.parse(r.body.toString("utf8") || "{}");
|
|
515
535
|
} catch {}
|
|
@@ -531,9 +551,9 @@ var Fe = /* @__PURE__ */ new Set([
|
|
|
531
551
|
body: Buffer.from(JSON.stringify(v.body))
|
|
532
552
|
}
|
|
533
553
|
};
|
|
534
|
-
}, gt = (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.`,
|
|
554
|
+
}, gt = (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", _t = (e, t, n) => {
|
|
535
555
|
if (t !== "replace" || e.includes("--no-rollback")) return;
|
|
536
|
-
let r =
|
|
556
|
+
let r = U(e, "--rollback-dir");
|
|
537
557
|
return r === void 0 ? o(n) : _(r);
|
|
538
558
|
}, vt = (e, t, n) => {
|
|
539
559
|
let r = _t(e, t, n);
|
|
@@ -542,56 +562,56 @@ var Fe = /* @__PURE__ */ new Set([
|
|
|
542
562
|
recordInterruption: !0,
|
|
543
563
|
via: "direct"
|
|
544
564
|
};
|
|
545
|
-
},
|
|
546
|
-
let t =
|
|
565
|
+
}, $ = (e) => {
|
|
566
|
+
let t = U(e, "--timeout");
|
|
547
567
|
if (t === void 0) return {};
|
|
548
568
|
let r = Number(t);
|
|
549
569
|
if (!Number.isFinite(r) || r <= 0) throw new n(`--timeout expects a positive number of seconds, got '${t}'`);
|
|
550
570
|
return { timeoutMs: Math.round(r * 1e3) };
|
|
551
571
|
}, yt = async (e) => {
|
|
552
|
-
let t =
|
|
553
|
-
if (!r && !(t && n)) return
|
|
572
|
+
let t = U(e, "--target") === "api", n = U(e, "--bundle-key"), r = W(e);
|
|
573
|
+
if (!r && !(t && n)) return V.error("usage: voltro data export <outDir> [--target direct|api] [--api-url <url>] [--token <secret>] [--timeout <seconds>] [--profile <name>] [--tenant <id>] [--tables a,b] [--exclude a,b] [--bundle-key <storage-key>] [--assets] [--compression zstd|gzip|none] [--consistency live|snapshot] [--dry-run]"), 1;
|
|
554
574
|
if (t) {
|
|
555
|
-
let t =
|
|
556
|
-
if (!t) return
|
|
557
|
-
let i =
|
|
558
|
-
if (!i) return
|
|
559
|
-
let a =
|
|
560
|
-
if (o !== void 0 && a !== void 0) return
|
|
575
|
+
let t = U(e, "--api-url");
|
|
576
|
+
if (!t) return V.error("--target api requires --api-url <url> (the running instance)"), 1;
|
|
577
|
+
let i = U(e, "--token") ?? process.env.VOLTRO_DATA_TRANSFER_SECRET;
|
|
578
|
+
if (!i) return V.error("--target api requires --token <secret> (or VOLTRO_DATA_TRANSFER_SECRET) matching the instance secret"), 1;
|
|
579
|
+
let a = U(e, "--tables"), o = U(e, "--exclude");
|
|
580
|
+
if (o !== void 0 && a !== void 0) return V.error("voltro data export: --exclude and --tables are two ways to say the same thing; use one."), 1;
|
|
561
581
|
let s = {
|
|
562
|
-
...
|
|
563
|
-
...
|
|
582
|
+
...U(e, "--profile") ? { profile: U(e, "--profile") } : {},
|
|
583
|
+
...U(e, "--tenant") ? { tenant: U(e, "--tenant") } : {},
|
|
564
584
|
...a ? { tables: a.split(",").map((e) => e.trim()).filter(Boolean) } : {},
|
|
565
585
|
...o ? { exclude: o.split(",").map((e) => e.trim()).filter(Boolean) } : {},
|
|
566
|
-
...
|
|
567
|
-
...
|
|
586
|
+
...U(e, "--consistency") === "snapshot" ? { consistency: "snapshot" } : {},
|
|
587
|
+
...U(e, "--compression") ? { compression: U(e, "--compression") } : {},
|
|
568
588
|
...e.includes("--assets") ? { assets: !0 } : {},
|
|
569
589
|
...n ? { bundleKey: n } : {}
|
|
570
590
|
}, c = ot(e), l;
|
|
571
591
|
try {
|
|
572
|
-
let n = await
|
|
592
|
+
let n = await I(`${t.replace(/\/+$/, "")}/_voltro/admin/export`, JSON.stringify(s), {
|
|
573
593
|
"content-type": "application/json",
|
|
574
594
|
authorization: `Bearer ${i}`
|
|
575
|
-
}, c.perRequest ??
|
|
576
|
-
if (n.status < 200 || n.status >= 300) return
|
|
595
|
+
}, c.perRequest ?? $(e));
|
|
596
|
+
if (n.status < 200 || n.status >= 300) return V.error(`api export failed: HTTP ${n.status} — ${n.body.toString("utf8")}`), 1;
|
|
577
597
|
l = JSON.parse(n.body.toString("utf8"));
|
|
578
598
|
} catch (e) {
|
|
579
|
-
if (e instanceof
|
|
580
|
-
if (e instanceof
|
|
599
|
+
if (e instanceof F) 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;
|
|
600
|
+
if (e instanceof P) return V.error(`api export: ${L(e)}`), 1;
|
|
581
601
|
throw e;
|
|
582
602
|
}
|
|
583
|
-
if (e.includes("--detach")) return
|
|
584
|
-
if (l.recorded === !1) return
|
|
603
|
+
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 ${Z} --target api --api-url ${t}`), 0;
|
|
604
|
+
if (l.recorded === !1) return V.warn(`api export: started as ${l.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 one is watchable.`), 0;
|
|
585
605
|
let p = await d({
|
|
586
606
|
read: () => st(t, i, l.runId),
|
|
587
607
|
onTick: (e) => {
|
|
588
|
-
|
|
608
|
+
V.info(f(e));
|
|
589
609
|
},
|
|
590
610
|
intervalMs: c.pollMs,
|
|
591
611
|
...c.total === void 0 ? {} : { timeoutMs: c.total }
|
|
592
612
|
}), m = u(p);
|
|
593
|
-
if (m !== 0) return
|
|
594
|
-
if (n) return
|
|
613
|
+
if (m !== 0) return V.error(m === 1 ? `api export FAILED (${l.runId}): ${String(p?.failure ?? "no reason recorded")}` : `api export: no outcome visible for ${l.runId}. The run is not cancelled; it is simply not readable from here.`), m;
|
|
614
|
+
if (n) return V.info("api export complete (pushed to storage)", {
|
|
595
615
|
via: t,
|
|
596
616
|
runId: l.runId,
|
|
597
617
|
bundleKey: n,
|
|
@@ -602,12 +622,12 @@ var Fe = /* @__PURE__ */ new Set([
|
|
|
602
622
|
apiUrl: t,
|
|
603
623
|
secret: i,
|
|
604
624
|
runId: l.runId,
|
|
605
|
-
log:
|
|
625
|
+
log: V,
|
|
606
626
|
...c.perRequest ? { perRequest: c.perRequest } : {}
|
|
607
627
|
});
|
|
608
628
|
return g === null ? 1 : (await Me((async function* () {
|
|
609
629
|
yield g;
|
|
610
|
-
})(), h(process.cwd(), r)),
|
|
630
|
+
})(), h(process.cwd(), r)), V.info("api export complete (downloaded from the running instance)", {
|
|
611
631
|
outDir: r,
|
|
612
632
|
runId: l.runId,
|
|
613
633
|
tables: p?.tablesWritten ?? 0,
|
|
@@ -615,11 +635,13 @@ var Fe = /* @__PURE__ */ new Set([
|
|
|
615
635
|
bytes: g.byteLength
|
|
616
636
|
}), 0);
|
|
617
637
|
}
|
|
618
|
-
if (!r) return
|
|
619
|
-
let i =
|
|
638
|
+
if (!r) return V.error("a <outDir> is required for a direct export"), 1;
|
|
639
|
+
let i = U(e, "--profile"), a = i ? await p(i, { allowPath: !0 }) : {}, { store: o, snapshot: s, dialect: c } = await K(), l = e.includes("--assets") || a.assets === !0, m = l ? D() : void 0;
|
|
640
|
+
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;
|
|
641
|
+
let h = U(e, "--compression") ?? a.compression, g = U(e, "--consistency") === "snapshot" ? "snapshot" : a.consistency ?? "live", _ = a.masking, v = a.subset, y = a.scope ?? ut(e, s.tables.map((e) => e.name)), b = _ ? await dt() : void 0, x = y.kind === "tenant" ? await pt() : void 0, S = x ? { tenantTables: x } : {};
|
|
620
642
|
if (e.includes("--dry-run")) {
|
|
621
643
|
let e = y.kind === "tables" && y.tables ? [...y.tables] : s.tables.map((e) => e.name);
|
|
622
|
-
if (
|
|
644
|
+
if (V.info("export DRY RUN — nothing was written", {
|
|
623
645
|
outDir: r,
|
|
624
646
|
tables: e.length,
|
|
625
647
|
scope: y.kind,
|
|
@@ -627,8 +649,8 @@ var Fe = /* @__PURE__ */ new Set([
|
|
|
627
649
|
compression: h ?? "zstd",
|
|
628
650
|
assets: l,
|
|
629
651
|
masking: _ ? "profile policy applies" : "none"
|
|
630
|
-
}),
|
|
631
|
-
let t = await
|
|
652
|
+
}), V.info(`would export: ${e.join(", ")}`), !_) return 0;
|
|
653
|
+
let t = await E.runPromise(Se({
|
|
632
654
|
store: o,
|
|
633
655
|
snapshot: s,
|
|
634
656
|
masking: _,
|
|
@@ -640,10 +662,10 @@ var Fe = /* @__PURE__ */ new Set([
|
|
|
640
662
|
}));
|
|
641
663
|
return mt(t), +(t.unclassified.length > 0);
|
|
642
664
|
}
|
|
643
|
-
if (
|
|
665
|
+
if (J(r)) {
|
|
644
666
|
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-"));
|
|
645
667
|
try {
|
|
646
|
-
let t = await
|
|
668
|
+
let t = await E.runPromise(Te({
|
|
647
669
|
store: o,
|
|
648
670
|
snapshot: s,
|
|
649
671
|
dialect: c,
|
|
@@ -656,17 +678,17 @@ var Fe = /* @__PURE__ */ new Set([
|
|
|
656
678
|
...h ? { compression: h } : {},
|
|
657
679
|
..._ ? { masking: _ } : {},
|
|
658
680
|
...b ? { classification: b } : {},
|
|
659
|
-
onProgress:
|
|
681
|
+
onProgress: H
|
|
660
682
|
})), n = xe({
|
|
661
683
|
dir: u,
|
|
662
|
-
...m ? { blobs: Ae(m, () =>
|
|
684
|
+
...m ? { blobs: Ae(m, () => q(o)) } : {}
|
|
663
685
|
}), a = rt(e);
|
|
664
686
|
if (a) {
|
|
665
|
-
let t =
|
|
666
|
-
if (!t) return
|
|
687
|
+
let t = Y(e);
|
|
688
|
+
if (!t) return V.error("--encrypt requires --passphrase <p> or VOLTRO_BUNDLE_KEY (a dedicated key, NOT the transfer secret)"), 1;
|
|
667
689
|
n = be(n, t);
|
|
668
690
|
}
|
|
669
|
-
return await it(n, i(process.cwd(), r)),
|
|
691
|
+
return await it(n, i(process.cwd(), r)), V.info("export complete (single file)", {
|
|
670
692
|
file: r,
|
|
671
693
|
encrypted: a,
|
|
672
694
|
assets: l,
|
|
@@ -680,7 +702,7 @@ var Fe = /* @__PURE__ */ new Set([
|
|
|
680
702
|
});
|
|
681
703
|
}
|
|
682
704
|
}
|
|
683
|
-
let C = await
|
|
705
|
+
let C = await E.runPromise(Te({
|
|
684
706
|
store: o,
|
|
685
707
|
snapshot: s,
|
|
686
708
|
dialect: c,
|
|
@@ -693,10 +715,10 @@ var Fe = /* @__PURE__ */ new Set([
|
|
|
693
715
|
...h ? { compression: h } : {},
|
|
694
716
|
..._ ? { masking: _ } : {},
|
|
695
717
|
...b ? { classification: b } : {},
|
|
696
|
-
...m ? { assets: Oe(m, () =>
|
|
697
|
-
onProgress:
|
|
718
|
+
...m ? { assets: Oe(m, () => q(o)) } : {},
|
|
719
|
+
onProgress: H
|
|
698
720
|
}));
|
|
699
|
-
return
|
|
721
|
+
return V.info("export complete", {
|
|
700
722
|
outDir: r,
|
|
701
723
|
tables: C.tables.length,
|
|
702
724
|
rows: C.tables.reduce((e, t) => e + t.rowCount, 0),
|
|
@@ -705,7 +727,7 @@ var Fe = /* @__PURE__ */ new Set([
|
|
|
705
727
|
...C.masking ? { maskedColumns: C.masking.transformed.length } : {}
|
|
706
728
|
}), 0;
|
|
707
729
|
}, bt = () => "dry run checked: schema fit, cross-dialect portability, mode legality, the table selection. It did NOT read per-table checksums (those stream during the load) and cannot see conflicts that depend on the target's current rows.", xt = (e) => {
|
|
708
|
-
let t =
|
|
730
|
+
let t = U(e, "--tables");
|
|
709
731
|
return {
|
|
710
732
|
...t ? { tables: t.split(",").map((e) => e.trim()).filter(Boolean) } : {},
|
|
711
733
|
...e.includes("--dry-run") ? { dryRun: !0 } : {}
|
|
@@ -720,25 +742,25 @@ var Fe = /* @__PURE__ */ new Set([
|
|
|
720
742
|
} : {}
|
|
721
743
|
}), Ct = (e, t) => {
|
|
722
744
|
if (e.includes("--dry-run")) {
|
|
723
|
-
|
|
745
|
+
V.info("import DRY RUN — nothing was written", t), V.info(bt());
|
|
724
746
|
return;
|
|
725
747
|
}
|
|
726
748
|
if (t.rows === 0 && typeof t.skippedRows == "number" && t.skippedRows !== 0) {
|
|
727
|
-
|
|
749
|
+
V.info("import complete — 0 rows written, every table was already applied by an earlier run of this bundle directory", t);
|
|
728
750
|
return;
|
|
729
751
|
}
|
|
730
|
-
|
|
752
|
+
V.info("import complete", t);
|
|
731
753
|
}, wt = Symbol("refused"), Tt = (e) => {
|
|
732
754
|
let t = he(e) ?? e;
|
|
733
755
|
if (t?._tag !== "RowsRefusedError" || !Array.isArray(t.rows)) return !1;
|
|
734
756
|
let n = t.rows, r = n.filter((e) => !e.derived), i = Number(t.totalCount ?? n.length), a = Number(t.primaryCount ?? r.length);
|
|
735
|
-
|
|
757
|
+
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.`);
|
|
736
758
|
let o = Array.isArray(t.byTable) ? t.byTable : [];
|
|
737
|
-
o.length > 1 &&
|
|
738
|
-
for (let e of r)
|
|
739
|
-
n.length < i &&
|
|
759
|
+
o.length > 1 && V.error(` by table: ${o.map((e) => `${e.table} ${e.refused}`).join(", ")}`);
|
|
760
|
+
for (let e of r) V.error(` ${e.table} ${e.id} ${e.reason}`);
|
|
761
|
+
n.length < i && V.error(` (the list above is capped at ${n.length} of ${i} — the counts by table are complete)`);
|
|
740
762
|
let s = i - a;
|
|
741
|
-
return s > 0 &&
|
|
763
|
+
return s > 0 && V.error(` … and ${s} row(s) behind them. Fix the ${a} above and re-run; they resolve with their parents.`), !0;
|
|
742
764
|
}, Et = (e) => {
|
|
743
765
|
let t = typeof e == "string" ? e : e.toString("utf8"), n = t;
|
|
744
766
|
try {
|
|
@@ -780,21 +802,21 @@ var Fe = /* @__PURE__ */ new Set([
|
|
|
780
802
|
}
|
|
781
803
|
}
|
|
782
804
|
}, Dt = async (e) => {
|
|
783
|
-
let t =
|
|
784
|
-
if (!r && !(t && n)) return
|
|
785
|
-
let i =
|
|
805
|
+
let t = U(e, "--target") === "api", n = U(e, "--bundle-key"), r = W(e);
|
|
806
|
+
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;
|
|
807
|
+
let i = U(e, "--mode") ?? "upsert", o = U(e, "--on-conflict") ?? "skip";
|
|
786
808
|
if (t) {
|
|
787
|
-
let t =
|
|
788
|
-
if (!t) return
|
|
789
|
-
let a =
|
|
790
|
-
if (!a) return
|
|
791
|
-
let s =
|
|
792
|
-
i === "replace" && c === void 0 &&
|
|
809
|
+
let t = U(e, "--api-url");
|
|
810
|
+
if (!t) return V.error("--target api requires --api-url <url> (the running instance)"), 1;
|
|
811
|
+
let a = U(e, "--token") ?? process.env.VOLTRO_DATA_TRANSFER_SECRET;
|
|
812
|
+
if (!a) return V.error("--target api requires --token <secret> (or VOLTRO_DATA_TRANSFER_SECRET) matching the instance secret"), 1;
|
|
813
|
+
let s = U(e, "--tables"), c = U(e, "--rollback-key");
|
|
814
|
+
i === "replace" && c === void 0 && V.warn("api replace: no rollback capture — this run empties the target first and nothing was asked to be kept.\n Pass --rollback-key <storage-key> and the INSTANCE stores the target's current rows in its own\n object storage before deleting them, which is the one place that survives the pod going away.");
|
|
793
815
|
let l = {
|
|
794
816
|
authorization: `Bearer ${a}`,
|
|
795
817
|
"x-import-mode": i,
|
|
796
818
|
"x-import-on-conflict": o,
|
|
797
|
-
...
|
|
819
|
+
...Q(e, i) ? { "x-import-atomic": "1" } : {},
|
|
798
820
|
...e.includes("--no-atomic") ? { "x-import-atomic": "0" } : {},
|
|
799
821
|
...c === void 0 ? {} : { "x-import-rollback-key": c },
|
|
800
822
|
...e.includes("--force") ? { "x-import-force": "1" } : {},
|
|
@@ -802,23 +824,23 @@ var Fe = /* @__PURE__ */ new Set([
|
|
|
802
824
|
...s ? { "x-import-tables": s } : {}
|
|
803
825
|
}, { resolve: p } = await import("node:path"), m;
|
|
804
826
|
if (n) l["content-type"] = "application/json", m = JSON.stringify({ bundleKey: n });
|
|
805
|
-
else if (
|
|
806
|
-
let t =
|
|
807
|
-
n && (t =
|
|
808
|
-
} else await (await import("node:fs/promises")).access(p(process.cwd(), r, "assets", "index.ndjson")).then(() => !0, () => !1) &&
|
|
827
|
+
else if (J(r)) {
|
|
828
|
+
let t = X(p(process.cwd(), r)), n = Y(e);
|
|
829
|
+
n && (t = j(t, n)), l["content-type"] = "application/octet-stream", m = t;
|
|
830
|
+
} 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 = xe({ dir: p(process.cwd(), r) });
|
|
809
831
|
if (i === "replace") {
|
|
810
832
|
let i = await at(r, n);
|
|
811
833
|
{
|
|
812
|
-
let r = i === null ? { bundleKey: n } : { tables: i }, a = await
|
|
834
|
+
let r = i === null ? { bundleKey: n } : { tables: i }, a = await I(`${t.replace(/\/+$/, "")}/_voltro/admin/import`, JSON.stringify(r), {
|
|
813
835
|
...l,
|
|
814
836
|
"content-type": "application/json",
|
|
815
837
|
"x-import-preflight": "1"
|
|
816
|
-
},
|
|
817
|
-
o === null ?
|
|
838
|
+
}, $(e)).catch(() => null), o = a !== null && a.status === 200 ? JSON.parse(a.body.toString("utf8")) : null;
|
|
839
|
+
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
|
|
818
840
|
interrupted run leaves the target empty.\n Reason: ${o.reason ?? "not stated"}`);
|
|
819
841
|
}
|
|
820
842
|
}
|
|
821
|
-
i === "replace" && !
|
|
843
|
+
i === "replace" && !Q(e, i) && V.warn(gt(!0));
|
|
822
844
|
let h = `${t.replace(/\/+$/, "")}/_voltro/admin/import`, g = ot(e), _;
|
|
823
845
|
try {
|
|
824
846
|
let t = typeof m == "string" ? null : await ht({
|
|
@@ -827,41 +849,41 @@ var Fe = /* @__PURE__ */ new Set([
|
|
|
827
849
|
headers: l,
|
|
828
850
|
bundleDir: r,
|
|
829
851
|
args: e,
|
|
830
|
-
log:
|
|
831
|
-
}), n = t === null ? await
|
|
832
|
-
if (n.status < 200 || n.status >= 300) return Tt(Et(n.body)) ||
|
|
852
|
+
log: V
|
|
853
|
+
}), n = t === null ? await I(h, m, l, g.perRequest ?? $(e)) : t.kind === "sent" ? t.res : await I(h, t.body, l, g.perRequest ?? $(e));
|
|
854
|
+
if (n.status < 200 || n.status >= 300) return Tt(Et(n.body)) || V.error(`api import failed: HTTP ${n.status} — ${n.body.toString("utf8")}`), 1;
|
|
833
855
|
_ = JSON.parse(n.body.toString("utf8"));
|
|
834
856
|
} catch (e) {
|
|
835
|
-
if (e instanceof
|
|
836
|
-
if (e instanceof
|
|
857
|
+
if (e instanceof F) return V.error(`api import: ${Ie(e, i)}`), 1;
|
|
858
|
+
if (e instanceof P) return V.error(`api import: ${L(e)}`), 1;
|
|
837
859
|
throw e;
|
|
838
860
|
}
|
|
839
|
-
let v = await
|
|
861
|
+
let v = await I(`${t.replace(/\/+$/, "")}/_voltro/admin/import/start`, JSON.stringify(_.bundleKey === void 0 ? { uploadId: _.uploadId } : { bundleKey: _.bundleKey }), {
|
|
840
862
|
...l,
|
|
841
863
|
"content-type": "application/json"
|
|
842
|
-
}, g.perRequest ??
|
|
843
|
-
if (e instanceof
|
|
864
|
+
}, g.perRequest ?? $(e)).catch((e) => {
|
|
865
|
+
if (e instanceof P) return V.error(`api import: ${L(e)}`), null;
|
|
844
866
|
throw e;
|
|
845
867
|
});
|
|
846
868
|
if (v === null) return 1;
|
|
847
|
-
if (v.status < 200 || v.status >= 300) return Tt(Et(v.body)) ||
|
|
869
|
+
if (v.status < 200 || v.status >= 300) return Tt(Et(v.body)) || V.error(`api import refused: HTTP ${v.status} — ${v.body.toString("utf8")}`), 1;
|
|
848
870
|
let y = JSON.parse(v.body.toString("utf8"));
|
|
849
|
-
if (y.alreadyStarted === !0 &&
|
|
850
|
-
if (y.recorded === !1) return
|
|
871
|
+
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 ${Z} --target api --api-url ${t}`), 0;
|
|
872
|
+
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;
|
|
851
873
|
let b = await d({
|
|
852
874
|
read: () => st(t, a, y.runId),
|
|
853
875
|
onTick: (e) => {
|
|
854
|
-
|
|
876
|
+
V.info(f(e));
|
|
855
877
|
},
|
|
856
878
|
intervalMs: g.pollMs,
|
|
857
879
|
...g.total === void 0 ? {} : { timeoutMs: g.total }
|
|
858
880
|
}), x = u(b);
|
|
859
|
-
return x === 2 ? (
|
|
881
|
+
return x === 2 ? (V.warn(b === null ? `api import: started as ${y.runId}, but its record could not be READ back from this instance.\n That is a channel problem, not a slow import — an older instance without the history route, a
|
|
860
882
|
proxy dropping the GET, or a target with no history table. The run itself is unaffected and
|
|
861
|
-
is NOT cancelled; its outcome is simply not visible from here.` : `api import: still running after the time allowed. The run is NOT cancelled — it is decoupled from\n this client by design. Watch it with: voltro data ${
|
|
883
|
+
is NOT cancelled; its outcome is simply not visible from here.` : `api import: still running after the time allowed. The run is NOT cancelled — it is decoupled from\n this client by design. Watch it with: voltro data ${Z} --target api --api-url ${t}`), 2) : x === 1 ? (V.error(`api import FAILED (${y.runId}): ${String(b?.failure ?? "no reason recorded")}`), 1) : e.includes("--dry-run") ? (V.info("api import DRY RUN — nothing was written", {
|
|
862
884
|
via: t,
|
|
863
885
|
runId: y.runId
|
|
864
|
-
}),
|
|
886
|
+
}), V.info(bt()), 0) : (V.info("api import complete (imported by the running instance)", {
|
|
865
887
|
via: t,
|
|
866
888
|
runId: y.runId,
|
|
867
889
|
tables: b?.tablesWritten ?? 0,
|
|
@@ -869,72 +891,74 @@ var Fe = /* @__PURE__ */ new Set([
|
|
|
869
891
|
...b?.staged !== null && b?.staged !== void 0 ? { staged: b.staged } : {}
|
|
870
892
|
}), 0);
|
|
871
893
|
}
|
|
872
|
-
if (!r) return
|
|
894
|
+
if (!r) return V.error("a <bundleDir> is required for a direct import"), 1;
|
|
873
895
|
if (!await Qe(e)) return 1;
|
|
874
|
-
i === "replace" && e.includes("--allow-live") && !
|
|
875
|
-
let { store: s, snapshot: c } = await
|
|
876
|
-
c.tables.some((e) => e.name === y) ||
|
|
877
|
-
let l = e.includes("--force"), p = (e) =>
|
|
878
|
-
if (
|
|
896
|
+
i === "replace" && e.includes("--allow-live") && !Q(e, i) && V.warn(gt(!1));
|
|
897
|
+
let { store: s, snapshot: c } = await K();
|
|
898
|
+
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.`);
|
|
899
|
+
let l = e.includes("--force"), p = (e) => V.warn(e), m = e.includes("--assets"), h = m ? D() : void 0;
|
|
900
|
+
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;
|
|
901
|
+
let g = G() === "postgres" ? (await import("@voltro/sql-postgres")).makePgCopySession(a()) : void 0;
|
|
902
|
+
if (J(r)) {
|
|
879
903
|
let t = await import("node:os"), n = await import("node:fs/promises"), { resolve: a, join: u } = await import("node:path"), d = await n.mkdtemp(u(t.tmpdir(), "voltro-import-"));
|
|
880
904
|
try {
|
|
881
|
-
let t =
|
|
882
|
-
return n && (t =
|
|
905
|
+
let t = X(a(process.cwd(), r)), n = Y(e);
|
|
906
|
+
return n && (t = j(t, n)), await je(t, {
|
|
883
907
|
dir: d,
|
|
884
|
-
blobs: ke(
|
|
908
|
+
blobs: ke(h ?? fe())
|
|
885
909
|
}), Ct(e, {
|
|
886
910
|
file: r,
|
|
887
|
-
...St(await
|
|
911
|
+
...St(await E.runPromise(Ee({
|
|
888
912
|
store: s,
|
|
889
913
|
bundleDir: d,
|
|
890
914
|
mode: i,
|
|
891
915
|
onConflict: o,
|
|
892
|
-
atomic:
|
|
916
|
+
atomic: Q(e, i),
|
|
893
917
|
...vt(e, i, r),
|
|
894
918
|
verify: !e.includes("--no-verify"),
|
|
895
|
-
targetDialect:
|
|
919
|
+
targetDialect: G(),
|
|
896
920
|
allowIncompatible: l,
|
|
897
921
|
targetSnapshot: c,
|
|
898
922
|
force: l,
|
|
899
923
|
onWarn: p,
|
|
900
|
-
onProgress:
|
|
924
|
+
onProgress: H,
|
|
901
925
|
assetsAlreadyRestored: !0,
|
|
902
926
|
...xt(e),
|
|
903
|
-
...
|
|
927
|
+
...g ? { copyLoader: g } : {}
|
|
904
928
|
})))
|
|
905
929
|
}), 0;
|
|
906
930
|
} finally {
|
|
907
|
-
await
|
|
931
|
+
await g?.close(), await n.rm(d, {
|
|
908
932
|
recursive: !0,
|
|
909
933
|
force: !0
|
|
910
934
|
});
|
|
911
935
|
}
|
|
912
936
|
}
|
|
913
|
-
let
|
|
937
|
+
let _ = await E.runPromise(Ee({
|
|
914
938
|
...vt(e, i, r),
|
|
915
939
|
store: s,
|
|
916
940
|
bundleDir: r,
|
|
917
941
|
mode: i,
|
|
918
942
|
onConflict: o,
|
|
919
|
-
atomic:
|
|
943
|
+
atomic: Q(e, i),
|
|
920
944
|
verify: !e.includes("--no-verify"),
|
|
921
|
-
targetDialect:
|
|
945
|
+
targetDialect: G(),
|
|
922
946
|
allowIncompatible: l,
|
|
923
947
|
targetSnapshot: c,
|
|
924
948
|
force: l,
|
|
925
949
|
onWarn: p,
|
|
926
|
-
onProgress:
|
|
950
|
+
onProgress: H,
|
|
927
951
|
...xt(e),
|
|
928
|
-
...
|
|
929
|
-
...
|
|
952
|
+
...h ? { assets: De(h) } : {},
|
|
953
|
+
...g ? { copyLoader: g } : {}
|
|
930
954
|
})).catch((e) => {
|
|
931
955
|
if (Tt(e)) return wt;
|
|
932
956
|
throw e;
|
|
933
|
-
}).finally(() =>
|
|
934
|
-
return typeof
|
|
957
|
+
}).finally(() => g?.close());
|
|
958
|
+
return typeof _ == "symbol" ? 1 : (Ct(e, {
|
|
935
959
|
bundleDir: r,
|
|
936
|
-
...St(
|
|
937
|
-
assets:
|
|
960
|
+
...St(_),
|
|
961
|
+
assets: _.manifest.assets.count
|
|
938
962
|
}), 0);
|
|
939
963
|
}, Ot = () => {
|
|
940
964
|
let e = a();
|
|
@@ -947,28 +971,37 @@ var Fe = /* @__PURE__ */ new Set([
|
|
|
947
971
|
...e.database ? { database: e.database } : {}
|
|
948
972
|
};
|
|
949
973
|
}, kt = async (e) => {
|
|
950
|
-
let t =
|
|
951
|
-
if (!t) return
|
|
952
|
-
let n =
|
|
953
|
-
if (r && !
|
|
974
|
+
let t = W(e);
|
|
975
|
+
if (!t) return V.error("usage: voltro data backup <outDir> [--assets] (native pg_dump/mariadb-dump/… + consistent artifact)"), 1;
|
|
976
|
+
let n = G(), r = e.includes("--assets"), { mkdir: i, writeFile: a } = await import("node:fs/promises"), { join: o } = await import("node:path"), s = await At(), c = r ? D() : void 0;
|
|
977
|
+
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;
|
|
954
978
|
await i(t, { recursive: !0 });
|
|
955
|
-
let
|
|
979
|
+
let l = o(t, A(n)), u = s ? await We(s.store, {
|
|
956
980
|
direction: "backup",
|
|
957
981
|
dialect: n,
|
|
958
|
-
artifact:
|
|
982
|
+
artifact: l,
|
|
959
983
|
assets: r
|
|
960
|
-
}) : void 0, d = await Ke(
|
|
961
|
-
if (d !== void 0) return
|
|
962
|
-
let f =
|
|
963
|
-
|
|
964
|
-
|
|
984
|
+
}) : void 0, d = await Ke(N(_e(n, Ot(), l)));
|
|
985
|
+
if (d !== void 0) return s && u && await z(s.store, u, { failure: d }), V.error(`native backup failed: ${d}`), 1;
|
|
986
|
+
let f = await Xe(n), p = await Ye(), m = (/* @__PURE__ */ new Date()).toISOString(), h = async (e) => {
|
|
987
|
+
let r = Ve({
|
|
988
|
+
dialect: n,
|
|
989
|
+
schemaFingerprint: f,
|
|
990
|
+
voltroVersion: p,
|
|
991
|
+
createdAt: m,
|
|
992
|
+
assets: e
|
|
993
|
+
});
|
|
994
|
+
return await a(o(t, R), `${JSON.stringify(r, null, 2)}\n`, "utf8"), r;
|
|
995
|
+
}, g = 0, _ = 0, v = [];
|
|
996
|
+
if (c && s) {
|
|
997
|
+
let e = o(t, O);
|
|
965
998
|
await i(e, { recursive: !0 });
|
|
966
|
-
let n = o(t,
|
|
967
|
-
source: Oe(
|
|
999
|
+
let n = o(t, k), r = s.store, a = await E.runPromiseExit(ve({
|
|
1000
|
+
source: Oe(c, () => q(r)),
|
|
968
1001
|
assetsDir: e,
|
|
969
1002
|
progress: {
|
|
970
1003
|
done: () => !1,
|
|
971
|
-
mark: (e, t) =>
|
|
1004
|
+
mark: (e, t) => E.tryPromise({
|
|
972
1005
|
try: async () => {
|
|
973
1006
|
await (await import("node:fs/promises")).appendFile(n, `${JSON.stringify(t)}\n`);
|
|
974
1007
|
},
|
|
@@ -980,28 +1013,23 @@ var Fe = /* @__PURE__ */ new Set([
|
|
|
980
1013
|
}
|
|
981
1014
|
}));
|
|
982
1015
|
if (a._tag === "Failure") {
|
|
983
|
-
let e =
|
|
984
|
-
return u && await
|
|
1016
|
+
let e = T.pretty(a.cause);
|
|
1017
|
+
return u && await z(s.store, u, { failure: `assets: ${e}` }), await h(null).catch(() => void 0), V.error(`the rows were dumped to ${l}, but the asset capture failed: ${e}\n This directory holds a ROWS-ONLY backup. Re-run with --assets once the storage
|
|
985
1018
|
backend is reachable, or restore without --assets and accept the dangling refs.`), 1;
|
|
986
1019
|
}
|
|
987
|
-
|
|
1020
|
+
g = a.value.count, _ = a.value.totalBytes, v = a.value.missing, v.length > 0 && V.warn(`${v.length} of ${v.length + g} blob reference(s) point at objects the storage provider does not have; they are NOT in this backup and no restore can bring them back.
|
|
1021
|
+
e.g. ${v.slice(0, 3).map((e) => `'${e}'`).join(", ")}` + (v.length > 3 ? `, and ${v.length - 3} more` : ""));
|
|
988
1022
|
}
|
|
989
|
-
let
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
assets: r ? {
|
|
995
|
-
count: f,
|
|
996
|
-
totalBytes: p
|
|
997
|
-
} : null
|
|
998
|
-
});
|
|
999
|
-
return await a(o(t, L), `${JSON.stringify(m, null, 2)}\n`, "utf8"), l && u && await R(l.store, u, { assets: f }), B.info("native backup complete", {
|
|
1023
|
+
let y = await h(r ? {
|
|
1024
|
+
count: g,
|
|
1025
|
+
totalBytes: _
|
|
1026
|
+
} : null);
|
|
1027
|
+
return s && u && await z(s.store, u, { assets: g }), V.info("native backup complete", {
|
|
1000
1028
|
dialect: n,
|
|
1001
|
-
artifact:
|
|
1002
|
-
stamp:
|
|
1003
|
-
schemaFingerprint:
|
|
1004
|
-
assets: r ? `${
|
|
1029
|
+
artifact: l,
|
|
1030
|
+
stamp: R,
|
|
1031
|
+
schemaFingerprint: y.schemaFingerprint ?? "<none>",
|
|
1032
|
+
assets: r ? `${g} blob(s), ${_} byte(s) under ${O}/` + (v.length > 0 ? ` — ${v.length} reference(s) had no object and are NOT in here` : "") : "NOT included — re-run with --assets, or use `voltro data export --assets`",
|
|
1005
1033
|
recorded: u?.recorded === !0 ? "voltro data transfers" : "NOT recorded (this target keeps no transfer history)"
|
|
1006
1034
|
}), 0;
|
|
1007
1035
|
}, At = async () => {
|
|
@@ -1012,89 +1040,89 @@ var Fe = /* @__PURE__ */ new Set([
|
|
|
1012
1040
|
return;
|
|
1013
1041
|
}
|
|
1014
1042
|
}, jt = async (e, t) => {
|
|
1015
|
-
let n =
|
|
1016
|
-
flagUrl:
|
|
1043
|
+
let n = G(), { join: r } = await import("node:path"), { readFile: i } = await import("node:fs/promises"), a = r(e, A(n)), o = qe({
|
|
1044
|
+
flagUrl: U(t, "--drill-url"),
|
|
1017
1045
|
envUrl: process.env.DRILL_DB_URL,
|
|
1018
1046
|
liveKeys: nt()
|
|
1019
1047
|
});
|
|
1020
|
-
if (!o.ok) return
|
|
1021
|
-
let s = await i(r(e,
|
|
1022
|
-
if (s && s.dialect !== n) return
|
|
1023
|
-
|
|
1048
|
+
if (!o.ok) return V.error(`restore --drill: ${o.error}`), 1;
|
|
1049
|
+
let s = await i(r(e, R), "utf8").then(He, () => null);
|
|
1050
|
+
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;
|
|
1051
|
+
V.info("restore drill: restoring into throwaway DB", {
|
|
1024
1052
|
dialect: n,
|
|
1025
|
-
target:
|
|
1026
|
-
}), await
|
|
1053
|
+
target: B(o.url)
|
|
1054
|
+
}), await E.runPromise(N(we(n, { url: o.url }, a)));
|
|
1027
1055
|
let c = null, l = 0;
|
|
1028
1056
|
try {
|
|
1029
1057
|
let e = await et(n, { url: o.url });
|
|
1030
|
-
c =
|
|
1058
|
+
c = le(e), l = e.tables.length;
|
|
1031
1059
|
} catch (e) {
|
|
1032
|
-
return
|
|
1060
|
+
return V.error(`restore --drill: could not introspect the restored DB — ${String(e)}`), 1;
|
|
1033
1061
|
}
|
|
1034
1062
|
let u = Je({
|
|
1035
1063
|
stampFingerprint: s?.schemaFingerprint ?? null,
|
|
1036
1064
|
restoredFingerprint: c,
|
|
1037
1065
|
restoredTableCount: l
|
|
1038
1066
|
});
|
|
1039
|
-
for (let e of u.lines) u.pass ?
|
|
1067
|
+
for (let e of u.lines) u.pass ? V.info(`restore drill: ${e}`) : V.error(`restore drill: ${e}`);
|
|
1040
1068
|
return +!u.pass;
|
|
1041
1069
|
}, Mt = async (e) => {
|
|
1042
|
-
let t =
|
|
1043
|
-
if (!t) return
|
|
1070
|
+
let t = W(e);
|
|
1071
|
+
if (!t) return V.error("usage: voltro data restore <backupDir> [--assets] [--allow-live] [--api-url <url>] [--force] [--drill [--drill-url <url>]] (native pg_restore/mariadb/…)"), 1;
|
|
1044
1072
|
if (e.includes("--drill")) return jt(t, e);
|
|
1045
1073
|
if (!await Qe(e)) return 1;
|
|
1046
|
-
let n =
|
|
1047
|
-
stamp: await i(r(t,
|
|
1074
|
+
let n = G(), { join: r } = await import("node:path"), { readFile: i } = await import("node:fs/promises"), a = r(t, A(n)), o = e.includes("--assets"), s = Ue({
|
|
1075
|
+
stamp: await i(r(t, R), "utf8").then(He, () => null),
|
|
1048
1076
|
currentDialect: n,
|
|
1049
1077
|
currentFingerprint: await Xe(n)
|
|
1050
1078
|
});
|
|
1051
|
-
for (let e of s.info)
|
|
1052
|
-
if (s.warn &&
|
|
1053
|
-
if (!e.includes("--force")) return
|
|
1054
|
-
|
|
1079
|
+
for (let e of s.info) V.info(`backup stamp: ${e}`);
|
|
1080
|
+
if (s.warn && V.warn(s.warn), s.refuse) {
|
|
1081
|
+
if (!e.includes("--force")) return V.error(`restore refused: ${s.refuse} (pass --force to override)`), 1;
|
|
1082
|
+
V.warn(`restore proceeding despite: ${s.refuse} (--force)`);
|
|
1055
1083
|
}
|
|
1056
|
-
let c = r(t,
|
|
1057
|
-
if (o && !l) return
|
|
1084
|
+
let c = r(t, k), l = await i(c, "utf8").then(() => !0, () => !1);
|
|
1085
|
+
if (o && !l) return V.error(`--assets: this backup carries no assets — there is no ${k} in ${t}.\n It was taken without --assets, so its rows will reference blobs this restore cannot
|
|
1058
1086
|
put back. Restore from a backup taken with --assets, or drop the flag to accept that.`), 1;
|
|
1059
|
-
!o && l &&
|
|
1060
|
-
let u = o ?
|
|
1061
|
-
if (o && !
|
|
1062
|
-
let
|
|
1063
|
-
if (
|
|
1087
|
+
!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.");
|
|
1088
|
+
let u = await At(), d = o ? D() : void 0;
|
|
1089
|
+
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;
|
|
1090
|
+
let f = u ? await Ge(u.store, { artifact: a }) : void 0;
|
|
1091
|
+
if (u && f === void 0) return V.error(`refusing to restore: could not write the in-progress marker.
|
|
1064
1092
|
It is one row in \`${b}\`, written before the first destructive\n statement so a killed restore refuses the next boot rather than serving a half-loaded
|
|
1065
1093
|
database. Run \`voltro db apply\` so the table exists, then retry.`), 1;
|
|
1066
|
-
let p =
|
|
1094
|
+
let p = u ? await We(u.store, {
|
|
1067
1095
|
direction: "restore",
|
|
1068
1096
|
dialect: n,
|
|
1069
1097
|
artifact: a,
|
|
1070
1098
|
assets: o
|
|
1071
|
-
}) : void 0, m = await Ke(
|
|
1072
|
-
if (
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
let
|
|
1076
|
-
if (
|
|
1077
|
-
let e = await
|
|
1078
|
-
sink: De(
|
|
1079
|
-
assetsDir: r(t,
|
|
1099
|
+
}) : void 0, m = await Ke(N(we(n, Ot(), a))), h = f === void 0 ? "held" : await f.reassert();
|
|
1100
|
+
if (h === "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
|
|
1101
|
+
and hand-made dumps.`) : h === "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
|
|
1102
|
+
result of this command before you start the app.`), m !== void 0) return u && p && await z(u.store, p, { failure: m }), V.error(`native restore failed: ${m}\n` + (h === "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;
|
|
1103
|
+
let g = 0;
|
|
1104
|
+
if (d) {
|
|
1105
|
+
let e = await E.runPromiseExit(Ce({
|
|
1106
|
+
sink: De(d),
|
|
1107
|
+
assetsDir: r(t, O),
|
|
1080
1108
|
indexPath: c
|
|
1081
1109
|
}));
|
|
1082
1110
|
if (e._tag === "Failure") {
|
|
1083
|
-
let t =
|
|
1084
|
-
return
|
|
1111
|
+
let t = T.pretty(e.cause);
|
|
1112
|
+
return u && p && await z(u.store, p, { failure: `assets: ${t}` }), V.error(`the rows were restored, but the asset restore failed: ${t}\n The in-progress marker is still set and the next boot will refuse. Re-run
|
|
1085
1113
|
\`voltro data restore <dir> --assets\` once storage is reachable.`), 1;
|
|
1086
1114
|
}
|
|
1087
|
-
|
|
1115
|
+
g = e.value;
|
|
1088
1116
|
}
|
|
1089
|
-
return f && await f(),
|
|
1117
|
+
return f && await f.clear(), u && p && await z(u.store, p, { assets: g }), V.info("native restore complete", {
|
|
1090
1118
|
dialect: n,
|
|
1091
1119
|
artifact: a,
|
|
1092
|
-
assets: o ? `${
|
|
1120
|
+
assets: o ? `${g} blob(s) restored` : "not restored (no --assets)",
|
|
1093
1121
|
recorded: p?.recorded === !0 ? "voltro data transfers" : "NOT recorded (this target keeps no transfer history)"
|
|
1094
1122
|
}), 0;
|
|
1095
1123
|
}, Nt = async (e) => {
|
|
1096
|
-
let t =
|
|
1097
|
-
if (!t) return
|
|
1124
|
+
let t = W(e);
|
|
1125
|
+
if (!t) return V.error("usage: voltro data inspect <file.vbundle|dir> [--json] [--passphrase <p>]"), 1;
|
|
1098
1126
|
let { resolve: n, join: r } = await import("node:path"), i = await import("node:fs/promises"), a = n(process.cwd(), t);
|
|
1099
1127
|
if ((await i.stat(a).catch(() => void 0))?.isDirectory()) {
|
|
1100
1128
|
let n = r(a, "manifest.json"), o = r(a, "README.md");
|
|
@@ -1103,24 +1131,24 @@ var Fe = /* @__PURE__ */ new Set([
|
|
|
1103
1131
|
if (e !== void 0) return console.log(e), 0;
|
|
1104
1132
|
}
|
|
1105
1133
|
let s = await i.readFile(n, "utf8").catch(() => void 0);
|
|
1106
|
-
return s === void 0 ? (
|
|
1134
|
+
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);
|
|
1107
1135
|
}
|
|
1108
|
-
let o =
|
|
1109
|
-
let e =
|
|
1110
|
-
return o ?
|
|
1136
|
+
let o = Y(e), s = () => {
|
|
1137
|
+
let e = X(a);
|
|
1138
|
+
return o ? j(e, o) : e;
|
|
1111
1139
|
};
|
|
1112
1140
|
if (e.includes("--json")) {
|
|
1113
|
-
let e = (await
|
|
1114
|
-
return e ? (console.log(e.toString("utf8")), 0) : (
|
|
1141
|
+
let e = (await M(s(), ["manifest.json"])).get("manifest.json");
|
|
1142
|
+
return e ? (console.log(e.toString("utf8")), 0) : (V.error("no manifest.json found in the bundle"), 1);
|
|
1115
1143
|
}
|
|
1116
|
-
let c = (await
|
|
1144
|
+
let c = (await M(s(), ["README.md"])).get("README.md");
|
|
1117
1145
|
if (c) return console.log(c.toString("utf8")), 0;
|
|
1118
|
-
let l = (await
|
|
1119
|
-
return l ? (console.log(l.toString("utf8")), 0) : (
|
|
1146
|
+
let l = (await M(s(), ["manifest.json"])).get("manifest.json");
|
|
1147
|
+
return l ? (console.log(l.toString("utf8")), 0) : (V.error("bundle has no README.md or manifest.json"), 1);
|
|
1120
1148
|
}, Pt = async (e) => {
|
|
1121
|
-
let { REPLACE_IN_PROGRESS_TABLE: t } = await import("@voltro/database"), { interruptedReplaceAtBoot: n } = await import("./interruptedReplace-CvmiAM9K.js"), { store: r } = await
|
|
1122
|
-
if (i === void 0) return
|
|
1123
|
-
if (!e.includes("--yes")) return
|
|
1149
|
+
let { REPLACE_IN_PROGRESS_TABLE: t } = await import("@voltro/database"), { interruptedReplaceAtBoot: n } = await import("./interruptedReplace-CvmiAM9K.js"), { store: r } = await K(), i = await n(r);
|
|
1150
|
+
if (i === void 0) return V.info("no interrupted replace is recorded — nothing to clear"), 0;
|
|
1151
|
+
if (!e.includes("--yes")) return V.error(`${i}\n\n Re-run with --yes to clear it.`), 1;
|
|
1124
1152
|
let a = await r.query({
|
|
1125
1153
|
table: t,
|
|
1126
1154
|
predicate: void 0,
|
|
@@ -1130,30 +1158,30 @@ var Fe = /* @__PURE__ */ new Set([
|
|
|
1130
1158
|
projection: void 0
|
|
1131
1159
|
});
|
|
1132
1160
|
for (let e of a) await r.delete(t, String(e.id));
|
|
1133
|
-
return
|
|
1161
|
+
return V.info(`cleared ${a.length} interrupted-replace marker(s) — the next boot will start`), 0;
|
|
1134
1162
|
}, Ft = async (e) => {
|
|
1135
|
-
let { STAGING_PREFIX: t, dropStagingSql: n, execStatements: r } = await import("@voltro/data-transfer"), { store: i } = await
|
|
1136
|
-
if (a !== "postgres" && a !== "mysql" && a !== "mariadb" && a !== "sqlite" && a !== "mssql") return
|
|
1163
|
+
let { STAGING_PREFIX: t, dropStagingSql: n, execStatements: r } = await import("@voltro/data-transfer"), { store: i } = await K(), a = G();
|
|
1164
|
+
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;
|
|
1137
1165
|
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));
|
|
1138
|
-
return s.length === 0 ? (
|
|
1166
|
+
return s.length === 0 ? (V.info("no staging tables are present — nothing to clear"), 0) : e.includes("--yes") ? (await r(i, s.map((e) => n(e.slice(t.length), a))), V.info(`dropped ${s.length} staging table(s)`), 0) : (V.error(`${s.length} staging table(s) from an earlier \`--mode replace\`:\n` + s.map((e) => ` ${e}`).join("\n") + "\n\n Each holds a copy of what a run was loading. Nothing else removes them.\n Make sure no import is running right now — this cannot tell a leftover from one in flight.\n Re-run with --yes to drop them."), 1);
|
|
1139
1167
|
}, It = async (e) => {
|
|
1140
|
-
let { describeTransferRun: t, DATA_TRANSFERS_TABLE: n } = await import("@voltro/database"), r = Number(
|
|
1141
|
-
if (
|
|
1142
|
-
let t =
|
|
1143
|
-
if (!t) return
|
|
1144
|
-
let n =
|
|
1145
|
-
if (!n) return
|
|
1168
|
+
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;
|
|
1169
|
+
if (U(e, "--target") === "api") {
|
|
1170
|
+
let t = U(e, "--api-url");
|
|
1171
|
+
if (!t) return V.error("--target api requires --api-url <url> (the running instance)"), 1;
|
|
1172
|
+
let n = U(e, "--token") ?? process.env.VOLTRO_DATA_TRANSFER_SECRET;
|
|
1173
|
+
if (!n) return V.error("--target api requires --token <secret> (or VOLTRO_DATA_TRANSFER_SECRET)"), 1;
|
|
1146
1174
|
let r = await fetch(`${t.replace(/\/+$/, "")}/_voltro/admin/transfers?limit=${i}`, { headers: { authorization: `Bearer ${n}` } }), a = await r.text();
|
|
1147
|
-
if (r.status !== 200) return
|
|
1175
|
+
if (r.status !== 200) return V.error(`api transfers: ${r.status} ${a}`), 1;
|
|
1148
1176
|
let o = JSON.parse(a);
|
|
1149
1177
|
if (e.includes("--json")) return console.log(a), 0;
|
|
1150
1178
|
let s = o.runs ?? [];
|
|
1151
|
-
if (s.length === 0) return
|
|
1152
|
-
(o.running ?? 0) > 0 &&
|
|
1153
|
-
for (let e of s)
|
|
1179
|
+
if (s.length === 0) return V.info("no data transfers recorded on this instance"), 0;
|
|
1180
|
+
(o.running ?? 0) > 0 && V.info(`${o.running} transfer(s) IN FLIGHT — re-run this to watch the counters move`);
|
|
1181
|
+
for (let e of s) V.info(` ${e.line ?? ""}`);
|
|
1154
1182
|
return 0;
|
|
1155
1183
|
}
|
|
1156
|
-
let { store: a } = await
|
|
1184
|
+
let { store: a } = await K(), o;
|
|
1157
1185
|
try {
|
|
1158
1186
|
o = await a.query({
|
|
1159
1187
|
table: n,
|
|
@@ -1167,12 +1195,12 @@ var Fe = /* @__PURE__ */ new Set([
|
|
|
1167
1195
|
take: i
|
|
1168
1196
|
});
|
|
1169
1197
|
} catch {
|
|
1170
|
-
return
|
|
1198
|
+
return V.error(`this target has no \`${n}\` table, so no transfer has been recorded here.\n Run \`voltro db apply\`; the transfers themselves are unaffected by its absence.`), 1;
|
|
1171
1199
|
}
|
|
1172
|
-
if (o.length === 0) return
|
|
1200
|
+
if (o.length === 0) return V.info("no data transfers recorded against this target"), 0;
|
|
1173
1201
|
for (let e of o) {
|
|
1174
1202
|
let n = e;
|
|
1175
|
-
|
|
1203
|
+
V.info(` ${t({
|
|
1176
1204
|
...e,
|
|
1177
1205
|
startedAt: new Date(n.startedAt),
|
|
1178
1206
|
finishedAt: n.finishedAt === null ? null : new Date(n.finishedAt)
|
|
@@ -1180,14 +1208,14 @@ var Fe = /* @__PURE__ */ new Set([
|
|
|
1180
1208
|
}
|
|
1181
1209
|
return 0;
|
|
1182
1210
|
}, Lt = async (e) => {
|
|
1183
|
-
let t =
|
|
1184
|
-
if (!t || !n) return
|
|
1185
|
-
let { resolve: r } = await import("node:path"), i =
|
|
1186
|
-
return a && (i =
|
|
1211
|
+
let t = W(e), n = W(e.filter((e) => e !== t));
|
|
1212
|
+
if (!t || !n) return V.error("usage: voltro data unpack <file.vbundle> <dir> [--passphrase <p>]"), 1;
|
|
1213
|
+
let { resolve: r } = await import("node:path"), i = X(r(process.cwd(), t)), a = Y(e);
|
|
1214
|
+
return a && (i = j(i, a)), await Me(i, r(process.cwd(), n)), V.info("unpack complete", {
|
|
1187
1215
|
file: t,
|
|
1188
1216
|
dir: n
|
|
1189
1217
|
}), 0;
|
|
1190
|
-
},
|
|
1218
|
+
}, Rt = {
|
|
1191
1219
|
export: {
|
|
1192
1220
|
common: [
|
|
1193
1221
|
"--target",
|
|
@@ -1267,23 +1295,23 @@ var Fe = /* @__PURE__ */ new Set([
|
|
|
1267
1295
|
"--drill-url",
|
|
1268
1296
|
"--api-url"
|
|
1269
1297
|
] }
|
|
1270
|
-
},
|
|
1271
|
-
let n =
|
|
1298
|
+
}, zt = (e) => e.split("=")[0], Bt = (e, t) => {
|
|
1299
|
+
let n = Rt[e];
|
|
1272
1300
|
if (!n) return;
|
|
1273
|
-
let r =
|
|
1301
|
+
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 ?? []);
|
|
1274
1302
|
for (let n of t) {
|
|
1275
1303
|
if (!n.startsWith("--")) continue;
|
|
1276
|
-
let t =
|
|
1304
|
+
let t = zt(n);
|
|
1277
1305
|
if (i.has(t)) continue;
|
|
1278
1306
|
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.`;
|
|
1279
|
-
let o = Object.entries(
|
|
1307
|
+
let o = Object.entries(Rt).filter(([n, r]) => n !== e && [
|
|
1280
1308
|
...r.common,
|
|
1281
1309
|
...r.directOnly ?? [],
|
|
1282
1310
|
...r.apiOnly ?? []
|
|
1283
1311
|
].includes(t)).map(([e]) => e);
|
|
1284
1312
|
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.`;
|
|
1285
1313
|
}
|
|
1286
|
-
},
|
|
1314
|
+
}, Vt = {
|
|
1287
1315
|
export: yt,
|
|
1288
1316
|
import: Dt,
|
|
1289
1317
|
transfers: It,
|
|
@@ -1293,14 +1321,14 @@ var Fe = /* @__PURE__ */ new Set([
|
|
|
1293
1321
|
restore: Mt,
|
|
1294
1322
|
"clear-replace-marker": Pt,
|
|
1295
1323
|
"clear-staging": Ft
|
|
1296
|
-
},
|
|
1324
|
+
}, Ht = async (n) => {
|
|
1297
1325
|
let [r, ...i] = n;
|
|
1298
|
-
if (r !== void 0 &&
|
|
1299
|
-
let e =
|
|
1300
|
-
if (e) return
|
|
1326
|
+
if (r !== void 0 && Rt[r] !== void 0) {
|
|
1327
|
+
let e = Bt(r, i);
|
|
1328
|
+
if (e) return V.error(e), 2;
|
|
1301
1329
|
}
|
|
1302
|
-
let a = r === void 0 ? void 0 :
|
|
1303
|
-
return a === void 0 ? (
|
|
1330
|
+
let a = r === void 0 ? void 0 : Vt[r];
|
|
1331
|
+
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);
|
|
1304
1332
|
};
|
|
1305
1333
|
//#endregion
|
|
1306
|
-
export {
|
|
1334
|
+
export { Ht as runDataCommand };
|