@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
@@ -15,14 +15,14 @@
15
15
  // Unit-Test pinned (policy-to-strategy.test.ts), nicht hier. Hier nur
16
16
  // der end-to-end-Default-Pfad (delete).
17
17
 
18
+ import { afterAll, beforeAll, beforeEach, describe, expect, test } from "bun:test";
19
+ import { asRawClient, insertOne } from "@cosmicdrift/kumiko-framework/bun-db";
18
20
  import {
19
21
  setupTestStack,
20
22
  type TestStack,
21
23
  unsafeCreateEntityTable,
22
24
  } from "@cosmicdrift/kumiko-framework/stack";
23
25
  import { getTemporal } from "@cosmicdrift/kumiko-framework/time";
24
- import { sql } from "drizzle-orm";
25
- import { afterAll, beforeAll, beforeEach, describe, expect, test } from "vitest";
26
26
  import { createComplianceProfilesFeature } from "../../compliance-profiles";
27
27
  import { createDataRetentionFeature, tenantRetentionOverrideEntity } from "../../data-retention";
28
28
  import { createFilesFeature } from "../../files";
@@ -70,7 +70,7 @@ beforeAll(async () => {
70
70
  await unsafeCreateEntityTable(stack.db, tenantRetentionOverrideEntity);
71
71
  // tenant-membership-Tabelle (von tenant-feature) manuell anlegen weil
72
72
  // wir ohne tenant-feature im stack arbeiten — minimaler Setup.
73
- await stack.db.execute(sql`
73
+ await asRawClient(stack.db).unsafe(`
74
74
  CREATE TABLE IF NOT EXISTS read_tenant_memberships (
75
75
  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
76
76
  tenant_id UUID NOT NULL,
@@ -87,7 +87,7 @@ beforeAll(async () => {
87
87
  UNIQUE(user_id, tenant_id)
88
88
  )
89
89
  `);
90
- await stack.db.execute(sql`
90
+ await asRawClient(stack.db).unsafe(`
91
91
  CREATE TABLE IF NOT EXISTS file_refs (
92
92
  id UUID PRIMARY KEY,
93
93
  tenant_id UUID NOT NULL,
@@ -109,9 +109,9 @@ afterAll(async () => {
109
109
  });
110
110
 
111
111
  beforeEach(async () => {
112
- await stack.db.delete(userTable);
113
- await stack.db.execute(sql`DELETE FROM read_tenant_memberships`);
114
- await stack.db.execute(sql`DELETE FROM file_refs`);
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`);
115
115
  });
116
116
 
117
117
  type Instant = InstanceType<ReturnType<typeof getTemporal>["Instant"]>;
@@ -131,7 +131,7 @@ async function seedUser(
131
131
  displayName?: string;
132
132
  } = {},
133
133
  ): Promise<void> {
134
- await stack.db.insert(userTable).values({
134
+ await insertOne(stack.db, userTable, {
135
135
  id,
136
136
  tenantId: TENANT_SYSTEM,
137
137
  email: overrides.email ?? `user-${id}@example.com`,
@@ -146,11 +146,14 @@ async function seedUser(
146
146
  }
147
147
 
148
148
  async function seedMembership(userId: string, tenantId: string): Promise<void> {
149
- await stack.db.execute(sql`
149
+ await asRawClient(stack.db).unsafe(
150
+ `
150
151
  INSERT INTO read_tenant_memberships (tenant_id, user_id, roles)
151
- VALUES (${tenantId}, ${userId}, '["Member"]')
152
+ VALUES ($1, $2, '["Member"]')
152
153
  ON CONFLICT (user_id, tenant_id) DO NOTHING
153
- `);
154
+ `,
155
+ [tenantId, userId],
156
+ );
154
157
  }
155
158
 
156
159
  async function seedFileRef(
@@ -159,11 +162,14 @@ async function seedFileRef(
159
162
  insertedById: string | null,
160
163
  fileName: string,
161
164
  ): Promise<void> {
162
- await stack.db.execute(sql`
165
+ await asRawClient(stack.db).unsafe(
166
+ `
163
167
  INSERT INTO file_refs (id, tenant_id, storage_key, file_name, mime_type, size, inserted_by_id)
164
- VALUES (${id}, ${tenantId}, ${`storage/${id}`}, ${fileName}, 'application/pdf', 1024, ${insertedById})
168
+ VALUES ($1, $2, $3, $4, 'application/pdf', 1024, $5)
165
169
  ON CONFLICT (id) DO NOTHING
166
- `);
170
+ `,
171
+ [id, tenantId, `storage/${id}`, fileName, insertedById],
172
+ );
167
173
  }
168
174
 
169
175
  async function fetchUser(id: string): Promise<{
@@ -173,10 +179,13 @@ async function fetchUser(id: string): Promise<{
173
179
  status: string;
174
180
  deleted_at: string | null;
175
181
  } | null> {
176
- const result = await stack.db.execute(sql`
182
+ const result = await asRawClient(stack.db).unsafe(
183
+ `
177
184
  SELECT email, display_name, password_hash, status, deleted_at
178
- FROM read_users WHERE id = ${id}
179
- `);
185
+ FROM read_users WHERE id = $1
186
+ `,
187
+ [id],
188
+ );
180
189
  // biome-ignore lint/suspicious/noExplicitAny: drizzle execute typing
181
190
  const rows = ((result as any).rows ?? result) as Array<{
182
191
  email: string;
@@ -189,18 +198,24 @@ async function fetchUser(id: string): Promise<{
189
198
  }
190
199
 
191
200
  async function fetchFileRefsForUser(tenantId: string, userId: string): Promise<unknown[]> {
192
- const result = await stack.db.execute(sql`
201
+ const result = await asRawClient(stack.db).unsafe(
202
+ `
193
203
  SELECT id, file_name, inserted_by_id
194
- FROM file_refs WHERE tenant_id = ${tenantId} AND inserted_by_id = ${userId}
195
- `);
204
+ FROM file_refs WHERE tenant_id = $1 AND inserted_by_id = $2
205
+ `,
206
+ [tenantId, userId],
207
+ );
196
208
  // biome-ignore lint/suspicious/noExplicitAny: drizzle execute typing
197
209
  return ((result as any).rows ?? result) as unknown[];
198
210
  }
199
211
 
200
212
  async function fetchAllFileRefs(tenantId: string): Promise<unknown[]> {
201
- const result = await stack.db.execute(sql`
202
- SELECT id, file_name, inserted_by_id FROM file_refs WHERE tenant_id = ${tenantId}
203
- `);
213
+ const result = await asRawClient(stack.db).unsafe(
214
+ `
215
+ SELECT id, file_name, inserted_by_id FROM file_refs WHERE tenant_id = $1
216
+ `,
217
+ [tenantId],
218
+ );
204
219
  // biome-ignore lint/suspicious/noExplicitAny: drizzle execute typing
205
220
  return ((result as any).rows ?? result) as unknown[];
206
221
  }
@@ -245,7 +260,7 @@ describe("runForgetCleanup :: happy path (Cross-Tenant Account-Deletion)", () =>
245
260
  expect(aliceRow?.email).not.toContain("alice@example.com");
246
261
  expect(aliceRow?.email).toContain("anonymized.invalid");
247
262
  expect([USER_DELETED_DISPLAY_NAME, USER_ANONYMIZED_DISPLAY_NAME]).toContain(
248
- aliceRow?.display_name,
263
+ aliceRow!.display_name,
249
264
  );
250
265
  expect(aliceRow?.password_hash).toBeNull();
251
266
 
@@ -352,9 +367,12 @@ describe("runForgetCleanup :: PII-Audit nach Cleanup", () => {
352
367
  });
353
368
 
354
369
  // Cross-Tabellen-PII-Check: koennen wir noch IRGENDWO Original-Werte finden?
355
- const userRows = await stack.db.execute(sql`
356
- SELECT id FROM read_users WHERE email = ${ORIGINAL_EMAIL} OR display_name = ${ORIGINAL_NAME}
357
- `);
370
+ const userRows = await asRawClient(stack.db).unsafe(
371
+ `
372
+ SELECT id FROM read_users WHERE email = $1 OR display_name = $2
373
+ `,
374
+ [ORIGINAL_EMAIL, ORIGINAL_NAME],
375
+ );
358
376
  // biome-ignore lint/suspicious/noExplicitAny: drizzle execute typing
359
377
  const userMatches = (userRows as any).rows ?? userRows;
360
378
  expect(userMatches).toHaveLength(0);
@@ -577,7 +595,7 @@ describe("runForgetCleanup :: sendDeletionExecutedEmail callback (Atom 5b)", ()
577
595
  // einem vorigen Run schon anonymisiert hat aber status haengen blieb,
578
596
  // oder durch external Migration). Skip schuetzt vor crashing-callback
579
597
  // mit invaliden Args.
580
- await stack.db.insert(userTable).values({
598
+ await insertOne(stack.db, userTable, {
581
599
  id: ALICE_ID,
582
600
  tenantId: TENANT_SYSTEM,
583
601
  email: "",
@@ -14,14 +14,14 @@
14
14
  // - Orphan-User (0 Memberships): user-Profil-Hook laeuft trotzdem
15
15
  // ueber Pseudo-Tenant.
16
16
 
17
+ import { afterAll, beforeAll, beforeEach, describe, expect, test } from "bun:test";
18
+ import { asRawClient, insertOne } from "@cosmicdrift/kumiko-framework/bun-db";
17
19
  import {
18
20
  setupTestStack,
19
21
  type TestStack,
20
22
  unsafeCreateEntityTable,
21
23
  } from "@cosmicdrift/kumiko-framework/stack";
22
24
  import { getTemporal } from "@cosmicdrift/kumiko-framework/time";
23
- import { sql } from "drizzle-orm";
24
- import { afterAll, beforeAll, beforeEach, describe, expect, test } from "vitest";
25
25
  import { createComplianceProfilesFeature } from "../../compliance-profiles";
26
26
  import { createDataRetentionFeature } from "../../data-retention";
27
27
  import { createFilesFeature } from "../../files";
@@ -57,7 +57,7 @@ beforeAll(async () => {
57
57
  });
58
58
 
59
59
  await unsafeCreateEntityTable(stack.db, userEntity);
60
- await stack.db.execute(sql`
60
+ await asRawClient(stack.db).unsafe(`
61
61
  CREATE TABLE IF NOT EXISTS read_tenant_memberships (
62
62
  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
63
63
  tenant_id UUID NOT NULL,
@@ -74,7 +74,7 @@ beforeAll(async () => {
74
74
  UNIQUE(user_id, tenant_id)
75
75
  )
76
76
  `);
77
- await stack.db.execute(sql`
77
+ await asRawClient(stack.db).unsafe(`
78
78
  CREATE TABLE IF NOT EXISTS file_refs (
79
79
  id UUID PRIMARY KEY,
80
80
  tenant_id UUID NOT NULL,
@@ -96,9 +96,9 @@ afterAll(async () => {
96
96
  });
97
97
 
98
98
  beforeEach(async () => {
99
- await stack.db.delete(userTable);
100
- await stack.db.execute(sql`DELETE FROM read_tenant_memberships`);
101
- await stack.db.execute(sql`DELETE FROM file_refs`);
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`);
102
102
  });
103
103
 
104
104
  const NOW = () => getTemporal().Now.instant();
@@ -107,7 +107,7 @@ async function seedUser(
107
107
  id: string,
108
108
  overrides: { email?: string; displayName?: string; roles?: string } = {},
109
109
  ): Promise<void> {
110
- await stack.db.insert(userTable).values({
110
+ await insertOne(stack.db, userTable, {
111
111
  id,
112
112
  tenantId: TENANT_SYSTEM,
113
113
  email: overrides.email ?? `user-${id}@example.com`,
@@ -121,11 +121,14 @@ async function seedUser(
121
121
  }
122
122
 
123
123
  async function seedMembership(userId: string, tenantId: string): Promise<void> {
124
- await stack.db.execute(sql`
124
+ await asRawClient(stack.db).unsafe(
125
+ `
125
126
  INSERT INTO read_tenant_memberships (tenant_id, user_id, roles)
126
- VALUES (${tenantId}, ${userId}, '["Member"]')
127
+ VALUES ($1, $2, '["Member"]')
127
128
  ON CONFLICT (user_id, tenant_id) DO NOTHING
128
- `);
129
+ `,
130
+ [tenantId, userId],
131
+ );
129
132
  }
130
133
 
131
134
  async function seedFileRef(
@@ -134,11 +137,14 @@ async function seedFileRef(
134
137
  insertedById: string | null,
135
138
  fileName: string,
136
139
  ): Promise<void> {
137
- await stack.db.execute(sql`
140
+ await asRawClient(stack.db).unsafe(
141
+ `
138
142
  INSERT INTO file_refs (id, tenant_id, storage_key, file_name, mime_type, size, inserted_by_id)
139
- VALUES (${id}, ${tenantId}, ${`storage/${id}`}, ${fileName}, 'application/pdf', 1024, ${insertedById})
143
+ VALUES ($1, $2, $3, $4, 'application/pdf', 1024, $5)
140
144
  ON CONFLICT (id) DO NOTHING
141
- `);
145
+ `,
146
+ [id, tenantId, `storage/${id}`, fileName, insertedById],
147
+ );
142
148
  }
143
149
 
144
150
  describe("runUserExport :: alle Daten enthalten + Cross-Tenant", () => {
@@ -6,7 +6,7 @@
6
6
  // - Web-Crypto-Universal: laeuft in vitest (= bun-runtime via vitest)
7
7
  // ohne node:crypto-Import. Memory `feedback_universal_deps`.
8
8
 
9
- import { describe, expect, test } from "vitest";
9
+ import { describe, expect, test } from "bun:test";
10
10
  import { generateDownloadToken, hashDownloadToken } from "../token-helpers";
11
11
 
12
12
  describe("generateDownloadToken", () => {
@@ -6,8 +6,8 @@
6
6
  // Tieferer Cross-Feature-Test (mit useExtension(EXT_USER_DATA, ...) +
7
7
  // Sprint-2-H1/H2-Hooks) kommt in S2.T1 (Cross-Data-Matrix).
8
8
 
9
+ import { afterAll, beforeAll, describe, expect, test } from "bun:test";
9
10
  import { setupTestStack, type TestStack } from "@cosmicdrift/kumiko-framework/stack";
10
- import { afterAll, beforeAll, describe, expect, test } from "vitest";
11
11
  import { createComplianceProfilesFeature } from "../../compliance-profiles";
12
12
  import { createDataRetentionFeature } from "../../data-retention";
13
13
  import { createUserFeature } from "../../user";
@@ -5,8 +5,8 @@
5
5
  // Filename mit "../" einen ZIP-Reader dazu bringen, ausserhalb des
6
6
  // Extract-Roots zu schreiben.
7
7
 
8
+ import { describe, expect, test } from "bun:test";
8
9
  import type { TenantId } from "@cosmicdrift/kumiko-framework/engine";
9
- import { describe, expect, test } from "vitest";
10
10
  import { buildFileRefZipPath, sanitizeZipFilename } from "../zip-path";
11
11
 
12
12
  const TENANT = "00000000-0000-0000-0000-000000000001" as TenantId;
@@ -15,7 +15,7 @@
15
15
  // **ES via tokenCrud.update:** kein direct-UPDATE. Memory
16
16
  // `feedback_no_fake_dispatcher` + `feedback_event_store_tenant_consistency`.
17
17
 
18
- import type { DbConnection } from "@cosmicdrift/kumiko-framework/db";
18
+ import type { DbRunner } from "@cosmicdrift/kumiko-framework/db";
19
19
  import { createEventStoreExecutor, createTenantDb } from "@cosmicdrift/kumiko-framework/db";
20
20
  import { createSystemUser, type TenantId } from "@cosmicdrift/kumiko-framework/engine";
21
21
  import type { getTemporal } from "@cosmicdrift/kumiko-framework/time";
@@ -29,7 +29,7 @@ const attemptCrud = createEventStoreExecutor(downloadAttemptsTable, downloadAtte
29
29
  type Instant = InstanceType<ReturnType<typeof getTemporal>["Instant"]>;
30
30
 
31
31
  export interface RecordDownloadUseArgs {
32
- readonly db: DbConnection;
32
+ readonly db: DbRunner;
33
33
  readonly tokenId: string;
34
34
  readonly tokenVersion: number;
35
35
  readonly tokenUseCount: number;
@@ -85,7 +85,7 @@ export async function recordDownloadUse(args: RecordDownloadUseArgs): Promise<vo
85
85
  export type DownloadAttemptResult = "notFound" | "expired" | "failed" | "signedUrlNotSupported";
86
86
 
87
87
  export interface RecordInvalidAttemptArgs {
88
- readonly db: DbConnection;
88
+ readonly db: DbRunner;
89
89
  readonly tenantId: TenantId;
90
90
  readonly now: Instant;
91
91
  readonly result: DownloadAttemptResult;
@@ -0,0 +1,23 @@
1
+ import { asRawClient } from "@cosmicdrift/kumiko-framework/bun-db";
2
+ import type { DbConnection } from "@cosmicdrift/kumiko-framework/db";
3
+ import type { TenantId } from "@cosmicdrift/kumiko-framework/engine";
4
+
5
+ export type ExportJobCleanupCandidate = {
6
+ readonly id: string;
7
+ readonly version: number;
8
+ readonly status: string;
9
+ readonly requestedFromTenantId: TenantId;
10
+ readonly downloadStorageKey: string | null;
11
+ readonly expiresAt: Temporal.Instant | null;
12
+ };
13
+
14
+ export async function selectExportJobsForStorageCleanup(
15
+ db: DbConnection,
16
+ doneStatus: string,
17
+ failedStatus: string,
18
+ ): 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
+ );
23
+ }
@@ -0,0 +1,13 @@
1
+ import { asRawClient } from "@cosmicdrift/kumiko-framework/bun-db";
2
+ import type { DbConnection } from "@cosmicdrift/kumiko-framework/db";
3
+
4
+ export async function selectUsersDueForForgetCleanup(
5
+ db: DbConnection,
6
+ status: string,
7
+ gracePeriodEnd: string,
8
+ ): 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
+ );
13
+ }
@@ -1,6 +1,6 @@
1
+ import { fetchOne, updateMany } from "@cosmicdrift/kumiko-framework/bun-db";
1
2
  import { defineWriteHandler } from "@cosmicdrift/kumiko-framework/engine";
2
3
  import { UnprocessableError, writeFailure } from "@cosmicdrift/kumiko-framework/errors";
3
- import { eq, sql } from "drizzle-orm";
4
4
  import { z } from "zod";
5
5
  import { USER_STATUS, userTable } from "../../user";
6
6
 
@@ -23,18 +23,13 @@ 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
- //
27
- // Combined query: status + grace_period_end-vs-now in einem Pass.
28
- const checkRows = await ctx.db.raw
29
- .select({
30
- status: userTable["status"],
31
- inGrace: sql<boolean>`(${userTable["gracePeriodEnd"]} > now())`,
32
- })
33
- .from(userTable)
34
- .where(eq(userTable["id"], event.user.id))
35
- .limit(1);
26
+ const row = await fetchOne<{ status: string; grace_period_end: Date | null }>(
27
+ ctx.db.raw,
28
+ userTable,
29
+ { id: event.user.id },
30
+ );
36
31
 
37
- if (checkRows.length === 0) {
32
+ if (!row) {
38
33
  return writeFailure(
39
34
  new UnprocessableError("user_not_found", {
40
35
  details: { reason: "user_not_found", userId: event.user.id },
@@ -42,19 +37,28 @@ export const cancelDeletionWrite = defineWriteHandler({
42
37
  );
43
38
  }
44
39
 
45
- const row = checkRows[0];
46
- if (!row || row.status !== USER_STATUS.DeletionRequested) {
40
+ if (row["status"] !== USER_STATUS.DeletionRequested) {
47
41
  return writeFailure(
48
42
  new UnprocessableError("no_pending_deletion", {
49
43
  details: {
50
44
  reason: "no_pending_deletion",
51
- currentStatus: row?.status,
45
+ currentStatus: row["status"],
52
46
  },
53
47
  }),
54
48
  );
55
49
  }
56
50
 
57
- if (!row.inGrace) {
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"];
54
+ const inGrace =
55
+ gracePeriodEnd != null &&
56
+ Temporal.Instant.compare(
57
+ gracePeriodEnd as unknown as Temporal.Instant,
58
+ Temporal.Now.instant(),
59
+ ) > 0;
60
+
61
+ if (!inGrace) {
58
62
  return writeFailure(
59
63
  new UnprocessableError("grace_period_expired", {
60
64
  details: { reason: "grace_period_expired" },
@@ -62,13 +66,15 @@ export const cancelDeletionWrite = defineWriteHandler({
62
66
  );
63
67
  }
64
68
 
65
- await ctx.db.raw
66
- .update(userTable)
67
- .set({
69
+ await updateMany(
70
+ ctx.db.raw,
71
+ userTable,
72
+ {
68
73
  status: USER_STATUS.Active,
69
74
  gracePeriodEnd: null,
70
- })
71
- .where(eq(userTable["id"], event.user.id));
75
+ },
76
+ { id: event.user.id },
77
+ );
72
78
 
73
79
  // gracePeriodEnd=null im Response symmetrisch zu request-deletion's
74
80
  // ISO-Timestamp — Frontend kann beide Endpoints uniform behandeln.
@@ -77,7 +83,7 @@ export const cancelDeletionWrite = defineWriteHandler({
77
83
  data: {
78
84
  userId: event.user.id,
79
85
  status: USER_STATUS.Active,
80
- gracePeriodEnd: null as string | null, // @cast-boundary generic-record
86
+ gracePeriodEnd: null as string | null,
81
87
  },
82
88
  };
83
89
  },
@@ -23,12 +23,10 @@
23
23
  // 6. Audit-Update: useCount + 1, IP, UA, lastUsedAt (best-effort)
24
24
  // 7. Return {url, expiresAt}
25
25
 
26
- import type { DbConnection } from "@cosmicdrift/kumiko-framework/db";
27
- import { fetchOne } from "@cosmicdrift/kumiko-framework/db";
26
+ import { fetchOne } from "@cosmicdrift/kumiko-framework/bun-db";
28
27
  import { defineQueryHandler } from "@cosmicdrift/kumiko-framework/engine";
29
28
  import { NotFoundError, UnprocessableError } from "@cosmicdrift/kumiko-framework/errors";
30
29
  import { getTemporal } from "@cosmicdrift/kumiko-framework/time";
31
- import { eq } from "drizzle-orm";
32
30
  import { z } from "zod";
33
31
  import { createFileProviderForTenant } from "../../file-foundation";
34
32
  import { recordDownloadUse, recordInvalidAttempt } from "../audit-download";
@@ -76,15 +74,11 @@ export const downloadByJobQuery = defineQueryHandler({
76
74
  // Step 1-2: job-lookup + cross-user-isolation
77
75
  // ctx.db.raw weil tenant-agnostisch — Alice in Tenant B sucht den
78
76
  // aus Tenant A erstellten Job.
79
- const jobRow = (await fetchOne(
80
- ctx.db.raw,
81
- exportJobsTable,
82
- eq(exportJobsTable["id"], jobId),
83
- )) as JobRow | null; // @cast-boundary db-row
77
+ const jobRow = await fetchOne<JobRow>(ctx.db.raw, exportJobsTable, { id: jobId });
84
78
 
85
79
  if (!jobRow || jobRow.userId !== userId) {
86
80
  await recordInvalidAttempt({
87
- db: ctx.db.raw as DbConnection, // @cast-boundary db-runner
81
+ db: ctx.db.raw,
88
82
  tenantId,
89
83
  now,
90
84
  result: "notFound",
@@ -102,7 +96,7 @@ export const downloadByJobQuery = defineQueryHandler({
102
96
 
103
97
  if (jobRow.status !== EXPORT_JOB_STATUS.Done) {
104
98
  await recordInvalidAttempt({
105
- db: ctx.db.raw as DbConnection, // @cast-boundary db-runner
99
+ db: ctx.db.raw,
106
100
  tenantId,
107
101
  now,
108
102
  result: "failed",
@@ -119,7 +113,7 @@ export const downloadByJobQuery = defineQueryHandler({
119
113
  }
120
114
  if (!jobRow.downloadStorageKey) {
121
115
  await recordInvalidAttempt({
122
- db: ctx.db.raw as DbConnection, // @cast-boundary db-runner
116
+ db: ctx.db.raw,
123
117
  tenantId,
124
118
  now,
125
119
  result: "expired",
@@ -142,7 +136,7 @@ export const downloadByJobQuery = defineQueryHandler({
142
136
  );
143
137
  if (!provider.getSignedUrl) {
144
138
  await recordInvalidAttempt({
145
- db: ctx.db.raw as DbConnection, // @cast-boundary db-runner
139
+ db: ctx.db.raw,
146
140
  tenantId,
147
141
  now,
148
142
  result: "signedUrlNotSupported",
@@ -173,21 +167,17 @@ export const downloadByJobQuery = defineQueryHandler({
173
167
  // den plain-Token, aber wir wollen den useCount inkrementieren
174
168
  // damit die Audit-Felder konsistent sind (UI-clicks zaehlen auch
175
169
  // als Use). Lookup via jobId — UNIQUE-Index garantiert max 1 Row.
176
- const tokenRow = (await fetchOne(
177
- ctx.db.raw,
178
- exportDownloadTokensTable,
179
- eq(exportDownloadTokensTable["jobId"], jobId),
180
- )) as TokenRow | null; // @cast-boundary db-row
170
+ const tokenRow = await fetchOne<TokenRow>(ctx.db.raw, exportDownloadTokensTable, {
171
+ jobId,
172
+ });
181
173
 
182
174
  if (tokenRow) {
183
175
  await recordDownloadUse({
184
- db: ctx.db.raw as DbConnection, // @cast-boundary db-runner
176
+ db: ctx.db.raw,
185
177
  tokenId: tokenRow.id,
186
178
  tokenVersion: tokenRow.version,
187
179
  tokenUseCount: tokenRow.useCount ?? 0,
188
- tenantId: jobRow.requestedFromTenantId as Parameters<
189
- typeof recordDownloadUse
190
- >[0]["tenantId"], // @cast-boundary engine-bridge
180
+ tenantId: jobRow.requestedFromTenantId,
191
181
  now,
192
182
  ip: query.payload.auditMeta?.ip ?? null,
193
183
  userAgent: query.payload.auditMeta?.userAgent ?? null,
@@ -26,12 +26,10 @@
26
26
  // signedUrl — User klickt 1× Email-Link, Browser folgt redirect, Download
27
27
  // startet. Dieser query-handler liefert nur das JSON.
28
28
 
29
- import type { DbConnection } from "@cosmicdrift/kumiko-framework/db";
30
- import { fetchOne } from "@cosmicdrift/kumiko-framework/db";
29
+ import { fetchOne } from "@cosmicdrift/kumiko-framework/bun-db";
31
30
  import { defineQueryHandler } from "@cosmicdrift/kumiko-framework/engine";
32
31
  import { NotFoundError, UnprocessableError } from "@cosmicdrift/kumiko-framework/errors";
33
32
  import { getTemporal } from "@cosmicdrift/kumiko-framework/time";
34
- import { eq } from "drizzle-orm";
35
33
  import { z } from "zod";
36
34
  import { createFileProviderForTenant } from "../../file-foundation";
37
35
  import { recordDownloadUse, recordInvalidAttempt } from "../audit-download";
@@ -87,11 +85,9 @@ export const downloadByTokenQuery = defineQueryHandler({
87
85
  const hash = await hashDownloadToken(query.payload.token);
88
86
  // ctx.db.raw weil Token+Job tenant-agnostisch — anonymous-pfad hat
89
87
  // keinen tenant-context im query.user.
90
- const tokenRow = (await fetchOne(
91
- ctx.db.raw,
92
- exportDownloadTokensTable,
93
- eq(exportDownloadTokensTable["tokenHash"], hash),
94
- )) as TokenRow | null; // @cast-boundary db-row
88
+ const tokenRow = await fetchOne<TokenRow>(ctx.db.raw, exportDownloadTokensTable, {
89
+ tokenHash: hash,
90
+ });
95
91
 
96
92
  if (!tokenRow) {
97
93
  // Invalid token — 404 ohne Existenz-Leak. Generic NotFoundError
@@ -116,18 +112,15 @@ export const downloadByTokenQuery = defineQueryHandler({
116
112
  // Audit-Skip noch nicht moeglich — jobRow noch nicht geladen,
117
113
  // tenantId unbekannt. Wir laden den Job hier noch fuer Audit-Context
118
114
  // (best-effort — wenn Job auch fehlt, audit-skip ist akzeptabel).
119
- const jobForAudit = (await fetchOne(
115
+ const jobForAudit = await fetchOne<{ requestedFromTenantId: string }>(
120
116
  ctx.db.raw,
121
117
  exportJobsTable,
122
- eq(exportJobsTable["id"], tokenRow.jobId),
123
- )) as { requestedFromTenantId: string } | null; // @cast-boundary db-row
118
+ { id: tokenRow.jobId },
119
+ );
124
120
  if (jobForAudit) {
125
- const auditDb = ctx.db.raw as DbConnection; // @cast-boundary db-runner
126
121
  await recordInvalidAttempt({
127
- db: auditDb,
128
- tenantId: jobForAudit.requestedFromTenantId as Parameters<
129
- typeof recordInvalidAttempt
130
- >[0]["tenantId"], // @cast-boundary engine-bridge
122
+ db: ctx.db.raw,
123
+ tenantId: jobForAudit.requestedFromTenantId,
131
124
  now,
132
125
  result: "expired",
133
126
  via: "token",
@@ -144,11 +137,9 @@ export const downloadByTokenQuery = defineQueryHandler({
144
137
  }
145
138
 
146
139
  // Step 3-4: job-checks
147
- const jobRow = (await fetchOne(
148
- ctx.db.raw,
149
- exportJobsTable,
150
- eq(exportJobsTable["id"], tokenRow.jobId),
151
- )) as JobRow | null; // @cast-boundary db-row
140
+ const jobRow = await fetchOne<JobRow>(ctx.db.raw, exportJobsTable, {
141
+ id: tokenRow.jobId,
142
+ });
152
143
 
153
144
  if (!jobRow) {
154
145
  throw new NotFoundError("export-download", undefined, {
@@ -157,10 +148,8 @@ export const downloadByTokenQuery = defineQueryHandler({
157
148
  }
158
149
  if (jobRow.status !== EXPORT_JOB_STATUS.Done) {
159
150
  await recordInvalidAttempt({
160
- db: ctx.db.raw as DbConnection, // @cast-boundary db-runner
161
- tenantId: jobRow.requestedFromTenantId as Parameters<
162
- typeof recordInvalidAttempt
163
- >[0]["tenantId"], // @cast-boundary engine-bridge
151
+ db: ctx.db.raw,
152
+ tenantId: jobRow.requestedFromTenantId,
164
153
  now,
165
154
  result: "failed",
166
155
  via: "token",
@@ -176,10 +165,8 @@ export const downloadByTokenQuery = defineQueryHandler({
176
165
  }
177
166
  if (!jobRow.downloadStorageKey) {
178
167
  await recordInvalidAttempt({
179
- db: ctx.db.raw as DbConnection, // @cast-boundary db-runner
180
- tenantId: jobRow.requestedFromTenantId as Parameters<
181
- typeof recordInvalidAttempt
182
- >[0]["tenantId"], // @cast-boundary engine-bridge
168
+ db: ctx.db.raw,
169
+ tenantId: jobRow.requestedFromTenantId,
183
170
  now,
184
171
  result: "expired",
185
172
  via: "token",
@@ -203,10 +190,8 @@ export const downloadByTokenQuery = defineQueryHandler({
203
190
  );
204
191
  if (!provider.getSignedUrl) {
205
192
  await recordInvalidAttempt({
206
- db: ctx.db.raw as DbConnection, // @cast-boundary db-runner
207
- tenantId: jobRow.requestedFromTenantId as Parameters<
208
- typeof recordInvalidAttempt
209
- >[0]["tenantId"], // @cast-boundary engine-bridge
193
+ db: ctx.db.raw,
194
+ tenantId: jobRow.requestedFromTenantId,
210
195
  now,
211
196
  result: "signedUrlNotSupported",
212
197
  via: "token",
@@ -236,11 +221,11 @@ export const downloadByTokenQuery = defineQueryHandler({
236
221
  // Wrapper (trusted-source). Direct-API-caller koennen luegen, aber
237
222
  // Audit ist nicht security-relevant.
238
223
  await recordDownloadUse({
239
- db: ctx.db.raw as DbConnection, // @cast-boundary db-runner
224
+ db: ctx.db.raw,
240
225
  tokenId: tokenRow.id,
241
226
  tokenVersion: tokenRow.version,
242
227
  tokenUseCount: tokenRow.useCount ?? 0,
243
- tenantId: jobRow.requestedFromTenantId as Parameters<typeof recordDownloadUse>[0]["tenantId"], // @cast-boundary engine-bridge
228
+ tenantId: jobRow.requestedFromTenantId,
244
229
  now,
245
230
  ip: query.payload.auditMeta?.ip ?? null,
246
231
  userAgent: query.payload.auditMeta?.userAgent ?? null,