@voltro/data-transfer 0.85.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.
Files changed (43) hide show
  1. package/CHANGELOG.md +5987 -0
  2. package/THIRD-PARTY-NOTICES.md +860 -249
  3. package/dist/assetAdmission.js +92 -0
  4. package/dist/assets.js +68 -0
  5. package/dist/blobStorage.js +64 -0
  6. package/dist/bundleArchive.js +310 -0
  7. package/dist/bundleCrypto.js +106 -0
  8. package/dist/bundlePaths.js +4 -0
  9. package/dist/casAssetPhase.js +87 -0
  10. package/dist/compress.js +6 -0
  11. package/dist/consistency.js +14 -0
  12. package/dist/crossDialect.js +58 -0
  13. package/dist/driftClassification.js +58 -0
  14. package/dist/environmentLocal.js +386 -0
  15. package/dist/errors.js +89 -0
  16. package/dist/exporter.js +271 -0
  17. package/dist/filesystemAdmission.js +33 -0
  18. package/dist/importer.js +576 -0
  19. package/dist/index.d.ts +202 -46
  20. package/dist/index.js +30 -2673
  21. package/dist/ledger.js +21 -0
  22. package/dist/liveGuard.js +11 -0
  23. package/dist/manifest.js +56 -0
  24. package/dist/masking.js +246 -0
  25. package/dist/maskingPreview.js +48 -0
  26. package/dist/nativeDump.js +418 -0
  27. package/dist/ndjson.js +13 -0
  28. package/dist/physicalRequest.js +10 -0
  29. package/dist/profile.js +4 -0
  30. package/dist/progress.js +17 -0
  31. package/dist/readme.js +43 -0
  32. package/dist/replacePreflight.js +23 -0
  33. package/dist/rollbackCapture.js +36 -0
  34. package/dist/rowFailureReason.js +36 -0
  35. package/dist/schemaDrift.js +40 -0
  36. package/dist/scope.js +40 -0
  37. package/dist/sealedKeys.js +40 -0
  38. package/dist/stagedSwap.js +164 -0
  39. package/dist/storageAssetMetadata.js +47 -0
  40. package/dist/subset.js +90 -0
  41. package/dist/topo.js +24 -0
  42. package/dist/typedCodec.js +36 -0
  43. package/package.json +3 -3
