@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
@@ -8,6 +8,8 @@
8
8
  // Gegen-Beweis: User OHNE globale Rollen verhält sich wie vorher
9
9
  // (nur tenant-membership-roles in der Session).
10
10
 
11
+ import { afterAll, beforeAll, beforeEach, describe, expect, test } from "bun:test";
12
+ import { asRawClient, insertOne, selectMany } from "@cosmicdrift/kumiko-framework/bun-db";
11
13
  import type { TenantId } from "@cosmicdrift/kumiko-framework/engine";
12
14
  import {
13
15
  setupTestStack,
@@ -17,7 +19,6 @@ import {
17
19
  unsafeCreateEntityTable,
18
20
  unsafePushTables,
19
21
  } from "@cosmicdrift/kumiko-framework/stack";
20
- import { afterAll, beforeAll, beforeEach, describe, expect, test } from "vitest";
21
22
  import { createConfigFeature } from "../../config";
22
23
  import { createConfigResolver } from "../../config/resolver";
23
24
  import { configValuesTable } from "../../config/table";
@@ -71,8 +72,8 @@ afterAll(async () => {
71
72
  });
72
73
 
73
74
  beforeEach(async () => {
74
- await stack.db.delete(userTable);
75
- await stack.db.delete(tenantMembershipsTable);
75
+ await asRawClient(stack.db).unsafe(`DELETE FROM "${userTable.tableName}"`);
76
+ await asRawClient(stack.db).unsafe(`DELETE FROM "${tenantMembershipsTable.tableName}"`);
76
77
  });
77
78
 
78
79
  async function seedUser(
@@ -101,7 +102,7 @@ async function addMembership(
101
102
  tenantId: TenantId,
102
103
  roles: readonly string[],
103
104
  ): Promise<void> {
104
- await stack.db.insert(tenantMembershipsTable).values({
105
+ await insertOne(stack.db, tenantMembershipsTable, {
105
106
  userId,
106
107
  tenantId,
107
108
  roles: JSON.stringify(roles),
@@ -129,11 +130,7 @@ describe("multi-roles: login mergt globale + membership-roles", () => {
129
130
  // Pin write-path: roles MUSS in DB landen, sonst ist der session-merge
130
131
  // nur Zufall (z.B. wenn login-handler hardcoded SystemAdmin reinpacken
131
132
  // würde). Direct DB-read schließt das aus.
132
- const { eq } = await import("drizzle-orm");
133
- const dbRow = await stack.db
134
- .select({ roles: userTable["roles"] })
135
- .from(userTable)
136
- .where(eq(userTable["id"], userId));
133
+ const dbRow = await selectMany(stack.db, userTable, { id: userId });
137
134
  expect(dbRow[0]?.roles).toBe(JSON.stringify(["SystemAdmin"]));
138
135
 
139
136
  const { user } = await login("syadmin@example.com", "pw-long-enough");
@@ -1,4 +1,6 @@
1
+ import { afterAll, beforeAll, beforeEach, describe, expect, test } from "bun:test";
1
2
  import { randomBytes } from "node:crypto";
3
+ import { asRawClient, selectMany } from "@cosmicdrift/kumiko-framework/bun-db";
2
4
  import { createEncryptionProvider } from "@cosmicdrift/kumiko-framework/db";
3
5
  import type { TenantId } from "@cosmicdrift/kumiko-framework/engine";
4
6
  import {
@@ -9,7 +11,6 @@ import {
9
11
  unsafePushTables,
10
12
  } from "@cosmicdrift/kumiko-framework/stack";
11
13
  import { Temporal } from "temporal-polyfill";
12
- import { afterAll, beforeAll, beforeEach, describe, expect, test } from "vitest";
13
14
  import { createConfigFeature } from "../../config";
14
15
  import { createConfigResolver } from "../../config/resolver";
15
16
  import { configValuesTable } from "../../config/table";
@@ -93,9 +94,9 @@ afterAll(async () => {
93
94
  });
94
95
 
95
96
  beforeEach(async () => {
96
- await stack.db.delete(userTable);
97
- await stack.db.delete(tenantMembershipsTable);
98
- await stack.db.delete(userSessionTable);
97
+ await asRawClient(stack.db).unsafe(`DELETE FROM "${userTable.tableName}"`);
98
+ await asRawClient(stack.db).unsafe(`DELETE FROM "${tenantMembershipsTable.tableName}"`);
99
+ await asRawClient(stack.db).unsafe(`DELETE FROM "${userSessionTable.tableName}"`);
99
100
  capturedEmails.length = 0;
100
101
  autoRevokeCalls.length = 0;
101
102
  });
@@ -182,7 +183,7 @@ describe("POST /auth/reset-password", () => {
182
183
 
183
184
  // Proof: the new password actually hashes in. Read the row, verify the
184
185
  // hash matches the new plaintext.
185
- const row = (await stack.db.select().from(userTable)).find((r) => r["id"] === seed.id);
186
+ const row = (await selectMany(stack.db, userTable)).find((r) => r["id"] === seed.id);
186
187
  if (!row?.["passwordHash"]) throw new Error("user row / hash missing");
187
188
  expect(await verifyPassword(row["passwordHash"] as string, "brand-new-pw-9876")).toBe(true);
188
189
  expect(await verifyPassword(row["passwordHash"] as string, "old-pw-1234")).toBe(false);
@@ -203,7 +204,7 @@ describe("POST /auth/reset-password", () => {
203
204
  expect(body.error?.details?.reason).toBe(AuthErrors.invalidResetToken);
204
205
 
205
206
  // Old password still wins.
206
- const row = (await stack.db.select().from(userTable)).find((r) => r["id"] === seed.id);
207
+ const row = (await selectMany(stack.db, userTable)).find((r) => r["id"] === seed.id);
207
208
  if (!row?.["passwordHash"]) throw new Error("user row / hash missing");
208
209
  expect(await verifyPassword(row["passwordHash"] as string, "keep-me!")).toBe(true);
209
210
  });
@@ -262,7 +263,7 @@ describe("POST /auth/reset-password", () => {
262
263
  const seed = await seedUser({ email: "retry@example.com", password: "pw-retry-1234" });
263
264
  const { token } = signResetToken(seed.id, 15, resetSecret);
264
265
 
265
- await stack.db.delete(tenantMembershipsTable);
266
+ await asRawClient(stack.db).unsafe(`DELETE FROM "${tenantMembershipsTable.tableName}"`);
266
267
  const firstAttempt = await post("/api/auth/reset-password", {
267
268
  token,
268
269
  newPassword: "never-lands-1234",
@@ -4,7 +4,9 @@
4
4
  // comment — a regression that silently moves the routes out of /api/auth/*
5
5
  // or tightens loginRateLimit but forgets these would sail through.
6
6
 
7
+ import { afterAll, beforeAll, beforeEach, describe, expect, test } from "bun:test";
7
8
  import { randomBytes } from "node:crypto";
9
+ import { asRawClient } from "@cosmicdrift/kumiko-framework/bun-db";
8
10
  import { createEncryptionProvider } from "@cosmicdrift/kumiko-framework/db";
9
11
  import {
10
12
  setupTestStack,
@@ -12,7 +14,6 @@ import {
12
14
  unsafeCreateEntityTable,
13
15
  unsafePushTables,
14
16
  } from "@cosmicdrift/kumiko-framework/stack";
15
- import { afterAll, beforeAll, beforeEach, describe, expect, test } from "vitest";
16
17
  import { createConfigFeature } from "../../config";
17
18
  import { createConfigResolver } from "../../config/resolver";
18
19
  import { configValuesTable } from "../../config/table";
@@ -77,8 +78,8 @@ afterAll(async () => {
77
78
  });
78
79
 
79
80
  beforeEach(async () => {
80
- await stack.db.delete(userTable);
81
- await stack.db.delete(tenantMembershipsTable);
81
+ await asRawClient(stack.db).unsafe(`DELETE FROM "${userTable.tableName}"`);
82
+ await asRawClient(stack.db).unsafe(`DELETE FROM "${tenantMembershipsTable.tableName}"`);
82
83
  });
83
84
 
84
85
  // Unique IP per test so buckets don't cross-contaminate. L2 default bucket
@@ -10,6 +10,8 @@
10
10
  // 4. Rollen pro Tenant landen korrekt (unterschiedliche Rollen-Listen
11
11
  // pro Membership).
12
12
 
13
+ import { afterAll, beforeAll, beforeEach, describe, expect, test } from "bun:test";
14
+ import { asRawClient, selectMany } from "@cosmicdrift/kumiko-framework/bun-db";
13
15
  import type { TenantId } from "@cosmicdrift/kumiko-framework/engine";
14
16
  import { createEventsTable, eventsTable } from "@cosmicdrift/kumiko-framework/event-store";
15
17
  import {
@@ -18,8 +20,6 @@ import {
18
20
  unsafeCreateEntityTable,
19
21
  unsafePushTables,
20
22
  } from "@cosmicdrift/kumiko-framework/stack";
21
- import { and, eq } from "drizzle-orm";
22
- import { afterAll, beforeAll, beforeEach, describe, expect, test } from "vitest";
23
23
  import { createConfigFeature } from "../../config/feature";
24
24
  import { createConfigResolver } from "../../config/resolver";
25
25
  import { configValuesTable } from "../../config/table";
@@ -53,10 +53,10 @@ afterAll(async () => {
53
53
  });
54
54
 
55
55
  beforeEach(async () => {
56
- await stack.db.delete(tenantMembershipsTable);
57
- await stack.db.delete(tenantTable);
58
- await stack.db.delete(userTable);
59
- await stack.db.delete(eventsTable);
56
+ await asRawClient(stack.db).unsafe(`DELETE FROM "${tenantMembershipsTable.tableName}"`);
57
+ await asRawClient(stack.db).unsafe(`DELETE FROM "${tenantTable.tableName}"`);
58
+ await asRawClient(stack.db).unsafe(`DELETE FROM "${userTable.tableName}"`);
59
+ await asRawClient(stack.db).unsafe(`DELETE FROM "${eventsTable.tableName}"`);
60
60
  });
61
61
 
62
62
  describe("seedAdmin", () => {
@@ -82,14 +82,11 @@ describe("seedAdmin", () => {
82
82
  });
83
83
 
84
84
  // Tenants angelegt
85
- const tenants = await stack.db.select().from(tenantTable);
85
+ const tenants = await selectMany(stack.db, tenantTable);
86
86
  expect(tenants.map((t) => t["id"]).sort()).toEqual([TENANT_DEV, TENANT_BETA].sort());
87
87
 
88
88
  // User angelegt mit Hash (NICHT plain-Password)
89
- const [user] = await stack.db
90
- .select()
91
- .from(userTable)
92
- .where(eq(userTable["email"], "admin@example.com"));
89
+ const [user] = await selectMany(stack.db, userTable, { email: "admin@example.com" });
93
90
  expect(user?.["id"]).toBe(userId);
94
91
  expect(user?.["passwordHash"]).not.toBe("secret-pw");
95
92
  expect(user?.["passwordHash"]).toMatch(/^\$argon2/);
@@ -101,26 +98,16 @@ describe("seedAdmin", () => {
101
98
  expect(invalid).toBe(false);
102
99
 
103
100
  // Memberships pro Tenant mit unterschiedlichen Rollen
104
- const devMembership = await stack.db
105
- .select()
106
- .from(tenantMembershipsTable)
107
- .where(
108
- and(
109
- eq(tenantMembershipsTable.userId, userId),
110
- eq(tenantMembershipsTable.tenantId, TENANT_DEV),
111
- ),
112
- );
101
+ const devMembership = await selectMany(stack.db, tenantMembershipsTable, {
102
+ userId: userId,
103
+ tenantId: TENANT_DEV,
104
+ });
113
105
  expect(devMembership[0]?.["roles"]).toBe(JSON.stringify(["Admin"]));
114
106
 
115
- const betaMembership = await stack.db
116
- .select()
117
- .from(tenantMembershipsTable)
118
- .where(
119
- and(
120
- eq(tenantMembershipsTable.userId, userId),
121
- eq(tenantMembershipsTable.tenantId, TENANT_BETA),
122
- ),
123
- );
107
+ const betaMembership = await selectMany(stack.db, tenantMembershipsTable, {
108
+ userId: userId,
109
+ tenantId: TENANT_BETA,
110
+ });
124
111
  expect(betaMembership[0]?.["roles"]).toBe(JSON.stringify(["User"]));
125
112
  });
126
113
 
@@ -148,7 +135,7 @@ describe("seedAdmin", () => {
148
135
  expect(userId2).toBe(userId1);
149
136
 
150
137
  // Genau ein User-Row, original-Hash (passt zu pw1, nicht pw2).
151
- const users = await stack.db.select().from(userTable);
138
+ const users = await selectMany(stack.db, userTable);
152
139
  expect(users).toHaveLength(1);
153
140
  const valid = await verifyPassword(users[0]?.["passwordHash"] as string, "pw1");
154
141
  expect(valid).toBe(true);
@@ -156,11 +143,11 @@ describe("seedAdmin", () => {
156
143
  expect(invalid).toBe(false);
157
144
 
158
145
  // Genau ein Membership-Row.
159
- const memberships = await stack.db.select().from(tenantMembershipsTable);
146
+ const memberships = await selectMany(stack.db, tenantMembershipsTable);
160
147
  expect(memberships).toHaveLength(1);
161
148
 
162
149
  // Genau ein .created-Event pro Aggregat-Typ.
163
- const events = await stack.db.select().from(eventsTable);
150
+ const events = await selectMany(stack.db, eventsTable);
164
151
  const createdByType = events
165
152
  .filter((e) => e.type.endsWith(".created"))
166
153
  .reduce<Record<string, number>>((acc, e) => {
@@ -1,4 +1,6 @@
1
+ import { afterAll, beforeAll, beforeEach, describe, expect, test } from "bun:test";
1
2
  import { randomBytes } from "node:crypto";
3
+ import { asRawClient } from "@cosmicdrift/kumiko-framework/bun-db";
2
4
  import { createEncryptionProvider } from "@cosmicdrift/kumiko-framework/db";
3
5
  import type { TenantId } from "@cosmicdrift/kumiko-framework/engine";
4
6
  import {
@@ -9,7 +11,6 @@ import {
9
11
  unsafePushTables,
10
12
  } from "@cosmicdrift/kumiko-framework/stack";
11
13
  import * as jose from "jose";
12
- import { afterAll, beforeAll, beforeEach, describe, expect, test } from "vitest";
13
14
  import { createConfigFeature } from "../../config";
14
15
  import { createConfigResolver } from "../../config/resolver";
15
16
  import { configValuesTable } from "../../config/table";
@@ -126,8 +127,8 @@ afterAll(async () => {
126
127
  });
127
128
 
128
129
  beforeEach(async () => {
129
- await stack.db.delete(userTable);
130
- await stack.db.delete(tenantMembershipsTable);
130
+ await asRawClient(stack.db).unsafe(`DELETE FROM "${userTable.tableName}"`);
131
+ await asRawClient(stack.db).unsafe(`DELETE FROM "${tenantMembershipsTable.tableName}"`);
131
132
  // Reset the in-memory store but KEEP sidStream running — otherwise a test
132
133
  // that leaks a sid into another test would produce confusing collisions.
133
134
  store.live.clear();
@@ -235,7 +236,7 @@ describe("logout routes through sessionRevoker", () => {
235
236
  expect(logoutRes.status).toBe(200);
236
237
 
237
238
  // Revoker was called with exactly the sid from the caller's JWT
238
- expect(store.revoked).toEqual([sidBefore]);
239
+ expect(store.revoked).toEqual([sidBefore!]);
239
240
  expect(store.live.has(sidBefore ?? "")).toBe(false);
240
241
  });
241
242
 
@@ -279,7 +280,7 @@ describe("switch-tenant rotates the session", () => {
279
280
  expect(sidB).not.toBe(sidA);
280
281
 
281
282
  // Old sid is revoked; new sid is live
282
- expect(store.revoked).toContain(sidA);
283
+ expect(store.revoked).toContain(sidA!);
283
284
  expect(store.live.has(sidA ?? "")).toBe(false);
284
285
  expect(store.live.has(sidB ?? "")).toBe(true);
285
286
 
@@ -4,6 +4,7 @@
4
4
  // so legacy stateless tokens are expected to have expired. Default false
5
5
  // keeps pre-upgrade tokens working; this suite flips it on and asserts.
6
6
 
7
+ import { afterAll, beforeAll, describe, expect, test } from "bun:test";
7
8
  import type { TenantId } from "@cosmicdrift/kumiko-framework/engine";
8
9
  import {
9
10
  setupTestStack,
@@ -11,7 +12,6 @@ import {
11
12
  TestUsers,
12
13
  testTenantId,
13
14
  } from "@cosmicdrift/kumiko-framework/stack";
14
- import { afterAll, beforeAll, describe, expect, test } from "vitest";
15
15
  import { createConfigFeature } from "../../config";
16
16
  import { createTenantFeature } from "../../tenant";
17
17
  import { createUserFeature } from "../../user";
@@ -1,5 +1,5 @@
1
+ import { describe, expect, test } from "bun:test";
1
2
  import { Temporal } from "temporal-polyfill";
2
- import { describe, expect, test } from "vitest";
3
3
  import { signToken, TokenPurpose, verifyToken } from "../signed-token";
4
4
 
5
5
  const SECRET = "test-hmac-secret-32-bytes-minimum!!";
@@ -24,14 +24,14 @@
24
24
  // via DB-unique-index + generateUniqueName-isAvailable-check
25
25
  // zusammen).
26
26
 
27
+ import { afterAll, beforeAll, beforeEach, describe, expect, test } from "bun:test";
28
+ import { asRawClient, selectMany } from "@cosmicdrift/kumiko-framework/bun-db";
27
29
  import {
28
30
  setupTestStack,
29
31
  type TestStack,
30
32
  unsafeCreateEntityTable,
31
33
  unsafePushTables,
32
34
  } from "@cosmicdrift/kumiko-framework/stack";
33
- import { eq } from "drizzle-orm";
34
- import { afterAll, beforeAll, beforeEach, describe, expect, test } from "vitest";
35
35
  import { createConfigFeature } from "../../config";
36
36
  import { createConfigResolver } from "../../config/resolver";
37
37
  import { configValuesTable } from "../../config/table";
@@ -80,7 +80,7 @@ beforeAll(async () => {
80
80
  await unsafeCreateEntityTable(stack.db, userEntity);
81
81
  // tenant-entity hat den unique-constraint auf .key (siehe
82
82
  // tenant.schema.indexes). unsafeCreateEntityTable baut das via
83
- // buildDrizzleTable nach — pinst den TOCTOU-Schutz für signup-confirm.
83
+ // buildEntityTable nach — pinst den TOCTOU-Schutz für signup-confirm.
84
84
  await unsafeCreateEntityTable(stack.db, tenantEntity);
85
85
  await unsafePushTables(stack.db, { configValuesTable, tenantMembershipsTable });
86
86
  });
@@ -90,9 +90,9 @@ afterAll(async () => {
90
90
  });
91
91
 
92
92
  beforeEach(async () => {
93
- await stack.db.delete(userTable);
94
- await stack.db.delete(tenantMembershipsTable);
95
- await stack.db.delete(tenantTable);
93
+ await asRawClient(stack.db).unsafe(`DELETE FROM "${userTable.tableName}"`);
94
+ await asRawClient(stack.db).unsafe(`DELETE FROM "${tenantMembershipsTable.tableName}"`);
95
+ await asRawClient(stack.db).unsafe(`DELETE FROM "${tenantTable.tableName}"`);
96
96
  capturedActivationEmails.length = 0;
97
97
  // Redis-cleanup damit Resend-Tests keine state-leaks haben.
98
98
  const allKeys = await stack.redis.redis.keys("signup:*");
@@ -185,22 +185,18 @@ describe("POST /api/auth/signup-confirm", () => {
185
185
  expect(setCookies).toContain("kumiko_csrf=");
186
186
 
187
187
  // DB-State pinst
188
- const userRows = await stack.db.select().from(userTable).where(eq(userTable.email, email));
188
+ const userRows = await selectMany(stack.db, userTable, { email: email });
189
189
  expect(userRows).toHaveLength(1);
190
190
  expect(userRows[0]?.["emailVerified"]).toBe(true);
191
191
  expect(userRows[0]?.["passwordHash"]).toBeTruthy();
192
192
 
193
- const tenantRows = await stack.db
194
- .select()
195
- .from(tenantTable)
196
- .where(eq(tenantTable.id, body.user?.tenantId ?? ""));
193
+ const tenantRows = await selectMany(stack.db, tenantTable, { id: body.user?.tenantId ?? "" });
197
194
  expect(tenantRows).toHaveLength(1);
198
195
  expect(tenantRows[0]?.["key"]).toBe(body.tenantKey);
199
196
 
200
- const memberships = await stack.db
201
- .select()
202
- .from(tenantMembershipsTable)
203
- .where(eq(tenantMembershipsTable.userId, body.user?.id ?? ""));
197
+ const memberships = await selectMany(stack.db, tenantMembershipsTable, {
198
+ userId: body.user?.id ?? "",
199
+ });
204
200
  expect(memberships).toHaveLength(1);
205
201
  const rolesRaw = memberships[0]?.["roles"];
206
202
  if (typeof rolesRaw === "string") {
@@ -17,12 +17,8 @@
17
17
  // User entsteht — beide existieren bereits. Magic ist die kombinierte
18
18
  // Login+Accept-Operation in einem Roundtrip.
19
19
 
20
- import {
21
- createEventStoreExecutor,
22
- createTenantDb,
23
- type DbConnection,
24
- fetchOne,
25
- } from "@cosmicdrift/kumiko-framework/db";
20
+ import { fetchOne } from "@cosmicdrift/kumiko-framework/bun-db";
21
+ import { createEventStoreExecutor, createTenantDb } from "@cosmicdrift/kumiko-framework/db";
26
22
  import {
27
23
  createSystemUser,
28
24
  defineWriteHandler,
@@ -34,7 +30,6 @@ import {
34
30
  UnprocessableError,
35
31
  writeFailure,
36
32
  } from "@cosmicdrift/kumiko-framework/errors";
37
- import { eq } from "drizzle-orm";
38
33
  import { z } from "zod";
39
34
  // kumiko-lint-ignore cross-feature-import invite-flow
40
35
  import {
@@ -104,20 +99,27 @@ export function createInviteAcceptWithLoginHandler() {
104
99
  const burn = await burnInviteToken(ctx.redis, event.payload.token);
105
100
  if (burn === "already-used") return invalidInviteToken();
106
101
 
102
+ type InvitationRow = {
103
+ readonly status: string;
104
+ readonly tenantId: TenantId;
105
+ readonly email: string;
106
+ readonly role: string;
107
+ readonly version: number;
108
+ };
109
+ type UserAuthRow = { readonly id: string; readonly passwordHash: string | null };
110
+
107
111
  let committed = false;
108
112
  try {
109
- const invitation = await fetchOne(
110
- ctx.db.raw,
111
- tenantInvitationsTable,
112
- eq(tenantInvitationsTable.id, invitationId),
113
- );
114
- if (!invitation || invitation["status"] !== INVITATION_STATUS.pending)
113
+ const invitation = await fetchOne<InvitationRow>(ctx.db.raw, tenantInvitationsTable, {
114
+ id: invitationId,
115
+ });
116
+ if (!invitation || invitation.status !== INVITATION_STATUS.pending)
115
117
  return invalidInviteToken();
116
118
 
117
- const invitationTenantId = invitation["tenantId"] as TenantId; // @cast-boundary db-row
118
- const invitationEmail = invitation["email"] as string; // @cast-boundary db-row
119
- const invitationRole = invitation["role"] as string; // @cast-boundary db-row
120
- const invitationVersion = invitation["version"] as number; // @cast-boundary db-row
119
+ const invitationTenantId = invitation.tenantId;
120
+ const invitationEmail = invitation.email;
121
+ const invitationRole = invitation.role;
122
+ const invitationVersion = invitation.version;
121
123
 
122
124
  // Email-Match vom User-Input (nicht aus session — User ist anon)
123
125
  if (event.payload.email.toLowerCase() !== invitationEmail) {
@@ -131,15 +133,14 @@ export function createInviteAcceptWithLoginHandler() {
131
133
  // Password-Check gegen userTable. Anti-enumeration: bei
132
134
  // user-not-found ODER wrong-password collapsed beides auf
133
135
  // invalidInviteToken (gleicher anti-enum-Trade-off wie reset).
134
- const userRow = await fetchOne(ctx.db.raw, userTable, eq(userTable.email, invitationEmail));
135
- if (!userRow?.["passwordHash"]) return invalidInviteToken();
136
- const passwordValid = await verifyPassword(
137
- userRow["passwordHash"] as string, // @cast-boundary db-row
138
- event.payload.password,
139
- );
136
+ const userRow = await fetchOne<UserAuthRow>(ctx.db.raw, userTable, {
137
+ email: invitationEmail,
138
+ });
139
+ if (!userRow?.passwordHash) return invalidInviteToken();
140
+ const passwordValid = await verifyPassword(userRow.passwordHash, event.payload.password);
140
141
  if (!passwordValid) return invalidInviteToken();
141
142
 
142
- const userId = userRow["id"] as string; // @cast-boundary db-row
143
+ const userId = userRow.id;
143
144
 
144
145
  // Already-Member-Check (idempotent)
145
146
  const memberships = (await ctx.queryAs(
@@ -149,8 +150,7 @@ export function createInviteAcceptWithLoginHandler() {
149
150
  )) as Array<{ tenantId: string }>; // @cast-boundary db-row
150
151
  const alreadyMember = memberships.some((m) => m.tenantId === invitationTenantId);
151
152
 
152
- // @cast-boundary db-runner — TenantDb.raw is DbRunner
153
- const dbConn = ctx.db.raw as DbConnection;
153
+ const dbConn = ctx.db.raw;
154
154
 
155
155
  if (!alreadyMember) {
156
156
  await seedTenantMembership(dbConn, {
@@ -15,12 +15,8 @@
15
15
  // Session"-Flow. Branch 2 (anon + existing email) und Branch 3 (anon +
16
16
  // new email) kommen als separate Handler.
17
17
 
18
- import {
19
- createEventStoreExecutor,
20
- createTenantDb,
21
- type DbConnection,
22
- fetchOne,
23
- } from "@cosmicdrift/kumiko-framework/db";
18
+ import { fetchOne } from "@cosmicdrift/kumiko-framework/bun-db";
19
+ import { createEventStoreExecutor, createTenantDb } from "@cosmicdrift/kumiko-framework/db";
24
20
  import {
25
21
  createSystemUser,
26
22
  defineWriteHandler,
@@ -31,7 +27,6 @@ import {
31
27
  UnprocessableError,
32
28
  writeFailure,
33
29
  } from "@cosmicdrift/kumiko-framework/errors";
34
- import { eq } from "drizzle-orm";
35
30
  import { z } from "zod";
36
31
  // kumiko-lint-ignore cross-feature-import invite-flow lebt in auth-email-password (Magic-Link), DB-row-owner ist tenant-feature
37
32
  import {
@@ -97,26 +92,35 @@ export function createInviteAcceptHandler() {
97
92
  const burn = await burnInviteToken(ctx.redis, event.payload.token);
98
93
  if (burn === "already-used") return invalidInviteToken();
99
94
 
95
+ type InvitationRow = {
96
+ readonly status: string;
97
+ readonly tenantId: TenantId;
98
+ readonly email: string;
99
+ readonly role: string;
100
+ readonly version: number;
101
+ };
102
+ type UserEmailRow = { readonly email: string };
103
+
100
104
  let committed = false;
101
105
  try {
102
- const invitation = await fetchOne(
103
- ctx.db.raw,
104
- tenantInvitationsTable,
105
- eq(tenantInvitationsTable.id, invitationId),
106
- );
107
- if (!invitation || invitation["status"] !== INVITATION_STATUS.pending)
106
+ const invitation = await fetchOne<InvitationRow>(ctx.db.raw, tenantInvitationsTable, {
107
+ id: invitationId,
108
+ });
109
+ if (!invitation || invitation.status !== INVITATION_STATUS.pending)
108
110
  return invalidInviteToken();
109
111
 
110
- const invitationTenantId = invitation["tenantId"] as TenantId; // @cast-boundary db-row
111
- const invitationEmail = invitation["email"] as string; // @cast-boundary db-row
112
- const invitationRole = invitation["role"] as string; // @cast-boundary db-row
113
- const invitationVersion = invitation["version"] as number; // @cast-boundary db-row
112
+ const invitationTenantId = invitation.tenantId;
113
+ const invitationEmail = invitation.email;
114
+ const invitationRole = invitation.role;
115
+ const invitationVersion = invitation.version;
114
116
 
115
117
  // Email-Match: User muss mit der eingeladenen Email matchen.
116
118
  // Sonst kann ein Angreifer mit Zugriff zur invitee-Mail seinen
117
119
  // eigenen Account dem Tenant zuschlagen.
118
- const userRow = await fetchOne(ctx.db.raw, userTable, eq(userTable.id, event.user.id));
119
- const userEmail = userRow?.["email"] as string | undefined; // @cast-boundary db-row
120
+ const userRow = await fetchOne<UserEmailRow>(ctx.db.raw, userTable, {
121
+ id: event.user.id,
122
+ });
123
+ const userEmail = userRow?.email;
120
124
  if (!userRow || !userEmail || userEmail.toLowerCase() !== invitationEmail) {
121
125
  return writeFailure(
122
126
  new UnprocessableError(AuthErrors.inviteEmailMismatch, {
@@ -135,8 +139,7 @@ export function createInviteAcceptHandler() {
135
139
  )) as Array<{ tenantId: string }>; // @cast-boundary db-row
136
140
  const alreadyMember = memberships.some((m) => m.tenantId === invitationTenantId);
137
141
 
138
- // @cast-boundary db-runner — TenantDb.raw is DbRunner
139
- const dbConn = ctx.db.raw as DbConnection;
142
+ const dbConn = ctx.db.raw;
140
143
 
141
144
  if (!alreadyMember) {
142
145
  // Membership-Add via seedTenantMembership-helper (event-store-
@@ -15,10 +15,10 @@
15
15
  // invite-create.
16
16
 
17
17
  import { generateToken } from "@cosmicdrift/kumiko-framework/api";
18
- import { createEventStoreExecutor, fetchOne } from "@cosmicdrift/kumiko-framework/db";
18
+ import { fetchOne } from "@cosmicdrift/kumiko-framework/bun-db";
19
+ import { createEventStoreExecutor } from "@cosmicdrift/kumiko-framework/db";
19
20
  import { defineWriteHandler } from "@cosmicdrift/kumiko-framework/engine";
20
21
  import { InternalError, writeFailure } from "@cosmicdrift/kumiko-framework/errors";
21
- import { eq } from "drizzle-orm";
22
22
  import { Temporal } from "temporal-polyfill";
23
23
  import { z } from "zod";
24
24
  // kumiko-lint-ignore cross-feature-import invite-flow lebt in auth-email-password (Magic-Link-Pattern), DB-row-owner ist tenant-feature
@@ -77,12 +77,7 @@ export function createInviteCreateHandler(opts: InviteCreateOptions = {}) {
77
77
  // max. eine Row. Status egal (cancelled/accepted/expired/pending);
78
78
  // wir setzen sie auf pending zurück und vergeben einen frischen
79
79
  // Token wenn der bisherige nicht mehr lebt.
80
- const existing = await fetchOne(
81
- ctx.db.raw,
82
- tenantInvitationsTable,
83
- eq(tenantInvitationsTable.tenantId, tenantId),
84
- eq(tenantInvitationsTable.email, email),
85
- );
80
+ const existing = await fetchOne(ctx.db.raw, tenantInvitationsTable, { tenantId, email });
86
81
 
87
82
  let invitationId: string;
88
83
  let token: string;
@@ -17,11 +17,11 @@
17
17
  // e. Invitation → accepted, Token gelöscht
18
18
  // 5. Response: SessionUser + tenantId für Auto-Login
19
19
 
20
+ import { fetchOne } from "@cosmicdrift/kumiko-framework/bun-db";
20
21
  import {
21
22
  createEventStoreExecutor,
22
23
  createTenantDb,
23
24
  type DbConnection,
24
- fetchOne,
25
25
  } from "@cosmicdrift/kumiko-framework/db";
26
26
  import {
27
27
  createSystemUser,
@@ -34,7 +34,6 @@ import {
34
34
  UnprocessableError,
35
35
  writeFailure,
36
36
  } from "@cosmicdrift/kumiko-framework/errors";
37
- import { eq } from "drizzle-orm";
38
37
  import { z } from "zod";
39
38
  // kumiko-lint-ignore cross-feature-import invite-flow
40
39
  import {
@@ -104,30 +103,34 @@ export function createInviteSignupCompleteHandler() {
104
103
  const burn = await burnInviteToken(ctx.redis, event.payload.token);
105
104
  if (burn === "already-used") return invalidInviteToken();
106
105
 
106
+ type InvitationRow = {
107
+ readonly status: string;
108
+ readonly tenantId: TenantId;
109
+ readonly email: string;
110
+ readonly role: string;
111
+ readonly version: number;
112
+ };
113
+
107
114
  let committed = false;
108
115
  try {
109
- const invitation = await fetchOne(
110
- ctx.db.raw,
111
- tenantInvitationsTable,
112
- eq(tenantInvitationsTable.id, invitationId),
113
- );
114
- if (!invitation || invitation["status"] !== INVITATION_STATUS.pending)
116
+ const invitation = await fetchOne<InvitationRow>(ctx.db.raw, tenantInvitationsTable, {
117
+ id: invitationId,
118
+ });
119
+ if (!invitation || invitation.status !== INVITATION_STATUS.pending)
115
120
  return invalidInviteToken();
116
121
 
117
- const invitationTenantId = invitation["tenantId"] as TenantId; // @cast-boundary db-row
118
- const invitationEmail = invitation["email"] as string; // @cast-boundary db-row
119
- const invitationRole = invitation["role"] as string; // @cast-boundary db-row
120
- const invitationVersion = invitation["version"] as number; // @cast-boundary db-row
122
+ const invitationTenantId = invitation.tenantId;
123
+ const invitationEmail = invitation.email;
124
+ const invitationRole = invitation.role;
125
+ const invitationVersion = invitation.version;
121
126
 
122
127
  // User-Not-Exists-Check: wenn die Email schon registriert ist,
123
128
  // muss der User Branch 2 (acceptWithLogin) nutzen. Hier ist
124
129
  // explizit "neue Email" — sonst hätten wir zwei Wege ein
125
130
  // Password zu setzen für denselben User.
126
- const existingUser = await fetchOne(
127
- ctx.db.raw,
128
- userTable,
129
- eq(userTable.email, invitationEmail),
130
- );
131
+ const existingUser = await fetchOne(ctx.db.raw, userTable, {
132
+ email: invitationEmail,
133
+ });
131
134
  if (existingUser) return invalidInviteToken();
132
135
 
133
136
  // User anlegen via seedUserWithPassword (gleiches Pattern wie