@cosmicdrift/kumiko-bundled-features 0.16.0 → 0.19.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 (106) hide show
  1. package/package.json +1 -1
  2. package/src/billing-foundation/get-subscription-for-tenant.ts +2 -2
  3. package/src/cap-counter/__tests__/{cap-counter.integration.ts → cap-counter.integration.test.ts} +14 -3
  4. package/src/cap-counter/__tests__/enforce-cap.test.ts +8 -4
  5. package/src/cap-counter/__tests__/{with-cap-enforcement.integration.ts → with-cap-enforcement.integration.test.ts} +14 -3
  6. package/src/cap-counter/enforce-cap.ts +2 -4
  7. package/src/cap-counter/handlers/get-counter.query.ts +1 -3
  8. package/src/cap-counter/handlers/increment.write.ts +1 -2
  9. package/src/cap-counter/handlers/mark-soft-warned.write.ts +1 -2
  10. package/src/channel-in-app/in-app-channel.ts +1 -3
  11. package/src/custom-fields/__tests__/cross-tenant-field-delete.integration.test.ts +177 -0
  12. package/src/custom-fields/__tests__/{custom-fields.integration.ts → custom-fields.integration.test.ts} +105 -0
  13. package/src/custom-fields/db/queries/projection.ts +33 -4
  14. package/src/custom-fields/db/queries/retention.ts +2 -2
  15. package/src/custom-fields/db/queries/user-data-rights.ts +6 -3
  16. package/src/custom-fields/feature.ts +10 -4
  17. package/src/custom-fields/handlers/delete-system-field.write.ts +5 -1
  18. package/src/custom-fields/handlers/delete-tenant-field.write.ts +1 -1
  19. package/src/custom-fields/handlers/set-custom-field.write.ts +33 -17
  20. package/src/custom-fields/lib/field-access.ts +39 -14
  21. package/src/custom-fields/lib/value-schema.ts +45 -0
  22. package/src/custom-fields/run-retention.ts +1 -1
  23. package/src/custom-fields/wire-for-entity.ts +22 -4
  24. package/src/custom-fields/wire-user-data-rights.ts +3 -2
  25. package/src/delivery/delivery-service.ts +1 -1
  26. package/src/delivery/types.ts +2 -2
  27. package/src/feature-toggles/__tests__/{feature-toggles.integration.ts → feature-toggles.integration.test.ts} +6 -6
  28. package/src/feature-toggles/handlers/set.write.ts +10 -8
  29. package/src/subscription-stripe/__tests__/{stripe-foundation.integration.ts → stripe-foundation.integration.test.ts} +7 -10
  30. package/src/tier-engine/__tests__/{resolver.integration.ts → resolver.integration.test.ts} +4 -3
  31. package/src/user-data-rights/__tests__/{audit-log.integration.ts → audit-log.integration.test.ts} +12 -5
  32. package/src/user-data-rights/__tests__/{cross-data-matrix.integration.ts → cross-data-matrix.integration.test.ts} +29 -12
  33. package/src/user-data-rights/__tests__/{download.integration.ts → download.integration.test.ts} +15 -7
  34. package/src/user-data-rights/__tests__/{export-job-idempotency.integration.ts → export-job-idempotency.integration.test.ts} +13 -11
  35. package/src/user-data-rights/__tests__/{request-cancel-deletion.integration.ts → request-cancel-deletion.integration.test.ts} +8 -7
  36. package/src/user-data-rights/__tests__/{request-deletion-callback.integration.ts → request-deletion-callback.integration.test.ts} +8 -5
  37. package/src/user-data-rights/__tests__/{request-export.integration.ts → request-export.integration.test.ts} +6 -3
  38. package/src/user-data-rights/__tests__/{restriction-flow.integration.ts → restriction-flow.integration.test.ts} +11 -8
  39. package/src/user-data-rights/__tests__/{run-export-jobs.integration.ts → run-export-jobs.integration.test.ts} +25 -13
  40. package/src/user-data-rights/__tests__/{run-forget-cleanup.integration.ts → run-forget-cleanup.integration.test.ts} +6 -3
  41. package/src/user-data-rights/__tests__/{run-user-export.integration.ts → run-user-export.integration.test.ts} +6 -3
  42. package/src/user-data-rights/__tests__/{user-data-rights.integration.ts → user-data-rights.integration.test.ts} +3 -1
  43. package/src/user-data-rights/db/queries/export-jobs.ts +6 -5
  44. package/src/user-data-rights/db/queries/forget-cleanup.ts +11 -6
  45. package/src/user-data-rights/handlers/cancel-deletion.write.ts +5 -10
  46. package/src/user-data-rights/handlers/export-status.query.ts +12 -12
  47. package/src/user-data-rights/run-export-jobs.ts +2 -5
  48. package/src/user-data-rights/run-forget-cleanup.ts +0 -1
  49. package/src/user-data-rights-defaults/__tests__/{user-data-rights-defaults.integration.ts → user-data-rights-defaults.integration.test.ts} +2 -0
  50. /package/src/__tests__/{es-ops-e2e.integration.ts → es-ops-e2e.integration.test.ts} +0 -0
  51. /package/src/audit/__tests__/{audit.integration.ts → audit.integration.test.ts} +0 -0
  52. /package/src/auth-email-password/__tests__/{account-lockout-no-redis.integration.ts → account-lockout-no-redis.integration.test.ts} +0 -0
  53. /package/src/auth-email-password/__tests__/{account-lockout.integration.ts → account-lockout.integration.test.ts} +0 -0
  54. /package/src/auth-email-password/__tests__/{auth-claims.integration.ts → auth-claims.integration.test.ts} +0 -0
  55. /package/src/auth-email-password/__tests__/{auth.integration.ts → auth.integration.test.ts} +0 -0
  56. /package/src/auth-email-password/__tests__/{email-verification.integration.ts → email-verification.integration.test.ts} +0 -0
  57. /package/src/auth-email-password/__tests__/{identity-v3-login.integration.ts → identity-v3-login.integration.test.ts} +0 -0
  58. /package/src/auth-email-password/__tests__/{invite-flow.integration.ts → invite-flow.integration.test.ts} +0 -0
  59. /package/src/auth-email-password/__tests__/{multi-roles.integration.ts → multi-roles.integration.test.ts} +0 -0
  60. /package/src/auth-email-password/__tests__/{password-reset.integration.ts → password-reset.integration.test.ts} +0 -0
  61. /package/src/auth-email-password/__tests__/{public-routes-rate-limit.integration.ts → public-routes-rate-limit.integration.test.ts} +0 -0
  62. /package/src/auth-email-password/__tests__/{seed-admin.integration.ts → seed-admin.integration.test.ts} +0 -0
  63. /package/src/auth-email-password/__tests__/{session-callbacks.integration.ts → session-callbacks.integration.test.ts} +0 -0
  64. /package/src/auth-email-password/__tests__/{session-strict-mode.integration.ts → session-strict-mode.integration.test.ts} +0 -0
  65. /package/src/auth-email-password/__tests__/{signup-flow.integration.ts → signup-flow.integration.test.ts} +0 -0
  66. /package/src/billing-foundation/__tests__/{billing-foundation.integration.ts → billing-foundation.integration.test.ts} +0 -0
  67. /package/src/compliance-profiles/__tests__/{compliance-profiles.integration.ts → compliance-profiles.integration.test.ts} +0 -0
  68. /package/src/compliance-profiles/__tests__/{seeding.integration.ts → seeding.integration.test.ts} +0 -0
  69. /package/src/config/__tests__/{cascade.integration.ts → cascade.integration.test.ts} +0 -0
  70. /package/src/config/__tests__/{config.integration.ts → config.integration.test.ts} +0 -0
  71. /package/src/custom-fields/__tests__/{audit-integration.integration.ts → audit-integration.integration.test.ts} +0 -0
  72. /package/src/custom-fields/__tests__/{field-access.integration.ts → field-access.integration.test.ts} +0 -0
  73. /package/src/custom-fields/__tests__/{quota.integration.ts → quota.integration.test.ts} +0 -0
  74. /package/src/custom-fields/__tests__/{retention.integration.ts → retention.integration.test.ts} +0 -0
  75. /package/src/custom-fields/__tests__/{user-data-rights.integration.ts → user-data-rights.integration.test.ts} +0 -0
  76. /package/src/data-retention/__tests__/{data-retention.integration.ts → data-retention.integration.test.ts} +0 -0
  77. /package/src/data-retention/__tests__/{policy-for.integration.ts → policy-for.integration.test.ts} +0 -0
  78. /package/src/delivery/__tests__/{delivery-events.integration.ts → delivery-events.integration.test.ts} +0 -0
  79. /package/src/delivery/__tests__/{delivery.integration.ts → delivery.integration.test.ts} +0 -0
  80. /package/src/file-foundation/__tests__/{file-foundation.integration.ts → file-foundation.integration.test.ts} +0 -0
  81. /package/src/files/__tests__/{files.integration.ts → files.integration.test.ts} +0 -0
  82. /package/src/files-provider-s3/__tests__/{s3-provider.integration.ts → s3-provider.integration.test.ts} +0 -0
  83. /package/src/jobs/__tests__/{job-system-user.integration.ts → job-system-user.integration.test.ts} +0 -0
  84. /package/src/jobs/__tests__/{jobs-events.integration.ts → jobs-events.integration.test.ts} +0 -0
  85. /package/src/jobs/__tests__/{jobs-feature.integration.ts → jobs-feature.integration.test.ts} +0 -0
  86. /package/src/legal-pages/__tests__/{legal-pages.integration.ts → legal-pages.integration.test.ts} +0 -0
  87. /package/src/mail-foundation/__tests__/{mail-foundation.integration.ts → mail-foundation.integration.test.ts} +0 -0
  88. /package/src/rate-limiting/__tests__/{rate-limiting.integration.ts → rate-limiting.integration.test.ts} +0 -0
  89. /package/src/renderer-foundation/__tests__/{collect-plugins.integration.ts → collect-plugins.integration.test.ts} +0 -0
  90. /package/src/secrets/__tests__/{rotate.integration.ts → rotate.integration.test.ts} +0 -0
  91. /package/src/secrets/__tests__/{secrets-events.integration.ts → secrets-events.integration.test.ts} +0 -0
  92. /package/src/secrets/__tests__/{secrets.integration.ts → secrets.integration.test.ts} +0 -0
  93. /package/src/sessions/__tests__/{cleanup.integration.ts → cleanup.integration.test.ts} +0 -0
  94. /package/src/sessions/__tests__/{password-auto-revoke.integration.ts → password-auto-revoke.integration.test.ts} +0 -0
  95. /package/src/sessions/__tests__/{sessions.integration.ts → sessions.integration.test.ts} +0 -0
  96. /package/src/subscription-mollie/__tests__/{mollie-foundation.integration.ts → mollie-foundation.integration.test.ts} +0 -0
  97. /package/src/template-resolver/__tests__/{handlers.integration.ts → handlers.integration.test.ts} +0 -0
  98. /package/src/template-resolver/__tests__/{template-resolver.integration.ts → template-resolver.integration.test.ts} +0 -0
  99. /package/src/tenant/__tests__/{multi-tenant.integration.ts → multi-tenant.integration.test.ts} +0 -0
  100. /package/src/tenant/__tests__/{seed-testing.integration.ts → seed-testing.integration.test.ts} +0 -0
  101. /package/src/tenant/__tests__/{tenant.integration.ts → tenant.integration.test.ts} +0 -0
  102. /package/src/text-content/__tests__/{text-content.integration.ts → text-content.integration.test.ts} +0 -0
  103. /package/src/tier-engine/__tests__/{auto-default-tier.integration.ts → auto-default-tier.integration.test.ts} +0 -0
  104. /package/src/tier-engine/__tests__/{tier-engine.integration.ts → tier-engine.integration.test.ts} +0 -0
  105. /package/src/user/__tests__/{seed-testing.integration.ts → seed-testing.integration.test.ts} +0 -0
  106. /package/src/user/__tests__/{user.integration.ts → user.integration.test.ts} +0 -0
