@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
@@ -1,35 +1,35 @@
1
+ import { selectMany } from "@cosmicdrift/kumiko-framework/bun-db";
1
2
  import { access, defineQueryHandler } from "@cosmicdrift/kumiko-framework/engine";
2
- import { desc } from "drizzle-orm";
3
3
  import { z } from "zod";
4
4
  import { userSessionTable } from "../schema/user-session";
5
5
 
6
- // Admin view of every session in the active tenant. Tenant admins use this
7
- // to investigate "who is logged in right now" or revoke a suspicious
8
- // device. Unlike `session:mine` this does NOT filter by userId — it's the
9
- // whole tenant. Tenant-scoping comes from ctx.db (TenantDb applies a tenant
10
- // filter automatically on select from tables with a tenantId column), so
11
- // cross-tenant bleed is impossible.
12
- //
13
- // Includes revoked rows too — distinct column in the response tells the UI
14
- // which entries are historical vs. live. The default ordering puts the
15
- // newest first so a security review starts at the recent activity.
6
+ // Admin view of every session in the active tenant. ctx.db (TenantDb)
7
+ // applies tenant-scoping automatically on selects from tables with a
8
+ // tenantId column. Includes revoked rows; UI shows revokedAt distinct.
16
9
  export const listQuery = defineQueryHandler({
17
10
  name: "user-session:list",
18
11
  schema: z.object({}),
19
12
  access: { roles: access.admin },
20
13
  handler: async (_query, ctx) => {
21
- const rows = await ctx.db
22
- .select({
23
- id: userSessionTable["id"],
24
- userId: userSessionTable["userId"],
25
- createdAt: userSessionTable["createdAt"],
26
- expiresAt: userSessionTable["expiresAt"],
27
- revokedAt: userSessionTable["revokedAt"],
28
- ip: userSessionTable["ip"],
29
- userAgent: userSessionTable["userAgent"],
30
- })
31
- .from(userSessionTable)
32
- .orderBy(desc(userSessionTable["createdAt"]));
33
- return rows;
14
+ const rows = await selectMany<{
15
+ id: string;
16
+ userId: string;
17
+ createdAt: unknown;
18
+ expiresAt: unknown;
19
+ revokedAt: unknown;
20
+ ip: string | null;
21
+ userAgent: string | null;
22
+ }>(ctx.db, userSessionTable, undefined, {
23
+ orderBy: { col: "createdAt", direction: "desc" },
24
+ });
25
+ return rows.map((r) => ({
26
+ id: r.id,
27
+ userId: r.userId,
28
+ createdAt: r.createdAt,
29
+ expiresAt: r.expiresAt,
30
+ revokedAt: r.revokedAt,
31
+ ip: r.ip,
32
+ userAgent: r.userAgent,
33
+ }));
34
34
  },
35
35
  });
@@ -1,37 +1,38 @@
1
+ import { selectMany } from "@cosmicdrift/kumiko-framework/bun-db";
1
2
  import { defineQueryHandler } from "@cosmicdrift/kumiko-framework/engine";
2
- import { and, desc, eq, isNull } from "drizzle-orm";
3
3
  import { z } from "zod";
4
4
  import { userSessionTable } from "../schema/user-session";
5
5
 
6
6
  // "My live sessions" — the backing data for a devices/sessions UI. Returns
7
7
  // ONLY the current user's own, currently-live sessions, ordered by most-
