@cosmicdrift/kumiko-bundled-features 0.14.0 → 0.16.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 (269) hide show
  1. package/package.json +2 -2
  2. package/src/__tests__/env-schemas.test.ts +1 -1
  3. package/src/__tests__/es-ops-e2e.integration.ts +10 -9
  4. package/src/audit/__tests__/audit.integration.ts +3 -3
  5. package/src/audit/handlers/list.query.ts +39 -51
  6. package/src/auth-email-password/__tests__/account-lockout-no-redis.integration.ts +4 -3
  7. package/src/auth-email-password/__tests__/account-lockout.integration.ts +4 -3
  8. package/src/auth-email-password/__tests__/auth-claims.integration.ts +5 -4
  9. package/src/auth-email-password/__tests__/auth.integration.ts +4 -3
  10. package/src/auth-email-password/__tests__/confirm-token-flow.test.ts +1 -1
  11. package/src/auth-email-password/__tests__/email-templates.test.ts +1 -1
  12. package/src/auth-email-password/__tests__/email-verification.integration.ts +7 -10
  13. package/src/auth-email-password/__tests__/identity-v3-hash.test.ts +1 -1
  14. package/src/auth-email-password/__tests__/identity-v3-login.integration.ts +4 -3
  15. package/src/auth-email-password/__tests__/invite-flow.integration.ts +16 -43
  16. package/src/auth-email-password/__tests__/multi-roles.integration.ts +6 -9
  17. package/src/auth-email-password/__tests__/password-reset.integration.ts +8 -7
  18. package/src/auth-email-password/__tests__/public-routes-rate-limit.integration.ts +4 -3
  19. package/src/auth-email-password/__tests__/seed-admin.integration.ts +19 -32
  20. package/src/auth-email-password/__tests__/session-callbacks.integration.ts +6 -5
  21. package/src/auth-email-password/__tests__/session-strict-mode.integration.ts +1 -1
  22. package/src/auth-email-password/__tests__/signed-token.test.ts +1 -1
  23. package/src/auth-email-password/__tests__/signup-flow.integration.ts +11 -15
  24. package/src/auth-email-password/handlers/invite-accept-with-login.write.ts +26 -26
  25. package/src/auth-email-password/handlers/invite-accept.write.ts +24 -21
  26. package/src/auth-email-password/handlers/invite-create.write.ts +3 -8
  27. package/src/auth-email-password/handlers/invite-signup-complete.write.ts +20 -17
  28. package/src/auth-email-password/handlers/signup-confirm.write.ts +3 -7
  29. package/src/auth-email-password/seeding.ts +1 -1
  30. package/src/auth-email-password/web/__tests__/auth-gate.test.tsx +1 -2
  31. package/src/auth-email-password/web/__tests__/forgot-password-screen.test.tsx +10 -19
  32. package/src/auth-email-password/web/__tests__/login-screen.test.tsx +12 -18
  33. package/src/auth-email-password/web/__tests__/reset-password-screen.test.tsx +12 -17
  34. package/src/auth-email-password/web/__tests__/session-roles.test.ts +1 -1
  35. package/src/auth-email-password/web/__tests__/tenant-switcher.test.tsx +1 -8
  36. package/src/auth-email-password/web/__tests__/test-utils.tsx +4 -8
  37. package/src/auth-email-password/web/__tests__/user-menu.test.tsx +2 -8
  38. package/src/auth-email-password/web/__tests__/verify-email-screen.test.tsx +10 -15
  39. package/src/billing-foundation/__tests__/billing-foundation.integration.ts +1 -1
  40. package/src/billing-foundation/__tests__/feature.test.ts +1 -1
  41. package/src/billing-foundation/__tests__/webhook-handler.test.ts +6 -5
  42. package/src/billing-foundation/db/queries/subscription-projection.ts +15 -0
  43. package/src/billing-foundation/get-subscription-for-tenant.ts +2 -6
  44. package/src/billing-foundation/handlers/create-portal-session.write.ts +2 -2
  45. package/src/billing-foundation/handlers/list-subscriptions.query.ts +4 -1
  46. package/src/billing-foundation/projection.ts +32 -13
  47. package/src/cap-counter/__tests__/cap-counter.integration.ts +1 -1
  48. package/src/cap-counter/__tests__/enforce-cap.test.ts +37 -32
  49. package/src/cap-counter/__tests__/with-cap-enforcement.integration.ts +1 -1
  50. package/src/cap-counter/enforce-cap.ts +14 -20
  51. package/src/cap-counter/handlers/get-counter.query.ts +7 -13
  52. package/src/cap-counter/handlers/increment.write.ts +2 -2
  53. package/src/cap-counter/handlers/mark-soft-warned.write.ts +2 -2
  54. package/src/channel-in-app/handlers/inbox.query.ts +7 -13
  55. package/src/channel-in-app/handlers/mark-all-read.write.ts +7 -9
  56. package/src/channel-in-app/handlers/mark-read.write.ts +8 -14
  57. package/src/channel-in-app/handlers/unread-count.query.ts +10 -9
  58. package/src/channel-in-app/in-app-channel.ts +10 -12
  59. package/src/channel-in-app/tables.ts +1 -1
  60. package/src/compliance-profiles/__tests__/compliance-profiles.integration.ts +1 -1
  61. package/src/compliance-profiles/__tests__/seeding.integration.ts +1 -1
  62. package/src/compliance-profiles/_internal/parse-override.ts +19 -0
  63. package/src/compliance-profiles/handlers/for-tenant.query.ts +10 -32
  64. package/src/compliance-profiles/handlers/needs-profile.query.ts +4 -7
  65. package/src/compliance-profiles/handlers/set-profile.write.ts +5 -7
  66. package/src/compliance-profiles/resolve-for-tenant.ts +11 -27
  67. package/src/compliance-profiles/schema/profile-selection.ts +2 -2
  68. package/src/compliance-profiles/seeding.ts +4 -7
  69. package/src/config/__tests__/app-overrides.test.ts +1 -1
  70. package/src/config/__tests__/cascade.integration.ts +1 -1
  71. package/src/config/__tests__/config.integration.ts +8 -27
  72. package/src/config/db/queries/resolver.ts +47 -0
  73. package/src/config/handlers/__tests__/prepare-config-write.test.ts +1 -1
  74. package/src/config/resolver.ts +14 -62
  75. package/src/config/table.ts +4 -4
  76. package/src/config/write-helpers.ts +7 -11
  77. package/src/custom-fields/__tests__/audit-integration.integration.ts +6 -6
  78. package/src/custom-fields/__tests__/custom-fields.integration.ts +7 -7
  79. package/src/custom-fields/__tests__/feature.test.ts +1 -1
  80. package/src/custom-fields/__tests__/field-access.integration.ts +6 -6
  81. package/src/custom-fields/__tests__/quota.integration.ts +6 -6
  82. package/src/custom-fields/__tests__/retention.integration.ts +12 -10
  83. package/src/custom-fields/__tests__/user-data-rights.integration.ts +27 -17
  84. package/src/custom-fields/__tests__/wire-for-entity.test.ts +5 -5
  85. package/src/custom-fields/db/queries/field-access.ts +16 -0
  86. package/src/custom-fields/db/queries/projection.ts +43 -0
  87. package/src/custom-fields/db/queries/quota.ts +14 -0
  88. package/src/custom-fields/db/queries/retention.ts +39 -0
  89. package/src/custom-fields/db/queries/user-data-rights.ts +54 -0
  90. package/src/custom-fields/lib/field-access.ts +2 -41
  91. package/src/custom-fields/lib/quota.ts +2 -25
  92. package/src/custom-fields/run-retention.ts +19 -21
  93. package/src/custom-fields/wire-for-entity.ts +30 -23
  94. package/src/custom-fields/wire-user-data-rights.ts +33 -85
  95. package/src/data-retention/__tests__/data-retention.integration.ts +1 -1
  96. package/src/data-retention/__tests__/keep-for.test.ts +1 -1
  97. package/src/data-retention/__tests__/override-schema.test.ts +1 -1
  98. package/src/data-retention/__tests__/policy-for.integration.ts +1 -1
  99. package/src/data-retention/__tests__/resolver.test.ts +1 -1
  100. package/src/data-retention/handlers/policy-for.query.ts +5 -8
  101. package/src/data-retention/resolve-for-tenant.ts +6 -8
  102. package/src/data-retention/schema/tenant-retention-override.ts +2 -2
  103. package/src/delivery/__tests__/delivery-events.integration.ts +8 -21
  104. package/src/delivery/__tests__/delivery.integration.ts +100 -190
  105. package/src/delivery/db/queries/preferences.ts +30 -0
  106. package/src/delivery/delivery-service.ts +8 -36
  107. package/src/delivery/feature.ts +10 -2
  108. package/src/delivery/handlers/log.query.ts +5 -7
  109. package/src/delivery/handlers/preferences.query.ts +2 -5
  110. package/src/delivery/tables.ts +26 -1
  111. package/src/delivery/upsert-preference.ts +8 -14
  112. package/src/feature-toggles/__tests__/feature-toggles.integration.ts +30 -30
  113. package/src/feature-toggles/__tests__/registered-system-tenant.test.ts +7 -6
  114. package/src/feature-toggles/db/queries/toggle-state.ts +25 -0
  115. package/src/feature-toggles/feature.ts +16 -2
  116. package/src/feature-toggles/global-feature-state-table.ts +1 -1
  117. package/src/feature-toggles/handlers/list.query.ts +9 -2
  118. package/src/feature-toggles/handlers/registered.query.ts +3 -7
  119. package/src/feature-toggles/handlers/set.write.ts +37 -25
  120. package/src/feature-toggles/toggle-runtime.ts +3 -6
  121. package/src/file-foundation/__tests__/feature.test.ts +1 -1
  122. package/src/file-foundation/__tests__/file-foundation.integration.ts +1 -1
  123. package/src/file-provider-inmemory/__tests__/feature.test.ts +1 -1
  124. package/src/file-provider-s3/__tests__/feature.test.ts +1 -1
  125. package/src/files/__tests__/files.integration.ts +18 -7
  126. package/src/files/schema/file-ref.ts +1 -1
  127. package/src/files-provider-s3/__tests__/env-helper.test.ts +1 -1
  128. package/src/files-provider-s3/__tests__/s3-provider.integration.ts +1 -1
  129. package/src/files-provider-s3/__tests__/s3-provider.test.ts +1 -1
  130. package/src/jobs/__tests__/job-system-user.integration.ts +1 -1
  131. package/src/jobs/__tests__/jobs-events.integration.ts +8 -21
  132. package/src/jobs/__tests__/jobs-feature.integration.ts +1 -1
  133. package/src/jobs/feature.ts +26 -15
  134. package/src/jobs/handlers/detail.query.ts +10 -8
  135. package/src/jobs/handlers/list.query.ts +9 -21
  136. package/src/jobs/handlers/retry.write.ts +2 -7
  137. package/src/jobs/job-run-logger.ts +3 -9
  138. package/src/jobs/job-run-table.ts +49 -17
  139. package/src/legal-pages/__tests__/legal-pages.integration.ts +1 -1
  140. package/src/mail-foundation/__tests__/feature.test.ts +1 -1
  141. package/src/mail-foundation/__tests__/mail-foundation.integration.ts +1 -1
  142. package/src/mail-transport-inmemory/__tests__/feature.test.ts +1 -1
  143. package/src/mail-transport-smtp/__tests__/feature.test.ts +1 -1
  144. package/src/rate-limiting/__tests__/rate-limiting.integration.ts +1 -1
  145. package/src/renderer-foundation/__tests__/api.test.ts +2 -2
  146. package/src/renderer-foundation/__tests__/collect-plugins.integration.ts +1 -1
  147. package/src/renderer-simple/__tests__/adapter.test.ts +2 -2
  148. package/src/renderer-simple/__tests__/simple-renderer.test.ts +1 -1
  149. package/src/secrets/__tests__/require-secrets-context.test.ts +6 -5
  150. package/src/secrets/__tests__/rotate.integration.ts +6 -9
  151. package/src/secrets/__tests__/secrets-events.integration.ts +6 -12
  152. package/src/secrets/__tests__/secrets.integration.ts +6 -11
  153. package/src/secrets/db/queries/read.ts +16 -0
  154. package/src/secrets/handlers/list.query.ts +16 -17
  155. package/src/secrets/handlers/rotate.job.ts +8 -12
  156. package/src/secrets/secrets-context.ts +9 -21
  157. package/src/secrets/table.ts +1 -1
  158. package/src/sessions/__tests__/cleanup.integration.ts +8 -6
  159. package/src/sessions/__tests__/password-auto-revoke.integration.ts +7 -6
  160. package/src/sessions/__tests__/sessions.integration.ts +23 -38
  161. package/src/sessions/__tests__/test-helpers.ts +1 -1
  162. package/src/sessions/db/queries/cleanup.ts +21 -0
  163. package/src/sessions/handlers/cleanup.job.ts +6 -29
  164. package/src/sessions/handlers/list.query.ts +24 -24
  165. package/src/sessions/handlers/mine.query.ts +24 -23
  166. package/src/sessions/handlers/revoke-all-for-user.write.ts +7 -11
  167. package/src/sessions/handlers/revoke-all-others.write.ts +7 -12
  168. package/src/sessions/handlers/revoke.write.ts +11 -18
  169. package/src/sessions/schema/user-session.ts +2 -2
  170. package/src/sessions/session-callbacks.ts +19 -21
  171. package/src/subscription-mollie/__tests__/feature.test.ts +1 -1
  172. package/src/subscription-mollie/__tests__/mollie-foundation.integration.ts +1 -1
  173. package/src/subscription-mollie/__tests__/verify-webhook.test.ts +8 -7
  174. package/src/subscription-stripe/__tests__/feature.test.ts +1 -1
  175. package/src/subscription-stripe/__tests__/plugin-methods.test.ts +14 -15
  176. package/src/subscription-stripe/__tests__/stripe-foundation.integration.ts +1 -1
  177. package/src/subscription-stripe/__tests__/verify-webhook.test.ts +14 -14
  178. package/src/subscription-stripe/verify-webhook.ts +1 -1
  179. package/src/template-resolver/__tests__/handlers.integration.ts +1 -1
  180. package/src/template-resolver/__tests__/template-resolver.integration.ts +3 -2
  181. package/src/template-resolver/api.ts +7 -13
  182. package/src/template-resolver/handlers/archive.write.ts +4 -7
  183. package/src/template-resolver/handlers/find-by-id.query.ts +4 -7
  184. package/src/template-resolver/handlers/list.query.ts +13 -21
  185. package/src/template-resolver/handlers/publish.write.ts +4 -7
  186. package/src/template-resolver/handlers/upsert-system.write.ts +7 -10
  187. package/src/template-resolver/handlers/upsert-tenant.write.ts +7 -10
  188. package/src/template-resolver/table.ts +2 -5
  189. package/src/tenant/__tests__/multi-tenant.integration.ts +1 -1
  190. package/src/tenant/__tests__/seed-testing.integration.ts +19 -45
  191. package/src/tenant/__tests__/tenant.integration.ts +1 -1
  192. package/src/tenant/handlers/active-tenant-ids.query.ts +3 -8
  193. package/src/tenant/handlers/add-member.write.ts +6 -8
  194. package/src/tenant/handlers/cancel-invitation.write.ts +5 -7
  195. package/src/tenant/handlers/invitations.query.ts +5 -10
  196. package/src/tenant/handlers/me.query.ts +2 -3
  197. package/src/tenant/handlers/members.query.ts +4 -5
  198. package/src/tenant/handlers/memberships.query.ts +2 -5
  199. package/src/tenant/handlers/remove-member.write.ts +6 -8
  200. package/src/tenant/handlers/resolve-user-ids.query.ts +6 -16
  201. package/src/tenant/handlers/update-member-roles.write.ts +6 -8
  202. package/src/tenant/invitation-table.ts +2 -5
  203. package/src/tenant/membership-table.ts +3 -6
  204. package/src/tenant/schema/tenant.ts +2 -2
  205. package/src/tenant/seeding.ts +12 -18
  206. package/src/text-content/README.md +1 -1
  207. package/src/text-content/__tests__/text-content.integration.ts +2 -2
  208. package/src/text-content/api.ts +2 -9
  209. package/src/text-content/handlers/by-slug.query.ts +6 -9
  210. package/src/text-content/handlers/by-tenant.query.ts +2 -2
  211. package/src/text-content/handlers/set.write.ts +7 -9
  212. package/src/text-content/seeding.ts +6 -9
  213. package/src/text-content/table.ts +2 -2
  214. package/src/text-content/web/__tests__/editor-read-only.test.tsx +31 -45
  215. package/src/text-content/web/__tests__/group-blocks.test.ts +1 -18
  216. package/src/text-content/web/client-plugin.tsx +11 -23
  217. package/src/tier-engine/__tests__/auto-default-tier.integration.ts +10 -16
  218. package/src/tier-engine/__tests__/compose-app.test.ts +1 -1
  219. package/src/tier-engine/__tests__/drift.test.ts +1 -1
  220. package/src/tier-engine/__tests__/resolver.integration.ts +6 -6
  221. package/src/tier-engine/__tests__/tier-engine.integration.ts +1 -1
  222. package/src/tier-engine/feature.ts +9 -16
  223. package/src/user/__tests__/seed-testing.integration.ts +10 -22
  224. package/src/user/__tests__/user-status.test.ts +1 -1
  225. package/src/user/__tests__/user.integration.ts +6 -5
  226. package/src/user/handlers/create.write.ts +5 -7
  227. package/src/user/handlers/find-for-auth.query.ts +5 -7
  228. package/src/user/schema/user.ts +2 -2
  229. package/src/user/seeding.ts +2 -3
  230. package/src/user-data-rights/__tests__/audit-log.integration.ts +24 -12
  231. package/src/user-data-rights/__tests__/cross-data-matrix.integration.ts +64 -37
  232. package/src/user-data-rights/__tests__/download.integration.ts +29 -46
  233. package/src/user-data-rights/__tests__/export-job-idempotency.integration.ts +35 -28
  234. package/src/user-data-rights/__tests__/export-job-schema.test.ts +2 -2
  235. package/src/user-data-rights/__tests__/policy-to-strategy.test.ts +1 -1
  236. package/src/user-data-rights/__tests__/request-cancel-deletion.integration.ts +11 -15
  237. package/src/user-data-rights/__tests__/request-deletion-callback.integration.ts +10 -12
  238. package/src/user-data-rights/__tests__/request-export.integration.ts +23 -16
  239. package/src/user-data-rights/__tests__/restriction-flow.integration.ts +24 -32
  240. package/src/user-data-rights/__tests__/run-export-jobs.integration.ts +142 -137
  241. package/src/user-data-rights/__tests__/run-forget-cleanup.integration.ts +46 -28
  242. package/src/user-data-rights/__tests__/run-user-export.integration.ts +20 -14
  243. package/src/user-data-rights/__tests__/token-helpers.test.ts +1 -1
  244. package/src/user-data-rights/__tests__/user-data-rights.integration.ts +1 -1
  245. package/src/user-data-rights/__tests__/zip-path.test.ts +1 -1
  246. package/src/user-data-rights/audit-download.ts +3 -3
  247. package/src/user-data-rights/db/queries/export-jobs.ts +23 -0
  248. package/src/user-data-rights/db/queries/forget-cleanup.ts +13 -0
  249. package/src/user-data-rights/handlers/cancel-deletion.write.ts +28 -22
  250. package/src/user-data-rights/handlers/download-by-job.query.ts +11 -21
  251. package/src/user-data-rights/handlers/download-by-token.query.ts +20 -35
  252. package/src/user-data-rights/handlers/export-status.query.ts +19 -33
  253. package/src/user-data-rights/handlers/lift-restriction.write.ts +7 -12
  254. package/src/user-data-rights/handlers/list-download-attempts.query.ts +14 -23
  255. package/src/user-data-rights/handlers/my-audit-log.query.ts +33 -23
  256. package/src/user-data-rights/handlers/request-deletion.write.ts +15 -15
  257. package/src/user-data-rights/handlers/request-export.write.ts +7 -11
  258. package/src/user-data-rights/handlers/restrict-account.write.ts +12 -12
  259. package/src/user-data-rights/run-export-jobs.ts +20 -60
  260. package/src/user-data-rights/run-forget-cleanup.ts +19 -33
  261. package/src/user-data-rights/run-user-export.ts +4 -6
  262. package/src/user-data-rights/schema/download-attempt.ts +2 -2
  263. package/src/user-data-rights/schema/download-token.ts +2 -2
  264. package/src/user-data-rights/schema/export-job.ts +2 -3
  265. package/src/user-data-rights-defaults/__tests__/user-data-rights-defaults.integration.ts +37 -30
  266. package/src/user-data-rights-defaults/db/queries/user-hook.ts +17 -0
  267. package/src/user-data-rights-defaults/hooks/file-ref.userdata-hook.ts +12 -27
  268. package/src/user-data-rights-defaults/hooks/user.userdata-hook.ts +16 -18
  269. package/CHANGELOG.md +0 -689