@@ -16,14 +16,17 @@ import {
16
16
  selectMany,
17
17
  updateMany,
18
18
  } from "@cosmicdrift/kumiko-framework/bun-db";
19
+ import { extractPgError } from "@cosmicdrift/kumiko-framework/db";
19
20
  import {
20
21
  setupTestStack,
21
22
  type TestStack,
22
23
  unsafeCreateEntityTable,
23
24
  } from "@cosmicdrift/kumiko-framework/stack";
25
+ import { resetTestTables } from "@cosmicdrift/kumiko-framework/testing";
24
26
  import { getTemporal } from "@cosmicdrift/kumiko-framework/time";
25
27
  import { createComplianceProfilesFeature } from "../../compliance-profiles";
26
28
  import { createDataRetentionFeature } from "../../data-retention";
29
+ import { createSessionsFeature } from "../../sessions";
27
30
  import { createUserFeature } from "../../user";
28
31
  import { createUserDataRightsFeature } from "../feature";
29
32
  import {
@@ -45,6 +48,8 @@ beforeAll(async () => {
45
48
  createUserFeature(),
46
49
  createDataRetentionFeature(),
47
50
  createComplianceProfilesFeature(),
51
+ createSessionsFeature(),
52
+
48
53
  createUserDataRightsFeature(),
49
54
  ],
50
55
  });