8
- // recently-used first. Revoked rows are excluded (they survive in DB for
9
- // audit but the UI shouldn't show them as active).
10
- //
11
- // Note the `current` marker: we compare against the caller's `user.sid` so
12
- // the UI can label the entry the user is looking at ("this device"). A user
13
- // without a sid (stateless-JWT deployment) will simply see `current: false`
14
- // on every row — the feature still works, just without the marker.
8
+ // recently-used first. Revoked rows excluded (revokedAt IS NULL).
15
9
  export const mineQuery = defineQueryHandler({
16
10
  name: "user-session:mine",
17
11
  schema: z.object({}),
18
12
  access: { openToAll: true },
19
13
  handler: async (query, ctx) => {
20
- const rows = await ctx.db
21
- .select({
22
- id: userSessionTable["id"],
23
- createdAt: userSessionTable["createdAt"],
24
- expiresAt: userSessionTable["expiresAt"],
25
- ip: userSessionTable["ip"],
26
- userAgent: userSessionTable["userAgent"],
27
- })
28
- .from(userSessionTable)
29
- .where(
30
- and(eq(userSessionTable["userId"], query.user.id), isNull(userSessionTable["revokedAt"])),
31
- )
32
- .orderBy(desc(userSessionTable["createdAt"]));
33
-
14
+ const rows = await selectMany<{
15
+ id: string;
16
+ createdAt: unknown;
17
+ expiresAt: unknown;
18
+ ip: string | null;
19
+ userAgent: string | null;
20
+ }>(
21
+ ctx.db,
22
+ userSessionTable,
23
+ { userId: query.user.id, revokedAt: null },
24
+ {
25
+ orderBy: { col: "createdAt", direction: "desc" },
26
+ },
27
+ );
34
28
  const currentSid = query.user.sid;
35
- return rows.map((r) => ({ ...r, current: currentSid === r["id"] }));
29
+ return rows.map((r) => ({
30
+ id: r.id,
31
+ createdAt: r.createdAt,
32
+ expiresAt: r.expiresAt,
33
+ ip: r.ip,
34
+ userAgent: r.userAgent,
35
+ current: currentSid === r.id,
36
+ }));
36
37
  },
37
38
  });
@@ -1,5 +1,5 @@
1
+ import { updateMany } from "@cosmicdrift/kumiko-framework/bun-db";
1
2
  import { access, defineWriteHandler } from "@cosmicdrift/kumiko-framework/engine";
2
- import { and, eq, isNull } from "drizzle-orm";
3
3
  import { Temporal } from "temporal-polyfill";
4
4
  import { z } from "zod";
5
5
  import { userSessionTable } from "../schema/user-session";
@@ -23,16 +23,12 @@ export const revokeAllForUserWrite = defineWriteHandler({
23
23
  }),
24
24
  access: { roles: access.privileged },
