@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
package/dist/ledger.js ADDED
@@ -0,0 +1,21 @@
1
+ import { BundleError as e } from "./errors.js";
2
+ import { Effect as t } from "effect";
3
+ //#region src/ledger.ts
4
+ var n = () => ({
5
+ tables: {},
6
+ assets: {}
7
+ }), r = (e) => t.tryPromise(async () => {
8
+ let t = await (await import("node:fs/promises")).readFile(e, "utf8");
9
+ return JSON.parse(t);
10
+ }).pipe(t.orElseSucceed(n)), i = (n, r) => t.tryPromise({
11
+ try: async () => {
12
+ let e = await import("node:fs/promises"), { randomUUID: t } = await import("node:crypto"), i = `${n}.tmp-${t()}`;
13
+ await e.writeFile(i, JSON.stringify(r)), await e.rename(i, n);
14
+ },
15
+ catch: (t) => new e({
16
+ reason: `ledger write failed: ${String(t?.message ?? t)}`,
17
+ path: n
18
+ })
19
+ }), a = (e, t) => t in e.tables, o = (e, t) => t in e.assets;
20
+ //#endregion
21
+ export { o as assetDone, n as emptyLedger, r as readLedger, a as tableDone, i as writeLedger };
@@ -0,0 +1,11 @@
1
+ //#region src/liveGuard.ts
2
+ var e = (e) => {
3
+ if (e.allowLive || e.liveInstance === null || e.sameTarget === "different") return { refuse: !1 };
4
+ let t = e.targetLabel === void 0 ? "" : ` Target: ${e.targetLabel}.`, n = e.sameTarget === "same" ? " That instance is connected to the SAME database." : " We could not confirm which database that instance is connected to, so this is refused on the conservative reading — an older instance, or one whose connection could not be parsed, reports nothing to compare.";
5
+ return {
6
+ refuse: !0,
7
+ message: `refusing to write into a target with a LIVE instance (${e.liveInstance}).${t}${n} A direct import is an uncoordinated concurrent writer — readers see partial state, the reactive layer either storms or goes stale, and rows race with live writes. Re-run with --allow-live to override, or use a live-safe strategy (shadow-swap for replace, or the api target).`
8
+ };
9
+ };
10
+ //#endregion
11
+ export { e as assessLiveImport };
@@ -0,0 +1,56 @@
1
+ import { isSafeBundlePath as e } from "./bundlePaths.js";
2
+ import { Schema as t } from "effect";
3
+ import { StorageAddress as n } from "@voltro/plugin-storage/errors";
4
+ //#region src/manifest.ts
5
+ var r = 2, i = t.Struct({
6
+ name: t.String,
7
+ file: t.String,
8
+ rowCount: t.Number,
9
+ checksum: t.String,
10
+ columnTypes: t.Record(t.String, t.String),
11
+ primaryKey: t.String
12
+ }), a = t.Struct({
13
+ kind: t.Literals([
14
+ "all",
15
+ "tenant",
16
+ "tables"
17
+ ]),
18
+ tenantId: t.optional(t.String),
19
+ tables: t.optional(t.Array(t.String)),
20
+ exclude: t.optional(t.Array(t.String))
21
+ }), o = t.Struct({
22
+ count: t.Number,
23
+ totalBytes: t.Number,
24
+ index: t.optional(t.String.pipe(t.check(t.makeFilter(e))))
25
+ }), s = n, c = t.Struct({
26
+ ...s.fields,
27
+ sha256: t.String.pipe(t.check(t.isPattern(/^[a-f0-9]{64}$/))),
28
+ size: t.Number.pipe(t.check(t.isInt()), t.check(t.isGreaterThanOrEqualTo(0)), t.check(t.isLessThanOrEqualTo(2 ** 53 - 1))),
29
+ contentType: t.String.pipe(t.check(t.isMinLength(1)), t.check(t.makeFilter((e) => !/[\uD800-\uDFFF]/u.test(e))))
30
+ }), l = t.Struct({
31
+ formatVersion: t.Number,
32
+ createdAt: t.String,
33
+ source: t.Struct({
34
+ dialect: t.String,
35
+ schemaFingerprint: t.String
36
+ }),
37
+ scope: a,
38
+ consistency: t.Literals(["live", "snapshot"]),
39
+ compression: t.Literals([
40
+ "zstd",
41
+ "gzip",
42
+ "none"
43
+ ]),
44
+ tables: t.Array(i),
45
+ assets: o,
46
+ fieldKeys: t.optional(t.Struct({
47
+ keyIds: t.Array(t.String),
48
+ unnamed: t.Number
49
+ })),
50
+ masking: t.optional(t.Struct({
51
+ policyId: t.optional(t.String),
52
+ transformed: t.Array(t.String)
53
+ }))
54
+ }), u = t.encodeSync(t.fromJsonString(l)), d = t.decodeUnknownSync(t.fromJsonString(l)), f = "manifest.json", p = "README.md", m = "data", h = "assets", g = "assets/index.ndjson", _ = ".ledger.json";
55
+ //#endregion
56
+ export { h as ASSETS_DIR, g as ASSET_INDEX_FILE, s as AssetAddress, c as AssetEntry, o as AssetSummary, m as DATA_DIR, a as ExportScope, r as FORMAT_VERSION, _ as LEDGER_FILE, f as MANIFEST_FILE, l as Manifest, p as README_FILE, i as TableEntry, d as decodeManifest, u as encodeManifest };
@@ -0,0 +1,246 @@
1
+ import { createHmac as e } from "node:crypto";
2
+ //#region src/masking.ts
3
+ var t = {
4
+ email: { fake: "email" },
5
+ fullName: { fake: "fullName" },
6
+ firstName: { fake: "firstName" },
7
+ lastName: { fake: "lastName" },
8
+ username: { fake: "username" },
9
+ phone: { fake: "phone" },
10
+ address: { fake: "address" },
11
+ company: { fake: "company" },
12
+ url: { fake: "url" },
13
+ ip: { fake: "ip" },
14
+ creditCard: { fake: "creditCard" },
15
+ date: "dateShift",
16
+ secret: "null",
17
+ freeText: "redact"
18
+ }, n = (e) => {
19
+ let t = {};
20
+ for (let n of e.tables) {
21
+ let e = {};
22
+ for (let t of n.columns) e[t.name] = {
23
+ ...t.sensitive ? { sensitive: t.sensitive } : {},
24
+ ...t.safe ? { safe: !0 } : {},
25
+ columnType: t.type
26
+ };
27
+ t[n.name] = e;
28
+ }
29
+ return t;
30
+ }, r = (e, n, r, i) => {
31
+ let a = i.columns?.[`${e}.${n}`];
32
+ if (a) return a;
33
+ if (r?.sensitive) {
34
+ let e = r.sensitive.class;
35
+ return i.classes?.[e] ?? t[e] ?? "redact";
36
+ }
37
+ return r?.safe ? i.onSafe ?? "keep" : r?.columnType === "id" || r?.columnType === "reference" ? "keep" : i.onUnclassified ?? "error";
38
+ }, i = (t, n) => e("sha256", t).update(n == null ? "∅" : String(n)).digest(), a = (e, t) => e.readUInt32BE(t * 4 % (e.length - 4)), o = [
39
+ "Ada",
40
+ "Alan",
41
+ "Grace",
42
+ "Linus",
43
+ "Ida",
44
+ "Ken",
45
+ "Barbara",
46
+ "Dennis",
47
+ "Radia",
48
+ "Guido",
49
+ "Margaret",
50
+ "Bjarne"
51
+ ], s = [
52
+ "Lovelace",
53
+ "Turing",
54
+ "Hopper",
55
+ "Torvalds",
56
+ "Rhodes",
57
+ "Thompson",
58
+ "Liskov",
59
+ "Ritchie",
60
+ "Perlman",
61
+ "Rossum",
62
+ "Hamilton",
63
+ "Stroustrup"
64
+ ], c = [
65
+ "Maple",
66
+ "Oak",
67
+ "Cedar",
68
+ "Pine",
69
+ "Elm",
70
+ "Birch",
71
+ "Willow",
72
+ "Ash"
73
+ ], l = [
74
+ "Initech",
75
+ "Hooli",
76
+ "Umbrella",
77
+ "Acme",
78
+ "Globex",
79
+ "Soylent",
80
+ "Stark",
81
+ "Wayne"
82
+ ], u = (e, t, n) => e[a(t, n) % e.length], d = (e, t, n) => {
83
+ let r = i(n, `${e}:${String(t)}`), d = a(r, 1) % 1e4;
84
+ switch (e) {
85
+ case "email": return `${u(o, r, 0).toLowerCase()}.${u(s, r, 2).toLowerCase()}${d}@example.com`;
86
+ case "fullName": return `${u(o, r, 0)} ${u(s, r, 1)}`;
87
+ case "firstName": return u(o, r, 0);
88
+ case "lastName": return u(s, r, 1);
89
+ case "username": return `${u(o, r, 0).toLowerCase()}${d}`;
90
+ case "phone": return `+1${String(a(r, 0) % 1e10).padStart(10, "0")}`;
91
+ case "address": return `${a(r, 0) % 9899 + 100} ${u(c, r, 1)} St`;
92
+ case "company": return u(l, r, 0);
93
+ case "url": return `https://example.com/${r.toString("hex").slice(0, 12)}`;
94
+ case "ip": return `${a(r, 0) % 256}.${a(r, 1) % 256}.${a(r, 2) % 256}.${a(r, 3) % 254 + 1}`;
95
+ case "creditCard": return f(`4${r.toString("hex").replace(/\D/g, "").padEnd(14, "0").slice(0, 14)}`);
96
+ default: return `${e}_${r.toString("hex").slice(0, 8)}`;
97
+ }
98
+ }, f = (e) => {
99
+ let t = e.slice(0, 15).split("").map(Number), n = 0;
100
+ t.forEach((e, t) => {
101
+ let r = e;
102
+ (15 - t) % 2 == 1 && (r *= 2, r > 9 && (r -= 9)), n += r;
103
+ });
104
+ let r = (10 - n % 10) % 10;
105
+ return e.slice(0, 15) + String(r);
106
+ }, p = 864e5, m = (e, t) => {
107
+ let n = e instanceof Date ? e.getTime() : Date.parse(String(e));
108
+ if (Number.isNaN(n)) return e;
109
+ let r = a(i(t, "date-shift-offset"), 0) % 730 - 365, o = new Date(n + r * p);
110
+ return e instanceof Date ? o : o.toISOString();
111
+ }, h = (e, t) => {
112
+ if (t.value === null || t.value === void 0) return null;
113
+ if (e === "error") throw Error(`masking: unresolved 'error' action for ${t.table}.${t.column}`);
114
+ if (e === "keep") return t.value;
115
+ if (e === "null") return null;
116
+ if (e === "redact") return t.columnType === "text" || typeof t.value == "string" ? "[redacted]" : null;
117
+ if (e === "hash") return i(t.seed, t.value).toString("hex").slice(0, 16);
118
+ if (e === "dateShift") return m(t.value, t.seed);
119
+ if (typeof e == "object" && "fake" in e) return d(e.fake, t.value, t.seed);
120
+ if (typeof e == "object" && "custom" in e) return e.custom(t);
121
+ if (typeof e == "object" && "json" in e) return g(e, t);
122
+ throw Error(`masking: '${t.table}.${t.column}' resolved to an action this build does not understand (${JSON.stringify(e)}). Refusing to copy the value through. Valid actions: 'keep' | 'null' | 'redact' | 'hash' | 'dateShift' | { fake: '<class>' } | { custom: fn } | { json: { fields, rest? } }.`);
123
+ }, g = (e, t) => {
124
+ let { fields: n, rest: r = "redact" } = e.json, i = typeof t.value == "string" ? _(t.value) : {
125
+ ok: !0,
126
+ value: t.value
127
+ };
128
+ if (!i.ok) return h(r, {
129
+ ...t,
130
+ columnType: "text"
131
+ });
132
+ let a = (e, i) => {
133
+ if (e == null) return null;
134
+ if (Array.isArray(e)) return e.map((e, t) => a(e, `${i}[${t}]`));
135
+ if (typeof e == "object") {
136
+ let a = {};
137
+ for (let [o, s] of Object.entries(e)) {
138
+ let e = n[o] ?? r, c = `${i}.${o}`;
139
+ a[o] = typeof e == "object" && "json" in e ? g(e, {
140
+ ...t,
141
+ value: s,
142
+ column: c,
143
+ columnType: "json"
144
+ }) : h(e, {
145
+ ...t,
146
+ value: s,
147
+ column: c,
148
+ columnType: typeof s == "string" ? "text" : "json"
149
+ });
150
+ }
151
+ return a;
152
+ }
153
+ return h(r, {
154
+ ...t,
155
+ value: e,
156
+ column: i,
157
+ columnType: typeof e == "string" ? "text" : "json"
158
+ });
159
+ }, o = a(i.value, t.column);
160
+ return typeof t.value == "string" ? JSON.stringify(o) : o;
161
+ }, _ = (e) => {
162
+ try {
163
+ return {
164
+ ok: !0,
165
+ value: JSON.parse(e)
166
+ };
167
+ } catch {
168
+ return { ok: !1 };
169
+ }
170
+ }, v = (e) => {
171
+ if (typeof e == "string") return e === "keep" || e === "null" || e === "redact" || e === "hash" || e === "dateShift" || e === "error";
172
+ if (typeof e != "object" || !e) return !1;
173
+ if ("fake" in e) return typeof e.fake == "string";
174
+ if ("custom" in e) return typeof e.custom == "function";
175
+ if ("json" in e) {
176
+ let t = e.json;
177
+ if (typeof t != "object" || !t) return !1;
178
+ let { fields: n, rest: r } = t;
179
+ return typeof n != "object" || !n ? !1 : Object.values(n).every(v) && (r === void 0 || v(r));
180
+ }
181
+ return !1;
182
+ }, y = (e, t, n) => {
183
+ let i = {}, a = [], o = [];
184
+ for (let s of e) for (let e of s.columns) {
185
+ let c = `${s.name}.${e.name}`, l = r(s.name, e.name, t[s.name]?.[e.name], n);
186
+ l === "error" ? a.push(c) : v(l) ? i[c] = l : o.push({
187
+ column: c,
188
+ action: l
189
+ });
190
+ }
191
+ return {
192
+ actions: i,
193
+ unclassified: a,
194
+ invalid: o
195
+ };
196
+ }, b = [
197
+ {
198
+ name: "email",
199
+ re: /[\w.+-]+@[\w-]+\.[\w.-]+/
200
+ },
201
+ {
202
+ name: "phone",
203
+ re: /(?:\+?\d[\d\s().-]{8,}\d)/
204
+ },
205
+ {
206
+ name: "ipv4",
207
+ re: /\b(?:\d{1,3}\.){3}\d{1,3}\b/
208
+ },
209
+ {
210
+ name: "creditCard",
211
+ re: /\b(?:\d[ -]?){13,16}\b/
212
+ }
213
+ ], x = (e, t, n) => {
214
+ let r = [], i = /* @__PURE__ */ new Set();
215
+ for (let [a, o] of Object.entries(n)) {
216
+ if (!a.startsWith(`${t}.`) || o !== "keep") continue;
217
+ let n = a.slice(t.length + 1);
218
+ for (let a of e) {
219
+ let e = a[n];
220
+ if (typeof e != "string" || e.length === 0) continue;
221
+ let o = b.find((t) => t.re.test(e));
222
+ if (o && !i.has(n)) {
223
+ i.add(n), r.push({
224
+ table: t,
225
+ column: n,
226
+ pattern: o.name,
227
+ sample: e.slice(0, 40)
228
+ });
229
+ break;
230
+ }
231
+ }
232
+ }
233
+ return r;
234
+ }, S = (e, t, n, r, i) => {
235
+ let a = {};
236
+ for (let [o, s] of Object.entries(e)) a[o] = h(n[`${t}.${o}`] ?? "keep", {
237
+ value: s,
238
+ table: t,
239
+ column: o,
240
+ seed: r,
241
+ ...i?.[o] ? { columnType: i[o] } : {}
242
+ });
243
+ return a;
244
+ };
245
+ //#endregion
246
+ export { t as DEFAULT_CLASS_ACTIONS, h as applyAction, n as classificationFromSnapshot, v as isMaskAction, S as maskRow, y as planMasking, r as resolveAction, x as scanForLeaks };
@@ -0,0 +1,48 @@
1
+ import { classificationFromSnapshot as e, maskRow as t, planMasking as n, scanForLeaks as r } from "./masking.js";
2
+ import { resolveScope as i } from "./scope.js";
3
+ import { topoSortTables as a } from "./topo.js";
4
+ import { Effect as o, Stream as s } from "effect";
5
+ import { streamTable as c } from "@voltro/database";
6
+ //#region src/maskingPreview.ts
7
+ var l = (e) => typeof e == "object" && e ? "fake" in e ? `fake:${e.fake}` : "custom" : String(e), u = (u) => o.gen(function* () {
8
+ let d = u.scope ?? { kind: "all" }, f = yield* i(d, u.snapshot, {
9
+ store: u.store,
10
+ ...u.tenantTables ? { tenantTables: u.tenantTables } : {}
11
+ }), p = a(f.tables, u.snapshot), m = u.classification ?? e(u.snapshot), h = u.sampleSize ?? 20, g = p.map((e) => ({
12
+ name: e,
13
+ columns: (u.snapshot.tables.find((t) => t.name === e)?.columns ?? []).map((e) => ({
14
+ name: e.name,
15
+ type: e.type
16
+ }))
17
+ })), _ = n(g, m, u.masking), v = [], y = [];
18
+ for (let e of p) {
19
+ let n = f.predicateFor(e), i = yield* c(u.store, {
20
+ table: e,
21
+ chunkSize: h,
22
+ ...n === void 0 ? {} : { where: n }
23
+ }).pipe(s.take(h), s.runCollect, o.map((e) => [...e])), a = i.map((n) => t(n, e, _.actions, u.masking.seed));
24
+ y.push(...r(a, e, _.actions));
25
+ let d = i[0], p = a[0], m = [];
26
+ if (d && p) for (let [t, n] of Object.entries(_.actions)) {
27
+ if (!t.startsWith(`${e}.`) || n === "keep") continue;
28
+ let r = t.slice(e.length + 1);
29
+ m.push({
30
+ column: r,
31
+ action: l(n),
32
+ before: d[r],
33
+ after: p[r]
34
+ });
35
+ }
36
+ v.push({
37
+ table: e,
38
+ samples: m
39
+ });
40
+ }
41
+ return {
42
+ tables: v,
43
+ leaks: y,
44
+ unclassified: _.unclassified
45
+ };
46
+ });
47
+ //#endregion
48
+ export { u as previewMasking };