@@ -0,0 +1,92 @@
1
+ import { BundleError as e, IntegrityError as t } from "./errors.js";
2
+ import { admitFilesystemPath as n } from "./filesystemAdmission.js";
3
+ import { AssetEntry as r } from "./manifest.js";
4
+ import { physicalRequest as i } from "./physicalRequest.js";
5
+ import { Effect as a, Schema as o } from "effect";
6
+ import { createHash as s } from "node:crypto";
7
+ import { constants as c, createWriteStream as l } from "node:fs";
8
+ import { lstat as u, mkdtemp as d, open as f, rm as p } from "node:fs/promises";
9
+ import { join as m } from "node:path";
10
+ import { tmpdir as h } from "node:os";
11
+ import { Transform as g } from "node:stream";
12
+ import { pipeline as _ } from "node:stream/promises";
13
+ import { storageAddressIdentity as v } from "@voltro/plugin-storage/errors";
14
+ //#region src/assetAdmission.ts
15
+ var y = Symbol("admitted-assets"), b = /* @__PURE__ */ new WeakMap(), x = (t) => o.decodeUnknownEffect(r)(t).pipe(a.mapError(() => new e({ reason: "asset index contains invalid key, digest, size or content type" }))), S = o.Struct({
16
+ ...r.fields,
17
+ sha256: o.optionalKey(r.fields.sha256)
18
+ }), C = (t) => o.decodeUnknownEffect(S)(t).pipe(a.mapError(() => new e({ reason: "asset source metadata contains invalid destination, tenant, key, digest, size or content type" }))), w = (t, n) => b.get(t) === n ? a.void : a.fail(new e({ reason: "asset admission is absent, belongs to another sink or its scope has closed" })), T = (e) => w(e.prepared, e.previousSink).pipe(a.flatMap(() => E({
19
+ entries: e.prepared.entries,
20
+ assetsDir: e.prepared.assetsDir,
21
+ sink: e.sink
22
+ }))), E = (r) => a.gen(function* () {
23
+ yield* a.tryPromise({
24
+ try: () => n(r.assetsDir, "directory"),
25
+ catch: () => new e({ reason: "asset source directory could not be admitted" })
26
+ });
27
+ let o = [], S = /* @__PURE__ */ new Map();
28
+ for (let t of r.entries) {
29
+ let n = yield* x(t), r = v(n), i = S.get(r);
30
+ if (i !== void 0) {
31
+ if (i.sha256 !== n.sha256 || i.size !== n.size || i.contentType !== n.contentType) return yield* a.fail(new e({ reason: "asset index contains conflicting entries for one key" }));
32
+ } else S.set(r, n), o.push(n);
33
+ }
34
+ let C = yield* a.acquireRelease(a.tryPromise({
35
+ try: () => d(m(h(), "voltro-admitted-assets-")),
36
+ catch: () => new e({ reason: "cannot create asset admission directory" })
37
+ }), (e) => a.promise(() => p(e, {
38
+ recursive: !0,
39
+ force: !0
40
+ }))), w = /* @__PURE__ */ new Set();
41
+ for (let d of o) if (yield* r.sink.has(d), w.has(d.sha256)) {
42
+ let n = yield* a.tryPromise({
43
+ try: () => u(m(C, d.sha256)).then((e) => e.size),
44
+ catch: () => new e({ reason: "admitted asset body is unavailable" })
45
+ });
46
+ if (n !== d.size) return yield* a.fail(new t({
47
+ what: `asset ${d.key} size`,
48
+ expected: String(d.size),
49
+ actual: String(n)
50
+ }));
51
+ } else {
52
+ let o = yield* i(async (e) => {
53
+ let t = m(r.assetsDir, d.sha256);
54
+ await n(t, "file");
55
+ let i = await f(t, c.O_RDONLY | c.O_NOFOLLOW | c.O_NONBLOCK);
56
+ try {
57
+ if (!(await i.stat()).isFile()) throw Error("asset body must be a regular file");
58
+ let t = s("sha256"), n = 0, r = new g({ transform(e, r, i) {
59
+ t.update(e), n += e.length, i(null, e);
60
+ } });
61
+ return await _(i.createReadStream({ autoClose: !1 }), r, l(m(C, d.sha256), {
62
+ flags: "wx",
63
+ mode: 384
64
+ }), { signal: e }), {
65
+ sha256: t.digest("hex"),
66
+ size: n
67
+ };
68
+ } finally {
69
+ await i.close();
70
+ }
71
+ }, () => new e({
72
+ reason: "asset body could not be admitted",
73
+ path: d.sha256
74
+ }));
75
+ if (o.sha256 !== d.sha256 || o.size !== d.size) return yield* a.fail(new t({
76
+ what: `asset ${d.key}`,
77
+ expected: `${d.sha256}/${d.size}`,
78
+ actual: `${o.sha256}/${o.size}`
79
+ }));
80
+ w.add(d.sha256);
81
+ }
82
+ let T = Object.freeze({
83
+ [y]: !0,
84
+ assetsDir: C,
85
+ entries: Object.freeze(o.map((e) => Object.freeze(e)))
86
+ });
87
+ return b.set(T, r.sink), yield* a.addFinalizer(() => a.sync(() => {
88
+ b.delete(T);
89
+ })), T;
90
+ });
91
+ //#endregion
92
+ export { S as AssetSourceInfo, w as assertPreparedAssets, x as decodeAssetEntry, C as decodeAssetSourceInfo, E as prepareAssetEntries, T as readmitPreparedAssets };
package/dist/assets.js ADDED
@@ -0,0 +1,68 @@
1
+ import { BundleError as e } from "./errors.js";
2
+ import { admitFilesystemPath as t } from "./filesystemAdmission.js";
3
+ import { physicalRequest as n } from "./physicalRequest.js";
4
+ import { assertPreparedAssets as r, prepareAssetEntries as i } from "./assetAdmission.js";
5
+ import { Effect as a, Stream as o } from "effect";
6
+ import { createHash as s } from "node:crypto";
7
+ import { fromNodeReadable as c, getObjectStream as l, putObjectStream as u, toNodeReadable as d } from "@voltro/plugin-storage";
8
+ import { storageAddressIdentity as f } from "@voltro/plugin-storage/errors";
9
+ //#region src/assets.ts
10
+ var p = (e, t) => ({
11
+ list: t,
12
+ open: (t) => e.resolve(t).pipe(a.flatMap((e) => l(e, t.key)))
13
+ }), m = (e) => ({
14
+ write: (t, n, r) => e.resolve(t).pipe(a.flatMap((e) => u(e, t.key, n, r))),
15
+ has: (t) => e.resolve(t).pipe(a.flatMap((e) => e.head(t.key)), a.map((e) => e !== null)),
16
+ matches: (t) => a.gen(function* () {
17
+ let n = yield* e.resolve(t), r = yield* n.head(t.key);
18
+ if (r === null || r.size !== t.size || r.contentType !== void 0 && r.contentType !== t.contentType) return !1;
19
+ let i = yield* l(n, t.key);
20
+ if (i.contentType !== t.contentType) return !1;
21
+ let a = s("sha256");
22
+ return (yield* o.runFold(i.stream, () => 0, (e, t) => (a.update(t), e + t.byteLength))) === t.size && a.digest("hex") === t.sha256;
23
+ })
24
+ }), h = (r, i) => n(async (e) => {
25
+ let { createWriteStream: n } = await import("node:fs"), a = await import("node:fs/promises"), { pipeline: o, finished: c } = await import("node:stream/promises"), { Transform: l } = await import("node:stream"), u = await import("node:path");
26
+ await t(i, "directory");
27
+ let f = await a.mkdtemp(u.join(i, ".voltro-capture-")), p;
28
+ try {
29
+ let c = u.join(f, "body"), m = s("sha256"), h = 0, g = new l({ transform(e, t, n) {
30
+ m.update(e), h += e.length, n(null, e);
31
+ } });
32
+ p = await d(r.stream), await o(p, g, n(c, {
33
+ flags: "wx",
34
+ mode: 384
35
+ }), { signal: e });
36
+ let _ = m.digest("hex"), v = u.join(i, _);
37
+ return await t(v, "file"), await a.rename(c, v), {
38
+ sha256: _,
39
+ size: h,
40
+ contentType: r.contentType
41
+ };
42
+ } finally {
43
+ p !== void 0 && (p.destroy(), await c(p, { cleanup: !0 }).catch(() => void 0)), await a.rm(f, {
44
+ recursive: !0,
45
+ force: !0
46
+ });
47
+ }
48
+ }, () => new e({
49
+ reason: "asset capture failed",
50
+ path: i
51
+ })), g = (e, t, n) => a.scoped(a.gen(function* () {
52
+ yield* _(e, yield* i({
53
+ sink: e,
54
+ assetsDir: t,
55
+ entries: [n]
56
+ }), n);
57
+ })), _ = (t, n, i) => a.gen(function* () {
58
+ yield* r(n, t);
59
+ let o = yield* a.promise(() => import("node:path")), { createReadStream: s } = yield* a.promise(() => import("node:fs"));
60
+ if (!n.entries.some((e) => f(e) === f(i) && e.sha256 === i.sha256 && e.size === i.size && e.contentType === i.contentType)) return yield* a.fail(new e({ reason: "asset is outside the admitted index" }));
61
+ let l = o.join(n.assetsDir, i.sha256), u = c("bundle", () => s(l));
62
+ yield* t.write(i, u, {
63
+ contentType: i.contentType,
64
+ totalSize: i.size
65
+ });
66
+ });
67
+ //#endregion
68
+ export { _ as applyAdmittedAsset, g as restoreAssetFromCas, m as storageAssetSink, p as storageAssetSource, h as writeAssetToCas };
@@ -0,0 +1,64 @@
1
+ import { physicalRequest as e } from "./physicalRequest.js";
2
+ import { storageAssetSink as t } from "./assets.js";
3
+ import { assetBundleContext as n, bundleAssetInfoStream as r, storageAssetInfo as i } from "./storageAssetMetadata.js";
4
+ import { Effect as a, Stream as o } from "effect";
5
+ import { streamTable as s } from "@voltro/database";
6
+ import { STORAGE_REFS_TABLE as c, StorageError as l, fromNodeReadable as u, getObjectStream as d, putObjectStream as f, toNodeReadable as p } from "@voltro/plugin-storage";
7
+ import { Readable as m } from "node:stream";
8
+ import { finished as h } from "node:stream/promises";
9
+ //#region src/blobStorage.ts
10
+ var g = (e, t) => ({
11
+ list: (e) => {
12
+ let n = t(e);
13
+ return { async *[Symbol.asyncIterator]() {
14
+ let e = await a.runPromise(o.runCollect(n));
15
+ for (let t of e) yield t;
16
+ } };
17
+ },
18
+ open: (t) => ({ async *[Symbol.asyncIterator]() {
19
+ let n = await a.runPromise(e.resolve(t)), r = await a.runPromise(n.head(t.key));
20
+ if (r !== null && (r.contentType !== void 0 && r.contentType !== t.contentType || r.size !== t.size)) throw Error("archive blob contradicts source metadata");
21
+ let i = await a.runPromise(d(n, t.key));
22
+ if (r?.contentType !== void 0 && i.contentType !== t.contentType || i.size !== void 0 && i.size !== t.size) throw i.close !== void 0 && await a.runPromise(i.close), Error("archive blob contradicts source metadata");
23
+ let o = await p(i.stream);
24
+ for await (let e of o) yield e instanceof Uint8Array ? e : new Uint8Array(e);
25
+ } })
26
+ }), _ = (e, t) => g(e, (e) => e === void 0 ? s(t, {
27
+ table: c,
28
+ chunkSize: 500
29
+ }).pipe(o.map(i)) : o.unwrap(n(e).pipe(a.map(r)))), v = (e) => ({
30
+ has: (n, r) => a.runPromise(t(e).has(n), r),
31
+ matches: (n, r) => a.runPromise(t(e).matches(n), r),
32
+ write: async (t, n, r, i) => {
33
+ let o = u("storage", () => m.from(n));
34
+ await a.runPromise(e.resolve(t).pipe(a.flatMap((e) => f(e, t.key, o, {
35
+ contentType: r.contentType,
36
+ totalSize: r.size
37
+ }))), i);
38
+ }
39
+ }), y = (t, n) => e(t, () => new l({
40
+ provider: "archive",
41
+ message: n,
42
+ transient: !1
43
+ })), b = (e) => ({
44
+ has: (t) => y((n) => e.has(t, { signal: n }), "asset destination admission failed"),
45
+ matches: (t) => y((n) => e.matches(t, { signal: n }), "asset destination content verification failed"),
46
+ write: (t, n, r) => y(async (i) => {
47
+ if (r.totalSize === void 0) throw Error("admitted asset size is required");
48
+ let a = await p(n), o = () => {
49
+ a.destroy();
50
+ };
51
+ i.addEventListener("abort", o, { once: !0 });
52
+ try {
53
+ if (i.aborted) throw o(), Error("asset write interrupted");
54
+ await e.write(t, a, {
55
+ contentType: r.contentType,
56
+ size: r.totalSize
57
+ }, { signal: i });
58
+ } finally {
59
+ i.removeEventListener("abort", o), a.destroy(), await h(a, { cleanup: !0 }).catch(() => void 0);
60
+ }
61
+ }, "admitted asset write failed")
62
+ });
63
+ //#endregion
64
+ export { b as blobAssetSink, v as storageBlobSink, g as storageBlobSource, _ as storageRefsBlobSource };
@@ -0,0 +1,310 @@
1
+ import { admitFilesystemPath as e, publishBundleFile as t } from "./filesystemAdmission.js";
2
+ import { isSafeBundlePath as n } from "./bundlePaths.js";
3
+ import { AssetAddress as r, MANIFEST_FILE as i, decodeManifest as a, encodeManifest as o } from "./manifest.js";
4
+ import { decodeAssetSourceInfo as s, prepareAssetEntries as c } from "./assetAdmission.js";
5
+ import { readAssetIndex as l } from "./casAssetPhase.js";
6
+ import { Effect as u, Schema as d, Stream as f } from "effect";
7
+ import { createHash as p } from "node:crypto";
8
+ import { createReadStream as m, createWriteStream as h } from "node:fs";
9
+ import { lstat as g, mkdir as _, mkdtemp as v, readdir as y, rm as b, stat as x } from "node:fs/promises";
10
+ import { dirname as S, join as C, resolve as w, sep as T } from "node:path";
11
+ import { tmpdir as E } from "node:os";
12
+ import { PassThrough as D } from "node:stream";
13
+ import { storageAddressIdentity as O } from "@voltro/plugin-storage/errors";
14
+ //#region src/bundleArchive.ts
15
+ var k = Buffer.from("VBUNDLE3"), A = 0, j = 1, M = 2, N = 32, P = async (e) => {
16
+ let t = [], n = async (r) => {
17
+ let i;
18
+ try {
19
+ i = await y(C(e, r), { withFileTypes: !0 });
20
+ } catch {
21
+ return;
22
+ }
23
+ for (let e of i) {
24
+ let i = r ? `${r}/${e.name}` : e.name;
25
+ e.isDirectory() ? await n(i) : e.isFile() && t.push(i);
26
+ }
27
+ };
28
+ return await n(""), t.sort();
29
+ }, F = (e, t) => {
30
+ let n = Buffer.from(e, "utf8"), r = Buffer.alloc(5 + n.length + 8);
31
+ return r.writeUInt8(j, 0), r.writeUInt32BE(n.length, 1), n.copy(r, 5), r.writeBigUInt64BE(BigInt(t), 5 + n.length), r;
32
+ }, I = (e, t, n) => {
33
+ let i = Buffer.from(JSON.stringify(d.decodeUnknownSync(r)(e)), "utf8"), a = Buffer.from(t, "utf8"), o = Buffer.alloc(5 + i.length + 2 + a.length + 8), s = 0;
34
+ return o.writeUInt8(M, s), s += 1, o.writeUInt32BE(i.length, s), s += 4, i.copy(o, s), s += i.length, o.writeUInt16BE(a.length, s), s += 2, a.copy(o, s), s += a.length, o.writeBigUInt64BE(BigInt(n), s), o;
35
+ };
36
+ async function* L(e) {
37
+ yield new Uint8Array(k);
38
+ for (let t of await P(e.dir)) {
39
+ let n = C(e.dir, t), r = await x(n);
40
+ if (yield new Uint8Array(F(t, r.size)), r.size > 0) for await (let e of m(n)) yield new Uint8Array(e);
41
+ }
42
+ if (e.blobs) for await (let t of e.blobs.list(e.dir)) {
43
+ let n = await u.runPromise(s(t));
44
+ yield new Uint8Array(I(n, n.contentType, n.size));
45
+ let r = p("sha256"), i = 0;
46
+ for await (let t of e.blobs.open(n)) {
47
+ let e = Buffer.from(t);
48
+ r.update(e), i += e.length, yield new Uint8Array(e);
49
+ }
50
+ if (i !== n.size) throw Error(`blob '${n.key}' size mismatch: header ${n.size}, streamed ${i}`);
51
+ let a = r.digest();
52
+ if (n.sha256 !== void 0 && a.toString("hex") !== n.sha256) throw Error("archive blob contradicts SQL checksum");
53
+ yield new Uint8Array(a);
54
+ }
55
+ yield new Uint8Array(Buffer.from([A]));
56
+ }
57
+ var R = (e, t) => new Promise((n, r) => e.write(t, (e) => e ? r(e) : n())), z = (e) => new Promise((t) => e.end(t)), B = async (n, r) => {
58
+ let i = await v(C(E(), "voltro-admitted-archive-")), a = C(i, "files"), o = C(i, "objects");
59
+ try {
60
+ await V(n, {
61
+ dir: a,
62
+ blobs: {
63
+ ...W(o),
64
+ has: async () => !1
65
+ }
66
+ });
67
+ let i = C(o, "assets", "index.ndjson"), s = await x(i).then(() => !0, () => !1) ? Array.from(await u.runPromise(f.runCollect(l(i)))) : [], c = await P(a);
68
+ await e(r.dir, "directory");
69
+ for (let t of c) await e(C(w(r.dir), t), "file");
70
+ let d = /* @__PURE__ */ new Map(), p = /* @__PURE__ */ new Set();
71
+ for (let e of s) {
72
+ let t = O(e), n = d.get(t);
73
+ if (n !== void 0 && (n.sha256 !== e.sha256 || n.size !== e.size || n.contentType !== e.contentType)) throw Error("archive contains conflicting blob entries");
74
+ d.set(t, e), await r.blobs.has(e), await r.blobs.matches(e) && p.add(t);
75
+ }
76
+ for (let e of c) {
77
+ let n = C(w(r.dir), e);
78
+ await t(C(a, e), n);
79
+ }
80
+ for (let [e, t] of d) {
81
+ if (p.has(e)) continue;
82
+ let n = m(C(o, "assets", t.sha256));
83
+ try {
84
+ await r.blobs.write(t, n, {
85
+ contentType: t.contentType,
86
+ size: t.size
87
+ });
88
+ } finally {
89
+ n.destroy();
90
+ }
91
+ }
92
+ } finally {
93
+ await b(i, {
94
+ recursive: !0,
95
+ force: !0
96
+ });
97
+ }
98
+ }, V = async (e, t) => {
99
+ let i = w(t.dir);
100
+ await _(i, { recursive: !0 });
101
+ let a = Buffer.alloc(0), o = e[Symbol.asyncIterator](), s = !1, c = /* @__PURE__ */ new Set(), l = /* @__PURE__ */ new Map(), u = async () => {
102
+ let { value: e, done: t } = await o.next();
103
+ return t ? (s = !0, !1) : (a = a.length === 0 ? Buffer.from(e) : Buffer.concat([a, Buffer.from(e)]), !0);
104
+ }, f = async (e) => {
105
+ for (; a.length < e;) if (!await u()) throw Error("truncated bundle archive");
106
+ }, m = (e) => {
107
+ let t = a.subarray(0, e);
108
+ return a = a.subarray(e), t;
109
+ };
110
+ try {
111
+ if (await f(k.length), !m(k.length).equals(k)) throw Error("not a bundle archive (bad magic)");
112
+ for (;;) {
113
+ await f(1);
114
+ let e = m(1).readUInt8(0);
115
+ if (e === A) {
116
+ if (a.length > 0) throw Error("trailing bytes after bundle end marker");
117
+ for (; await u();) if (a.length > 0) throw Error("trailing bytes after bundle end marker");
118
+ return;
119
+ }
120
+ if (e === j) {
121
+ await f(4);
122
+ let e = m(4).readUInt32BE(0);
123
+ if (e === 0 || e > 65536) throw Error("invalid archive path length");
124
+ await f(e);
125
+ let t = m(e).toString("utf8");
126
+ await f(8);
127
+ let r = m(8).readBigUInt64BE(0), o = w(i, t);
128
+ if (o === i || !o.startsWith(i + T) || !n(t)) throw Error(`archive path '${t}' escapes the destination`);
129
+ if (c.has(o)) throw Error("duplicate plain archive path");
130
+ c.add(o), await _(S(o), { recursive: !0 });
131
+ let s = h(o);
132
+ try {
133
+ for (; r > 0n;) {
134
+ a.length === 0 && await f(1);
135
+ let e = r < BigInt(a.length) ? Number(r) : a.length;
136
+ await R(s, m(e)), r -= BigInt(e);
137
+ }
138
+ await z(s);
139
+ } finally {
140
+ s.destroy();
141
+ }
142
+ } else if (e === M) {
143
+ await f(4);
144
+ let e = m(4).readUInt32BE(0);
145
+ if (e === 0 || e > 65536) throw Error("invalid archive key length");
146
+ await f(e);
147
+ let n = d.decodeUnknownSync(r)(JSON.parse(m(e).toString("utf8"))), i = O(n);
148
+ await f(2);
149
+ let o = m(2).readUInt16BE(0);
150
+ await f(o);
151
+ let s = m(o).toString("utf8");
152
+ await f(8);
153
+ let c = m(8).readBigUInt64BE(0);
154
+ if (c > BigInt(2 ** 53 - 1)) throw Error("archive blob size exceeds exact integer range");
155
+ let u = p("sha256"), h = new D(), g = t.blobs.write(n, h, {
156
+ contentType: s,
157
+ size: Number(c)
158
+ });
159
+ g.catch((e) => {
160
+ h.destroy(e instanceof Error ? e : /* @__PURE__ */ Error("archive staging failed"));
161
+ });
162
+ let _ = c;
163
+ try {
164
+ for (; _ > 0n;) {
165
+ a.length === 0 && await f(1);
166
+ let e = _ < BigInt(a.length) ? Number(_) : a.length, t = m(e);
167
+ u.update(t), await R(h, t), _ -= BigInt(e);
168
+ }
169
+ await z(h), await g, await f(N);
170
+ let e = m(N), t = u.digest();
171
+ if (!t.equals(e)) throw Error(`blob '${i}' failed integrity (sha mismatch)`);
172
+ let r = {
173
+ ...n,
174
+ sha256: t.toString("hex"),
175
+ size: Number(c),
176
+ contentType: s
177
+ }, o = l.get(i);
178
+ if (o !== void 0 && (o.sha256 !== r.sha256 || o.size !== r.size || o.contentType !== r.contentType)) throw Error("archive contains conflicting blob addresses");
179
+ l.set(i, r);
180
+ } finally {
181
+ h.destroy(), await g.catch(() => void 0);
182
+ }
183
+ } else throw Error(`unknown bundle entry kind ${e}`);
184
+ }
185
+ } finally {
186
+ s || await Promise.resolve(o.return?.()).catch(() => void 0);
187
+ }
188
+ }, H = async (e, t) => {
189
+ let n = new Set(t), r = /* @__PURE__ */ new Map(), i = Buffer.alloc(0), a = e[Symbol.asyncIterator](), o = async () => {
190
+ let { value: e, done: t } = await a.next();
191
+ return !t && (i = i.length === 0 ? Buffer.from(e) : Buffer.concat([i, Buffer.from(e)]), !0);
192
+ }, s = async (e) => {
193
+ for (; i.length < e;) if (!await o()) return !1;
194
+ return !0;
195
+ }, c = (e) => {
196
+ let t = i.subarray(0, e);
197
+ return i = i.subarray(e), t;
198
+ }, l = async (e) => {
199
+ let t = [], n = e;
200
+ for (; n > 0n;) {
201
+ if (i.length === 0 && !await s(1)) return null;
202
+ let e = n < BigInt(i.length) ? Number(n) : i.length;
203
+ t.push(c(e)), n -= BigInt(e);
204
+ }
205
+ return Buffer.concat(t);
206
+ };
207
+ if (!await s(k.length) || !c(k.length).equals(k)) throw Error("not a bundle archive (bad magic)");
208
+ for (; await s(1);) {
209
+ let e = c(1).readUInt8(0);
210
+ if (e === A || e === M) break;
211
+ if (e !== j) throw Error(`unknown bundle entry kind ${e}`);
212
+ if (!await s(4)) break;
213
+ let t = c(4).readUInt32BE(0);
214
+ if (!await s(t)) break;
215
+ let i = c(t).toString("utf8");
216
+ if (!await s(8)) break;
217
+ let a = await l(c(8).readBigUInt64BE(0));
218
+ if (a === null || n.has(i) && (r.set(i, a), n.delete(i), n.size === 0)) break;
219
+ }
220
+ return r;
221
+ }, U = (e, t) => B(e, {
222
+ dir: t,
223
+ blobs: W(t)
224
+ }).then(async () => {
225
+ let e = await import("node:fs/promises"), n = C(t, "assets", "index.ndjson");
226
+ if (!await x(n).then(() => !0, () => !1)) return;
227
+ let r = await e.readFile(C(t, i), "utf8").catch(() => void 0);
228
+ if (r === void 0) return;
229
+ let s;
230
+ try {
231
+ s = a(r);
232
+ } catch {
233
+ return;
234
+ }
235
+ let c = Array.from(await u.runPromise(f.runCollect(l(n)))), d = new Map(c.map((e) => [O(e), e]));
236
+ await e.writeFile(C(t, i), o({
237
+ ...s,
238
+ assets: {
239
+ count: d.size,
240
+ totalBytes: [...d.values()].reduce((e, t) => e + t.size, 0),
241
+ index: "assets/index.ndjson"
242
+ }
243
+ }));
244
+ }), W = (t) => {
245
+ let n = /* @__PURE__ */ new Set();
246
+ return {
247
+ has: async (r) => (await e(C(t, "assets"), "directory"), await e(C(t, "assets", "index.ndjson"), "file"), n.has(O(r))),
248
+ matches: async (n) => {
249
+ let r = C(t, "assets", "index.ndjson");
250
+ if (await e(r, "file"), !await x(r).then(() => !0, () => !1)) return !1;
251
+ let i = Array.from(await u.runPromise(f.runCollect(l(r)))).filter((e) => O(e) === O(n));
252
+ if (i.length === 0 || i.some((e) => e.sha256 !== n.sha256 || e.size !== n.size || e.contentType !== n.contentType)) return !1;
253
+ let a = C(t, "assets", n.sha256);
254
+ await e(a, "file");
255
+ try {
256
+ await g(a);
257
+ } catch (e) {
258
+ if (typeof e == "object" && e && "code" in e && e.code === "ENOENT") return !1;
259
+ throw e;
260
+ }
261
+ return u.runPromise(u.scoped(c({
262
+ entries: [n],
263
+ assetsDir: C(t, "assets"),
264
+ sink: {
265
+ has: () => u.succeed(!1),
266
+ matches: () => u.succeed(!1),
267
+ write: () => u.void
268
+ }
269
+ }).pipe(u.as(!0), u.catchTag("IntegrityError", () => u.succeed(!1)))));
270
+ },
271
+ write: async (r, i, a) => {
272
+ let o = await import("node:fs/promises"), { pipeline: s } = await import("node:stream/promises"), { Transform: c } = await import("node:stream"), d = C(t, "assets");
273
+ await e(d, "directory"), await _(d, { recursive: !0 });
274
+ let m = p("sha256"), g = 0, y = new c({ transform(e, t, n) {
275
+ m.update(e), g += e.length, n(null, e);
276
+ } });
277
+ await e(d, "directory"), await e(C(d, "index.ndjson"), "file");
278
+ let S = await v(C(d, ".voltro-blob-"));
279
+ try {
280
+ let t = C(S, "body");
281
+ if (await s(i, y, h(t, {
282
+ flags: "wx",
283
+ mode: 384
284
+ })), g !== a.size) throw Error("blob body size does not match its metadata");
285
+ let n = m.digest("hex"), c = C(d, n);
286
+ await e(c, "file"), await e(C(d, "index.ndjson"), "file"), await o.rename(t, c);
287
+ let p = C(d, "index.ndjson"), _ = (await x(p).then(() => !0, () => !1) ? Array.from(await u.runPromise(f.runCollect(l(p)))) : []).filter((e) => O(e) !== O(r));
288
+ _.push({
289
+ ...r,
290
+ sha256: n,
291
+ size: g,
292
+ contentType: a.contentType
293
+ });
294
+ let v = C(S, "index");
295
+ await o.writeFile(v, _.map((e) => JSON.stringify(e)).join("\n") + "\n", {
296
+ flag: "wx",
297
+ mode: 384
298
+ }), await e(p, "file"), await o.rename(v, p);
299
+ } finally {
300
+ await b(S, {
301
+ recursive: !0,
302
+ force: !0
303
+ });
304
+ }
305
+ n.add(O(r));
306
+ }
307
+ };
308
+ };
309
+ //#endregion
310
+ export { W as dirBlobSink, L as packBundle, H as peekBundle, B as unpackBundle, U as unpackBundleToDir };
@@ -0,0 +1,106 @@
1
+ import { createCipheriv as e, createDecipheriv as t, randomBytes as n, scrypt as r } from "node:crypto";
2
+ import { promisify as i } from "node:util";
3
+ //#region src/bundleCrypto.ts
4
+ var a = i(r), o = Buffer.from("VENC1\0\0\0"), s = 32, c = 16, l = 7, u = 16, d = 262144, f = {
5
+ N: 32768,
6
+ r: 8,
7
+ p: 1
8
+ }, p = 134217728, m = 43, h = (e, t, n) => {
9
+ let r = Buffer.alloc(12);
10
+ return e.copy(r, 0, 0, l), r.writeUInt32BE(t >>> 0, l), r.writeUInt8(+!!n, 11), r;
11
+ };
12
+ async function* g(t, r) {
13
+ if (!r) throw Error("encryptStream: a non-empty passphrase is required");
14
+ let i = n(c), g = n(l), _ = await a(r, i, s, {
15
+ ...f,
16
+ maxmem: p
17
+ }), v = Buffer.alloc(m);
18
+ o.copy(v, 0), v.writeUInt32BE(f.N, 8), v.writeUInt32BE(f.r, 12), v.writeUInt32BE(f.p, 16), i.copy(v, 20), g.copy(v, 36), yield new Uint8Array(v);
19
+ let y = 0, b = (t, n) => {
20
+ let r = e("aes-256-gcm", _, h(g, y, n));
21
+ y === 0 && r.setAAD(v);
22
+ let i = Buffer.concat([r.update(t), r.final()]), a = r.getAuthTag();
23
+ y++;
24
+ let o = Buffer.alloc(5 + i.length + u);
25
+ return o.writeUInt8(+!!n, 0), o.writeUInt32BE(i.length + u, 1), i.copy(o, 5), a.copy(o, 5 + i.length), new Uint8Array(o);
26
+ }, x = Buffer.alloc(0), S = 0;
27
+ for await (let e of t) {
28
+ let t = Buffer.from(e), n = 0;
29
+ for (; n < t.length;) {
30
+ if (S === d && (yield b(x, !1), S = 0), S === 0 && t.length - n >= d) {
31
+ let e = t.subarray(n, n + d);
32
+ n += d, n < t.length ? yield b(e, !1) : (x = e, S = d);
33
+ continue;
34
+ }
35
+ x.length !== d && (x = Buffer.alloc(d));
36
+ let e = Math.min(d - S, t.length - n);
37
+ t.copy(x, S, n, n + e), S += e, n += e;
38
+ }
39
+ }
40
+ yield b(x.subarray(0, S), !0);
41
+ }
42
+ async function* _(e, n) {
43
+ if (!n) throw Error("decryptStream: a non-empty passphrase is required");
44
+ let r = e[Symbol.asyncIterator](), i = Buffer.alloc(0), c = 0, l = !1, d = async (e) => {
45
+ let t, n = 0;
46
+ for (; n < e;) {
47
+ if (c === i.length) {
48
+ if (l) break;
49
+ let e = await r.next();
50
+ if (e.done) {
51
+ l = !0;
52
+ break;
53
+ }
54
+ if (i = Buffer.from(e.value), c = 0, i.length === 0) continue;
55
+ }
56
+ if (n === 0 && i.length - c >= e) {
57
+ let t = i.subarray(c, c + e);
58
+ return c += e, t;
59
+ }
60
+ t ??= Buffer.alloc(e);
61
+ let a = Math.min(e - n, i.length - c);
62
+ i.copy(t, n, c, c + a), c += a, n += a;
63
+ }
64
+ return t?.subarray(0, n) ?? Buffer.alloc(0);
65
+ }, f = !1;
66
+ try {
67
+ let e = await d(m);
68
+ if (e.length !== m) throw Error("encrypted bundle truncated (no header)");
69
+ if (!e.subarray(0, 8).equals(o)) throw Error("not an encrypted bundle (bad magic / wrong passphrase target)");
70
+ let r = e.readUInt32BE(8), i = e.readUInt32BE(12), c = e.readUInt32BE(16), l = Buffer.from(e.subarray(20, 36)), f = Buffer.from(e.subarray(36, m)), g = await a(n, l, s, {
71
+ N: r,
72
+ r: i,
73
+ p: c,
74
+ maxmem: p
75
+ }), _ = 0, v = !1;
76
+ for (;;) {
77
+ let n = await d(5);
78
+ if (n.length !== 5) {
79
+ if (n.length === 0 && v) return;
80
+ throw Error("encrypted bundle truncated (no authenticated final frame)");
81
+ }
82
+ if (v) throw Error("encrypted bundle has trailing data after the final frame");
83
+ let r = n.readUInt8(0);
84
+ if (r !== 0 && r !== 1) throw Error("encrypted bundle corrupt (invalid final flag)");
85
+ let i = r === 1, a = n.readUInt32BE(1);
86
+ if (a < u) throw Error("encrypted bundle corrupt (short frame)");
87
+ if (a > 262160) throw Error("encrypted bundle corrupt (oversized frame)");
88
+ let o = await d(a);
89
+ if (o.length !== a) throw Error("encrypted bundle truncated (frame body)");
90
+ let s = o.subarray(0, a - u), c = o.subarray(a - u), l = t("aes-256-gcm", g, h(f, _, i));
91
+ _ === 0 && l.setAAD(e), l.setAuthTag(c);
92
+ let p = Buffer.concat([l.update(s), l.final()]);
93
+ _++, i && (v = !0), p.length > 0 && (yield new Uint8Array(p));
94
+ }
95
+ } catch (e) {
96
+ throw f = !0, e;
97
+ } finally {
98
+ if (!l) try {
99
+ await r.return?.();
100
+ } catch (e) {
101
+ if (!f) throw e;
102
+ }
103
+ }
104
+ }
105
+ //#endregion
106
+ export { _ as decryptStream, g as encryptStream };
@@ -0,0 +1,4 @@
1
+ //#region src/bundlePaths.ts
2
+ var e = (e) => e.length > 0 && !e.includes("\0") && !e.includes("\\") && !e.startsWith("/") && !/^[a-z]:/i.test(e) && !e.split("/").some((e) => e === "" || e === "." || e === "..");
3
+ //#endregion
4
+ export { e as isSafeBundlePath };