25
25
  handler: async (event, ctx) => {
26
- const updated = await ctx.db.raw
27
- .update(userSessionTable)
28
- .set({ revokedAt: Temporal.Now.instant() })
29
- .where(
30
- and(
31
- eq(userSessionTable["userId"], event.payload.userId),
32
- isNull(userSessionTable["revokedAt"]),
33
- ),
34
- )
35
- .returning();
26
+ const updated = await updateMany(
27
+ ctx.db.raw,
28
+ userSessionTable,
29
+ { revokedAt: Temporal.Now.instant() },
30
+ { userId: event.payload.userId, revokedAt: null },
31
+ );
36
32
 
37
33
  return {
38
34
  isSuccess: true as const,
@@ -1,6 +1,6 @@
1
+ import { updateMany } from "@cosmicdrift/kumiko-framework/bun-db";
1
2
  import { defineWriteHandler } from "@cosmicdrift/kumiko-framework/engine";
2
3
  import { UnprocessableError, writeFailure } from "@cosmicdrift/kumiko-framework/errors";
3
- import { and, eq, isNull, ne } from "drizzle-orm";
4
4
  import { Temporal } from "temporal-polyfill";
5
5
  import { z } from "zod";
6
6
  import { SessionErrors } from "../constants";
@@ -25,17 +25,12 @@ export const revokeAllOthersWrite = defineWriteHandler({
25
25
  );
26
26
  }
27
27
 
28
- const updated = await ctx.db
29
- .update(userSessionTable)
30
- .set({ revokedAt: Temporal.Now.instant() })
31
- .where(
32
- and(
33
- eq(userSessionTable["userId"], event.user.id),
34
- isNull(userSessionTable["revokedAt"]),
35
- ne(userSessionTable["id"], keepSid),
36
- ),
37
- )
38
- .returning();
28
+ const updated = await updateMany(
29
+ ctx.db,
30
+ userSessionTable,
31
+ { revokedAt: Temporal.Now.instant() },
32
+ { userId: event.user.id, revokedAt: null, id: { ne: keepSid } },
33
+ );
39
34
 
40
35
  return { isSuccess: true, data: { count: updated.length } };
41
36
  },
@@ -1,6 +1,6 @@
1
+ import { fetchOne, updateMany } from "@cosmicdrift/kumiko-framework/bun-db";
1
2
  import { defineWriteHandler } from "@cosmicdrift/kumiko-framework/engine";
2
3
  import { UnprocessableError, writeFailure } from "@cosmicdrift/kumiko-framework/errors";
3
- import { and, eq, isNull } from "drizzle-orm";
4
4
  import { Temporal } from "temporal-polyfill";
5
5
  import { z } from "zod";
6
6
  import { SessionErrors } from "../constants";
@@ -28,17 +28,12 @@ export const revokeWrite = defineWriteHandler({
28
28
  }),
29
29
  access: { openToAll: true },
30
30
  handler: async (event, ctx) => {
31
- const updated = await ctx.db
32
- .update(userSessionTable)
33
- .set({ revokedAt: Temporal.Now.instant() })
34
- .where(
35
- and(
36
- eq(userSessionTable["id"], event.payload.id),
37
- eq(userSessionTable["userId"], event.user.id),
38
- isNull(userSessionTable["revokedAt"]),
39
- ),
40
- )
41
- .returning();
31
+ const updated = await updateMany(
32
+ ctx.db,
33
+ userSessionTable,
34
+ { revokedAt: Temporal.Now.instant() },
35
+ { id: event.payload.id, userId: event.user.id, revokedAt: null },
36
+ );
42
37
 
43
38
  if (updated.length > 0) {
44
39
  return { isSuccess: true, data: { id: event.payload.id } };
@@ -46,13 +41,11 @@ export const revokeWrite = defineWriteHandler({
46
41
 
47
42
  // Zero rows touched — disambiguate between "not yours" and "already
48
43
  // revoked" via a point-read. Only hits on the error path.
49
- const [row] = await ctx.db
50
- .select({ userId: userSessionTable["userId"], revokedAt: userSessionTable["revokedAt"] })
51
- .from(userSessionTable)
52
- .where(eq(userSessionTable["id"], event.payload.id))
53
- .limit(1);
44
+ const row = await fetchOne<{ userId: string; revokedAt: unknown }>(ctx.db, userSessionTable, {
45
+ id: event.payload.id,
46
+ });
54
47
 
55
- if (row && row["userId"] === event.user.id && row["revokedAt"] !== null) {
48
+ if (row && row.userId === event.user.id && row.revokedAt !== null) {
56
49
  return writeFailure(
57
50
  new UnprocessableError(SessionErrors.alreadyRevoked, {
58
51
  i18nKey: "sessions.errors.alreadyRevoked",
@@ -1,4 +1,4 @@
1
- import { buildDrizzleTable } from "@cosmicdrift/kumiko-framework/db";
1
+ import { buildEntityTable } from "@cosmicdrift/kumiko-framework/db";
2
2
  import {
3
3
  access,
4
4
  createEntity,
@@ -64,4 +64,4 @@ export const userSessionEntity = createEntity({
64
64
  },
65
65
  });
66
66
 
67
- export const userSessionTable = buildDrizzleTable("user_session", userSessionEntity);
67
+ export const userSessionTable = buildEntityTable("user_session", userSessionEntity);
@@ -5,10 +5,10 @@ import type {
5
5
  SessionMetadata,
6
6
  SessionRevoker,
7
7
  } from "@cosmicdrift/kumiko-framework/api";
8
+ import { fetchOne, insertOne, updateMany } from "@cosmicdrift/kumiko-framework/bun-db";
8
9
  import type { DbConnection } from "@cosmicdrift/kumiko-framework/db";
9
10
  import type { SessionUser } from "@cosmicdrift/kumiko-framework/engine";
10
11
  import { generateId } from "@cosmicdrift/kumiko-framework/utils";
11
- import { and, eq, isNull } from "drizzle-orm";
12
12
  import { Temporal } from "temporal-polyfill";
13
13
  import { DEFAULT_SESSION_EXPIRY_MS } from "./constants";
14
14
  import { userSessionTable } from "./schema/user-session";
@@ -46,7 +46,7 @@ export function createSessionCallbacks(opts: SessionCallbacksOptions): SessionCa
46
46
  const sid = generateId();
47
47
  const now = Temporal.Now.instant();
48
48
  const expiresAt = now.add({ milliseconds: ttlMs });
49
- await db.insert(userSessionTable).values({
49
+ await insertOne(db, userSessionTable, {
50
50
  id: sid,
51
51
  tenantId: user.tenantId,
52
52
  userId: user.id,
@@ -64,23 +64,20 @@ export function createSessionCallbacks(opts: SessionCallbacksOptions): SessionCa
64
64
  // original timestamp. Double-revoke races land here via logout +
65
65
  // switch-tenant on the same sid. (Password-change uses a different
66
66
  // callback — sessionMassRevoker — and isn't in scope for this guard.)
67
- await db
68
- .update(userSessionTable)
69
- .set({ revokedAt: Temporal.Now.instant() })
70
- .where(and(eq(userSessionTable.id, sid), isNull(userSessionTable.revokedAt)));
67
+ await updateMany(
68
+ db,
69
+ userSessionTable,
70
+ { revokedAt: Temporal.Now.instant() },
71
+ { id: sid, revokedAt: null },
72
+ );
71
73
  },
72
74
 
73
75
  async sessionChecker(sid: string, expectedUserId: string): Promise<AuthSessionStatus> {
74
- const rows = await db
75
- .select({
76
- userId: userSessionTable.userId,
77
- revokedAt: userSessionTable.revokedAt,
78
- expiresAt: userSessionTable.expiresAt,
79
- })
80
- .from(userSessionTable)
81
- .where(eq(userSessionTable.id, sid))
82
- .limit(1);
83
- const row = rows[0];
76
+ const row = await fetchOne<{
77
+ userId: string;
78
+ revokedAt: unknown;
79
+ expiresAt: { epochMilliseconds: number };
80
+ }>(db, userSessionTable, { id: sid });
84
81
  if (!row) return "missing";
85
82
  // Cross-user check: if the sid belongs to someone else, treat it
86
83
  // identically to "missing" so a compromised sid paired with a valid
@@ -99,11 +96,12 @@ export function createSessionCallbacks(opts: SessionCallbacksOptions): SessionCa
99
96
  async sessionMassRevoker(userId: string): Promise<number> {
100
97
  // Count is accurate because we only touch live rows — a previously
101
98
  // revoked row stays in its state and isn't double-counted.
102
- const result = await db
103
- .update(userSessionTable)
104
- .set({ revokedAt: Temporal.Now.instant() })
105
- .where(and(eq(userSessionTable.userId, userId), isNull(userSessionTable.revokedAt)))
106
- .returning({ id: userSessionTable.id });
99
+ const result = await updateMany(
100
+ db,
101
+ userSessionTable,
102
+ { revokedAt: Temporal.Now.instant() },
103
+ { userId, revokedAt: null },
104
+ );
107
105
  return result.length;
108
106
  },
109
107
  };
@@ -1,6 +1,6 @@
1
1
  // feature.ts contract tests for subscription-mollie.
2
2
 
3
- import { describe, expect, test } from "vitest";
3
+ import { describe, expect, test } from "bun:test";
4
4
  import { MOLLIE_PROVIDER_NAME, SUBSCRIPTION_MOLLIE_FEATURE } from "../constants";
5
5
  import { createSubscriptionMollieFeature } from "../feature";
6
6
 
@@ -18,6 +18,7 @@
18
18
  // Unit-Tests (verify-webhook.test.ts) abgedeckt, aber die Verdrahtung
19
19
  // von factory bis foundation-DB-row beweist nur dieser Test.
20
20
 
21
+ import { afterAll, beforeAll, beforeEach, describe, expect, test } from "bun:test";
21
22
  import {
22
23
  billingFoundationFeature,
23
24
  createSubscriptionWebhookHandler,
@@ -38,7 +39,6 @@ import type {
38
39
  Subscription as MollieSubscription,
39
40
  } from "@mollie/api-client";
40
41
  import { Hono } from "hono";
41
- import { afterAll, beforeAll, beforeEach, describe, expect, test } from "vitest";
42
42
  import { createSubscriptionMollieFeature } from "../feature";
43
43
  import type { MollieClientShape } from "../verify-webhook";
44
44
 
@@ -3,11 +3,11 @@
3
3
  // als minimal-mock-shape (`MollieClientShape`) injiziert; Plugin-
4
4
  // Verhalten ist vom konkreten Mollie-SDK entkoppelt.
5
5
 
6
+ import { describe, expect, mock, test } from "bun:test";
6
7
  import {
7
8
  SubscriptionEventTypes,
8
9
  SubscriptionStatuses,
9
10
  } from "@cosmicdrift/kumiko-bundled-features/billing-foundation";
10
- import { describe, expect, test, vi } from "vitest";
11
11
  import {
12
12
  extractMollieId,
13
13
  type MollieClientShape,
@@ -61,18 +61,18 @@ function buildClient(
61
61
  ): MollieClientShape {
62
62
  return {
63
63
  payments: {
64
- get: vi.fn(async () => {
64
+ get: mock(async () => {
65
65
  if (overrides.paymentReject) throw overrides.paymentReject;
66
66
  return overrides.paymentResolve ?? buildMockPayment();
67
67
  }),
68
68
  },
69
69
  customerSubscriptions: {
70
- get: vi.fn(async () => {
70
+ get: mock(async () => {
71
71
  if (overrides.subReject) throw overrides.subReject;
72
72
  return overrides.subResolve ?? buildMockSubscription();
73
73
  }),
74
- list: vi.fn(async () => overrides.listResolve ?? []),
75
- create: vi.fn(
74
+ list: mock(async () => overrides.listResolve ?? []),
75
+ create: mock(
76
76
  async () => overrides.createResolve ?? buildMockSubscription({ id: "sub_just_created" }),
77
77
  ),
78
78
  },
@@ -197,7 +197,8 @@ describe("verifyAndParseMollieWebhook — mandate-setup-flow (= first-payment-pa
197
197
  expect(event).not.toBeNull();
198
198
  expect(event?.type).toBe(SubscriptionEventTypes.created);
199
199
  expect(event?.providerSubscriptionId).toBe("sub_just_created");
200
- expect(client.customerSubscriptions.create).toHaveBeenCalledExactlyOnceWith("cst_test_001", {
200
+ expect(client.customerSubscriptions.create).toHaveBeenCalledTimes(1);
201
+ expect(client.customerSubscriptions.create).toHaveBeenCalledWith("cst_test_001", {
201
202
  amount: { currency: "EUR", value: "9.99" },
202
203
  interval: "1 month",
203
204
  description: "Pro-Abo monatlich",
@@ -250,7 +251,7 @@ describe("verifyAndParseMollieWebhook — mandate-setup-flow (= first-payment-pa
250
251
  const event = await verify(client)("id=tr_upgrade", {});
251
252
 
252
253
  expect(event?.providerSubscriptionId).toBe("sub_pro_new");
253
- expect(client.customerSubscriptions.create).toHaveBeenCalledOnce();
254
+ expect(client.customerSubscriptions.create).toHaveBeenCalledTimes(1);
254
255
  });
255
256
  });
256
257
 
@@ -1,6 +1,6 @@
1
1
  // feature.ts contract tests for subscription-stripe.
2
2
 
3
- import { describe, expect, test } from "vitest";
3
+ import { describe, expect, test } from "bun:test";
4
4
  import { STRIPE_PROVIDER_NAME, StripeEventTypes, SUBSCRIPTION_STRIPE_FEATURE } from "../constants";
5
5
  import { createSubscriptionStripeFeature } from "../feature";
6
6
 
@@ -1,11 +1,11 @@
1
1
  // Unit-Tests für die Stripe-Plugin-Methoden (createCheckoutSession,
2
2
  // createPortalSession, cancelSubscription). Stripe-SDK-calls werden via
3
- // vi.spyOn gemockt — wir testen unsere Mapping-Logik (Argumente die wir
3
+ // spyOn gemockt — wir testen unsere Mapping-Logik (Argumente die wir
4
4
  // an Stripe schicken + Antwort-Parsing), NICHT Stripe selbst.
5
5
 
6
+ import { describe, expect, spyOn, test } from "bun:test";
6
7
  import type { HandlerContext } from "@cosmicdrift/kumiko-framework/engine";
7
8
  import Stripe from "stripe";
8
- import { describe, expect, test, vi } from "vitest";
9
9
  import {
10
10
  createStripeCancelSubscription,
11
11
  createStripeCheckoutSession,
@@ -27,8 +27,7 @@ const stubCtx = {} as HandlerContext;
27
27
  describe("createStripeCheckoutSession", () => {
28
28
  test("ruft stripe.checkout.sessions.create mit mode=subscription + tenant-metadata", async () => {
29
29
  const stripe = buildStripe();
30
- const createMock = vi
31
- .spyOn(stripe.checkout.sessions, "create")
30
+ const createMock = spyOn(stripe.checkout.sessions, "create")
32
31
  // biome-ignore lint/suspicious/noExplicitAny: Stripe-SDK-typed mock-return
33
32
  .mockResolvedValue({ url: "https://checkout.stripe.com/c/pay/test" } as any);
34
33
 
@@ -41,7 +40,8 @@ describe("createStripeCheckoutSession", () => {
41
40
  });
42
41
 
43
42
  expect(result).toEqual({ url: "https://checkout.stripe.com/c/pay/test" });
44
- expect(createMock).toHaveBeenCalledExactlyOnceWith({
43
+ expect(createMock).toHaveBeenCalledTimes(1);
44
+ expect(createMock).toHaveBeenCalledWith({
45
45
  mode: "subscription",
46
46
  line_items: [{ price: "price_pro_monthly", quantity: 1 }],
47
47
  success_url: "https://example.com/success",
@@ -57,8 +57,7 @@ describe("createStripeCheckoutSession", () => {
57
57
 
58
58
  test("passes existing customer-id wenn gesetzt (Plan-Wechsel-Flow)", async () => {
59
59
  const stripe = buildStripe();
60
- const createMock = vi
61
- .spyOn(stripe.checkout.sessions, "create")
60
+ const createMock = spyOn(stripe.checkout.sessions, "create")
62
61
  // biome-ignore lint/suspicious/noExplicitAny: Stripe-SDK-typed mock-return
63
62
  .mockResolvedValue({ url: "https://x" } as any);
64
63
 
@@ -78,7 +77,7 @@ describe("createStripeCheckoutSession", () => {
78
77
 
79
78
  test("throws wenn Stripe keine url returnt (defensive — sollte nie passieren bei mode=subscription)", async () => {
80
79
  const stripe = buildStripe();
81
- vi.spyOn(stripe.checkout.sessions, "create")
80
+ spyOn(stripe.checkout.sessions, "create")
82
81
  // biome-ignore lint/suspicious/noExplicitAny: SDK-Drift-Test
83
82
  .mockResolvedValue({ url: null } as any);
84
83
 
@@ -99,7 +98,7 @@ describe("createStripeCheckoutSession", () => {
99
98
  // throw kriegt + zur HTTP 500 mapped (transient — Provider/Stripe
100
99
  // soll retried werden statt silent-success-mit-leerer-URL).
101
100
  const stripe = buildStripe();
102
- vi.spyOn(stripe.checkout.sessions, "create").mockRejectedValue(
101
+ spyOn(stripe.checkout.sessions, "create").mockRejectedValue(
103
102
  new Error("Stripe API: Internal server error"),
104
103
  );
105
104
 
@@ -122,8 +121,7 @@ describe("createStripeCheckoutSession", () => {
122
121
  describe("createStripePortalSession", () => {
123
122
  test("ruft stripe.billingPortal.sessions.create mit customer + return_url", async () => {
124
123
  const stripe = buildStripe();
125
- const createMock = vi
126
- .spyOn(stripe.billingPortal.sessions, "create")
124
+ const createMock = spyOn(stripe.billingPortal.sessions, "create")
127
125
  // biome-ignore lint/suspicious/noExplicitAny: Stripe-SDK-typed mock-return
128
126
  .mockResolvedValue({ url: "https://billing.stripe.com/p/session/test" } as any);
129
127
 
@@ -134,7 +132,8 @@ describe("createStripePortalSession", () => {
134
132
  });
135
133
 
136
134
  expect(result).toEqual({ url: "https://billing.stripe.com/p/session/test" });
137
- expect(createMock).toHaveBeenCalledExactlyOnceWith({
135
+ expect(createMock).toHaveBeenCalledTimes(1);
136
+ expect(createMock).toHaveBeenCalledWith({
138
137
  customer: "cus_001",
139
138
  return_url: "https://example.com/return",
140
139
  });
@@ -148,14 +147,14 @@ describe("createStripePortalSession", () => {
148
147
  describe("createStripeCancelSubscription", () => {
149
148
  test("ruft stripe.subscriptions.cancel mit subscription-id", async () => {
150
149
  const stripe = buildStripe();
151
- const cancelMock = vi
152
- .spyOn(stripe.subscriptions, "cancel")
150
+ const cancelMock = spyOn(stripe.subscriptions, "cancel")
153
151
  // biome-ignore lint/suspicious/noExplicitAny: Stripe-SDK-typed mock-return
154
152
  .mockResolvedValue({ id: "sub_001", status: "canceled" } as any);
155
153
 
156
154
  const cancel = createStripeCancelSubscription(stripe);
157
155
  await cancel(stubCtx, "sub_001");
158
156
 
159
- expect(cancelMock).toHaveBeenCalledExactlyOnceWith("sub_001");
157
+ expect(cancelMock).toHaveBeenCalledTimes(1);
158
+ expect(cancelMock).toHaveBeenCalledWith("sub_001");
160
159
  });
161
160
  });
@@ -12,6 +12,7 @@
12
12
  // Stripe-output liefert). Dieser Test fängt das Spalten-Mapping +
13
13
  // Verdrahtungs-Bugs ab.
14
14
 
15
+ import { afterAll, beforeAll, describe, expect, test } from "bun:test";
15
16
  import {
16
17
  billingFoundationFeature,
17
18
  createSubscriptionWebhookHandler,
@@ -29,7 +30,6 @@ import {
29
30
  } from "@cosmicdrift/kumiko-framework/stack";
30
31
  import { Hono } from "hono";
31
32
  import Stripe from "stripe";
32
- import { afterAll, beforeAll, describe, expect, test } from "vitest";
33
33
  import { createSubscriptionStripeFeature } from "../feature";
34
34
 
35
35
  // =============================================================================
@@ -7,12 +7,12 @@
7
7
  // events sind >100 Felder; full-fidelity-fixtures wären Maintenance-
8
8
  // Aufwand ohne Test-Wert.
9
9
 
10
+ import { describe, expect, test } from "bun:test";
10
11
  import {
11
12
  SubscriptionEventTypes,
12
13
  SubscriptionStatuses,
13
14
  } from "@cosmicdrift/kumiko-bundled-features/billing-foundation";
14
15
  import Stripe from "stripe";
15
- import { describe, expect, test } from "vitest";
16
16
  import {
17
17
  mapStripeEventType,
18
18
  mapStripeStatus,
@@ -83,8 +83,8 @@ function buildSubscriptionEvent(overrides: {
83
83
  }
84
84
 
85
85
  /** Erstellt einen valid Stripe-signed-Header für ein gegebenes payload. */
86
- function signEvent(payload: string, secret = TEST_SECRET): string {
87
- return stripeForFixtures.webhooks.generateTestHeaderString({
86
+ async function signEvent(payload: string, secret = TEST_SECRET): Promise<string> {
87
+ return stripeForFixtures.webhooks.generateTestHeaderStringAsync({
88
88
  payload,
89
89
  secret,
90
90
  });
@@ -102,7 +102,7 @@ describe("verifyAndParseStripeWebhook — sig-verify", () => {
102
102
 
103
103
  test("happy path: valid sig + bekannter event-type → SubscriptionEvent", async () => {
104
104
  const payload = JSON.stringify(buildSubscriptionEvent({}));
105
- const sig = signEvent(payload);
105
+ const sig = await signEvent(payload);
106
106
 
107
107
  const event = await verify(payload, { "stripe-signature": sig });
108
108
  expect(event).not.toBeNull();
@@ -120,7 +120,7 @@ describe("verifyAndParseStripeWebhook — sig-verify", () => {
120
120
 
121
121
  test("wrong secret → sig-verify failed → throws", async () => {
122
122
  const payload = JSON.stringify(buildSubscriptionEvent({}));
123
- const sig = signEvent(payload, "whsec_wrong_secret");
123
+ const sig = await signEvent(payload, "whsec_wrong_secret");
124
124
  await expect(verify(payload, { "stripe-signature": sig })).rejects.toThrow(
125
125
  /signature verify failed/,
126
126
  );
@@ -128,7 +128,7 @@ describe("verifyAndParseStripeWebhook — sig-verify", () => {
128
128
 
129
129
  test("modified body → sig-verify failed (Replay-Protection)", async () => {
130
130
  const original = JSON.stringify(buildSubscriptionEvent({}));
131
- const sig = signEvent(original);
131
+ const sig = await signEvent(original);
132
132
  // Tamper with body — Stripe-sig matched die exakten bytes.
133
133
  const tampered = original.replace("tenant-test-1", "tenant-attacker");
134
134
  await expect(verify(tampered, { "stripe-signature": sig })).rejects.toThrow(
@@ -151,7 +151,7 @@ describe("verifyAndParseStripeWebhook — event-filter", () => {
151
151
  // customer.created ist gültiger Stripe-event aber nicht in unserer
152
152
  // 5-types-Whitelist.
153
153
  const payload = JSON.stringify(buildSubscriptionEvent({ eventType: "customer.created" }));
154
- const sig = signEvent(payload);
154
+ const sig = await signEvent(payload);
155
155
  const event = await verify(payload, { "stripe-signature": sig });
156
156
  expect(event).toBeNull();
157
157
  });
@@ -160,7 +160,7 @@ describe("verifyAndParseStripeWebhook — event-filter", () => {
160
160
  const payload = JSON.stringify(
161
161
  buildSubscriptionEvent({ eventType: "customer.subscription.updated" }),
162
162
  );
163
- const sig = signEvent(payload);
163
+ const sig = await signEvent(payload);
164
164
  const event = await verify(payload, { "stripe-signature": sig });
165
165
  expect(event?.type).toBe(SubscriptionEventTypes.updated);
166
166
  });
@@ -172,7 +172,7 @@ describe("verifyAndParseStripeWebhook — event-filter", () => {
172
172
  status: "canceled",
173
173
  }),
174
174
  );
175
- const sig = signEvent(payload);
175
+ const sig = await signEvent(payload);
176
176
  const event = await verify(payload, { "stripe-signature": sig });
177
177
  expect(event?.type).toBe(SubscriptionEventTypes.canceled);
178
178
  expect(event?.status).toBe(SubscriptionStatuses.canceled);
@@ -200,7 +200,7 @@ describe("verifyAndParseStripeWebhook — event-filter", () => {
200
200
  },
201
201
  };
202
202
  const payload = JSON.stringify(ev);
203
- const sig = signEvent(payload);
203
+ const sig = await signEvent(payload);
204
204
  const event = await verify(payload, { "stripe-signature": sig });
205
205
  expect(event).toBeNull();
206
206
  });
@@ -221,21 +221,21 @@ describe("verifyAndParseStripeWebhook — tenant-resolution + price-to-tier", ()
221
221
  // @ts-expect-error — entferne metadata für Test
222
222
  ev.data.object.metadata = {};
223
223
  const payload = JSON.stringify(ev);
224
- const sig = signEvent(payload);
224
+ const sig = await signEvent(payload);
225
225
  const event = await verify(payload, { "stripe-signature": sig });
226
226
  expect(event).toBeNull();
227
227
  });
228
228
 
229
229
  test("price-id im Mapping → korrekter tier-Wert", async () => {
230
230
  const payload = JSON.stringify(buildSubscriptionEvent({ priceId: "price_business_yearly" }));
231
- const sig = signEvent(payload);
231
+ const sig = await signEvent(payload);
232
232
  const event = await verify(payload, { "stripe-signature": sig });
233
233
  expect(event?.tier).toBe("business");
234
234
  });
235
235
 
236
236
  test("price-id NICHT im Mapping → null", async () => {
237
237
  const payload = JSON.stringify(buildSubscriptionEvent({ priceId: "price_unknown_xyz" }));
238
- const sig = signEvent(payload);
238
+ const sig = await signEvent(payload);
239
239
  const event = await verify(payload, { "stripe-signature": sig });
240
240
  expect(event).toBeNull();
241
241
  });
@@ -243,7 +243,7 @@ describe("verifyAndParseStripeWebhook — tenant-resolution + price-to-tier", ()
243
243
  test("currentPeriodEnd wird aus subscription.items[0].current_period_end (Unix-sec) zu ISO konvertiert", async () => {
244
244
  const periodEndUnix = 1_780_000_000;
245
245
  const payload = JSON.stringify(buildSubscriptionEvent({ currentPeriodEndUnix: periodEndUnix }));
246
- const sig = signEvent(payload);
246
+ const sig = await signEvent(payload);
247
247
  const event = await verify(payload, { "stripe-signature": sig });
248
248
  // 1_780_000_000 sec = 2026-05-28T20:26:40Z (in ms: 1.78e12)
249
249
  // Temporal.Instant.toString() droppt Trailing-Zeros — keine .000Z