@@ -57,18 +62,15 @@ afterAll(async () => {
57
62
  });
58
63
 
59
64
  beforeEach(async () => {
60
- await asRawClient(stack.db).unsafe(`DELETE FROM "${exportJobsTable.tableName}"`);
65
+ await resetTestTables(stack.db, [exportJobsTable]);
61
66
  });
62
67
 
63
68
  const NOW = () => getTemporal().Now.instant();
64
69
 
65
- // Drizzle wraps PostgresError mit "Failed query: ..."; die original
66
- // PG-Message ist im `cause`. Unique-Violation hat sqlstate 23505.
67
- //
68
- // Constraint-Name pinnen damit der Test nur unsere Idempotency-
69
- // Constraint pinst, nicht zufaellig eine andere unique-violation
70
- // (z.B. UUID-Kollision auf id-PK durch wiederholtes Test-Setup) —
71
- // silent-Pass durch fremden Constraint waere falsche Bestaetigung.
70
+ // postgres-js wirft PostgresError direkt; Drizzle wrappt in .cause.
71
+ // extractPgError normalisiert beide Shapes. Constraint-Name pinnen damit
72
+ // der Test nur unsere Idempotency-Constraint pinst, nicht zufaellig eine
73
+ // andere unique-violation (z.B. UUID-Kollision auf id-PK).
72
74
  async function expectUniqueViolation(
73
75
  promise: Promise<unknown>,
74
76
  expectedConstraint: string,
@@ -80,9 +82,9 @@ async function expectUniqueViolation(
80
82
  caught = e;
81
83
  }
82
84
  expect(caught).toBeDefined();
83
- const cause = (caught as { cause?: { code?: string; constraint_name?: string } }).cause;
84
- expect(cause?.code).toBe("23505");
85
- expect(cause?.constraint_name).toBe(expectedConstraint);
85
+ const pg = extractPgError(caught);
86
+ expect(pg?.code).toBe("23505");
87
+ expect(pg?.constraint_name).toBe(expectedConstraint);
86
88
  }
87
89
 
88
90
  // Re-Export aus dem Schema — Single source of truth, Rename hier
@@ -10,8 +10,8 @@
10
10
  // hier; der Frist-Ablauf-Cleanup folgt mit S2.U5b.
11
11
 
12
12
  import { afterAll, beforeAll, beforeEach, describe, expect, test } from "bun:test";
