@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,271 @@
1
+ import { snapshotPrelude as e } from "./consistency.js";
2
+ import { classificationFromSnapshot as t, maskRow as n, planMasking as r } from "./masking.js";
3
+ import { emptyTally as i, mergeTally as a, tallyRow as o } from "./sealedKeys.js";
4
+ import { BundleError as s, MaskingError as c, ScopeError as l } from "./errors.js";
5
+ import { computeSubsetIds as u, subsetToScope as d } from "./subset.js";
6
+ import { resolveScope as f } from "./scope.js";
7
+ import { ASSETS_DIR as p, ASSET_INDEX_FILE as m, DATA_DIR as h, LEDGER_FILE as g, MANIFEST_FILE as _, README_FILE as v, encodeManifest as y } from "./manifest.js";
8
+ import { captureAssetsToCas as b } from "./casAssetPhase.js";
9
+ import { compressionExt as x, makeCompressor as S, pickCompression as C } from "./compress.js";
10
+ import { buildReadme as w } from "./readme.js";
11
+ import { reportProgress as T, withTableSpan as E } from "./progress.js";
12
+ import { readLedger as D, tableDone as O, writeLedger as k } from "./ledger.js";
13
+ import { rowsToNdjson as A } from "./ndjson.js";
14
+ import { topoSortTables as j } from "./topo.js";
15
+ import { Cause as M, Effect as N, Exit as P, Schedule as F, Stream as I } from "effect";
16
+ import { streamTable as L } from "@voltro/database";
17
+ import { fingerprintSchema as R } from "@voltro/database/sql";
18
+ //#region src/exporter.ts
19
+ var z = (e) => {
20
+ let t = e, n = [];
21
+ typeof t?._tag == "string" && t._tag !== "" && n.push(t._tag);
22
+ let r = typeof t?.message == "string" ? t.message.trim() : "";
23
+ if (r === "" ? t?.cause !== void 0 && t.cause !== null && n.push(z(t.cause)) : n.push(r), n.length === 0) {
24
+ let t = String(e ?? "").trim();
25
+ return t === "" || t === "[object Object]" ? B(e) : t;
26
+ }
27
+ return n.join(": ");
28
+ }, B = (e) => {
29
+ try {
30
+ let t = JSON.stringify(e);
31
+ return t === void 0 || t === "{}" ? "unknown error (no message, cause or tag)" : t;
32
+ } catch {
33
+ return "unknown error (not serialisable)";
34
+ }
35
+ }, V = (e, t) => {
36
+ let n = e.tables.find((e) => e.name === t), r = {};
37
+ for (let e of n?.columns ?? []) e.generatedAs === void 0 && (r[e.name] = e.type);
38
+ return r;
39
+ }, H = (e, t) => (e.tables.find((e) => e.name === t)?.columns ?? []).filter((e) => e.generatedAs !== void 0).map((e) => e.name), U = (e, t) => {
40
+ if (t.length === 0) return e;
41
+ let n = { ...e };
42
+ for (let e of t) delete n[e];
43
+ return n;
44
+ }, W = (e, t) => {
45
+ let n = e.tables.find((e) => e.name === t);
46
+ if (n === void 0) return { refusal: `${t}: not in the schema snapshot, so its primary key is unknown.` };
47
+ let r = n.columns.find((e) => e.type === "id");
48
+ if (r !== void 0) return { column: r.name };
49
+ let i = n.indexes.find((e) => e.name === `${t}_pkey`)?.columns ?? n.primaryKey ?? [];
50
+ return i.length === 1 ? { column: i[0] } : i.length > 1 ? { refusal: `${t}: composite primary key (${i.join(", ")}). Keyset export orders by ONE column; ordering by the first alone would split equal values across pages and drop or duplicate rows. Exclude this table from the scope, or give it a single-column key.` } : { refusal: `${t}: no primary key, so there is no stable order to paginate by. A keyset export would return rows in an order the database may change between pages. Exclude this table from the scope.` };
51
+ }, G = (e, t, n) => N.tryPromise({
52
+ try: async () => {
53
+ let { createWriteStream: r } = await import("node:fs"), i = await import("node:fs/promises"), { createHash: a, randomUUID: o } = await import("node:crypto"), { pipeline: s } = await import("node:stream/promises"), { Readable: c, Transform: l } = await import("node:stream"), u = a("sha256"), d = 0, f = new l({ transform(e, t, n) {
54
+ u.update(e);
55
+ let r = e;
56
+ for (let e = 0; e < r.length; e++) r[e] === 10 && d++;
57
+ n(null, e);
58
+ } }), p = c.fromWeb(I.toReadableStream(e)), m = `${t}.tmp-${o()}`;
59
+ try {
60
+ await s(p, f, S(n), r(m)), await i.rename(m, t);
61
+ } catch (e) {
62
+ throw await i.rm(m, { force: !0 }).catch(() => {}), e;
63
+ }
64
+ return {
65
+ rowCount: d,
66
+ checksum: u.digest("hex")
67
+ };
68
+ },
69
+ catch: (e) => new s({
70
+ reason: `write table failed: ${z(e)}`,
71
+ path: t
72
+ })
73
+ }), K = (e, t) => N.tryPromise({
74
+ try: async () => {
75
+ await (await import("node:fs/promises")).appendFile(e, `${t}\n`);
76
+ },
77
+ catch: (t) => new s({
78
+ reason: `append failed: ${z(t)}`,
79
+ path: e
80
+ })
81
+ }), q = (e, t) => N.gen(function* () {
82
+ let r = [], s = t.ordered.length;
83
+ for (let c = 0; c < t.ordered.length; c++) {
84
+ let u = t.ordered[c], d = V(t.snapshot, u), f = W(t.snapshot, u);
85
+ if ("refusal" in f) return yield* N.fail(new l({ reason: `export: ${f.refusal}` }));
86
+ let p = f.column, m = `${h}/${u}.ndjson${x(t.compression)}`;
87
+ if (O(t.ledger, u)) {
88
+ let e = t.ledger.tables[u];
89
+ e.sealed !== void 0 && a(t.sealed, e.sealed), T(t.onProgress, {
90
+ phase: "export",
91
+ table: u,
92
+ index: c,
93
+ tableCount: s,
94
+ event: "done",
95
+ rowsDone: e.rowCount,
96
+ total: e.rowCount
97
+ }), r.push({
98
+ name: u,
99
+ file: m,
100
+ rowCount: e.rowCount,
101
+ checksum: e.checksum,
102
+ columnTypes: d,
103
+ primaryKey: p
104
+ });
105
+ continue;
106
+ }
107
+ T(t.onProgress, {
108
+ phase: "export",
109
+ table: u,
110
+ index: c,
111
+ tableCount: s,
112
+ event: "start",
113
+ rowsDone: 0
114
+ });
115
+ let g = t.resolved.predicateFor(u), _ = L(e, {
116
+ table: u,
117
+ pk: p,
118
+ chunkSize: t.chunkSize,
119
+ retry: t.retry,
120
+ ...g === void 0 ? {} : { where: g }
121
+ }), v = H(t.snapshot, u), y = v.length === 0 ? _ : _.pipe(I.map((e) => U(e, v))), b = t.mask ? y.pipe(I.map((e) => n(e, u, t.mask.actions, t.mask.seed, d))) : y, S = i(), C = b.pipe(I.map((e) => (o(e, S), e))), { rowCount: w, checksum: D } = yield* E("export", u, c, s, (e) => e.rowCount, G(A(C, d), t.bundleFile(m), t.compression));
122
+ t.ledger.tables[u] = {
123
+ rowCount: w,
124
+ checksum: D,
125
+ sealed: S
126
+ }, a(t.sealed, S), yield* k(t.ledgerPath, t.ledger), T(t.onProgress, {
127
+ phase: "export",
128
+ table: u,
129
+ index: c,
130
+ tableCount: s,
131
+ event: "done",
132
+ rowsDone: w,
133
+ total: w
134
+ }), r.push({
135
+ name: u,
136
+ file: m,
137
+ rowCount: w,
138
+ checksum: D,
139
+ columnTypes: d,
140
+ primaryKey: p
141
+ });
142
+ }
143
+ return r;
144
+ }), J = (e) => ({
145
+ _tag: "RawSqlFragment",
146
+ strings: [e],
147
+ values: []
148
+ }), Y = (t, n, r) => N.tryPromise({
149
+ try: () => t.transactional(async (t) => {
150
+ if (t.raw) for (let r of e(n)) await t.raw(J(r));
151
+ let i = await N.runPromiseExit(r(t));
152
+ if (P.isFailure(i)) throw Error(M.pretty(i.cause));
153
+ return i.value;
154
+ }),
155
+ catch: (e) => e instanceof s ? e : new s({ reason: `snapshot export failed: ${z(e)}` })
156
+ }), X = (e) => N.gen(function* () {
157
+ let n = e.scope ?? { kind: "all" }, a = C(e.compression), o = e.chunkSize ?? 1e3, l = e.retryTimes ?? 5, x = F.max([F.exponential("50 millis").pipe(F.jittered), F.recurs(l)]), S = yield* N.promise(() => import("node:path")), T = (t) => S.join(e.outDir, t);
158
+ yield* N.tryPromise({
159
+ try: async () => {
160
+ let t = await import("node:fs/promises");
161
+ await t.mkdir(T(h), { recursive: !0 }), e.assets && await t.mkdir(T(p), { recursive: !0 });
162
+ },
163
+ catch: (t) => new s({
164
+ reason: `mkdir failed: ${z(t)}`,
165
+ path: e.outDir
166
+ })
167
+ });
168
+ let E = T(g), O = yield* D(E), A = e.subset ? d(yield* u(e.store, e.snapshot, e.subset), e.snapshot) : yield* f(n, e.snapshot, {
169
+ store: e.store,
170
+ ...e.tenantTables ? { tenantTables: e.tenantTables } : {}
171
+ }), M = j(A.tables, e.snapshot), P = e.consistency ?? "live", I, L;
172
+ if (e.masking) {
173
+ let n = e.classification ?? t(e.snapshot), i = M.map((t) => ({
174
+ name: t,
175
+ columns: (e.snapshot.tables.find((e) => e.name === t)?.columns ?? []).map((e) => ({
176
+ name: e.name,
177
+ type: e.type
178
+ }))
179
+ })), a = r(i, n, e.masking);
180
+ if (a.unclassified.length > 0 || a.invalid.length > 0) return yield* N.fail(new c({
181
+ unclassified: a.unclassified,
182
+ ...a.invalid.length > 0 ? { invalidActions: a.invalid.map((e) => `${e.column}: ${JSON.stringify(e.action)}`) } : {}
183
+ }));
184
+ L = {
185
+ actions: a.actions,
186
+ seed: e.masking.seed
187
+ };
188
+ let o = Object.entries(a.actions).filter(([, e]) => e !== "keep").map(([e]) => e);
189
+ I = {
190
+ ...e.masking.id ? { policyId: e.masking.id } : {},
191
+ transformed: o
192
+ };
193
+ }
194
+ let B = i(), V = {
195
+ sealed: B,
196
+ snapshot: e.snapshot,
197
+ ordered: M,
198
+ resolved: A,
199
+ compression: a,
200
+ chunkSize: o,
201
+ retry: x,
202
+ ledger: O,
203
+ ledgerPath: E,
204
+ bundleFile: T,
205
+ ...L ? { mask: L } : {},
206
+ ...e.onProgress ? { onProgress: e.onProgress } : {}
207
+ }, H = P === "snapshot" ? yield* Y(e.store, e.dialect, (e) => q(e, V)) : yield* q(e.store, V), U = 0, W = 0;
208
+ if (e.assets) {
209
+ let t = T(m), n = yield* b({
210
+ source: {
211
+ ...e.assets,
212
+ list: () => e.assets.list({
213
+ dir: e.outDir,
214
+ tables: H,
215
+ compression: a
216
+ })
217
+ },
218
+ assetsDir: T(p),
219
+ progress: {
220
+ done: (e) => e in O.assets,
221
+ mark: (e, n) => (O.assets[e] = !0, K(t, JSON.stringify(n)).pipe(N.andThen(k(E, O))))
222
+ }
223
+ });
224
+ U = n.count, W = n.totalBytes, n.missing.length > 0 && (yield* N.logWarning(`${n.missing.length} of ${n.missing.length + n.count} blob reference(s) point at objects the storage provider does not have; they are NOT in this bundle and no import can bring them back. e.g. ${n.missing.slice(0, 3).map((e) => `'${e}'`).join(", ")}` + (n.missing.length > 3 ? `, and ${n.missing.length - 3} more` : "")));
225
+ }
226
+ let G = {
227
+ formatVersion: 2,
228
+ createdAt: e.createdAt ?? (/* @__PURE__ */ new Date()).toISOString(),
229
+ source: {
230
+ dialect: e.dialect,
231
+ schemaFingerprint: R(e.snapshot)
232
+ },
233
+ scope: n,
234
+ consistency: P,
235
+ compression: a,
236
+ tables: H,
237
+ assets: U > 0 ? {
238
+ count: U,
239
+ totalBytes: W,
240
+ index: m
241
+ } : {
242
+ count: 0,
243
+ totalBytes: 0
244
+ },
245
+ ...I ? { masking: I } : {},
246
+ fieldKeys: {
247
+ keyIds: Object.entries(B.byKey).filter(([, e]) => e > 0).map(([e]) => e).sort(),
248
+ unnamed: B.unnamed
249
+ }
250
+ };
251
+ return yield* N.tryPromise({
252
+ try: async () => {
253
+ await (await import("node:fs/promises")).writeFile(T(_), y(G));
254
+ },
255
+ catch: (e) => new s({
256
+ reason: `manifest write failed: ${z(e)}`,
257
+ path: _
258
+ })
259
+ }), yield* N.tryPromise({
260
+ try: async () => {
261
+ let e = await import("node:fs/promises"), t = await import("node:os");
262
+ await e.writeFile(T(v), w(G, { hostname: t.hostname() }));
263
+ },
264
+ catch: (e) => new s({
265
+ reason: `readme write failed: ${z(e)}`,
266
+ path: v
267
+ })
268
+ }), G;
269
+ });
270
+ //#endregion
271
+ export { X as runExport };
@@ -0,0 +1,33 @@
1
+ import { constants as e } from "node:fs";
2
+ import { copyFile as t, lstat as n, mkdir as r, mkdtemp as i, rename as a, rm as o } from "node:fs/promises";
3
+ import { dirname as s, join as c, parse as l, resolve as u, sep as d } from "node:path";
4
+ //#region src/filesystemAdmission.ts
5
+ var f = async (e, t) => {
6
+ let r = u(e), i = l(r).root, a = r.slice(i.length).split(d).filter(Boolean), o = i;
7
+ for (let e = 0; e < a.length; e++) {
8
+ o = c(o, a[e]);
9
+ let r;
10
+ try {
11
+ r = await n(o);
12
+ } catch (e) {
13
+ if (typeof e == "object" && e && "code" in e && e.code === "ENOENT") return;
14
+ throw e;
15
+ }
16
+ if (r.isSymbolicLink()) throw Error("bundle path contains a symbolic link");
17
+ if (e === a.length - 1 && t === "file" ? !r.isFile() : !r.isDirectory()) throw Error("bundle path has an incompatible filesystem entry");
18
+ }
19
+ }, p = async (n, l) => {
20
+ await f(l, "file"), await r(s(l), { recursive: !0 }), await f(l, "file");
21
+ let u = await i(c(s(l), ".voltro-unpack-"));
22
+ try {
23
+ let r = c(u, "file");
24
+ await t(n, r, e.COPYFILE_EXCL), await f(l, "file"), await a(r, l);
25
+ } finally {
26
+ await o(u, {
27
+ recursive: !0,
28
+ force: !0
29
+ });
30
+ }
31
+ };
32
+ //#endregion
33
+ export { f as admitFilesystemPath, p as publishBundleFile };