@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
@@ -19,6 +19,7 @@
19
19
  // bei !committed wird der burn released damit ein legitimer Retry
20
20
  // nicht durch einen stale Marker geblockt wird (wie reset/verify).
21
21
 
22
+ import { fetchOne } from "@cosmicdrift/kumiko-framework/bun-db";
22
23
  import type { DbConnection } from "@cosmicdrift/kumiko-framework/db";
23
24
  import {
24
25
  defineWriteHandler,
@@ -32,7 +33,6 @@ import {
32
33
  } from "@cosmicdrift/kumiko-framework/errors";
33
34
  import { generateUniqueName } from "@cosmicdrift/kumiko-framework/random";
34
35
  import { generateId } from "@cosmicdrift/kumiko-framework/utils";
35
- import { eq } from "drizzle-orm";
36
36
  import { z } from "zod";
37
37
  // kumiko-lint-ignore cross-feature-import signup-confirm reads tenants.key for slug-uniqueness check (TOCTOU + DB-unique-index zusammen)
38
38
  import { tenantTable } from "../../tenant/schema/tenant";
@@ -108,12 +108,8 @@ export function createSignupConfirmHandler() {
108
108
 
109
109
  const tenantKey = await generateUniqueName({
110
110
  isAvailable: async (slug) => {
111
- const existing = await dbConn
112
- .select({ id: tenantTable.id })
113
- .from(tenantTable)
114
- .where(eq(tenantTable.key, slug))
115
- .limit(1);
116
- return existing.length === 0;
111
+ const existing = await fetchOne<{ id: string }>(dbConn, tenantTable, { key: slug });
112
+ return existing === undefined;
117
113
  },
118
114
  });
119
115
 
@@ -78,7 +78,7 @@ export async function seedUserWithPassword(
78
78
  * innerhalb dieses Sprints, weil alle existing tests berührt würden.
79
79
  *
80
80
  * Atomicity: läuft inside einer Drizzle-Tx wenn der Caller das angibt
81
- * (db.transaction(tx => provisionSignupAccount(tx, ...)) — die seed-
81
+ * (db.begin(tx => provisionSignupAccount(tx, ...)) — die seed-
82
82
  * helpers nehmen DbConnection|DbTx strukturell. Bei pure DbConnection
83
83
  * sind die 3 writes nicht atomic; bei Failure zwischen Schritten kann
84
84
  * ein orphan-Tenant zurückbleiben (Tenant ohne User → unused row;
@@ -1,7 +1,6 @@
1
- // @vitest-environment jsdom
1
+ import { describe, expect, test } from "bun:test";
2
2
  import { screen } from "@testing-library/react";
3
3
  import type { ReactNode } from "react";
4
- import { describe, expect, test } from "vitest";
5
4
  import { makeAuthGate } from "../auth-gate";
6
5
  import { makeSessionApi, renderWithProviders } from "./test-utils";
7
6
 
@@ -1,19 +1,14 @@
1
- // @vitest-environment jsdom
1
+ import { afterEach, beforeEach, describe, expect, mock, test } from "bun:test";
2
2
  import { fireEvent, screen, waitFor } from "@testing-library/react";
3
- import { afterEach, beforeEach, describe, expect, test, vi } from "vitest";
4
3
  import { ForgotPasswordScreen } from "../forgot-password-screen";
5
4
  import { renderWithProviders } from "./test-utils";
6
5
 
7
6
  beforeEach(() => {
8
- // global fetch wird von auth-client.ts gerufen — wir mocken pro Test.
9
- vi.stubGlobal(
10
- "fetch",
11
- vi.fn(async () => new Response(null, { status: 200 })),
12
- );
13
- });
14
- afterEach(() => {
15
- vi.unstubAllGlobals();
7
+ globalThis.fetch = mock(
8
+ async () => new Response(null, { status: 200 }),
9
+ ) as unknown as typeof fetch;
16
10
  });
11
+ afterEach(() => {});
17
12
 
18
13
  describe("ForgotPasswordScreen", () => {
19
14
  test("rendert title + email-input + submit-button (de)", () => {
@@ -24,8 +19,8 @@ describe("ForgotPasswordScreen", () => {
24
19
  });
25
20
 
26
21
  test("submit ruft /api/auth/request-password-reset mit der Email", async () => {
27
- const fetchMock = vi.fn(async () => new Response(null, { status: 200 }));
28
- vi.stubGlobal("fetch", fetchMock);
22
+ const fetchMock = mock(async () => new Response(null, { status: 200 }));
23
+ globalThis.fetch = fetchMock as unknown as typeof fetch;
29
24
 
30
25
  renderWithProviders(<ForgotPasswordScreen />);
31
26
  fireEvent.change(screen.getByLabelText(/^E-Mail/), {
@@ -54,15 +49,13 @@ describe("ForgotPasswordScreen", () => {
54
49
  await waitFor(() => {
55
50
  expect(screen.getByText("Mail gesendet")).toBeTruthy();
56
51
  });
57
- // Link zurück zum Login muss im Success-State da sein.
58
52
  expect(screen.getByRole("link", { name: /Zurück zum Login/i })).toBeTruthy();
59
53
  });
60
54
 
61
55
  test("server 5xx → error-banner statt Success-State", async () => {
62
- vi.stubGlobal(
63
- "fetch",
64
- vi.fn(async () => new Response(null, { status: 500 })),
65
- );
56
+ globalThis.fetch = mock(
57
+ async () => new Response(null, { status: 500 }),
58
+ ) as unknown as typeof fetch;
66
59
 
67
60
  renderWithProviders(<ForgotPasswordScreen />);
68
61
  fireEvent.change(screen.getByLabelText(/^E-Mail/), {
@@ -71,10 +64,8 @@ describe("ForgotPasswordScreen", () => {
71
64
  fireEvent.click(screen.getByRole("button", { name: "Link anfordern" }));
72
65
 
73
66
  await waitFor(() => {
74
- // unknownError-Bundle-key → "Etwas ist schief gegangen..."
75
67
  expect(screen.getByRole("alert").textContent).toContain("schief");
76
68
  });
77
- // Kein success-state.
78
69
  expect(screen.queryByText("Mail gesendet")).toBeNull();
79
70
  });
80
71
  });
@@ -1,22 +1,16 @@
1
- // @vitest-environment jsdom
1
+ import { beforeEach, describe, expect, mock, test } from "bun:test";
2
2
  import { fireEvent, screen, waitFor } from "@testing-library/react";
3
- import { beforeEach, describe, expect, test, vi } from "vitest";
4
-
5
- // Mock-Bridge für den resend-Flow: LoginScreen ruft requestEmailVerification
6
- // aus ../auth-client auf, wir stubben das hier um die Server-Antwort pro
7
- // Test-Case zu kontrollieren. vi.hoisted weil vi.mock() vor allen anderen
8
- // Statements gehoisted wird und sonst die Variable nicht sehen kann.
9
- const { requestEmailVerificationMock } = vi.hoisted(() => ({
10
- requestEmailVerificationMock: vi.fn(),
11
- }));
12
- vi.mock("../auth-client", async () => {
13
- const actual = await vi.importActual<typeof import("../auth-client")>("../auth-client");
14
- return { ...actual, requestEmailVerification: requestEmailVerificationMock };
15
- });
16
3
 
17
4
  import { LoginScreen } from "../login-screen";
18
5
  import { makeSessionApi, renderWithProviders } from "./test-utils";
19
6
 
7
+ const requestEmailVerificationMock = mock<() => Promise<unknown>>(() => Promise.resolve());
8
+ const actual_authClient = await import("../auth-client");
9
+ mock.module("../auth-client", () => ({
10
+ ...actual_authClient,
11
+ requestEmailVerification: requestEmailVerificationMock,
12
+ }));
13
+
20
14
  describe("LoginScreen", () => {
21
15
  beforeEach(() => {
22
16
  requestEmailVerificationMock.mockReset();
@@ -54,7 +48,7 @@ describe("LoginScreen", () => {
54
48
  const session = makeSessionApi({
55
49
  status: "unauthenticated",
56
50
  user: null,
57
- login: vi.fn(async () => ({ ok: false, error: { reason: "invalid_credentials" } })),
51
+ login: mock(async () => ({ ok: false, error: { reason: "invalid_credentials" } })),
58
52
  });
59
53
  renderWithProviders(<LoginScreen />, { session });
60
54
 
@@ -76,7 +70,7 @@ describe("LoginScreen", () => {
76
70
  const session = makeSessionApi({
77
71
  status: "unauthenticated",
78
72
  user: null,
79
- login: vi.fn(async () => ({
73
+ login: mock(async () => ({
80
74
  ok: false,
81
75
  error: { reason: "account_locked", retryAfterSeconds: 540 },
82
76
  })),
@@ -129,7 +123,7 @@ describe("LoginScreen", () => {
129
123
  return makeSessionApi({
130
124
  status: "unauthenticated",
131
125
  user: null,
132
- login: vi.fn(async () => ({ ok: false, error: { reason: "email_not_verified" } })),
126
+ login: mock(async () => ({ ok: false, error: { reason: "email_not_verified" } })),
133
127
  });
134
128
  }
135
129
 
@@ -211,7 +205,7 @@ describe("LoginScreen", () => {
211
205
  const session = makeSessionApi({
212
206
  status: "unauthenticated",
213
207
  user: null,
214
- login: vi.fn(async () => ({ ok: false, error: { reason: "invalid_credentials" } })),
208
+ login: mock(async () => ({ ok: false, error: { reason: "invalid_credentials" } })),
215
209
  });
216
210
  renderWithProviders(<LoginScreen />, { session });
217
211
  await loginUntilEmailNotVerified();
@@ -1,18 +1,14 @@
1
- // @vitest-environment jsdom
1
+ import { afterEach, beforeEach, describe, expect, mock, test } from "bun:test";
2
2
  import { fireEvent, screen, waitFor } from "@testing-library/react";
3
- import { afterEach, beforeEach, describe, expect, test, vi } from "vitest";
4
3
  import { ResetPasswordScreen } from "../reset-password-screen";
5
4
  import { renderWithProviders } from "./test-utils";
6
5
 
7
6
  beforeEach(() => {
8
- vi.stubGlobal(
9
- "fetch",
10
- vi.fn(async () => new Response(null, { status: 200 })),
11
- );
12
- });
13
- afterEach(() => {
14
- vi.unstubAllGlobals();
7
+ globalThis.fetch = mock(
8
+ async () => new Response(null, { status: 200 }),
9
+ ) as unknown as typeof fetch;
15
10
  });
11
+ afterEach(() => {});
16
12
 
17
13
  describe("ResetPasswordScreen", () => {
18
14
  test("ohne Token in URL UND ohne token-Prop → missing-token-Page", () => {
@@ -29,8 +25,8 @@ describe("ResetPasswordScreen", () => {
29
25
  });
30
26
 
31
27
  test("Passwort < 8 Zeichen → client-side error, kein fetch-Call", async () => {
32
- const fetchMock = vi.fn(async () => new Response(null, { status: 200 }));
33
- vi.stubGlobal("fetch", fetchMock);
28
+ const fetchMock = mock(async () => new Response(null, { status: 200 }));
29
+ globalThis.fetch = fetchMock as unknown as typeof fetch;
34
30
 
35
31
  renderWithProviders(<ResetPasswordScreen token="abc" />);
36
32
  fireEvent.change(screen.getByLabelText(/^Neues Passwort/), { target: { value: "short" } });
@@ -59,8 +55,8 @@ describe("ResetPasswordScreen", () => {
59
55
  });
60
56
 
61
57
  test("happy path: gültiges Passwort → fetch-Call + success-State", async () => {
62
- const fetchMock = vi.fn(async () => new Response(null, { status: 200 }));
63
- vi.stubGlobal("fetch", fetchMock);
58
+ const fetchMock = mock(async () => new Response(null, { status: 200 }));
59
+ globalThis.fetch = fetchMock as unknown as typeof fetch;
64
60
 
65
61
  renderWithProviders(<ResetPasswordScreen token="abc-token" />);
66
62
  fireEvent.change(screen.getByLabelText(/^Neues Passwort/), {
@@ -87,10 +83,9 @@ describe("ResetPasswordScreen", () => {
87
83
  const errBody = JSON.stringify({
88
84
  error: { code: "invalid_reset_token", details: { reason: "invalid_reset_token" } },
89
85
  });
90
- vi.stubGlobal(
91
- "fetch",
92
- vi.fn(async () => new Response(errBody, { status: 422 })),
93
- );
86
+ globalThis.fetch = mock(
87
+ async () => new Response(errBody, { status: 422 }),
88
+ ) as unknown as typeof fetch;
94
89
 
95
90
  renderWithProviders(<ResetPasswordScreen token="bad" />);
96
91
  fireEvent.change(screen.getByLabelText(/^Neues Passwort/), {
@@ -3,7 +3,7 @@
3
3
  // Sonst sieht client andere roles als server → role-gating divergiert
4
4
  // (entweder UI zeigt was nicht erlaubt ist, oder umgekehrt).
5
5
 
6
- import { describe, expect, test } from "vitest";
6
+ import { describe, expect, test } from "bun:test";
7
7
  import type { CurrentUserProfile, TenantSummary } from "../auth-client";
8
8
  import { computeActiveRoles } from "../session";
9
9
 
@@ -1,20 +1,17 @@
1
- // @vitest-environment jsdom
1
+ import { describe, expect, test } from "bun:test";
2
2
  import { screen } from "@testing-library/react";
3
3
  import userEvent from "@testing-library/user-event";
4
- import { describe, expect, test } from "vitest";
5
4
  import { TenantSwitcher } from "../tenant-switcher";
6
5
  import { makeSessionApi, renderWithProviders } from "./test-utils";
7
6
 
8
7
  // Radix-DropdownMenu reagiert auf pointerdown, nicht auf click — daher
9
8
  // userEvent statt fireEvent.
10
-
11
9
  describe("TenantSwitcher", () => {
12
10
  test("renders nothing when user is null", () => {
13
11
  const session = makeSessionApi({ status: "unauthenticated", user: null });
14
12
  const { container } = renderWithProviders(<TenantSwitcher />, { session });
15
13
  expect(container.firstChild).toBeNull();
16
14
  });
17
-
18
15
  test("renders nothing when user has only one tenant", () => {
19
16
  const session = makeSessionApi({
20
17
  tenants: [{ tenantId: "t1", roles: ["Admin"] }],
@@ -22,7 +19,6 @@ describe("TenantSwitcher", () => {
22
19
  const { container } = renderWithProviders(<TenantSwitcher />, { session });
23
20
  expect(container.firstChild).toBeNull();
24
21
  });
25
-
26
22
  test("renders trigger when user has multiple tenants", () => {
27
23
  const session = makeSessionApi({
28
24
  activeTenantId: "tenant-a",
@@ -35,7 +31,6 @@ describe("TenantSwitcher", () => {
35
31
  // tenantName-Resolver liefert "Tenant tenant-a" als Trigger-Label
36
32
  expect(screen.getByText("Tenant tenant-a")).toBeTruthy();
37
33
  });
38
-
39
34
  test("opens dropdown showing all memberships with roles", async () => {
40
35
  const user = userEvent.setup();
41
36
  const session = makeSessionApi({
@@ -57,7 +52,6 @@ describe("TenantSwitcher", () => {
57
52
  expect(screen.getByText("Admin")).toBeTruthy();
58
53
  expect(screen.getByText("User, Billing")).toBeTruthy();
59
54
  });
60
-
61
55
  test("clicking a tenant triggers switchTenant", async () => {
62
56
  const user = userEvent.setup();
63
57
  const session = makeSessionApi({
@@ -74,7 +68,6 @@ describe("TenantSwitcher", () => {
74
68
  await user.click(screen.getByText("Tenant tenant-b"));
75
69
  expect(session.switchTenant).toHaveBeenCalledWith("tenant-b");
76
70
  });
77
-
78
71
  test("clicking the active tenant is a no-op (closes menu, no switch call)", async () => {
79
72
  const user = userEvent.setup();
80
73
  const session = makeSessionApi({
@@ -1,9 +1,9 @@
1
- // @vitest-environment jsdom
2
1
  //
3
2
  // Shared test setup für die Web-UI-Components. Mountet das Minimum
4
3
  // an Provider-Tree den die Components zur Laufzeit voraussetzen
5
4
  // (LocaleProvider mit Bundle, SessionContext mit injizierbarem Wert).
6
5
 
6
+ import { mock } from "bun:test";
7
7
  import type { LocaleResolver } from "@cosmicdrift/kumiko-headless";
8
8
  import {
9
9
  createStaticLocaleResolver,
@@ -13,7 +13,6 @@ import {
13
13
  import { defaultPrimitives } from "@cosmicdrift/kumiko-renderer-web";
14
14
  import { render as _render, type RenderResult } from "@testing-library/react";
15
15
  import type { ReactElement } from "react";
16
- import { vi } from "vitest";
17
16
  import { defaultTranslations } from "../../i18n";
18
17
  import type { SessionApi, SessionState } from "../session";
19
18
  import { SessionContext } from "../session";
@@ -23,13 +22,11 @@ import { SessionContext } from "../session";
23
22
  // einen *anderen* Locale brauchen, übergeben ihren eigenen Resolver
24
23
  // über options.resolver.
25
24
  const sharedDeResolver = createStaticLocaleResolver({ locale: "de" });
26
-
27
25
  export type MakeSessionApiOptions = Partial<SessionState> & {
28
26
  readonly login?: SessionApi["login"];
29
27
  readonly logout?: SessionApi["logout"];
30
28
  readonly switchTenant?: SessionApi["switchTenant"];
31
29
  };
32
-
33
30
  export function makeSessionApi(overrides: MakeSessionApiOptions = {}): SessionApi {
34
31
  const { login, logout, switchTenant, ...stateOverrides } = overrides;
35
32
  const base: SessionState = {
@@ -47,12 +44,11 @@ export function makeSessionApi(overrides: MakeSessionApiOptions = {}): SessionAp
47
44
  };
48
45
  return {
49
46
  ...base,
50
- login: login ?? vi.fn<SessionApi["login"]>(async () => ({ ok: true })),
51
- logout: logout ?? vi.fn<SessionApi["logout"]>(async () => {}),
52
- switchTenant: switchTenant ?? vi.fn<SessionApi["switchTenant"]>(async () => {}),
47
+ login: login ?? mock<SessionApi["login"]>(async () => ({ ok: true })),
48
+ logout: logout ?? mock<SessionApi["logout"]>(async () => {}),
49
+ switchTenant: switchTenant ?? mock<SessionApi["switchTenant"]>(async () => {}),
53
50
  };
54
51
  }
55
-
56
52
  export function renderWithProviders(
57
53
  ui: ReactElement,
58
54
  options: {
@@ -1,21 +1,18 @@
1
- // @vitest-environment jsdom
1
+ import { describe, expect, test } from "bun:test";
2
2
  import { screen } from "@testing-library/react";
3
3
  import userEvent from "@testing-library/user-event";
4
- import { describe, expect, test } from "vitest";
5
4
  import { UserMenu } from "../user-menu";
6
5
  import { makeSessionApi, renderWithProviders } from "./test-utils";
7
6
 
8
7
  // Radix-DropdownMenu reagiert auf pointerdown — fireEvent.click greift
9
8
  // dort nicht. userEvent simuliert die volle Pointer-Sequenz und Radix
10
9
  // öffnet sauber.
11
-
12
10
  describe("UserMenu", () => {
13
11
  test("renders nothing when user is null", () => {
14
12
  const session = makeSessionApi({ status: "unauthenticated", user: null });
15
13
  const { container } = renderWithProviders(<UserMenu />, { session });
16
14
  expect(container.firstChild).toBeNull();
17
15
  });
18
-
19
16
  test("shows displayName + initials when authenticated", () => {
20
17
  const session = makeSessionApi({
21
18
  user: { id: "u1", email: "alice@example.com", displayName: "Alice Wonder", globalRoles: [] },
@@ -25,7 +22,6 @@ describe("UserMenu", () => {
25
22
  expect(screen.getByText("AW")).toBeTruthy();
26
23
  expect(screen.getByText("Alice Wonder")).toBeTruthy();
27
24
  });
28
-
29
25
  test("falls back to email-based initials when displayName empty", () => {
30
26
  const session = makeSessionApi({
31
27
  user: { id: "u1", email: "bob@example.com", displayName: "", globalRoles: [] },
@@ -34,7 +30,6 @@ describe("UserMenu", () => {
34
30
  // Trim "" → leerer displayName → fallback auf email → erste 2 Chars
35
31
  expect(screen.getByText("BO")).toBeTruthy();
36
32
  });
37
-
38
33
  test("opens dropdown on click and shows logout button", async () => {
39
34
  const user = userEvent.setup();
40
35
  const session = makeSessionApi();
@@ -43,13 +38,12 @@ describe("UserMenu", () => {
43
38
  expect(screen.getByText("Abmelden")).toBeTruthy();
44
39
  expect(screen.getByText("user@example.com")).toBeTruthy();
45
40
  });
46
-
47
41
  test("logout-click triggers session.logout", async () => {
48
42
  const user = userEvent.setup();
49
43
  const session = makeSessionApi();
50
44
  renderWithProviders(<UserMenu />, { session });
51
45
  await user.click(screen.getByRole("button", { name: /Test User/ }));
52
46
  await user.click(screen.getByText("Abmelden"));
53
- expect(session.logout).toHaveBeenCalledOnce();
47
+ expect(session.logout).toHaveBeenCalledTimes(1);
54
48
  });
55
49
  });
@@ -1,18 +1,14 @@
1
- // @vitest-environment jsdom
1
+ import { afterEach, beforeEach, describe, expect, mock, test } from "bun:test";
2
2
  import { screen, waitFor } from "@testing-library/react";
3
- import { afterEach, beforeEach, describe, expect, test, vi } from "vitest";
4
3
  import { VerifyEmailScreen } from "../verify-email-screen";
5
4
  import { renderWithProviders } from "./test-utils";
6
5
 
7
6
  beforeEach(() => {
8
- vi.stubGlobal(
9
- "fetch",
10
- vi.fn(async () => new Response(null, { status: 200 })),
11
- );
12
- });
13
- afterEach(() => {
14
- vi.unstubAllGlobals();
7
+ globalThis.fetch = mock(
8
+ async () => new Response(null, { status: 200 }),
9
+ ) as unknown as typeof fetch;
15
10
  });
11
+ afterEach(() => {});
16
12
 
17
13
  describe("VerifyEmailScreen", () => {
18
14
  test("ohne Token → missing-token-Page", () => {
@@ -21,8 +17,8 @@ describe("VerifyEmailScreen", () => {
21
17
  });
22
18
 
23
19
  test("mit Token + 200 → success-state nach auto-submit", async () => {
24
- const fetchMock = vi.fn(async () => new Response(null, { status: 200 }));
25
- vi.stubGlobal("fetch", fetchMock);
20
+ const fetchMock = mock(async () => new Response(null, { status: 200 }));
21
+ globalThis.fetch = fetchMock as unknown as typeof fetch;
26
22
 
27
23
  renderWithProviders(<VerifyEmailScreen token="t-abc" />);
28
24
 
@@ -45,10 +41,9 @@ describe("VerifyEmailScreen", () => {
45
41
  details: { reason: "invalid_verification_token" },
46
42
  },
47
43
  });
48
- vi.stubGlobal(
49
- "fetch",
50
- vi.fn(async () => new Response(errBody, { status: 422 })),
51
- );
44
+ globalThis.fetch = mock(
45
+ async () => new Response(errBody, { status: 422 }),
46
+ ) as unknown as typeof fetch;
52
47
 
53
48
  renderWithProviders(<VerifyEmailScreen token="bad" />);
54
49
 
@@ -11,6 +11,7 @@
11
11
  // Webhook-Handler-Factory (createSubscriptionWebhookHandler) wird in
12
12
  // einem separaten Test mit Hono-mock geprüft.
13
13
 
14
+ import { afterAll, beforeAll, describe, expect, test } from "bun:test";
14
15
  import type { DbConnection } from "@cosmicdrift/kumiko-framework/db";
15
16
  import { defineFeature } from "@cosmicdrift/kumiko-framework/engine";
16
17
  import { createEventsTable, loadAggregate } from "@cosmicdrift/kumiko-framework/event-store";
@@ -20,7 +21,6 @@ import {
20
21
  type TestStack,
21
22
  testTenantId,
22
23
  } from "@cosmicdrift/kumiko-framework/stack";
23
- import { afterAll, beforeAll, describe, expect, test } from "vitest";
24
24
  import { subscriptionAggregateId } from "../aggregate-id";
25
25
  import {
26
26
  SubscriptionEventTypes,
@@ -1,6 +1,6 @@
1
1
  // feature.ts contract tests for subscription-foundation.
2
2
 
3
- import { describe, expect, test } from "vitest";
3
+ import { describe, expect, test } from "bun:test";
4
4
  import { subscriptionAggregateId } from "../aggregate-id";
5
5
  import {
6
6
  BILLING_FOUNDATION_FEATURE,
@@ -3,8 +3,8 @@
3
3
  // ohne setupTestStack, weil der webhook-handler nur über die deps-
4
4
  // injection geht und keinen DB-roundtrip braucht.
5
5
 
6
+ import { describe, expect, mock, test } from "bun:test";
6
7
  import { Hono } from "hono";
7
- import { describe, expect, test, vi } from "vitest";
8
8
  import { SubscriptionEventTypes, SubscriptionStatuses } from "../constants";
9
9
  import type { SubscriptionEvent, SubscriptionProviderPlugin } from "../types";
10
10
  import { createSubscriptionWebhookHandler, type SubscriptionWebhookDeps } from "../webhook-handler";
@@ -65,7 +65,7 @@ async function postWebhook(app: Hono, providerName: string, body = '{"id":"evt_t
65
65
 
66
66
  describe("webhook-handler — happy path", () => {
67
67
  test("verifyAndParseWebhook → SubscriptionEvent → dispatchWrite → 200 processed", async () => {
68
- const dispatchWrite = vi.fn(async () => ({
68
+ const dispatchWrite = mock(async () => ({
69
69
  isSuccess: true,
70
70
  data: { duplicate: false, eventAggregateId: "evt-id" },
71
71
  }));
@@ -77,7 +77,8 @@ describe("webhook-handler — happy path", () => {
77
77
  expect(body.processed).toBe(true);
78
78
  expect(body.duplicate).toBe(false);
79
79
 
80
- expect(dispatchWrite).toHaveBeenCalledExactlyOnceWith(
80
+ expect(dispatchWrite).toHaveBeenCalledTimes(1);
81
+ expect(dispatchWrite).toHaveBeenCalledWith(
81
82
  expect.objectContaining({
82
83
  handlerQn: "billing-foundation:write:process-event",
83
84
  tenantId: "tenant-test",
@@ -92,7 +93,7 @@ describe("webhook-handler — happy path", () => {
92
93
  });
93
94
 
94
95
  test("plugin returns null (= unbekannter event-type) → 200 ignored, kein dispatch", async () => {
95
- const dispatchWrite = vi.fn();
96
+ const dispatchWrite = mock();
96
97
  const plugin = buildPlugin({ verifyAndParseWebhook: async () => null });
97
98
  const app = buildApp(buildDeps({ dispatchWrite, resolveProvider: () => plugin }));
98
99
 
@@ -137,7 +138,7 @@ describe("webhook-handler — error paths", () => {
137
138
  });
138
139
 
139
140
  test("dispatchWrite returns isSuccess: false → 500 mit subscription_webhook_processing_failed", async () => {
140
- const dispatchWrite = vi.fn(async () => ({
141
+ const dispatchWrite = mock(async () => ({
141
142
  isSuccess: false,
142
143
  error: { code: "internal_error", message: "DB unavailable" },
143
144
  }));
@@ -0,0 +1,15 @@
1
+ import { asRawClient } from "@cosmicdrift/kumiko-framework/bun-db";
2
+ import type { DbRunner } from "@cosmicdrift/kumiko-framework/db";
3
+
4
+ export async function upsertSubscriptionProjectionRow(
5
+ tx: DbRunner,
6
+ tableName: string,
7
+ insertCols: Record<string, unknown>,
8
+ setClauses: readonly string[],
9
+ params: readonly unknown[],
10
+ ): Promise<void> {
11
+ const insertKeys = Object.keys(insertCols);
12
+ const insertPlaceholders = insertKeys.map((_, i) => `$${i + 1}`);
13
+ const sqlText = `INSERT INTO "${tableName}" (${insertKeys.map((k) => `"${k}"`).join(", ")}) VALUES (${insertPlaceholders.join(", ")}) ON CONFLICT ("id") DO UPDATE SET ${setClauses.join(", ")}`;
14
+ await asRawClient(tx).unsafe(sqlText, params);
15
+ }
@@ -1,8 +1,8 @@
1
1
  // Resolver-helper: liest die current subscription-row für einen Tenant
2
2
  // aus der read_subscriptions-projection.
3
3
 
4
+ import { selectMany } from "@cosmicdrift/kumiko-framework/bun-db";
4
5
  import type { HandlerContext } from "@cosmicdrift/kumiko-framework/engine";
5
- import { eq } from "drizzle-orm";
6
6
  import { subscriptionAggregateId } from "./aggregate-id";
7
7
  import { subscriptionsProjectionTable } from "./projection";
8
8
 
@@ -22,11 +22,7 @@ export async function getSubscriptionForTenant(
22
22
  tenantId: string,
23
23
  ): Promise<SubscriptionView | null> {
24
24
  const aggId = subscriptionAggregateId(tenantId);
25
- const [row] = await ctx.db
26
- .select()
27
- .from(subscriptionsProjectionTable)
28
- .where(eq(subscriptionsProjectionTable["id"], aggId))
29
- .limit(1);
25
+ const [row] = await selectMany(ctx.db, subscriptionsProjectionTable, { id: aggId }, { limit: 1 });
30
26
  if (!row) return null;
31
27
  // @cast-boundary db-row — drizzle-row carries column-as-unknown
32
28
  return {
@@ -9,8 +9,8 @@
9
9
  // kann nicht zum Portal eines OTHER Providers, weil der ihn nicht
10
10
  // kennt.
11
11
 
12
+ import { selectMany } from "@cosmicdrift/kumiko-framework/bun-db";
12
13
  import type { WriteHandlerDef } from "@cosmicdrift/kumiko-framework/engine";
13
- import { eq } from "drizzle-orm";
14
14
  import { z } from "zod";
15
15
  import { subscriptionAggregateId } from "../aggregate-id";
16
16
  import { SUBSCRIPTION_PROVIDER_EXTENSION } from "../constants";
@@ -34,7 +34,7 @@ export const createPortalSessionHandler: WriteHandlerDef = {
34
34
  // 1. Hol current subscription-row für den Tenant. Aggregate-id ist
35
35
  // deterministic per tenant — eine row pro tenant.
36
36
  const subAggId = subscriptionAggregateId(tenantId);
37
- const rows = await ctx.db.select().from(subTable).where(eq(subTable["id"], subAggId)).limit(1);
37
+ const rows = await selectMany(ctx.db, subTable, { id: subAggId }, { limit: 1 });
38
38
  const row = rows[0];
39
39
  if (!row) {
40
40
  throw new Error(
@@ -3,6 +3,7 @@
3
3
  // subscription via getSubscriptionForTenant-helper (= ctx.db ist
4
4
  // tenant-scoped, gibt automatisch nur die row des Callers zurück).
5
5
 
6
+ import { selectMany } from "@cosmicdrift/kumiko-framework/bun-db";
6
7
  import type { QueryHandlerDef } from "@cosmicdrift/kumiko-framework/engine";
7
8
  import { z } from "zod";
8
9
  import { subscriptionsProjectionTable } from "../projection";
@@ -14,7 +15,9 @@ export const listSubscriptionsQuery: QueryHandlerDef = {
14
15
  schema: listSchema,
15
16
  access: { roles: ["SystemAdmin", "TenantAdmin"] },
16
17
  handler: async (_query, ctx) => {
17
- const rows = await ctx.db.select().from(subscriptionsProjectionTable);
18
+ const rows = await selectMany(ctx.db.raw, subscriptionsProjectionTable, {
19
+ tenantId: ctx.user.tenantId,
20
+ });
18
21
  return { rows };
19
22
  },
20
23
  };