13
- import { asRawClient, insertOne, selectMany } from "@cosmicdrift/kumiko-framework/bun-db";
14
- import { createEventsTable } from "@cosmicdrift/kumiko-framework/event-store";
13
+ import { insertOne, selectMany } from "@cosmicdrift/kumiko-framework/bun-db";
14
+ import { createEventsTable, eventsTable } from "@cosmicdrift/kumiko-framework/event-store";
15
15
  import {
16
16
  createTestUser,
17
17
  setupTestStack,
@@ -20,12 +20,15 @@ import {
20
20
  testUserId,
21
21
  unsafeCreateEntityTable,
22
22
  } from "@cosmicdrift/kumiko-framework/stack";
23
+ import { resetTestTables } from "@cosmicdrift/kumiko-framework/testing";
23
24
  import { getTemporal } from "@cosmicdrift/kumiko-framework/time";
24
25
  import {
25
26
  createComplianceProfilesFeature,
26
27
  tenantComplianceProfileEntity,
28
+ tenantComplianceProfileTable,
27
29
  } from "../../compliance-profiles";
28
30
  import { createDataRetentionFeature } from "../../data-retention";
31
+ import { createSessionsFeature } from "../../sessions";
29
32
  import { USER_STATUS, userEntity, userTable } from "../../user";
30
33
  import { createUserFeature } from "../../user/feature";
31
34
  import { createUserDataRightsFeature } from "../feature";
@@ -54,6 +57,8 @@ const features = [
54
57
  createUserFeature(),
55
58
  createDataRetentionFeature(),
56
59
  createComplianceProfilesFeature(),
60
+ createSessionsFeature(),
61
+
57
62
  createUserDataRightsFeature(),
58
63
  ];
59
64
 
@@ -69,11 +74,7 @@ afterAll(async () => {
69
74
  });
70
75
 
71
76
  beforeEach(async () => {
72
- // Hard-clean User-Rows fuer einen sauberen Start je Test. softDelete
73
- // wuerde sonst row-state aus voherigen Tests einschleppen.
74
- await asRawClient(stack.db).unsafe(`DELETE FROM "${userTable.tableName}"`);
75
- await asRawClient(stack.db).unsafe(`DELETE FROM read_tenant_compliance_profiles`);
76
- await asRawClient(stack.db).unsafe(`DELETE FROM kumiko_events`);
77
+ await resetTestTables(stack.db, [userTable, tenantComplianceProfileTable, eventsTable]);
77
78
  });
78
79
 
79
80
  // gracePeriodEnd ist `instant()` (Temporal.Instant in JS). Nicht JS-Date —
@@ -8,8 +8,8 @@
8
8
  // Properties — dieser Test verifiziert sie end-to-end.
9
9
 
10
10
  import { afterAll, beforeAll, beforeEach, describe, expect, test } from "bun:test";
11
- import { asRawClient, insertOne, selectMany } from "@cosmicdrift/kumiko-framework/bun-db";
12
- import { createEventsTable } from "@cosmicdrift/kumiko-framework/event-store";
11
+ import { insertOne, selectMany } from "@cosmicdrift/kumiko-framework/bun-db";
12
+ import { createEventsTable, eventsTable } from "@cosmicdrift/kumiko-framework/event-store";
13
13
  import {
14
14
  createTestUser,
15
15
  setupTestStack,
@@ -17,11 +17,14 @@ import {
17
17
  testTenantId,
18
18
  unsafeCreateEntityTable,
19
19
  } from "@cosmicdrift/kumiko-framework/stack";
20
+ import { resetTestTables } from "@cosmicdrift/kumiko-framework/testing";
20
21
  import {
21
22
  createComplianceProfilesFeature,
22
23
  tenantComplianceProfileEntity,
24
+ tenantComplianceProfileTable,
23
25
  } from "../../compliance-profiles";
24
26
  import { createDataRetentionFeature } from "../../data-retention";
27
+ import { createSessionsFeature } from "../../sessions";
25
28
  import { USER_STATUS, userEntity, userTable } from "../../user";
26
29
  import { createUserFeature } from "../../user/feature";
27
30
  import { createUserDataRightsFeature } from "../feature";
@@ -61,6 +64,8 @@ beforeAll(async () => {
61
64
  createUserFeature(),
62
65
  createDataRetentionFeature(),
63
66
  createComplianceProfilesFeature(),
67
+ createSessionsFeature(),
68
+
64
69
  createUserDataRightsFeature({ sendDeletionRequestedEmail }),
65
70
  ],
66
71
  });
@@ -76,9 +81,7 @@ afterAll(async () => {
76
81
  beforeEach(async () => {
77
82
  state.calls = [];
78
83
  state.shouldThrow = false;
79
- await asRawClient(stack.db).unsafe(`DELETE FROM "${userTable.tableName}"`);
80
- await asRawClient(stack.db).unsafe(`DELETE FROM read_tenant_compliance_profiles`);
81
- await asRawClient(stack.db).unsafe(`DELETE FROM kumiko_events`);
84
+ await resetTestTables(stack.db, [userTable, tenantComplianceProfileTable, eventsTable]);
82
85
  });
83
86
 
84
87
  async function seedAlice(email: string = "alice@example.com"): Promise<void> {
@@ -24,7 +24,7 @@ import {
24
24
  selectMany,
25
25
  updateMany,
26
26
  } from "@cosmicdrift/kumiko-framework/bun-db";
27
- import { createEventsTable } from "@cosmicdrift/kumiko-framework/event-store";
27
+ import { createEventsTable, eventsTable } from "@cosmicdrift/kumiko-framework/event-store";
28
28
  import {
29
29
  createTestUser,
30
30
  setupTestStack,
@@ -32,9 +32,11 @@ import {
32
32
  testTenantId,
33
33
  unsafeCreateEntityTable,
34
34
  } from "@cosmicdrift/kumiko-framework/stack";
35
+ import { resetTestTables } from "@cosmicdrift/kumiko-framework/testing";
35
36
  import { getTemporal } from "@cosmicdrift/kumiko-framework/time";
36
37
  import { createComplianceProfilesFeature } from "../../compliance-profiles";
37
38
  import { createDataRetentionFeature } from "../../data-retention";
39
+ import { createSessionsFeature } from "../../sessions";
38
40
  import { createUserFeature } from "../../user";
39
41
  import { createUserDataRightsFeature } from "../feature";
40
42
  import { EXPORT_JOB_STATUS, exportJobEntity, exportJobsTable } from "../schema/export-job";
@@ -56,6 +58,8 @@ beforeAll(async () => {
56
58
  createUserFeature(),
57
59
  createDataRetentionFeature(),
58
60
  createComplianceProfilesFeature(),
61
+ createSessionsFeature(),
62
+
59
63
  createUserDataRightsFeature(),
60
64
  ],
61
65
  });
@@ -68,8 +72,7 @@ afterAll(async () => {
68
72
  });
69
73
 
70
74
  beforeEach(async () => {
71
- await asRawClient(stack.db).unsafe(`DELETE FROM "${exportJobsTable.tableName}"`);
72
- await asRawClient(stack.db).unsafe(`DELETE FROM kumiko_events`);
75
+ await resetTestTables(stack.db, [exportJobsTable, eventsTable]);
73
76
  });
74
77
 
75
78
  type RequestExportResponse = {
@@ -12,10 +12,10 @@
12
12
 
13
13
  import { afterAll, beforeAll, beforeEach, describe, expect, test } from "bun:test";
14
14
  import { randomBytes } from "node:crypto";
15
- import { asRawClient, selectMany, updateMany } from "@cosmicdrift/kumiko-framework/bun-db";
15
+ import { selectMany, updateMany } from "@cosmicdrift/kumiko-framework/bun-db";
16
16
  import { createEncryptionProvider } from "@cosmicdrift/kumiko-framework/db";
17
17
  import type { TenantId } from "@cosmicdrift/kumiko-framework/engine";
18
- import { createEventsTable } from "@cosmicdrift/kumiko-framework/event-store";
18
+ import { createEventsTable, eventsTable } from "@cosmicdrift/kumiko-framework/event-store";
19
19
  import {
20
20
  setupTestStack,
21
21
  type TestStack,
@@ -24,13 +24,14 @@ import {
24
24
  unsafeCreateEntityTable,
25
25
  unsafePushTables,
26
26
  } from "@cosmicdrift/kumiko-framework/stack";
27
- import { createLateBoundHolder } from "@cosmicdrift/kumiko-framework/testing";
27
+ import { createLateBoundHolder, resetTestTables } from "@cosmicdrift/kumiko-framework/testing";
28
28
  import { AuthErrors, AuthHandlers } from "../../auth-email-password/constants";
29
29
  import { createAuthEmailPasswordFeature } from "../../auth-email-password/feature";
30
30
  import { hashPassword } from "../../auth-email-password/password-hashing";
31
31
  import {
32
32
  createComplianceProfilesFeature,
33
33
  tenantComplianceProfileEntity,
34
+ tenantComplianceProfileTable,
34
35
  } from "../../compliance-profiles";
35
36
  import { createConfigFeature } from "../../config";
36
37
  import { createConfigResolver } from "../../config/resolver";
@@ -97,11 +98,13 @@ afterAll(async () => {
97
98
  });
98
99
 
99
100
  beforeEach(async () => {
100
- await asRawClient(stack.db).unsafe(`DELETE FROM "${userSessionTable.tableName}"`);
101
- await asRawClient(stack.db).unsafe(`DELETE FROM "${userTable.tableName}"`);
102
- await asRawClient(stack.db).unsafe(`DELETE FROM "${tenantMembershipsTable.tableName}"`);
103
- await asRawClient(stack.db).unsafe(`DELETE FROM read_tenant_compliance_profiles`);
104
- await asRawClient(stack.db).unsafe(`DELETE FROM kumiko_events`);
101
+ await resetTestTables(stack.db, [
102
+ userSessionTable,
103
+ userTable,
104
+ tenantMembershipsTable,
105
+ tenantComplianceProfileTable,
106
+ eventsTable,
107
+ ]);
105
108
  });
106
109
 
107
110
  async function seedAliceWithMembership(
@@ -23,7 +23,7 @@ import {
23
23
  selectMany,
24
24
  updateMany,
25
25
  } from "@cosmicdrift/kumiko-framework/bun-db";
26
- import { createEventsTable } from "@cosmicdrift/kumiko-framework/event-store";
26
+ import { createEventsTable, eventsTable } from "@cosmicdrift/kumiko-framework/event-store";
27
27
  import {
28
28
  createInMemoryFileProvider,
29
29
  type FileStorageProvider,
@@ -35,12 +35,16 @@ import {
35
35
  testTenantId,
36
36
  unsafeCreateEntityTable,
37
37
  } from "@cosmicdrift/kumiko-framework/stack";
38
+ import { resetTestTables } from "@cosmicdrift/kumiko-framework/testing";
38
39
  import { getTemporal } from "@cosmicdrift/kumiko-framework/time";
39
40
  import {
40
41
  createComplianceProfilesFeature,
41
42
  tenantComplianceProfileEntity,
43
+ tenantComplianceProfileTable,
42
44
  } from "../../compliance-profiles";
43
45
  import { createDataRetentionFeature } from "../../data-retention";
46
+ import { createSessionsFeature } from "../../sessions";
47
+ import { tenantMembershipsTable } from "../../tenant";
44
48
  import { createUserFeature, USER_STATUS, userEntity, userTable } from "../../user";
45
49
  import { createUserDataRightsFeature } from "../feature";
46
50
  import { runExportJobs } from "../run-export-jobs";
@@ -60,6 +64,8 @@ beforeAll(async () => {
60
64
  createUserFeature(),
61
65
  createDataRetentionFeature(),
62
66
  createComplianceProfilesFeature(),
67
+ createSessionsFeature(),
68
+
63
69
  createUserDataRightsFeature(),
64
70
  ],
65
71
  });
@@ -94,12 +100,14 @@ afterAll(async () => {
94
100
  });
95
101
 
96
102
  beforeEach(async () => {
97
- await asRawClient(stack.db).unsafe(`DELETE FROM "${exportDownloadTokensTable.tableName}"`);
98
- await asRawClient(stack.db).unsafe(`DELETE FROM "${exportJobsTable.tableName}"`);
99
- await asRawClient(stack.db).unsafe(`DELETE FROM "${userTable.tableName}"`);
100
- await asRawClient(stack.db).unsafe(`DELETE FROM kumiko_events`);
101
- await asRawClient(stack.db).unsafe(`DELETE FROM read_tenant_compliance_profiles`);
102
- await asRawClient(stack.db).unsafe(`DELETE FROM read_tenant_memberships`);
103
+ await resetTestTables(stack.db, [
104
+ exportDownloadTokensTable,
105
+ exportJobsTable,
106
+ userTable,
107
+ eventsTable,
108
+ tenantComplianceProfileTable,
109
+ tenantMembershipsTable,
110
+ ]);
103
111
  providerPerTenant = new Map();
104
112
 
105
113
  // Atom 5: aliceUser-Row mit email seeden — Worker-Notification-Callback
@@ -734,6 +742,8 @@ describe("runExportJobs :: Atom 3c file-binaries", () => {
734
742
  createUserFeature(),
735
743
  createDataRetentionFeature(),
736
744
  createComplianceProfilesFeature(),
745
+ createSessionsFeature(),
746
+
737
747
  createUserDataRightsFeature(),
738
748
  testFileExporter,
739
749
  ],
@@ -767,11 +777,13 @@ describe("runExportJobs :: Atom 3c file-binaries", () => {
767
777
 
768
778
  beforeEach(async () => {
769
779
  if (!localStack) return;
770
- await asRawClient(localStack.db).unsafe(`DELETE FROM "${exportDownloadTokensTable.tableName}"`);
771
- await asRawClient(localStack.db).unsafe(`DELETE FROM "${exportJobsTable.tableName}"`);
772
- await asRawClient(localStack.db).unsafe(`DELETE FROM kumiko_events`);
773
- await asRawClient(localStack.db).unsafe(`DELETE FROM read_tenant_compliance_profiles`);
774
- await asRawClient(localStack.db).unsafe(`DELETE FROM read_tenant_memberships`);
780
+ await resetTestTables(localStack.db, [
781
+ exportDownloadTokensTable,
782
+ exportJobsTable,
783
+ eventsTable,
784
+ tenantComplianceProfileTable,
785
+ tenantMembershipsTable,
786
+ ]);
775
787
  // Reset zu safe Default damit kein Test den State an den naechsten leakt.
776
788
  currentTestFileName = "report.pdf";
777
789
  });
@@ -1033,7 +1045,7 @@ describe("runExportJobs :: Atom 5 notification-callbacks", () => {
1033
1045
  test("user ohne email → Callback skipped + console.warn (kein Throw)", async () => {
1034
1046
  // User-Row mit email=null seeden (override). Worker logged warn,
1035
1047
  // Callback wird NICHT gerufen, Worker-Run bleibt successful.
1036
- await asRawClient(stack.db).unsafe(`DELETE FROM "${userTable.tableName}"`);
1048
+ await resetTestTables(stack.db, [userTable]);
1037
1049
  await insertOne(stack.db, userTable, {
1038
1050
  id: String(aliceUser.id),
1039
1051
  tenantId: tenantA,
@@ -17,15 +17,18 @@
17
17
 
18
18
  import { afterAll, beforeAll, beforeEach, describe, expect, test } from "bun:test";
19
19
  import { asRawClient, insertOne } from "@cosmicdrift/kumiko-framework/bun-db";
20
+ import { fileRefsTable } from "@cosmicdrift/kumiko-framework/files";
20
21
  import {
21
22
  setupTestStack,
22
23
  type TestStack,
23
24
  unsafeCreateEntityTable,
24
25
  } from "@cosmicdrift/kumiko-framework/stack";
26
+ import { resetTestTables } from "@cosmicdrift/kumiko-framework/testing";
25
27
  import { getTemporal } from "@cosmicdrift/kumiko-framework/time";
26
28
  import { createComplianceProfilesFeature } from "../../compliance-profiles";
27
29
  import { createDataRetentionFeature, tenantRetentionOverrideEntity } from "../../data-retention";
28
30
  import { createFilesFeature } from "../../files";
31
+ import { createSessionsFeature } from "../../sessions";
29
32
  import {
30
33
  createUserFeature,
31
34
  USER_ANONYMIZED_DISPLAY_NAME,
@@ -61,6 +64,8 @@ beforeAll(async () => {
61
64
  createFilesFeature(),
62
65
  createDataRetentionFeature(),
63
66
  createComplianceProfilesFeature(),
67
+ createSessionsFeature(),
68
+
64
69
  createUserDataRightsFeature(),
65
70
  createUserDataRightsDefaultsFeature(),
66
71
  ],
@@ -109,9 +114,7 @@ afterAll(async () => {
109
114
  });
110
115
 
111
116
  beforeEach(async () => {
112
- await asRawClient(stack.db).unsafe(`DELETE FROM "${userTable.tableName}"`);
113
- await asRawClient(stack.db).unsafe(`DELETE FROM read_tenant_memberships`);
114
- await asRawClient(stack.db).unsafe(`DELETE FROM file_refs`);
117
+ await resetTestTables(stack.db, [userTable, "read_tenant_memberships", fileRefsTable]);
115
118
  });
116
119
 
117
120
  type Instant = InstanceType<ReturnType<typeof getTemporal>["Instant"]>;
@@ -16,15 +16,18 @@
16
16
 
17
17
  import { afterAll, beforeAll, beforeEach, describe, expect, test } from "bun:test";
18
18
  import { asRawClient, insertOne } from "@cosmicdrift/kumiko-framework/bun-db";
19
+ import { fileRefsTable } from "@cosmicdrift/kumiko-framework/files";
19
20
  import {
20
21
  setupTestStack,
21
22
  type TestStack,
22
23
  unsafeCreateEntityTable,
23
24
  } from "@cosmicdrift/kumiko-framework/stack";
25
+ import { resetTestTables } from "@cosmicdrift/kumiko-framework/testing";
24
26
  import { getTemporal } from "@cosmicdrift/kumiko-framework/time";
25
27
  import { createComplianceProfilesFeature } from "../../compliance-profiles";
26
28
  import { createDataRetentionFeature } from "../../data-retention";
27
29
  import { createFilesFeature } from "../../files";
30
+ import { createSessionsFeature } from "../../sessions";
28
31
  import { createUserFeature, USER_STATUS, userEntity, userTable } from "../../user";
29
32
  import { createUserDataRightsDefaultsFeature } from "../../user-data-rights-defaults";
30
33
  import { createUserDataRightsFeature } from "../feature";
@@ -51,6 +54,8 @@ beforeAll(async () => {
51
54
  createFilesFeature(),
52
55
  createDataRetentionFeature(),
53
56
  createComplianceProfilesFeature(),
57
+ createSessionsFeature(),
58
+
54
59
  createUserDataRightsFeature(),
55
60
  createUserDataRightsDefaultsFeature(),
56
61
  ],
@@ -96,9 +101,7 @@ afterAll(async () => {
96
101
  });
97
102
 
98
103
  beforeEach(async () => {
99
- await asRawClient(stack.db).unsafe(`DELETE FROM "${userTable.tableName}"`);
100
- await asRawClient(stack.db).unsafe(`DELETE FROM read_tenant_memberships`);
101
- await asRawClient(stack.db).unsafe(`DELETE FROM file_refs`);
104
+ await resetTestTables(stack.db, [userTable, "read_tenant_memberships", fileRefsTable]);
102
105
  });
103
106
 
104
107
  const NOW = () => getTemporal().Now.instant();
@@ -10,6 +10,7 @@ import { afterAll, beforeAll, describe, expect, test } from "bun:test";
10
10
  import { setupTestStack, type TestStack } from "@cosmicdrift/kumiko-framework/stack";
11
11
  import { createComplianceProfilesFeature } from "../../compliance-profiles";
12
12
  import { createDataRetentionFeature } from "../../data-retention";
13
+ import { createSessionsFeature } from "../../sessions";
13
14
  import { createUserFeature } from "../../user";
14
15
  import { createUserDataRightsFeature } from "../feature";
15
16
 
@@ -18,11 +19,12 @@ let stack: TestStack;
18
19
  const userFeature = createUserFeature();
19
20
  const dataRetention = createDataRetentionFeature();
20
21
  const complianceProfiles = createComplianceProfilesFeature();
22
+ const sessionsFeature = createSessionsFeature();
21
23
  const userDataRights = createUserDataRightsFeature();
22
24
 
23
25
  beforeAll(async () => {
24
26
  stack = await setupTestStack({
25
- features: [userFeature, dataRetention, complianceProfiles, userDataRights],
27
+ features: [userFeature, sessionsFeature, dataRetention, complianceProfiles, userDataRights],
26
28
  });
27
29
  });
28
30
 
@@ -1,6 +1,7 @@
1
- import { asRawClient } from "@cosmicdrift/kumiko-framework/bun-db";
1
+ import { selectMany } from "@cosmicdrift/kumiko-framework/bun-db";
2
2
  import type { DbConnection } from "@cosmicdrift/kumiko-framework/db";
3
3
  import type { TenantId } from "@cosmicdrift/kumiko-framework/engine";
4
+ import { exportJobsTable } from "../../schema/export-job";
4
5
 
5
6
  export type ExportJobCleanupCandidate = {
6
7
  readonly id: string;
@@ -16,8 +17,8 @@ export async function selectExportJobsForStorageCleanup(
16
17
  doneStatus: string,
17
18
  failedStatus: string,
18
19
  ): Promise<readonly ExportJobCleanupCandidate[]> {
19
- return asRawClient(db).unsafe<ExportJobCleanupCandidate>(
20
- `SELECT id, version, status, requested_from_tenant_id AS "requestedFromTenantId", download_storage_key AS "downloadStorageKey", expires_at AS "expiresAt" FROM read_export_jobs WHERE status IN ($1, $2) AND download_storage_key IS NOT NULL`,
21
- [doneStatus, failedStatus],
22
- );
20
+ return selectMany<ExportJobCleanupCandidate>(db, exportJobsTable, {
21
+ status: { in: [doneStatus, failedStatus] },
22
+ downloadStorageKey: { ne: null },
23
+ });
23
24
  }
@@ -1,13 +1,18 @@
1
- import { asRawClient } from "@cosmicdrift/kumiko-framework/bun-db";
1
+ import { selectMany } from "@cosmicdrift/kumiko-framework/bun-db";
2
2
  import type { DbConnection } from "@cosmicdrift/kumiko-framework/db";
3
+ import { userTable } from "../../../user";
3
4
 
4
5
  export async function selectUsersDueForForgetCleanup(
5
6
  db: DbConnection,
6
7
  status: string,
7
- gracePeriodEnd: string,
8
+ gracePeriodEndCutoff: Temporal.Instant | string,
8
9
  ): Promise<readonly { id: string }[]> {
9
- return asRawClient(db).unsafe<{ id: string }>(
10
- `SELECT id FROM read_users WHERE status = $1 AND grace_period_end <= $2`,
11
- [status, gracePeriodEnd],
12
- );
10
+ const cutoff =
11
+ typeof gracePeriodEndCutoff === "string"
12
+ ? Temporal.Instant.from(gracePeriodEndCutoff)
13
+ : gracePeriodEndCutoff;
14
+ return selectMany<{ id: string }>(db, userTable, {
15
+ status,
16
+ gracePeriodEnd: { lte: cutoff },
17
+ });
13
18
  }
@@ -23,7 +23,7 @@ export const cancelDeletionWrite = defineWriteHandler({
23
23
  // ctx.db.raw (kein TenantDb-Wrapper) weil User-Entity tenant-agnostisch
24
24
  // ist — siehe request-deletion.write.ts fuer die Begruendung. Cancel
25
25
  // muss aus jedem Tenant-Mode den User finden + zuruecksetzen koennen.
26
- const row = await fetchOne<{ status: string; grace_period_end: Date | null }>(
26
+ const row = await fetchOne<{ status: string; gracePeriodEnd: Temporal.Instant | null }>(
27
27
  ctx.db.raw,
28
28
  userTable,
29
29
  { id: event.user.id },
@@ -37,26 +37,21 @@ export const cancelDeletionWrite = defineWriteHandler({
37
37
  );
38
38
  }
39
39
 
40
- if (row["status"] !== USER_STATUS.DeletionRequested) {
40
+ if (row.status !== USER_STATUS.DeletionRequested) {
41
41
  return writeFailure(
42
42
  new UnprocessableError("no_pending_deletion", {
43
43
  details: {
44
44
  reason: "no_pending_deletion",
45
- currentStatus: row["status"],
45
+ currentStatus: row.status,
46
46
  },
47
47
  }),
48
48
  );
49
49
  }
50
50
 
51
- // inGrace computed JS-side: compare grace_period_end (Temporal.Instant
52
- // from bun-db boundary) against current server clock.
53
- const gracePeriodEnd = row["grace_period_end"];
51
+ const gracePeriodEnd = row.gracePeriodEnd;
54
52
  const inGrace =
55
53
  gracePeriodEnd != null &&
56
- Temporal.Instant.compare(
57
- gracePeriodEnd as unknown as Temporal.Instant,
58
- Temporal.Now.instant(),
59
- ) > 0;
54
+ Temporal.Instant.compare(gracePeriodEnd, Temporal.Now.instant()) > 0;
60
55
 
61
56
  if (!inGrace) {
62
57
  return writeFailure(
@@ -22,11 +22,11 @@ type Instant = InstanceType<ReturnType<typeof getTemporal>["Instant"]>;
22
22
  type ExportJobRow = {
23
23
  readonly id: string;
24
24
  readonly status: string;
25
- readonly requested_at: Instant;
26
- readonly completed_at: Instant | null;
27
- readonly expires_at: Instant | null;
28
- readonly error_message: string | null;
29
- readonly bytes_written: number | null;
25
+ readonly requestedAt: Instant;
26
+ readonly completedAt: Instant | null;
27
+ readonly expiresAt: Instant | null;
28
+ readonly errorMessage: string | null;
29
+ readonly bytesWritten: number | null;
30
30
  };
31
31
 
32
32
  export const exportStatusQuery = defineQueryHandler({
@@ -49,13 +49,13 @@ export const exportStatusQuery = defineQueryHandler({
49
49
  return {
50
50
  hasJob: true as const,
51
51
  job: {
52
- id: latest["id"],
53
- status: latest["status"],
54
- requestedAt: latest["requested_at"].toString(),
55
- completedAt: latest["completed_at"]?.toString() ?? null,
56
- expiresAt: latest["expires_at"]?.toString() ?? null,
57
- errorMessage: latest["error_message"],
58
- bytesWritten: latest["bytes_written"],
52
+ id: latest.id,
53
+ status: latest.status,
54
+ requestedAt: latest.requestedAt.toString(),
55
+ completedAt: latest.completedAt?.toString() ?? null,
56
+ expiresAt: latest.expiresAt?.toString() ?? null,
57
+ errorMessage: latest.errorMessage,
58
+ bytesWritten: latest.bytesWritten,
59
59
  },
60
60
  };
61
61
  },
@@ -595,11 +595,8 @@ async function storageCleanupPass(args: {
595
595
  // → Trade-off zugunsten DSGVO entschieden. Wenn ein Operator forensik
596
596
  // braucht, muss er das vor dem Cleanup-Pass capturen (out-of-band).
597
597
  //
598
- // **SQL-Filter:** WHERE-clause auf downloadStorageKey IS NOT NULL filtert
599
- // bereits in der DB statt im Loop. Bei skalierender DB-Historie (10k+
600
- // done-jobs nach 30 Tagen) reduziert das den Worker-Roundtrip drastisch.
601
- //
602
- // or() + isNotNull(): no bun-db helper covers this combination — raw SQL.
598
+ // **SQL-Filter:** status IN (done, failed) + downloadStorageKey IS NOT NULL
599
+ // via selectMany (db/queries/export-jobs.ts).
603
600
  const candidates = await selectExportJobsForStorageCleanup(
604
601
  db,
605
602
  EXPORT_JOB_STATUS.Done,
@@ -112,7 +112,6 @@ export async function runForgetCleanup(
112
112
  const { db, registry, now, sendDeletionExecutedEmail } = args;
113
113
 
114
114
  // Step 1: Find users with expired grace period.
115
- // lte with Instant: no bun-db operator covers this — raw SQL.
116
115
  const dueUsers = await selectUsersDueForForgetCleanup(
117
116
  db,
118
117
  USER_STATUS.DeletionRequested,
@@ -20,6 +20,7 @@ import {
20
20
  import { createComplianceProfilesFeature } from "../../compliance-profiles";
21
21
  import { createDataRetentionFeature } from "../../data-retention";
22
22
  import { createFilesFeature } from "../../files";
23
+ import { createSessionsFeature } from "../../sessions";
23
24
  import {
24
25
  createUserFeature,
25
26
  USER_ANONYMIZED_DISPLAY_NAME,
@@ -39,6 +40,7 @@ const features = [
39
40
  createFilesFeature(),
40
41
  createDataRetentionFeature(),
41
42
  createComplianceProfilesFeature(),
43
+ createSessionsFeature(),
42
44
  createUserDataRightsFeature(),
43
45
  createUserDataRightsDefaultsFeature(),
44
46
  ];