@voltro/plugin-governance 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.
- package/CHANGELOG.md +5987 -0
- package/THIRD-PARTY-NOTICES.md +864 -253
- package/dist/consent.js +55 -0
- package/dist/gdpr.js +118 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.js +193 -442
- package/dist/retention.js +52 -0
- package/dist/store.js +120 -0
- package/package.json +4 -4
package/dist/consent.js
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { and as e, boolean as t, eq as n, id as r, table as i, text as a } from "@voltro/database";
|
|
2
|
+
//#region src/consent.ts
|
|
3
|
+
var o = 1e4, s = (e = o) => {
|
|
4
|
+
let t = /* @__PURE__ */ new Map(), n = [], r = (e, t) => `${e}|${t}`;
|
|
5
|
+
return {
|
|
6
|
+
record: async (i) => {
|
|
7
|
+
t.set(r(i.subjectId, i.purpose), i), n.unshift(i), n.length > e && (n.length = e);
|
|
8
|
+
},
|
|
9
|
+
has: async (e, n) => t.get(r(e, n))?.granted ?? !1,
|
|
10
|
+
list: async (e) => n.filter((t) => t.subjectId === e)
|
|
11
|
+
};
|
|
12
|
+
}, c = "_voltro_consent", l = i(c, {
|
|
13
|
+
id: r({ prefix: "consent" }),
|
|
14
|
+
subjectId: a(),
|
|
15
|
+
purpose: a(),
|
|
16
|
+
granted: t(),
|
|
17
|
+
at: a(),
|
|
18
|
+
tenantId: a().nullable()
|
|
19
|
+
}).index("byConsentSubjectPurpose", ["subjectId", "purpose"]).index("byConsentSubject", ["subjectId"]), u = [l], d = (e) => ({
|
|
20
|
+
subjectId: String(e.subjectId),
|
|
21
|
+
purpose: String(e.purpose),
|
|
22
|
+
granted: e.granted === !0 || e.granted === 1 || e.granted === "true",
|
|
23
|
+
at: String(e.at),
|
|
24
|
+
tenantId: e.tenantId ?? null
|
|
25
|
+
}), f = (t) => {
|
|
26
|
+
let r = (e, n) => t.query({
|
|
27
|
+
table: c,
|
|
28
|
+
predicate: e,
|
|
29
|
+
order: [{
|
|
30
|
+
column: "at",
|
|
31
|
+
direction: "desc"
|
|
32
|
+
}],
|
|
33
|
+
take: n,
|
|
34
|
+
skip: void 0,
|
|
35
|
+
projection: void 0
|
|
36
|
+
});
|
|
37
|
+
return {
|
|
38
|
+
record: async (e) => {
|
|
39
|
+
await t.insert(c, {
|
|
40
|
+
subjectId: e.subjectId,
|
|
41
|
+
purpose: e.purpose,
|
|
42
|
+
granted: e.granted,
|
|
43
|
+
at: e.at,
|
|
44
|
+
tenantId: e.tenantId ?? null
|
|
45
|
+
});
|
|
46
|
+
},
|
|
47
|
+
has: async (t, i) => {
|
|
48
|
+
let a = await r(e(n("subjectId", t), n("purpose", i)), 1);
|
|
49
|
+
return a[0] ? d(a[0]).granted : !1;
|
|
50
|
+
},
|
|
51
|
+
list: async (e) => (await r(n("subjectId", e))).map(d)
|
|
52
|
+
};
|
|
53
|
+
};
|
|
54
|
+
//#endregion
|
|
55
|
+
export { o as CONSENT_HISTORY_CAP, c as CONSENT_TABLE, l as consentTable, u as consentTables, f as dataStoreConsentStore, s as memoryConsentStore };
|
package/dist/gdpr.js
ADDED
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import { writeGovernanceRows as e } from "./store.js";
|
|
2
|
+
//#region src/gdpr.ts
|
|
3
|
+
var t = 500, n = 5e4, r = (e, t) => {
|
|
4
|
+
let n = [];
|
|
5
|
+
for (let r = 0; r < e.length; r += t) n.push(e.slice(r, r + t));
|
|
6
|
+
return n;
|
|
7
|
+
}, i = (e, t) => e.table === t && e.subjectField === "id" ? {
|
|
8
|
+
table: e.table,
|
|
9
|
+
hops: [],
|
|
10
|
+
depth: 0,
|
|
11
|
+
via: "declared"
|
|
12
|
+
} : {
|
|
13
|
+
table: e.table,
|
|
14
|
+
hops: [{
|
|
15
|
+
table: e.table,
|
|
16
|
+
foreignKey: e.subjectField,
|
|
17
|
+
parentKey: "id"
|
|
18
|
+
}],
|
|
19
|
+
depth: 1,
|
|
20
|
+
via: "declared"
|
|
21
|
+
}, a = async (e, t, i, a = n) => {
|
|
22
|
+
let o = /* @__PURE__ */ new Map();
|
|
23
|
+
if (!Number.isSafeInteger(a) || a < 1) throw Error("subject rowCap must be a positive safe integer");
|
|
24
|
+
let s = /* @__PURE__ */ new Map();
|
|
25
|
+
for (let e of t) {
|
|
26
|
+
let t = e.hops.length ? e.hops : [{
|
|
27
|
+
table: e.table,
|
|
28
|
+
foreignKey: "id",
|
|
29
|
+
parentKey: "id"
|
|
30
|
+
}], n = s;
|
|
31
|
+
for (let e of t) {
|
|
32
|
+
let t = JSON.stringify([
|
|
33
|
+
e.table,
|
|
34
|
+
e.foreignKey,
|
|
35
|
+
e.parentKey
|
|
36
|
+
]), r = n.get(t);
|
|
37
|
+
r || (r = {
|
|
38
|
+
hop: e,
|
|
39
|
+
children: /* @__PURE__ */ new Map()
|
|
40
|
+
}, n.set(t, r)), n = r.children;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
let c = (e) => {
|
|
44
|
+
let t = o.get(e);
|
|
45
|
+
return t || (t = {
|
|
46
|
+
rows: /* @__PURE__ */ new Map(),
|
|
47
|
+
truncated: !1
|
|
48
|
+
}, o.set(e, t)), t;
|
|
49
|
+
}, l = (e, t) => {
|
|
50
|
+
let n = c(e);
|
|
51
|
+
for (let e of t) {
|
|
52
|
+
let t = String(e.id ?? JSON.stringify(e));
|
|
53
|
+
n.rows.has(t) || (n.rows.size >= a ? n.truncated = !0 : n.rows.set(t, e));
|
|
54
|
+
}
|
|
55
|
+
}, u = async (t, n) => {
|
|
56
|
+
let { hop: i, children: o } = t, s = c(i.table);
|
|
57
|
+
for (let t of r(n, 500)) {
|
|
58
|
+
let n, r = !1;
|
|
59
|
+
for (;;) {
|
|
60
|
+
if (o.size === 0 && s.truncated) return;
|
|
61
|
+
let c = o.size || r ? 500 : Math.min(500, a - s.rows.size + 1), d = e.scan ? await e.scan(i.table, {
|
|
62
|
+
column: i.foreignKey,
|
|
63
|
+
values: t,
|
|
64
|
+
limit: c,
|
|
65
|
+
...n === void 0 ? {} : { after: n }
|
|
66
|
+
}) : await e.where(i.table, i.foreignKey, t), f = s.rows.size;
|
|
67
|
+
if (l(i.table, d), r = d.length > 0 && s.rows.size === f, d.length === 0) break;
|
|
68
|
+
let p = [...new Set(d.map((e) => String(e[i.parentKey] ?? e.id)))];
|
|
69
|
+
for (let e of o.values()) await u(e, p);
|
|
70
|
+
if (!e.scan || d.length < c) break;
|
|
71
|
+
let m = d[d.length - 1].id;
|
|
72
|
+
if (m === void 0 || m === n) throw Error("governance scan must advance a stable id cursor");
|
|
73
|
+
n = m;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
};
|
|
77
|
+
for (let e of s.values()) await u(e, [i]);
|
|
78
|
+
return [...o.entries()].map(([e, t]) => ({
|
|
79
|
+
table: e,
|
|
80
|
+
rows: [...t.rows.values()],
|
|
81
|
+
...t.truncated ? { truncated: !0 } : {}
|
|
82
|
+
}));
|
|
83
|
+
}, o = async (e, t, n) => {
|
|
84
|
+
let r = await a(e, t, n);
|
|
85
|
+
if (r.some((e) => e.truncated === !0)) throw Error("governance: subject export exceeds the per-table row limit; no bundle was returned");
|
|
86
|
+
let i = {};
|
|
87
|
+
for (let e of r) e.rows.length > 0 && (i[e.table] = e.rows);
|
|
88
|
+
return i;
|
|
89
|
+
}, s = (e, t) => {
|
|
90
|
+
if (t === void 0 || t.length === 0) throw Error(`${e}: mode 'anonymize' needs a non-empty 'anonymizeFields' — with none, nothing is written and the result would report rows as affected that were not changed`);
|
|
91
|
+
}, c = async (t, n, r, i = {}, o = () => (/* @__PURE__ */ new Date()).toISOString()) => {
|
|
92
|
+
let c = i.mode ?? "delete";
|
|
93
|
+
c === "anonymize" && s("governance erasure", i.anonymizeFields);
|
|
94
|
+
let l = await a(t, n, r), u = /* @__PURE__ */ new Map();
|
|
95
|
+
for (let e of n) {
|
|
96
|
+
let t = u.get(e.table);
|
|
97
|
+
(t === void 0 || e.depth > t) && u.set(e.table, e.depth);
|
|
98
|
+
}
|
|
99
|
+
let d = [...l].sort((e, t) => (u.get(t.table) ?? 0) - (u.get(e.table) ?? 0)), f = [], p = [];
|
|
100
|
+
for (let n of d) {
|
|
101
|
+
if (n.rows.length === 0) continue;
|
|
102
|
+
n.truncated === !0 && p.push(n.table);
|
|
103
|
+
let r = c === "delete" ? void 0 : Object.fromEntries((i.anonymizeFields ?? []).map((e) => [e, null])), a = await e(t, n.table, n.rows.map((e) => String(e.id)), r);
|
|
104
|
+
f.push({
|
|
105
|
+
table: n.table,
|
|
106
|
+
count: a
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
return {
|
|
110
|
+
subjectId: r,
|
|
111
|
+
at: o(),
|
|
112
|
+
mode: c,
|
|
113
|
+
affected: f,
|
|
114
|
+
...p.length > 0 ? { truncated: p } : {}
|
|
115
|
+
};
|
|
116
|
+
};
|
|
117
|
+
//#endregion
|
|
118
|
+
export { t as SUBJECT_KEY_CHUNK, n as SUBJECT_ROW_CAP, s as assertAnonymizable, a as collectSubjectRows, c as eraseSubject, o as exportSubject, i as scopeToPath };
|
package/dist/index.d.ts
CHANGED
|
@@ -5,10 +5,13 @@ import { deriveSubjectGraph } from '@voltro/database';
|
|
|
5
5
|
import { DeriveSubjectGraphInput } from '@voltro/database';
|
|
6
6
|
import { ENCRYPTED_PREFIX } from '@voltro/database';
|
|
7
7
|
import { FieldCipher } from '@voltro/database';
|
|
8
|
+
import { fieldKeyIdOf } from '@voltro/runtime';
|
|
8
9
|
import { formatSubjectGraph } from '@voltro/database';
|
|
9
10
|
import { isEncrypted } from '@voltro/database';
|
|
10
11
|
import { makeAesCipher } from '@voltro/runtime';
|
|
11
12
|
import { makeFieldCipher } from '@voltro/runtime';
|
|
13
|
+
import { makeFieldKeyring } from '@voltro/runtime';
|
|
14
|
+
import { resolveFieldKeyring } from '@voltro/runtime';
|
|
12
15
|
import { SchemaTable } from '@voltro/database';
|
|
13
16
|
import { setFieldCipher } from '@voltro/runtime';
|
|
14
17
|
import { SubjectGraph } from '@voltro/database';
|
|
@@ -155,6 +158,8 @@ export declare const exportSubject: (store: GovStore, paths: ReadonlyArray<Subje
|
|
|
155
158
|
|
|
156
159
|
export { FieldCipher }
|
|
157
160
|
|
|
161
|
+
export { fieldKeyIdOf }
|
|
162
|
+
|
|
158
163
|
export { formatSubjectGraph }
|
|
159
164
|
|
|
160
165
|
export declare const governancePlugin: (options?: GovernancePluginOptions) => VoltroPlugin;
|
|
@@ -334,6 +339,8 @@ export { makeAesCipher }
|
|
|
334
339
|
|
|
335
340
|
export { makeFieldCipher }
|
|
336
341
|
|
|
342
|
+
export { makeFieldKeyring }
|
|
343
|
+
|
|
337
344
|
/**
|
|
338
345
|
* In-memory consent store (default + test). Latest-write-wins per
|
|
339
346
|
* (subject,purpose).
|
|
@@ -354,6 +361,8 @@ export declare const memoryGovStore: (seed?: Record<string, Array<Record<string,
|
|
|
354
361
|
dump: () => Record<string, Array<Record<string, unknown>>>;
|
|
355
362
|
};
|
|
356
363
|
|
|
364
|
+
export { resolveFieldKeyring }
|
|
365
|
+
|
|
357
366
|
export declare interface RetentionPolicy {
|
|
358
367
|
readonly table: string;
|
|
359
368
|
/** Time-to-live in ms (e.g. 90 days = 90*86_400_000). */
|