@cosmicdrift/kumiko-bundled-features 0.89.0 → 0.90.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.
@@ -0,0 +1,58 @@
1
+ // EXT_USER_DATA hooks for the folder + folder-assignment entities (GDPR Art. 20
2
+ // export / Art. 17 erasure). Lives apart from the folders feature so folders
3
+ // consumers without the user-data-rights pipeline don't pull a hard dependency.
4
+ // Mirrors credit-user-data — standard tenant-scoped pattern, no name-stripping
5
+ // (a folder name is tenant data, not per-user PII).
6
+
7
+ import { deleteMany, selectMany } from "@cosmicdrift/kumiko-framework/bun-db";
8
+ import {
9
+ createEntityExecutor,
10
+ type UserDataDeleteHook,
11
+ type UserDataExportHook,
12
+ } from "@cosmicdrift/kumiko-framework/engine";
13
+ import { folderAssignmentEntity, folderEntity } from "../folders";
14
+
15
+ const { table: folderTable } = createEntityExecutor("folder", folderEntity);
16
+ const { table: folderAssignmentTable } = createEntityExecutor(
17
+ "folder-assignment",
18
+ folderAssignmentEntity,
19
+ );
20
+
21
+ // Folders are tenant-scoped (no per-user owner column). Every tenant user reads
22
+ // all tenant folders in-app, so bundling them into the user's export is no new
23
+ // exposure — it gives the data subject the organisation of the loans they work with.
24
+ export const folderExportHook: UserDataExportHook = async (ctx) => {
25
+ const rows = await selectMany<Record<string, unknown>>(ctx.db, folderTable, {
26
+ tenantId: ctx.tenantId,
27
+ });
28
+ if (rows.length === 0) return null;
29
+ return { entity: "folder", rows };
30
+ };
31
+
32
+ export const folderAssignmentExportHook: UserDataExportHook = async (ctx) => {
33
+ const rows = await selectMany<Record<string, unknown>>(ctx.db, folderAssignmentTable, {
34
+ tenantId: ctx.tenantId,
35
+ });
36
+ if (rows.length === 0) return null;
37
+ return { entity: "folder-assignment", rows };
38
+ };
39
+
40
+ // Tenant-scoped erasure is only safe when the tenant is effectively single-user
41
+ // (set by the forget orchestrator from the app's tenantModel + a runtime
42
+ // sole-member check). In a multi-user tenant this stays a no-op: deleting by
43
+ // tenant would destroy co-members' folders. anonymize is also a no-op — folder
44
+ // rows carry no person-link to strip (name is tenant data, not PII), so a
45
+ // retention hold simply keeps them.
46
+ function tenantScopedDelete(table: typeof folderTable): UserDataDeleteHook {
47
+ return async (ctx, strategy) => {
48
+ // skip: multi-user tenant — a tenant-wide delete would destroy co-members' folders
49
+ if (ctx.tenantModel !== "single-user") return;
50
+ // skip: anonymize is a no-op — folder rows carry no per-user PII to strip
51
+ if (strategy === "anonymize") return;
52
+ await deleteMany(ctx.db, table, { tenantId: ctx.tenantId });
53
+ };
54
+ }
55
+
56
+ export const folderDeleteHook: UserDataDeleteHook = tenantScopedDelete(folderTable);
57
+ export const folderAssignmentDeleteHook: UserDataDeleteHook =
58
+ tenantScopedDelete(folderAssignmentTable);
@@ -0,0 +1,33 @@
1
+ // Provides the EXT_USER_DATA export/delete hooks for the folder + folder-assignment
2
+ // entities as a standalone feature — mount it alongside the folders feature +
3
+ // user-data-rights when an app needs folders in its GDPR export/forget pipeline.
4
+ // Kept separate from the folders feature (which only requires "tenant") so
5
+ // folders stays usable without the user-data-rights stack. Mirrors credit-user-data.
6
+
7
+ import { defineFeature, EXT_USER_DATA } from "@cosmicdrift/kumiko-framework/engine";
8
+ import {
9
+ folderAssignmentDeleteHook,
10
+ folderAssignmentExportHook,
11
+ folderDeleteHook,
12
+ folderExportHook,
13
+ } from "./hooks";
14
+
15
+ export const foldersUserDataFeature = defineFeature("folders-user-data", (r) => {
16
+ r.describe(
17
+ "GDPR (Art. 20 export / Art. 17 erasure) coverage for the `folders` feature's `folder` + `folder-assignment` entities. Mounts the EXT_USER_DATA export + delete hooks so a tenant's folder tree and its entity-to-folder assignments are included in the user-data export bundle and erased on a tenant-scoped forget (single-user tenants only; multi-user + anonymize are no-ops since folder rows carry no per-user PII). Kept separate from `folders` so folder consumers without the user-data-rights pipeline don't pull a hard dependency — requires `user-data-rights`, optionalRequires `folders`.",
18
+ );
19
+ // user-data-rights ist die harte Abhängigkeit (EXT_USER_DATA-Host). `folders` ist
20
+ // OPTIONAL: ist es toggleable(default=false) gemountet (z.B. per-Tenant via Tier),
21
+ // würde ein hartes r.requires eine „effectively disabled"-Boot-Warnung werfen,
22
+ // obwohl die folder-Entities existieren und die Hooks korrekt greifen.
23
+ r.requires("user-data-rights");
24
+ r.optionalRequires("folders");
25
+ r.useExtension(EXT_USER_DATA, "folder", {
26
+ export: folderExportHook,
27
+ delete: folderDeleteHook,
28
+ });
29
+ r.useExtension(EXT_USER_DATA, "folder-assignment", {
30
+ export: folderAssignmentExportHook,
31
+ delete: folderAssignmentDeleteHook,
32
+ });
33
+ });