@@ -12,10 +12,17 @@
12
12
  // downloadStorageKey wird genullt + storage-key geloescht
13
13
  // 6. Idempotency: 2× run → kein Re-Processing von done/failed-Jobs
14
14
 
15
+ import { afterAll, beforeAll, beforeEach, describe, expect, test } from "bun:test";
15
16
  import { spawn } from "node:child_process";
16
17
  import { mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
17
18
  import { tmpdir } from "node:os";
18
19
  import { join } from "node:path";
20
+ import {
21
+ asRawClient,
22
+ insertOne,
23
+ selectMany,
24
+ updateMany,
25
+ } from "@cosmicdrift/kumiko-framework/bun-db";
19
26
  import { createEventsTable } from "@cosmicdrift/kumiko-framework/event-store";
20
27
  import {
21
28
  createInMemoryFileProvider,
@@ -29,8 +36,6 @@ import {
29
36
  unsafeCreateEntityTable,
30
37
  } from "@cosmicdrift/kumiko-framework/stack";
31
38
  import { getTemporal } from "@cosmicdrift/kumiko-framework/time";
32
- import { sql } from "drizzle-orm";
33
- import { afterAll, beforeAll, beforeEach, describe, expect, test } from "vitest";
34
39
  import {
35
40
  createComplianceProfilesFeature,
36
41
  tenantComplianceProfileEntity,
@@ -65,7 +70,7 @@ beforeAll(async () => {
65
70
  await createEventsTable(stack.db);
66
71
  // tenant-membership-table fuer runUserExport's Cross-Tenant-Iteration.
67
72
  // Pattern matched user-data-rights-defaults integration-test.
68
- await stack.db.execute(sql`
73
+ await asRawClient(stack.db).unsafe(`
69
74
  CREATE TABLE IF NOT EXISTS read_tenant_memberships (
70
75
  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
71
76
  tenant_id UUID NOT NULL,
@@ -89,30 +94,27 @@ afterAll(async () => {
89
94
  });
90
95
 
91
96
  beforeEach(async () => {
92
- await stack.db.delete(exportDownloadTokensTable);
93
- await stack.db.delete(exportJobsTable);
94
- await stack.db.delete(userTable);
95
- await stack.db.execute(sql`DELETE FROM kumiko_events`);
96
- await stack.db.execute(sql`DELETE FROM read_tenant_compliance_profiles`);
97
- await stack.db.execute(sql`DELETE FROM read_tenant_memberships`);
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`);
98
103
  providerPerTenant = new Map();
99
104
 
100
105
  // Atom 5: aliceUser-Row mit email seeden — Worker-Notification-Callback
101
106
  // schaut email via lookupUserEmail an.
102
- await stack.db
103
- .insert(userTable)
104
- .values({
105
- id: String(aliceUser.id),
106
- tenantId: tenantA,
107
- email: "alice@example.com",
108
- passwordHash: "hashed",
109
- displayName: "Alice",
110
- locale: "de",
111
- emailVerified: true,
112
- roles: '["Member"]',
113
- status: USER_STATUS.Active,
114
- })
115
- .onConflictDoNothing();
107
+ await insertOne(stack.db, userTable, {
108
+ id: String(aliceUser.id),
109
+ tenantId: tenantA,
110
+ email: "alice@example.com",
111
+ passwordHash: "hashed",
112
+ displayName: "Alice",
113
+ locale: "de",
114
+ emailVerified: true,
115
+ roles: '["Member"]',
116
+ status: USER_STATUS.Active,
117
+ });
116
118
  });
117
119
 
118
120
  const NOW = () => getTemporal().Now.instant();
@@ -154,10 +156,7 @@ describe("runExportJobs :: happy path", () => {
154
156
  expect(result.errors).toEqual([]);
155
157
 
156
158
  // Job-Row ist done
157
- const [row] = (await stack.db
158
- .select()
159
- .from(exportJobsTable)
160
- .where(sql`id = ${jobId}`)) as Array<{
159
+ const [row] = (await selectMany(stack.db, exportJobsTable, { id: jobId })) as Array<{
161
160
  status: string;
162
161
  downloadStorageKey: string | null;
163
162
  expiresAt: { toString(): string } | null;
@@ -215,10 +214,12 @@ describe("runExportJobs :: stale-detection", () => {
215
214
  const jobId = await seedPendingJob();
216
215
  const T = getTemporal();
217
216
  const twoHoursAgo = T.Instant.fromEpochMilliseconds(Date.now() - 2 * 60 * 60 * 1000);
218
- await stack.db
219
- .update(exportJobsTable)
220
- .set({ status: EXPORT_JOB_STATUS.Running, startedAt: twoHoursAgo })
221
- .where(sql`id = ${jobId}`);
217
+ await updateMany(
218
+ stack.db,
219
+ exportJobsTable,
220
+ { status: EXPORT_JOB_STATUS.Running, startedAt: twoHoursAgo },
221
+ { id: jobId },
222
+ );
222
223
 
223
224
  const result = await runExportJobs({
224
225
  db: stack.db,
@@ -229,10 +230,7 @@ describe("runExportJobs :: stale-detection", () => {
229
230
 
230
231
  expect(result.staleFailedJobIds).toContain(jobId);
231
232
 
232
- const [row] = (await stack.db
233
- .select()
234
- .from(exportJobsTable)
235
- .where(sql`id = ${jobId}`)) as Array<{
233
+ const [row] = (await selectMany(stack.db, exportJobsTable, { id: jobId })) as Array<{
236
234
  status: string;
237
235
  errorMessage: string | null;
238
236
  }>;
@@ -244,10 +242,12 @@ describe("runExportJobs :: stale-detection", () => {
244
242
  const jobId = await seedPendingJob();
245
243
  const T = getTemporal();
246
244
  const justNow = T.Instant.fromEpochMilliseconds(Date.now() - 60 * 1000); // 1min ago
247
- await stack.db
248
- .update(exportJobsTable)
249
- .set({ status: EXPORT_JOB_STATUS.Running, startedAt: justNow })
250
- .where(sql`id = ${jobId}`);
245
+ await updateMany(
246
+ stack.db,
247
+ exportJobsTable,
248
+ { status: EXPORT_JOB_STATUS.Running, startedAt: justNow },
249
+ { id: jobId },
250
+ );
251
251
 
252
252
  const result = await runExportJobs({
253
253
  db: stack.db,
@@ -257,10 +257,9 @@ describe("runExportJobs :: stale-detection", () => {
257
257
  });
258
258
 
259
259
  expect(result.staleFailedJobIds).not.toContain(jobId);
260
- const [row] = (await stack.db
261
- .select()
262
- .from(exportJobsTable)
263
- .where(sql`id = ${jobId}`)) as Array<{ status: string }>;
260
+ const [row] = (await selectMany(stack.db, exportJobsTable, { id: jobId })) as Array<{
261
+ status: string;
262
+ }>;
264
263
  expect(row?.status).toBe(EXPORT_JOB_STATUS.Running);
265
264
  });
266
265
 
@@ -278,14 +277,16 @@ describe("runExportJobs :: stale-detection", () => {
278
277
  // gesetzt + ZIP geschrieben. Worker dann gecrashed (kein done-flip).
279
278
  const provider = await buildProvider(tenantA);
280
279
  await provider.write(storageKey, new Uint8Array([1, 2, 3]));
281
- await stack.db
282
- .update(exportJobsTable)
283
- .set({
280
+ await updateMany(
281
+ stack.db,
282
+ exportJobsTable,
283
+ {
284
284
  status: EXPORT_JOB_STATUS.Running,
285
285
  startedAt: twoHoursAgo,
286
286
  downloadStorageKey: storageKey,
287
- })
288
- .where(sql`id = ${jobId}`);
287
+ },
288
+ { id: jobId },
289
+ );
289
290
 
290
291
  const result = await runExportJobs({
291
292
  db: stack.db,
@@ -298,10 +299,10 @@ describe("runExportJobs :: stale-detection", () => {
298
299
  expect(result.cleanedJobIds).toContain(jobId);
299
300
  expect(await provider.exists(storageKey)).toBe(false);
300
301
 
301
- const [row] = (await stack.db
302
- .select()
303
- .from(exportJobsTable)
304
- .where(sql`id = ${jobId}`)) as Array<{ status: string; downloadStorageKey: string | null }>;
302
+ const [row] = (await selectMany(stack.db, exportJobsTable, { id: jobId })) as Array<{
303
+ status: string;
304
+ downloadStorageKey: string | null;
305
+ }>;
305
306
  expect(row?.status).toBe(EXPORT_JOB_STATUS.Failed);
306
307
  expect(row?.downloadStorageKey).toBeNull();
307
308
  });
@@ -318,16 +319,18 @@ describe("runExportJobs :: storage-cleanup", () => {
318
319
  const provider = await buildProvider(tenantA);
319
320
  await provider.write(storageKey, new Uint8Array([1, 2, 3]));
320
321
 
321
- await stack.db
322
- .update(exportJobsTable)
323
- .set({
322
+ await updateMany(
323
+ stack.db,
324
+ exportJobsTable,
325
+ {
324
326
  status: EXPORT_JOB_STATUS.Done,
325
327
  startedAt: longAgo,
326
328
  completedAt: longAgo,
327
329
  downloadStorageKey: storageKey,
328
330
  expiresAt: longAgo,
329
- })
330
- .where(sql`id = ${jobId}`);
331
+ },
332
+ { id: jobId },
333
+ );
331
334
 
332
335
  const result = await runExportJobs({
333
336
  db: stack.db,
@@ -339,10 +342,7 @@ describe("runExportJobs :: storage-cleanup", () => {
339
342
  expect(result.cleanedJobIds).toContain(jobId);
340
343
  expect(await provider.exists(storageKey)).toBe(false);
341
344
 
342
- const [row] = (await stack.db
343
- .select()
344
- .from(exportJobsTable)
345
- .where(sql`id = ${jobId}`)) as Array<{
345
+ const [row] = (await selectMany(stack.db, exportJobsTable, { id: jobId })) as Array<{
346
346
  status: string;
347
347
  downloadStorageKey: string | null;
348
348
  }>;
@@ -358,16 +358,18 @@ describe("runExportJobs :: storage-cleanup", () => {
358
358
  const provider = await buildProvider(tenantA);
359
359
  await provider.write(storageKey, new Uint8Array([4, 5, 6]));
360
360
 
361
- await stack.db
362
- .update(exportJobsTable)
363
- .set({
361
+ await updateMany(
362
+ stack.db,
363
+ exportJobsTable,
364
+ {
364
365
  status: EXPORT_JOB_STATUS.Done,
365
366
  startedAt: oneHourAgo,
366
367
  completedAt: oneHourAgo,
367
368
  downloadStorageKey: storageKey,
368
369
  expiresAt: oneHourAgo,
369
- })
370
- .where(sql`id = ${jobId}`);
370
+ },
371
+ { id: jobId },
372
+ );
371
373
 
372
374
  const result = await runExportJobs({
373
375
  db: stack.db,
@@ -436,10 +438,10 @@ describe("runExportJobs :: concurrency", () => {
436
438
  expect([...resultA.failedJobIds, ...resultB.failedJobIds]).toEqual([]);
437
439
 
438
440
  // DB hat genau eine done-Row
439
- const rows = (await stack.db
440
- .select()
441
- .from(exportJobsTable)
442
- .where(sql`id = ${jobId}`)) as Array<{ status: string; downloadStorageKey: string | null }>;
441
+ const rows = (await selectMany(stack.db, exportJobsTable, { id: jobId })) as Array<{
442
+ status: string;
443
+ downloadStorageKey: string | null;
444
+ }>;
443
445
  expect(rows).toHaveLength(1);
444
446
  expect(rows[0]?.status).toBe(EXPORT_JOB_STATUS.Done);
445
447
  expect(rows[0]?.downloadStorageKey).toBe(`${tenantA}/exports/${jobId}.zip`);
@@ -460,10 +462,12 @@ describe("runExportJobs :: stale-detection profile-driven cutoff", () => {
460
462
  const jobId = await seedPendingJob();
461
463
  const T = getTemporal();
462
464
  const fortyFiveMinAgo = T.Instant.fromEpochMilliseconds(Date.now() - 45 * 60 * 1000);
463
- await stack.db
464
- .update(exportJobsTable)
465
- .set({ status: EXPORT_JOB_STATUS.Running, startedAt: fortyFiveMinAgo })
466
- .where(sql`id = ${jobId}`);
465
+ await updateMany(
466
+ stack.db,
467
+ exportJobsTable,
468
+ { status: EXPORT_JOB_STATUS.Running, startedAt: fortyFiveMinAgo },
469
+ { id: jobId },
470
+ );
467
471
 
468
472
  const result = await runExportJobs({
469
473
  db: stack.db,
@@ -473,10 +477,9 @@ describe("runExportJobs :: stale-detection profile-driven cutoff", () => {
473
477
  });
474
478
 
475
479
  expect(result.staleFailedJobIds).toContain(jobId);
476
- const [row] = (await stack.db
477
- .select()
478
- .from(exportJobsTable)
479
- .where(sql`id = ${jobId}`)) as Array<{ status: string }>;
480
+ const [row] = (await selectMany(stack.db, exportJobsTable, { id: jobId })) as Array<{
481
+ status: string;
482
+ }>;
480
483
  expect(row?.status).toBe(EXPORT_JOB_STATUS.Failed);
481
484
  });
482
485
  });
@@ -500,10 +503,7 @@ describe("runExportJobs :: Atom 4a download-tokens", () => {
500
503
  expect(plainToken).toMatch(/^[A-Za-z0-9_-]{43}$/);
501
504
 
502
505
  // Hash in DB matched plain via hashDownloadToken roundtrip
503
- const tokenRows = (await stack.db
504
- .select()
505
- .from(exportDownloadTokensTable)
506
- .where(sql`job_id = ${jobId}`)) as Array<{
506
+ const tokenRows = (await selectMany(stack.db, exportDownloadTokensTable, { jobId })) as Array<{
507
507
  jobId: string;
508
508
  tokenHash: string;
509
509
  issuedAt: { toString(): string };
@@ -519,10 +519,9 @@ describe("runExportJobs :: Atom 4a download-tokens", () => {
519
519
  expect(tokenRows[0]?.useCount).toBe(0);
520
520
 
521
521
  // expiresAt im Token = job.expiresAt (denormalized)
522
- const [jobRow] = (await stack.db
523
- .select()
524
- .from(exportJobsTable)
525
- .where(sql`id = ${jobId}`)) as Array<{ expiresAt: { toString(): string } | null }>;
522
+ const [jobRow] = (await selectMany(stack.db, exportJobsTable, { id: jobId })) as Array<{
523
+ expiresAt: { toString(): string } | null;
524
+ }>;
526
525
  expect(tokenRows[0]?.expiresAt.toString()).toBe(jobRow?.expiresAt?.toString());
527
526
  });
528
527
 
@@ -539,8 +538,8 @@ describe("runExportJobs :: Atom 4a download-tokens", () => {
539
538
  now: NOW(),
540
539
  });
541
540
 
542
- const events = (await stack.db.execute(
543
- sql`SELECT type FROM kumiko_events WHERE type LIKE 'export-download-token.%'`,
541
+ const events = (await asRawClient(stack.db).unsafe(
542
+ `SELECT type FROM kumiko_events WHERE type LIKE 'export-download-token.%'`,
544
543
  )) as unknown as Array<{ type: string }>;
545
544
  // Mindestens 1 created-Event fuer den Token
546
545
  expect(events.some((e) => e.type === "export-download-token.created")).toBe(true);
@@ -565,10 +564,9 @@ describe("runExportJobs :: Atom 4a download-tokens", () => {
565
564
  expect(second.completedJobIds).toEqual([]);
566
565
  expect(second.tokenByJobId.size).toBe(0);
567
566
 
568
- const tokenRows = (await stack.db
569
- .select()
570
- .from(exportDownloadTokensTable)
571
- .where(sql`job_id = ${jobId}`)) as Array<unknown>;
567
+ const tokenRows = (await selectMany(stack.db, exportDownloadTokensTable, {
568
+ jobId,
569
+ })) as Array<unknown>;
572
570
  expect(tokenRows).toHaveLength(1);
573
571
  });
574
572
 
@@ -585,7 +583,7 @@ describe("runExportJobs :: Atom 4a download-tokens", () => {
585
583
  expect(result.completedJobIds).toEqual([]);
586
584
  expect(result.tokenByJobId.size).toBe(0);
587
585
 
588
- const allTokens = (await stack.db.select().from(exportDownloadTokensTable)) as Array<unknown>;
586
+ const allTokens = (await selectMany(stack.db, exportDownloadTokensTable)) as Array<unknown>;
589
587
  expect(allTokens).toHaveLength(0);
590
588
  });
591
589
 
@@ -605,21 +603,24 @@ describe("runExportJobs :: Atom 4a download-tokens", () => {
605
603
  // direct-INSERT (Test-Exemption). Worker's tokenCrud.create wird
606
604
  // dann mit constraintName "read_export_download_tokens_one_per_job"
607
605
  // failen.
608
- await stack.db.execute(sql`
606
+ await asRawClient(stack.db).unsafe(
607
+ `
609
608
  INSERT INTO read_export_download_tokens
610
609
  (id, tenant_id, job_id, token_hash, issued_at, expires_at, version, inserted_at, modified_at)
611
610
  VALUES (
612
611
  gen_random_uuid(),
613
- ${tenantA},
614
- ${jobId},
615
- ${"existing-hash"},
612
+ $1,
613
+ $2,
614
+ $3,
616
615
  now(),
617
616
  now() + interval '7 days',
618
617
  1,
619
618
  now(),
620
619
  now()
621
620
  )
622
- `);
621
+ `,
622
+ [tenantA, jobId, "existing-hash"],
623
+ );
623
624
 
624
625
  const result = await runExportJobs({
625
626
  db: stack.db,
@@ -632,10 +633,10 @@ describe("runExportJobs :: Atom 4a download-tokens", () => {
632
633
  expect(result.completedJobIds).not.toContain(jobId);
633
634
  expect(result.failedJobIds).toContain(jobId);
634
635
 
635
- const [row] = (await stack.db
636
- .select()
637
- .from(exportJobsTable)
638
- .where(sql`id = ${jobId}`)) as Array<{ status: string; errorMessage: string | null }>;
636
+ const [row] = (await selectMany(stack.db, exportJobsTable, { id: jobId })) as Array<{
637
+ status: string;
638
+ errorMessage: string | null;
639
+ }>;
639
640
  expect(row?.status).toBe(EXPORT_JOB_STATUS.Failed);
640
641
  expect(row?.errorMessage).toMatch(/Token-Creation failed/);
641
642
  });
@@ -652,14 +653,16 @@ describe("runExportJobs :: Atom 4a download-tokens", () => {
652
653
  // (simuliert orphan-state nach Worker-crash).
653
654
  const provider = await buildProvider(tenantA);
654
655
  await provider.write(storageKey, new Uint8Array([99, 99, 99]));
655
- await stack.db
656
- .update(exportJobsTable)
657
- .set({
656
+ await updateMany(
657
+ stack.db,
658
+ exportJobsTable,
659
+ {
658
660
  status: EXPORT_JOB_STATUS.Failed,
659
661
  downloadStorageKey: storageKey,
660
662
  errorMessage: "synthetic crash mid-run",
661
- })
662
- .where(sql`id = ${jobId}`);
663
+ },
664
+ { id: jobId },
665
+ );
663
666
 
664
667
  const result = await runExportJobs({
665
668
  db: stack.db,
@@ -671,10 +674,9 @@ describe("runExportJobs :: Atom 4a download-tokens", () => {
671
674
  expect(result.cleanedJobIds).toContain(jobId);
672
675
  expect(await provider.exists(storageKey)).toBe(false);
673
676
 
674
- const [row] = (await stack.db
675
- .select()
676
- .from(exportJobsTable)
677
- .where(sql`id = ${jobId}`)) as Array<{ downloadStorageKey: string | null }>;
677
+ const [row] = (await selectMany(stack.db, exportJobsTable, { id: jobId })) as Array<{
678
+ downloadStorageKey: string | null;
679
+ }>;
678
680
  expect(row?.downloadStorageKey).toBeNull();
679
681
  });
680
682
  });
@@ -740,7 +742,7 @@ describe("runExportJobs :: Atom 3c file-binaries", () => {
740
742
  await unsafeCreateEntityTable(localStack.db, exportDownloadTokenEntity);
741
743
  await unsafeCreateEntityTable(localStack.db, tenantComplianceProfileEntity);
742
744
  await createEventsTable(localStack.db);
743
- await localStack.db.execute(sql`
745
+ await asRawClient(localStack.db).unsafe(`
744
746
  CREATE TABLE IF NOT EXISTS read_tenant_memberships (
745
747
  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
746
748
  tenant_id UUID NOT NULL,
@@ -765,21 +767,24 @@ describe("runExportJobs :: Atom 3c file-binaries", () => {
765
767
 
766
768
  beforeEach(async () => {
767
769
  if (!localStack) return;
768
- await localStack.db.delete(exportDownloadTokensTable);
769
- await localStack.db.delete(exportJobsTable);
770
- await localStack.db.execute(sql`DELETE FROM kumiko_events`);
771
- await localStack.db.execute(sql`DELETE FROM read_tenant_compliance_profiles`);
772
- await localStack.db.execute(sql`DELETE FROM read_tenant_memberships`);
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`);
773
775
  // Reset zu safe Default damit kein Test den State an den naechsten leakt.
774
776
  currentTestFileName = "report.pdf";
775
777
  });
776
778
 
777
779
  async function seedMembership(tenantId: string, userId: string | number) {
778
- await localStack.db.execute(sql`
780
+ await asRawClient(localStack.db).unsafe(
781
+ `
779
782
  INSERT INTO read_tenant_memberships (tenant_id, user_id)
780
- VALUES (${tenantId}, ${String(userId)})
783
+ VALUES ($1, $2)
781
784
  ON CONFLICT (user_id, tenant_id) DO NOTHING
782
- `);
785
+ `,
786
+ [tenantId, String(userId)],
787
+ );
783
788
  }
784
789
 
785
790
  async function seedPendingJobLocal(user: typeof aliceUser): Promise<string> {
@@ -879,10 +884,10 @@ describe("runExportJobs :: Atom 3c file-binaries", () => {
879
884
  expect(result.failedJobIds).toContain(jobId);
880
885
  expect(result.errors[0]?.message).toMatch(/in-memory file not found/);
881
886
 
882
- const [row] = (await localStack.db
883
- .select()
884
- .from(exportJobsTable)
885
- .where(sql`id = ${jobId}`)) as Array<{ status: string; errorMessage: string | null }>;
887
+ const [row] = (await selectMany(localStack.db, exportJobsTable, { id: jobId })) as Array<{
888
+ status: string;
889
+ errorMessage: string | null;
890
+ }>;
886
891
  expect(row?.status).toBe(EXPORT_JOB_STATUS.Failed);
887
892
  expect(row?.errorMessage).toMatch(/in-memory file not found/);
888
893
  });
@@ -1028,8 +1033,8 @@ describe("runExportJobs :: Atom 5 notification-callbacks", () => {
1028
1033
  test("user ohne email → Callback skipped + console.warn (kein Throw)", async () => {
1029
1034
  // User-Row mit email=null seeden (override). Worker logged warn,
1030
1035
  // Callback wird NICHT gerufen, Worker-Run bleibt successful.
1031
- await stack.db.delete(userTable);
1032
- await stack.db.insert(userTable).values({
1036
+ await asRawClient(stack.db).unsafe(`DELETE FROM "${userTable.tableName}"`);
1037
+ await insertOne(stack.db, userTable, {
1033
1038
  id: String(aliceUser.id),
1034
1039
  tenantId: tenantA,
1035
1040
  email: "" as string, // empty string — lookupUserEmail returnt null
@@ -1056,10 +1061,9 @@ describe("runExportJobs :: Atom 5 notification-callbacks", () => {
1056
1061
  expect(callbackInvoked).toBe(false);
1057
1062
  // Job ist trotzdem done (Notification ist best-effort, Audit-Trail
1058
1063
  // existiert via Token-DB-row)
1059
- const [row] = (await stack.db
1060
- .select()
1061
- .from(exportJobsTable)
1062
- .where(sql`id = ${jobId}`)) as Array<{ status: string }>;
1064
+ const [row] = (await selectMany(stack.db, exportJobsTable, { id: jobId })) as Array<{
1065
+ status: string;
1066
+ }>;
1063
1067
  expect(row?.status).toBe("done");
1064
1068
  });
1065
1069
 
@@ -1076,7 +1080,7 @@ describe("runExportJobs :: Atom 5 notification-callbacks", () => {
1076
1080
  tenantId: tenantA,
1077
1081
  roles: ["Member"],
1078
1082
  });
1079
- await stack.db.insert(userTable).values({
1083
+ await insertOne(stack.db, userTable, {
1080
1084
  id: String(bobUser.id),
1081
1085
  tenantId: tenantA,
1082
1086
  email: "bob@example.com",
@@ -1114,11 +1118,12 @@ describe("runExportJobs :: Atom 5 notification-callbacks", () => {
1114
1118
 
1115
1119
  // Beide DB-Rows tatsaechlich done (Job A's Status wurde VOR dem
1116
1120
  // Email-Versand committed — der Throw aenderte daran nichts).
1117
- const rows = (await stack.db
1118
- .select()
1119
- .from(exportJobsTable)
1120
- .where(sql`id IN (${jobAId}, ${jobBId})`)) as Array<{ id: string; status: string }>;
1121
- expect(rows).toHaveLength(2);
1122
- expect(rows.every((r) => r.status === "done")).toBe(true);
1121
+ const rows = (await selectMany(stack.db, exportJobsTable, {})) as Array<{
1122
+ id: string;
1123
+ status: string;
1124
+ }>;
1125
+ const filteredRows = rows.filter((r) => r.id === jobAId || r.id === jobBId);
1126
+ expect(filteredRows).toHaveLength(2);
1127
+ expect(filteredRows.every((r) => r.status === "done")).toBe(true);
1123
1128
  });
1124
1129
  });