@voltro/data-transfer 0.86.0 → 0.87.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 +5321 -0
- package/THIRD-PARTY-NOTICES.md +856 -217
- package/dist/assetAdmission.js +92 -0
- package/dist/assets.js +61 -40
- package/dist/blobStorage.js +54 -30
- package/dist/bundleArchive.js +248 -103
- package/dist/bundlePaths.js +4 -0
- package/dist/casAssetPhase.js +77 -33
- package/dist/compress.js +3 -3
- package/dist/environmentLocal.js +266 -2
- package/dist/errors.js +12 -5
- package/dist/exporter.js +145 -131
- package/dist/filesystemAdmission.js +33 -0
- package/dist/importer.js +325 -285
- package/dist/index.d.ts +200 -46
- package/dist/index.js +28 -26
- package/dist/manifest.js +44 -40
- package/dist/nativeDump.js +133 -84
- package/dist/physicalRequest.js +10 -0
- package/dist/sealedKeys.js +40 -0
- package/dist/storageAssetMetadata.js +47 -0
- package/package.json +3 -3
package/dist/nativeDump.js
CHANGED
|
@@ -104,12 +104,16 @@ var g = (e, t) => {
|
|
|
104
104
|
stdoutFile: r
|
|
105
105
|
};
|
|
106
106
|
}
|
|
107
|
-
case "sqlite":
|
|
108
|
-
|
|
109
|
-
kind: "copy",
|
|
107
|
+
case "sqlite": return {
|
|
108
|
+
kind: "sqlite-backup",
|
|
110
109
|
from: y(n),
|
|
111
110
|
to: r
|
|
112
111
|
};
|
|
112
|
+
case "turso": return {
|
|
113
|
+
kind: "vacuum-into",
|
|
114
|
+
conn: n,
|
|
115
|
+
to: r
|
|
116
|
+
};
|
|
113
117
|
case "mssql": return {
|
|
114
118
|
kind: "spawn",
|
|
115
119
|
tool: "sqlpackage",
|
|
@@ -273,92 +277,137 @@ END`;
|
|
|
273
277
|
return t.gen(function* () {
|
|
274
278
|
yield* a(`preparing ${n.scratch}`, i.prepare), yield* L(n.load).pipe(t.tapError(() => o)), yield* a(`carrying ${n.carry.join(", ")} into the imported copy`, i.carry).pipe(t.tapError(() => o)), yield* a(`swapping ${n.scratch} in as ${n.live}`, i.swap).pipe(t.tapError(() => a("putting the original database back", i.undoSwap).pipe(t.ignore)));
|
|
275
279
|
});
|
|
276
|
-
}, L = (n, r) =>
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
})))
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
for (let e of [
|
|
290
|
-
`${n.to}-wal`,
|
|
291
|
-
`${n.to}-shm`,
|
|
292
|
-
`${n.to}-journal`
|
|
293
|
-
]) try {
|
|
294
|
-
d(e);
|
|
280
|
+
}, L = (n, r) => {
|
|
281
|
+
if (n.kind === "mssql-replace") return I(n, r);
|
|
282
|
+
if (n.kind === "vacuum-into") {
|
|
283
|
+
if (r === void 0) return t.fail(new e({
|
|
284
|
+
tool: "vacuum-into",
|
|
285
|
+
reason: "a VACUUM INTO backup needs a SQL connection to the database"
|
|
286
|
+
}));
|
|
287
|
+
let a = `${n.to}.backing-up-${i()}`;
|
|
288
|
+
return r(n.conn, [`VACUUM INTO '${a.replaceAll("'", "''")}'`]).pipe(t.flatMap(() => t.callback((e) => {
|
|
289
|
+
l(a, n.to, (n) => e(n ? t.fail(n) : t.void));
|
|
290
|
+
})), t.mapError((t) => {
|
|
291
|
+
try {
|
|
292
|
+
d(a);
|
|
295
293
|
} catch {}
|
|
296
|
-
|
|
297
|
-
|
|
294
|
+
return new e({
|
|
295
|
+
tool: "vacuum-into",
|
|
296
|
+
reason: `VACUUM INTO ${n.to}: ${t instanceof Error ? t.message : String(t)}`
|
|
298
297
|
});
|
|
299
|
-
}
|
|
300
|
-
});
|
|
301
|
-
}) : t.callback((r) => {
|
|
302
|
-
let i = h(n.tool, [...n.args], {
|
|
303
|
-
env: {
|
|
304
|
-
...process.env,
|
|
305
|
-
...n.env
|
|
306
|
-
},
|
|
307
|
-
stdio: [
|
|
308
|
-
"pipe",
|
|
309
|
-
n.stdoutFile ? "pipe" : "inherit",
|
|
310
|
-
"pipe"
|
|
311
|
-
]
|
|
312
|
-
}), a = "";
|
|
313
|
-
i.stderr?.on("data", (e) => {
|
|
314
|
-
a += String(e);
|
|
315
|
-
});
|
|
316
|
-
let c = !1, l = (e) => {
|
|
317
|
-
c || (c = !0, r(e));
|
|
318
|
-
}, u, p = (e) => {
|
|
319
|
-
u ??= e;
|
|
320
|
-
};
|
|
321
|
-
if (n.stdoutFile && i.stdout) {
|
|
322
|
-
let e = s(n.stdoutFile);
|
|
323
|
-
e.on("error", (e) => p(`writing ${f(n.stdoutFile)}: ${e.message}`)), i.stdout.on("error", (e) => p(`reading the tool's output: ${e.message}`)), i.stdout.pipe(e);
|
|
324
|
-
}
|
|
325
|
-
let m = 0;
|
|
326
|
-
if (n.stdinFile && i.stdin) {
|
|
327
|
-
let e = o(n.stdinFile);
|
|
328
|
-
e.on("data", (e) => {
|
|
329
|
-
m += e.length;
|
|
330
|
-
}), e.on("error", (e) => {
|
|
331
|
-
p(`reading ${f(n.stdinFile)}: ${e.message}`), i.stdin?.end();
|
|
332
|
-
}), i.stdin.on("error", (e) => {
|
|
333
|
-
e.code !== "EPIPE" && p(`feeding ${f(n.stdinFile)} to ${n.tool}: ${e.message}`);
|
|
334
|
-
}), e.pipe(i.stdin);
|
|
298
|
+
}));
|
|
335
299
|
}
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
300
|
+
return n.kind === "sqlite-backup" ? t.tryPromise({
|
|
301
|
+
try: async () => {
|
|
302
|
+
let { backup: e, DatabaseSync: t } = await import("node:sqlite"), r = `${n.to}.backing-up-${i()}`, a = new t(n.from, { readOnly: !0 });
|
|
303
|
+
try {
|
|
304
|
+
await e(a, r);
|
|
305
|
+
} catch (e) {
|
|
306
|
+
try {
|
|
307
|
+
d(r);
|
|
308
|
+
} catch {}
|
|
309
|
+
throw e;
|
|
310
|
+
} finally {
|
|
311
|
+
a.close();
|
|
312
|
+
}
|
|
313
|
+
await new Promise((e, t) => {
|
|
314
|
+
l(r, n.to, (n) => {
|
|
315
|
+
n ? t(n) : e();
|
|
316
|
+
});
|
|
317
|
+
});
|
|
318
|
+
},
|
|
319
|
+
catch: (t) => new e({
|
|
320
|
+
tool: "sqlite-backup",
|
|
321
|
+
reason: `backup ${n.from} → ${n.to}: ${t instanceof Error ? t.message : String(t)}`
|
|
322
|
+
})
|
|
323
|
+
}) : n.kind === "copy" ? t.callback((r) => {
|
|
324
|
+
let o = `${n.to}.restoring-${i()}`, s = (n) => {
|
|
325
|
+
try {
|
|
326
|
+
d(o);
|
|
327
|
+
} catch {}
|
|
328
|
+
r(t.fail(new e({
|
|
329
|
+
tool: "copy",
|
|
330
|
+
reason: n
|
|
331
|
+
})));
|
|
332
|
+
};
|
|
333
|
+
a(n.from, o, (e) => {
|
|
334
|
+
if (e) s(`copy ${n.from} → ${n.to}: ${e.message}`);
|
|
335
|
+
else {
|
|
336
|
+
for (let e of [
|
|
337
|
+
`${n.to}-wal`,
|
|
338
|
+
`${n.to}-shm`,
|
|
339
|
+
`${n.to}-journal`,
|
|
340
|
+
`${n.to}-log`
|
|
341
|
+
]) try {
|
|
342
|
+
d(e);
|
|
343
|
+
} catch {}
|
|
344
|
+
l(o, n.to, (e) => {
|
|
345
|
+
e ? s(`copy ${n.from} → ${n.to}: ${e.message}`) : r(t.void);
|
|
346
|
+
});
|
|
347
|
+
}
|
|
348
|
+
});
|
|
349
|
+
}) : t.callback((r) => {
|
|
350
|
+
let i = h(n.tool, [...n.args], {
|
|
351
|
+
env: {
|
|
352
|
+
...process.env,
|
|
353
|
+
...n.env
|
|
354
|
+
},
|
|
355
|
+
stdio: [
|
|
356
|
+
"pipe",
|
|
357
|
+
n.stdoutFile ? "pipe" : "inherit",
|
|
358
|
+
"pipe"
|
|
359
|
+
]
|
|
360
|
+
}), a = "";
|
|
361
|
+
i.stderr?.on("data", (e) => {
|
|
362
|
+
a += String(e);
|
|
363
|
+
});
|
|
364
|
+
let c = !1, l = (e) => {
|
|
365
|
+
c || (c = !0, r(e));
|
|
366
|
+
}, u, p = (e) => {
|
|
367
|
+
u ??= e;
|
|
368
|
+
};
|
|
369
|
+
if (n.stdoutFile && i.stdout) {
|
|
370
|
+
let e = s(n.stdoutFile);
|
|
371
|
+
e.on("error", (e) => p(`writing ${f(n.stdoutFile)}: ${e.message}`)), i.stdout.on("error", (e) => p(`reading the tool's output: ${e.message}`)), i.stdout.pipe(e);
|
|
372
|
+
}
|
|
373
|
+
let m = 0;
|
|
374
|
+
if (n.stdinFile && i.stdin) {
|
|
375
|
+
let e = o(n.stdinFile);
|
|
376
|
+
e.on("data", (e) => {
|
|
377
|
+
m += e.length;
|
|
378
|
+
}), e.on("error", (e) => {
|
|
379
|
+
p(`reading ${f(n.stdinFile)}: ${e.message}`), i.stdin?.end();
|
|
380
|
+
}), i.stdin.on("error", (e) => {
|
|
381
|
+
e.code !== "EPIPE" && p(`feeding ${f(n.stdinFile)} to ${n.tool}: ${e.message}`);
|
|
382
|
+
}), e.pipe(i.stdin);
|
|
351
383
|
}
|
|
352
|
-
g()
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
384
|
+
let g = () => {
|
|
385
|
+
if (n.stdoutFile !== void 0) try {
|
|
386
|
+
d(n.stdoutFile);
|
|
387
|
+
} catch {}
|
|
388
|
+
};
|
|
389
|
+
i.on("error", (r) => {
|
|
390
|
+
g(), l(t.fail(new e({
|
|
391
|
+
tool: n.tool,
|
|
392
|
+
reason: `failed to launch (is '${n.tool}' installed + on PATH?): ${r.message}`,
|
|
393
|
+
...n.hint === void 0 ? {} : { hint: n.hint }
|
|
394
|
+
})));
|
|
395
|
+
}), i.on("close", (r) => {
|
|
396
|
+
if (r === 0 && u === void 0) {
|
|
397
|
+
l(t.void);
|
|
398
|
+
return;
|
|
399
|
+
}
|
|
400
|
+
g();
|
|
401
|
+
let i = n.stdinFile === void 0 ? "" : ` — ${m} of ${R(n.stdinFile)} byte(s) of ${f(n.stdinFile)} had been fed to it`;
|
|
402
|
+
l(t.fail(new e({
|
|
403
|
+
tool: n.tool,
|
|
404
|
+
reason: (r === 0 ? `exited cleanly, but ${u}` : `exited with code ${r}${u === void 0 ? "" : ` (${u})`}`) + i + (n.stdoutFile === void 0 ? "" : ` — the partial ${f(n.stdoutFile)} was removed, so it cannot be mistaken for a backup`),
|
|
405
|
+
stderr: a.trim() === "" ? `(${n.tool} printed nothing to stderr)` : a.slice(-2e3),
|
|
406
|
+
...n.hint === void 0 ? {} : { hint: n.hint }
|
|
407
|
+
})));
|
|
408
|
+
});
|
|
360
409
|
});
|
|
361
|
-
}
|
|
410
|
+
}, R = (e) => {
|
|
362
411
|
try {
|
|
363
412
|
return String(u(e).size);
|
|
364
413
|
} catch {
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { Effect as e } from "effect";
|
|
2
|
+
//#region src/physicalRequest.ts
|
|
3
|
+
var t = (t, n) => e.callback((r) => {
|
|
4
|
+
let i = new AbortController(), a = Promise.resolve().then(() => t(i.signal));
|
|
5
|
+
return a.then((t) => r(e.succeed(t)), (t) => r(e.fail(n(t)))), e.sync(() => {
|
|
6
|
+
i.abort();
|
|
7
|
+
}).pipe(e.andThen(e.promise(() => a.then(() => void 0, () => void 0))));
|
|
8
|
+
});
|
|
9
|
+
//#endregion
|
|
10
|
+
export { t as physicalRequest };
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { ciphertextKeyId as e } from "@voltro/database";
|
|
2
|
+
//#region src/sealedKeys.ts
|
|
3
|
+
var t = /enc:v[12]:[A-Za-z0-9+/=:_-]+/g, n = () => ({
|
|
4
|
+
byKey: {},
|
|
5
|
+
unnamed: 0
|
|
6
|
+
}), r = (e) => typeof e == "string" ? e : typeof e == "object" && e && !(e instanceof Uint8Array) && !(e instanceof Date) ? JSON.stringify(e) : void 0, i = (n, i) => {
|
|
7
|
+
for (let a of Object.values(n)) {
|
|
8
|
+
let n = r(a);
|
|
9
|
+
if (n !== void 0 && n.includes("enc:v")) for (let r of n.match(t) ?? []) {
|
|
10
|
+
let t = e(r);
|
|
11
|
+
t === void 0 ? i.unnamed += 1 : i.byKey[t] = (i.byKey[t] ?? 0) + 1;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
}, a = (e, t) => {
|
|
15
|
+
for (let [n, r] of Object.entries(t.byKey)) e.byKey[n] = (e.byKey[n] ?? 0) + r;
|
|
16
|
+
e.unnamed += t.unnamed;
|
|
17
|
+
}, o = (e) => {
|
|
18
|
+
let n = e.match(t);
|
|
19
|
+
return n !== null && n.length === 1 && n[0] === e;
|
|
20
|
+
}, s = (n, i, a, s) => {
|
|
21
|
+
let c = new Set(a.held), l = (t, r) => {
|
|
22
|
+
let o = e(t);
|
|
23
|
+
return o === void 0 ? a.opens?.(t, {
|
|
24
|
+
table: n,
|
|
25
|
+
column: r,
|
|
26
|
+
row: i
|
|
27
|
+
}) ?? !0 : c.has(o);
|
|
28
|
+
}, u = { ...i };
|
|
29
|
+
for (let [e, n] of Object.entries(i)) {
|
|
30
|
+
if (typeof n == "string" && o(n)) {
|
|
31
|
+
l(n, e) || (u[e] = null, s.nulled.set(e, (s.nulled.get(e) ?? 0) + 1));
|
|
32
|
+
continue;
|
|
33
|
+
}
|
|
34
|
+
let i = r(n);
|
|
35
|
+
if (i !== void 0 && i.includes("enc:v")) for (let n of i.match(t) ?? []) l(n, e) || s.unreadable.set(e, (s.unreadable.get(e) ?? 0) + 1);
|
|
36
|
+
}
|
|
37
|
+
return u;
|
|
38
|
+
};
|
|
39
|
+
//#endregion
|
|
40
|
+
export { n as emptyTally, a as mergeTally, s as nullUnreadableSealed, i as tallyRow };
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { BundleError as e } from "./errors.js";
|
|
2
|
+
import { admitFilesystemPath as t } from "./filesystemAdmission.js";
|
|
3
|
+
import { isSafeBundlePath as n } from "./bundlePaths.js";
|
|
4
|
+
import { MANIFEST_FILE as r, decodeManifest as i } from "./manifest.js";
|
|
5
|
+
import { AssetSourceInfo as a } from "./assetAdmission.js";
|
|
6
|
+
import { makeDecompressor as o } from "./compress.js";
|
|
7
|
+
import { ndjsonToRows as s } from "./ndjson.js";
|
|
8
|
+
import { Effect as c, Schema as l, Stream as u } from "effect";
|
|
9
|
+
import { createReadStream as d } from "node:fs";
|
|
10
|
+
import { readFile as f } from "node:fs/promises";
|
|
11
|
+
import { join as p } from "node:path";
|
|
12
|
+
import { STORAGE_REFS_TABLE as m, fromNodeReadable as h } from "@voltro/plugin-storage";
|
|
13
|
+
import { compose as g } from "node:stream";
|
|
14
|
+
//#region src/storageAssetMetadata.ts
|
|
15
|
+
var _ = (t) => c.tryPromise({
|
|
16
|
+
try: async () => {
|
|
17
|
+
let e = i(await f(p(t, r), "utf8"));
|
|
18
|
+
return {
|
|
19
|
+
dir: t,
|
|
20
|
+
tables: e.tables,
|
|
21
|
+
compression: e.compression
|
|
22
|
+
};
|
|
23
|
+
},
|
|
24
|
+
catch: () => new e({ reason: "exported asset manifest unavailable" })
|
|
25
|
+
}), v = (e) => l.decodeUnknownSync(a)({
|
|
26
|
+
destination: e.bucket,
|
|
27
|
+
tenantId: e.tenantId,
|
|
28
|
+
key: e.key,
|
|
29
|
+
...typeof e.checksum == "string" && /^[a-f0-9]{64}$/.test(e.checksum) ? { sha256: e.checksum } : {},
|
|
30
|
+
contentType: e.contentType,
|
|
31
|
+
size: typeof e.size == "bigint" || typeof e.size == "string" && /^(0|[1-9]\d*)$/.test(e.size) ? Number(e.size) : e.size
|
|
32
|
+
}), y = (r) => {
|
|
33
|
+
let i = r.tables.find((e) => e.name === m);
|
|
34
|
+
return i === void 0 ? u.empty : u.unwrap(c.gen(function* () {
|
|
35
|
+
if (!n(i.file)) return yield* c.fail(new e({ reason: "invalid asset metadata table path" }));
|
|
36
|
+
let a = p(r.dir, i.file);
|
|
37
|
+
return yield* c.tryPromise({
|
|
38
|
+
try: () => t(a, "file"),
|
|
39
|
+
catch: () => new e({ reason: "asset metadata path unavailable" })
|
|
40
|
+
}), s(h("bundle", () => g(d(a), o(r.compression))), i.name, i.columnTypes).pipe(u.mapEffect((t) => c.try({
|
|
41
|
+
try: () => v(t),
|
|
42
|
+
catch: () => new e({ reason: "invalid storage ownership in exported rows" })
|
|
43
|
+
})));
|
|
44
|
+
}));
|
|
45
|
+
};
|
|
46
|
+
//#endregion
|
|
47
|
+
export { _ as assetBundleContext, y as bundleAssetInfoStream, v as storageAssetInfo };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@voltro/data-transfer",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.87.0",
|
|
4
4
|
"description": "Portable export/import for a Voltro app's data + assets — typed-NDJSON row codec, content-addressed assets, framed compression, a resumable chunk ledger, and FK-ordered idempotent import. Dialect-agnostic; the resilient backbone under `voltro data export|import` and native backups.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"voltro",
|
|
@@ -33,8 +33,8 @@
|
|
|
33
33
|
"node": ">=24.0.0"
|
|
34
34
|
},
|
|
35
35
|
"dependencies": {
|
|
36
|
-
"@voltro/database": "0.
|
|
37
|
-
"@voltro/plugin-storage": "0.
|
|
36
|
+
"@voltro/database": "0.87.0",
|
|
37
|
+
"@voltro/plugin-storage": "0.87.0"
|
|
38
38
|
},
|
|
39
39
|
"peerDependencies": {
|
|
40
40
|
"effect": "4.0.1"
|