@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,6 +1,6 @@
1
+ import { selectMany } from "@cosmicdrift/kumiko-framework/bun-db";
1
2
  import { defineQueryHandler, SYSTEM_ROLE } from "@cosmicdrift/kumiko-framework/engine";
2
3
  import { parseRoles } from "@cosmicdrift/kumiko-framework/utils";
3
- import { eq } from "drizzle-orm";
4
4
  import { z } from "zod";
5
5
  import { tenantMembershipsTable } from "../membership-table";
6
6
 
@@ -11,10 +11,7 @@ export const membershipsQuery = defineQueryHandler({
11
11
  // directly by tenant admins managing memberships in the admin UI.
12
12
  access: { roles: [SYSTEM_ROLE, "SystemAdmin"] },
13
13
  handler: async (query, ctx) => {
14
- const rows = await ctx.db
15
- ?.select()
16
- .from(tenantMembershipsTable)
17
- .where(eq(tenantMembershipsTable.userId, query.payload.userId));
14
+ const rows = await selectMany(ctx.db, tenantMembershipsTable, { userId: query.payload.userId });
18
15
 
19
16
  return rows.map((row) => ({
20
17
  ...row,
@@ -1,7 +1,7 @@
1
- import { createEventStoreExecutor, type DbRow, fetchOne } from "@cosmicdrift/kumiko-framework/db";
1
+ import { fetchOne } from "@cosmicdrift/kumiko-framework/bun-db";
2
+ import { createEventStoreExecutor, type DbRow } from "@cosmicdrift/kumiko-framework/db";
2
3
  import { defineWriteHandler, withResponseData } from "@cosmicdrift/kumiko-framework/engine";
3
4
  import { NotFoundError, writeFailure } from "@cosmicdrift/kumiko-framework/errors";
4
- import { eq } from "drizzle-orm";
5
5
  import { z } from "zod";
6
6
  import { tenantMembershipEntity, tenantMembershipsTable } from "../membership-table";
7
7
 
@@ -15,12 +15,10 @@ export const removeMemberWrite = defineWriteHandler({
15
15
  access: { roles: ["SystemAdmin"] },
16
16
  handler: async (event, ctx) => {
17
17
  const db = ctx.db;
18
- const existing = await fetchOne(
19
- db,
20
- tenantMembershipsTable,
21
- eq(tenantMembershipsTable.userId, event.payload.userId),
22
- eq(tenantMembershipsTable.tenantId, event.payload.tenantId),
23
- );
18
+ const existing = await fetchOne(db, tenantMembershipsTable, {
19
+ userId: event.payload.userId,
20
+ tenantId: event.payload.tenantId,
21
+ });
24
22
  if (!existing) {
25
23
  return writeFailure(
26
24
  new NotFoundError("membership", undefined, {
@@ -1,40 +1,30 @@
1
+ import { selectMany } from "@cosmicdrift/kumiko-framework/bun-db";
1
2
  import { defineQueryHandler, SYSTEM_ROLE } from "@cosmicdrift/kumiko-framework/engine";
2
- import { eq } from "drizzle-orm";
3
3
  import { z } from "zod";
4
4
  import { tenantMembershipsTable } from "../membership-table";
5
5
 
6
6
  // Cross-feature query: resolve user IDs by tenantId or userId.
7
7
  // Other features (delivery, jobs, etc.) use this to get user lists
8
8
  // without knowing about membership internals.
9
- //
10
- // Examples:
11
- // { tenantId: 1 } → all user IDs in tenant 1
12
- // { userId: 5 } → [5] if member of any tenant, [] if not
13
9
  export const resolveUserIdsQuery = defineQueryHandler({
14
10
  name: "resolveUserIds",
15
11
  schema: z.object({
16
12
  tenantId: z.string().optional(),
17
13
  userId: z.string().optional(),
18
14
  }),
19
- // System-internal: invoked by other features (delivery, jobs) through queryAs(systemUser, ...).
20
- // Never called directly by an end-user request.
21
15
  access: { roles: [SYSTEM_ROLE] },
22
16
  handler: async (query, ctx) => {
23
17
  const { tenantId, userId } = query.payload;
24
18
 
25
19
  if (tenantId !== undefined) {
26
- const rows = await ctx.db
27
- .select({ userId: tenantMembershipsTable.userId })
28
- .from(tenantMembershipsTable)
29
- .where(eq(tenantMembershipsTable.tenantId, tenantId));
30
- return rows.map((r) => r["userId"] as number); // @cast-boundary db-row
20
+ const rows = await selectMany<{ userId: number }>(ctx.db, tenantMembershipsTable, {
21
+ tenantId,
22
+ });
23
+ return rows.map((r) => r.userId);
31
24
  }
32
25
 
33
26
  if (userId !== undefined) {
34
- const rows = await ctx.db
35
- .select({ userId: tenantMembershipsTable.userId })
36
- .from(tenantMembershipsTable)
37
- .where(eq(tenantMembershipsTable.userId, userId));
27
+ const rows = await selectMany(ctx.db, tenantMembershipsTable, { userId });
38
28
  return rows.length > 0 ? [userId] : [];
39
29
  }
40
30
 
@@ -1,7 +1,7 @@
1
- import { createEventStoreExecutor, type DbRow, fetchOne } from "@cosmicdrift/kumiko-framework/db";
1
+ import { fetchOne } from "@cosmicdrift/kumiko-framework/bun-db";
2
+ import { createEventStoreExecutor, type DbRow } from "@cosmicdrift/kumiko-framework/db";
2
3
  import { defineWriteHandler, withResponseData } from "@cosmicdrift/kumiko-framework/engine";
3
4
  import { NotFoundError, writeFailure } from "@cosmicdrift/kumiko-framework/errors";
4
- import { eq } from "drizzle-orm";
5
5
  import { z } from "zod";
6
6
  import { tenantMembershipEntity, tenantMembershipsTable } from "../membership-table";
7
7
 
@@ -23,12 +23,10 @@ export const updateMemberRolesWrite = defineWriteHandler({
23
23
  access: { roles: ["system", "SystemAdmin"] },
24
24
  handler: async (event, ctx) => {
25
25
  const db = ctx.db;
26
- const existing = await fetchOne(
27
- db,
28
- tenantMembershipsTable,
29
- eq(tenantMembershipsTable.userId, event.payload.userId),
30
- eq(tenantMembershipsTable.tenantId, event.payload.tenantId),
31
- );
26
+ const existing = await fetchOne(db, tenantMembershipsTable, {
27
+ userId: event.payload.userId,
28
+ tenantId: event.payload.tenantId,
29
+ });
32
30
  if (!existing) {
33
31
  return writeFailure(
34
32
  new NotFoundError("membership", undefined, {
@@ -22,7 +22,7 @@
22
22
  // pending → re-use existing row + refresh Redis-token + send mail
23
23
  // (analog zu signup-Resend).
24
24
 
25
- import { buildDrizzleTable } from "@cosmicdrift/kumiko-framework/db";
25
+ import { buildEntityTable } from "@cosmicdrift/kumiko-framework/db";
26
26
  import {
27
27
  createEntity,
28
28
  createSelectField,
@@ -88,7 +88,4 @@ export const tenantInvitationEntity = createEntity({
88
88
  ],
89
89
  });
90
90
 
91
- export const tenantInvitationsTable = buildDrizzleTable(
92
- "tenant-invitation",
93
- tenantInvitationEntity,
94
- );
91
+ export const tenantInvitationsTable = buildEntityTable("tenant-invitation", tenantInvitationEntity);
@@ -1,4 +1,4 @@
1
- import { buildDrizzleTable } from "@cosmicdrift/kumiko-framework/db";
1
+ import { buildEntityTable } from "@cosmicdrift/kumiko-framework/db";
2
2
  import { createEntity, createTextField } from "@cosmicdrift/kumiko-framework/engine";
3
3
 
4
4
  // Membership is event-sourced. Each (userId, tenantId) pair is its own
@@ -13,7 +13,7 @@ import { createEntity, createTextField } from "@cosmicdrift/kumiko-framework/eng
13
13
  // database level independent of the handler lookup.
14
14
  //
15
15
  // Single-Source-of-Truth: `tenantMembershipEntity`. Die DB-Tabelle wird
16
- // aus der EntityDefinition über buildDrizzleTable abgeleitet, der
16
+ // aus der EntityDefinition über buildEntityTable abgeleitet, der
17
17
  // unique-Index ist via entity.indexes deklariert.
18
18
  export const tenantMembershipEntity = createEntity({
19
19
  table: "read_tenant_memberships",
@@ -29,7 +29,4 @@ export const tenantMembershipEntity = createEntity({
29
29
  ],
30
30
  });
31
31
 
32
- export const tenantMembershipsTable = buildDrizzleTable(
33
- "tenant-membership",
34
- tenantMembershipEntity,
35
- );
32
+ export const tenantMembershipsTable = buildEntityTable("tenant-membership", tenantMembershipEntity);
@@ -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
  createBooleanField,
4
4
  createEntity,
@@ -24,4 +24,4 @@ export const tenantEntity = createEntity({
24
24
  indexes: [{ unique: true, columns: ["key"] }],
25
25
  });
26
26
 
27
- export const tenantTable = buildDrizzleTable("tenant", tenantEntity);
27
+ export const tenantTable = buildEntityTable("tenant", tenantEntity);
@@ -31,16 +31,15 @@
31
31
  // Fixture-seeding prioritises "make the state exist" over "detect duplicate
32
32
  // seeding", which is usually a test-author bug we don't need to surface.
33
33
 
34
+ import { fetchOne } from "@cosmicdrift/kumiko-framework/bun-db";
34
35
  import {
35
36
  createEventStoreExecutor,
36
37
  createTenantDb,
37
- type DbConnection,
38
- fetchOne,
38
+ type DbRunner,
39
39
  } from "@cosmicdrift/kumiko-framework/db";
40
40
  import type { SessionUser, TenantId } from "@cosmicdrift/kumiko-framework/engine";
41
- import { eventsTable } from "@cosmicdrift/kumiko-framework/event-store";
41
+ import { getAggregateStreamMaxVersion } from "@cosmicdrift/kumiko-framework/event-store";
42
42
  import { TestUsers } from "@cosmicdrift/kumiko-framework/stack";
43
- import { eq, max as maxFn } from "drizzle-orm";
44
43
  import { tenantMembershipEntity, tenantMembershipsTable } from "./membership-table";
45
44
  import { tenantEntity, tenantTable } from "./schema/tenant";
46
45
 
@@ -83,7 +82,7 @@ export type SeedTenantOptions = {
83
82
  * `TenantHandlers.create`, minus the SystemAdmin-access-check and minus
84
83
  * ConflictError-on-duplicate.
85
84
  */
86
- export async function seedTenant(db: DbConnection, options: SeedTenantOptions): Promise<TenantId> {
85
+ export async function seedTenant(db: DbRunner, options: SeedTenantOptions): Promise<TenantId> {
87
86
  const by = options.by ?? TestUsers.systemAdmin;
88
87
  // executor.create erwartet eine TenantDb (mit .insert()-API), nicht
89
88
  // die rohe DbConnection. Auch wenn das Tenant-Aggregat selbst NICHT
@@ -92,18 +91,15 @@ export async function seedTenant(db: DbConnection, options: SeedTenantOptions):
92
91
  // seedTenantMembership nötig.
93
92
  const tdb = createTenantDb(db, by.tenantId, "system");
94
93
 
95
- const existing = await fetchOne(db, tenantTable, eq(tenantTable["id"], options.id));
94
+ const existing = await fetchOne(db, tenantTable, { id: options.id });
96
95
  if (existing) return options.id;
97
96
 
98
97
  // Idempotenz: Aggregate kann im Event-Store existieren ohne Projection-Row
99
98
  // (Projection-Drift nach rebuild, manuellem DELETE, oder async-lag). Wenn
100
99
  // Stream-Version > 0 → kein create() — wäre version_conflict. Caller
101
100
  // bekommt die ID, Projection wird beim nächsten Dispatcher-Cycle aufgebaut.
102
- const [streamRow] = await db
103
- .select({ v: maxFn(eventsTable.version) })
104
- .from(eventsTable)
105
- .where(eq(eventsTable.aggregateId, options.id));
106
- if ((streamRow?.v ?? 0) > 0) return options.id;
101
+ const streamVersion = await getAggregateStreamMaxVersion(db, options.id);
102
+ if (streamVersion > 0) return options.id;
107
103
 
108
104
  const result = await tenantExecutor.create(
109
105
  { id: options.id, key: options.key, name: options.name },
@@ -126,7 +122,7 @@ export async function seedTenant(db: DbConnection, options: SeedTenantOptions):
126
122
  * ConflictError on duplicates (duplicate calls no-op).
127
123
  */
128
124
  export async function seedTenantMembership(
129
- db: DbConnection,
125
+ db: DbRunner,
130
126
  options: SeedTenantMembershipOptions,
131
127
  ): Promise<void> {
132
128
  const by = options.by ?? TestUsers.systemAdmin;
@@ -138,12 +134,10 @@ export async function seedTenantMembership(
138
134
  // only certain tables get truncated. A plain executor.create would trip
139
135
  // the (user_id, tenant_id) unique index; the fixture call-site would then
140
136
  // have to juggle try/catch. Lookup-first keeps call-sites clean.
141
- const existing = await fetchOne(
142
- db,
143
- tenantMembershipsTable,
144
- eq(tenantMembershipsTable.userId, options.userId),
145
- eq(tenantMembershipsTable.tenantId, options.tenantId),
146
- );
137
+ const existing = await fetchOne(db, tenantMembershipsTable, {
138
+ userId: options.userId,
139
+ tenantId: options.tenantId,
140
+ });
147
141
  // skip: idempotent no-op — duplicate seed is expected across beforeEach-
148
142
  // resets that don't truncate this table. Cheaper than try/catch on the
149
143
  // unique-index, and documented in the function JSDoc above.
@@ -180,7 +180,7 @@ convenience routes (`/legal/impressum`, `/legal/datenschutz`,
180
180
  ## Architecture
181
181
 
182
182
  - **Single source of truth:** `textBlockEntity` in `table.ts`.
183
- The Drizzle table is derived via `buildDrizzleTable("text-block",
183
+ The Drizzle table is derived via `buildEntityTable("text-block",
184
184
  textBlockEntity)`, the unique index on `(tenantId, slug, lang)` is
185
185
  declared via `entity.indexes`.
186
186
  - **Event-sourced:** the write path goes through
@@ -1,3 +1,4 @@
1
+ import { afterAll, beforeAll, describe, expect, test } from "bun:test";
1
2
  import type { DbConnection } from "@cosmicdrift/kumiko-framework/db";
2
3
  import { createEventsTable } from "@cosmicdrift/kumiko-framework/event-store";
3
4
  import {
@@ -8,7 +9,6 @@ import {
8
9
  unsafeCreateEntityTable,
9
10
  } from "@cosmicdrift/kumiko-framework/stack";
10
11
  import { expectErrorIncludes } from "@cosmicdrift/kumiko-framework/testing";
11
- import { afterAll, beforeAll, describe, expect, test } from "vitest";
12
12
  import { TextContentHandlers, TextContentQueries } from "../constants";
13
13
  import { createTextContentFeature } from "../feature";
14
14
  import { seedTextBlock } from "../seeding";
@@ -389,7 +389,7 @@ describe("text-content :: edge-cases", () => {
389
389
  { slug: "race-test", lang: "de" },
390
390
  tenantAdmin,
391
391
  );
392
- const finalBody = fetched!["body"];
392
+ const finalBody = fetched!["body"] as string;
393
393
  expect(["from-a", "from-b", "v1"]).toContain(finalBody);
394
394
  });
395
395
  });
@@ -8,11 +8,10 @@
8
8
  // bleiben Features durch Refactorings entkoppelt — wer textBlocksTable
9
9
  // umzieht oder die Query-Signatur ändert, muss nur die Factory anpassen.
10
10
 
11
+ import { fetchOne } from "@cosmicdrift/kumiko-framework/bun-db";
11
12
  import type { DbConnection } from "@cosmicdrift/kumiko-framework/db";
12
- import { fetchOne } from "@cosmicdrift/kumiko-framework/db";
13
13
  import type { SessionUser, TenantId } from "@cosmicdrift/kumiko-framework/engine";
14
14
  import { InternalError } from "@cosmicdrift/kumiko-framework/errors";
15
- import { eq } from "drizzle-orm";
16
15
  import { type TextBlockRow, textBlocksTable } from "./table";
17
16
 
18
17
  export type TextBlock = {
@@ -40,13 +39,7 @@ export type TextContentApi = {
40
39
  export function createTextContentApi(db: DbConnection): TextContentApi {
41
40
  return {
42
41
  getBlock: async ({ tenantId, slug, lang }) => {
43
- const row = await fetchOne<TextBlockRow>(
44
- db,
45
- textBlocksTable,
46
- eq(textBlocksTable["tenantId"], tenantId),
47
- eq(textBlocksTable["slug"], slug),
48
- eq(textBlocksTable["lang"], lang),
49
- );
42
+ const row = await fetchOne<TextBlockRow>(db, textBlocksTable, { tenantId, slug, lang });
50
43
  if (!row) return null;
51
44
  return {
52
45
  slug: row.slug,
@@ -1,7 +1,6 @@
1
- import { fetchOne } from "@cosmicdrift/kumiko-framework/db";
1
+ import { fetchOne } from "@cosmicdrift/kumiko-framework/bun-db";
2
2
  import { defineQueryHandler } from "@cosmicdrift/kumiko-framework/engine";
3
3
  import { AccessDeniedError } from "@cosmicdrift/kumiko-framework/errors";
4
- import { eq } from "drizzle-orm";
5
4
  import { z } from "zod";
6
5
  import { type TextBlockRow, textBlocksTable } from "../table";
7
6
 
@@ -35,13 +34,11 @@ export const bySlugQuery = defineQueryHandler({
35
34
  });
36
35
  }
37
36
  const tenantId = override ?? query.user.tenantId;
38
- const row = await fetchOne<TextBlockRow>(
39
- ctx.db,
40
- textBlocksTable,
41
- eq(textBlocksTable["tenantId"], tenantId),
42
- eq(textBlocksTable["slug"], query.payload.slug),
43
- eq(textBlocksTable["lang"], query.payload.lang),
44
- );
37
+ const row = await fetchOne<TextBlockRow>(ctx.db, textBlocksTable, {
38
+ tenantId,
39
+ slug: query.payload.slug,
40
+ lang: query.payload.lang,
41
+ });
45
42
 
46
43
  if (!row) return null;
47
44
  return {
@@ -1,7 +1,7 @@
1
+ import { selectMany } from "@cosmicdrift/kumiko-framework/bun-db";
1
2
  import { castTenantRows } from "@cosmicdrift/kumiko-framework/db";
2
3
  import { defineQueryHandler } from "@cosmicdrift/kumiko-framework/engine";
3
4
  import { AccessDeniedError } from "@cosmicdrift/kumiko-framework/errors";
4
- import { eq } from "drizzle-orm";
5
5
  import { z } from "zod";
6
6
  import { type TextBlockRow, textBlocksTable } from "../table";
7
7
 
@@ -42,7 +42,7 @@ export const byTenantQuery = defineQueryHandler({
42
42
  }
43
43
  const tenantId = override ?? query.user.tenantId;
44
44
  const rows = castTenantRows<TextBlockRow>(
45
- await ctx.db.select().from(textBlocksTable).where(eq(textBlocksTable["tenantId"], tenantId)),
45
+ await selectMany(ctx.db, textBlocksTable, { tenantId: tenantId }),
46
46
  );
47
47
  return {
48
48
  blocks: rows.map((row) => ({
@@ -1,7 +1,7 @@
1
- import { createEventStoreExecutor, fetchOne } from "@cosmicdrift/kumiko-framework/db";
1
+ import { fetchOne } from "@cosmicdrift/kumiko-framework/bun-db";
2
+ import { createEventStoreExecutor } from "@cosmicdrift/kumiko-framework/db";
2
3
  import { defineWriteHandler, type TenantId } from "@cosmicdrift/kumiko-framework/engine";
3
4
  import { AccessDeniedError, writeFailure } from "@cosmicdrift/kumiko-framework/errors";
4
- import { eq } from "drizzle-orm";
5
5
  import { z } from "zod";
6
6
  import { type TextBlockRow, textBlockEntity, textBlocksTable } from "../table";
7
7
 
@@ -86,13 +86,11 @@ export const setWrite = defineWriteHandler({
86
86
  const executorUser =
87
87
  override !== undefined ? { ...event.user, tenantId: override as TenantId } : event.user; // @cast-boundary engine-bridge
88
88
 
89
- const existing = await fetchOne<TextBlockRow>(
90
- db,
91
- textBlocksTable,
92
- eq(textBlocksTable["tenantId"], tenantId),
93
- eq(textBlocksTable["slug"], event.payload.slug),
94
- eq(textBlocksTable["lang"], event.payload.lang),
95
- );
89
+ const existing = await fetchOne<TextBlockRow>(db, textBlocksTable, {
90
+ tenantId,
91
+ slug: event.payload.slug,
92
+ lang: event.payload.lang,
93
+ });
96
94
 
97
95
  // V.1.4 folder: optional + null erlaubt (root-node). Optional-Chain
98
96
  // mapped undefined → null damit drizzle nullable-column konsistent
@@ -3,15 +3,14 @@
3
3
  // aber ohne Access-Check. Idempotent: zweiter Call mit gleichem
4
4
  // (tenantId, slug, lang) updated den existing Block.
5
5
 
6
+ import { fetchOne } from "@cosmicdrift/kumiko-framework/bun-db";
6
7
  import {
7
8
  createEventStoreExecutor,
8
9
  createTenantDb,
9
10
  type DbConnection,
10
- fetchOne,
11
11
  } from "@cosmicdrift/kumiko-framework/db";
12
12
  import type { SessionUser, TenantId } from "@cosmicdrift/kumiko-framework/engine";
13
13
  import { TestUsers } from "@cosmicdrift/kumiko-framework/stack";
14
- import { eq } from "drizzle-orm";
15
14
  import { type TextBlockRow, textBlockEntity, textBlocksTable } from "./table";
16
15
 
17
16
  const executor = createEventStoreExecutor(textBlocksTable, textBlockEntity, {
@@ -47,13 +46,11 @@ export async function seedTextBlock(
47
46
  // checks (tenant-scope-validation) greifen.
48
47
  const tdb = createTenantDb(db, opts.tenantId, "system");
49
48
 
50
- const existing = await fetchOne<TextBlockRow>(
51
- db,
52
- textBlocksTable,
53
- eq(textBlocksTable["tenantId"], opts.tenantId),
54
- eq(textBlocksTable["slug"], opts.slug),
55
- eq(textBlocksTable["lang"], opts.lang),
56
- );
49
+ const existing = await fetchOne<TextBlockRow>(db, textBlocksTable, {
50
+ tenantId: opts.tenantId,
51
+ slug: opts.slug,
52
+ lang: opts.lang,
53
+ });
57
54
 
58
55
  const folder = opts.folder ?? null;
59
56
 
@@ -1,4 +1,4 @@
1
- import { buildDrizzleTable } from "@cosmicdrift/kumiko-framework/db";
1
+ import { buildEntityTable } from "@cosmicdrift/kumiko-framework/db";
2
2
  import { createEntity, createTextField } from "@cosmicdrift/kumiko-framework/engine";
3
3
 
4
4
  // TextBlock — generischer Container für statische Texte (legal pages,
@@ -29,7 +29,7 @@ export const textBlockEntity = createEntity({
29
29
  ],
30
30
  });
31
31
 
32
- export const textBlocksTable = buildDrizzleTable("text-block", textBlockEntity);
32
+ export const textBlocksTable = buildEntityTable("text-block", textBlockEntity);
33
33
 
34
34
  // Concrete Row-Type — single-source dafür dass die unknown-Werte die
35
35
  // Drizzle aus `Record<string, unknown>` liefert genau einmal benannt
@@ -1,5 +1,8 @@
1
- // @vitest-environment jsdom
2
-
1
+ import { describe, expect, mock, test } from "bun:test";
2
+ // mock.module eretzt imports für alle Konsumenten — statische imports
3
+ // vor mock.module sehen die gemockte Version weil Bun am Loader-Level
4
+ // intercepted. useShellUser ist hier ein Mock-Objekt.
5
+ import { useShellUser } from "@cosmicdrift/kumiko-bundled-features/auth-email-password/web";
3
6
  import {
4
7
  createStaticLocaleResolver,
5
8
  LocaleProvider,
@@ -8,37 +11,26 @@ import {
8
11
  import { defaultPrimitives } from "@cosmicdrift/kumiko-renderer-web";
9
12
  import { render, screen } from "@testing-library/react";
10
13
  import type { ReactNode } from "react";
11
- import { describe, expect, test, vi } from "vitest";
12
14
  import { textContentClient } from "../client-plugin";
13
15
 
14
- // Mock-Setup für die drei externen Hooks die TextContentEditor benutzt.
15
- // vi.mock + vi.fn() erlaubt pro-Test verschiedene Roles + Dispatcher-
16
- // Antworten. Memory `[Keine Fake-Tests]`: wir testen echtes Rendering,
17
- // nicht nur die canWrite-Bedingung.
18
- vi.mock("@cosmicdrift/kumiko-bundled-features/auth-email-password/web", () => ({
19
- useShellUser: vi.fn(),
16
+ mock.module("@cosmicdrift/kumiko-bundled-features/auth-email-password/web", () => ({
17
+ useShellUser: mock(),
20
18
  }));
21
19
 
22
- vi.mock("@cosmicdrift/kumiko-renderer", async () => {
23
- const actual = await vi.importActual<typeof import("@cosmicdrift/kumiko-renderer")>(
24
- "@cosmicdrift/kumiko-renderer",
25
- );
26
- return {
27
- ...actual,
28
- useDispatcher: vi.fn(() => ({
29
- write: vi.fn(),
30
- query: vi.fn(),
31
- })),
32
- useQuery: vi.fn(() => ({
33
- data: { slug: "imprint", lang: "de", title: "Impressum", body: "Inhalt" },
34
- loading: false,
35
- error: null,
36
- refetch: vi.fn(),
37
- })),
38
- };
39
- });
40
-
41
- import { useShellUser } from "@cosmicdrift/kumiko-bundled-features/auth-email-password/web";
20
+ const actual_renderer = await import("@cosmicdrift/kumiko-renderer");
21
+ mock.module("@cosmicdrift/kumiko-renderer", () => ({
22
+ ...actual_renderer,
23
+ useDispatcher: mock(() => ({
24
+ write: mock(),
25
+ query: mock(),
26
+ })),
27
+ useQuery: mock(() => ({
28
+ data: { slug: "imprint", lang: "de", title: "Impressum", body: "Inhalt" },
29
+ loading: false,
30
+ error: null,
31
+ refetch: mock(),
32
+ })),
33
+ }));
42
34
 
43
35
  const TARGET = {
44
36
  featureId: "text-content",
@@ -65,21 +57,21 @@ function Wrapper({ children }: { readonly children: ReactNode }): ReactNode {
65
57
 
66
58
  describe("TextContentEditor — role-based write-access", () => {
67
59
  test("TenantAdmin sieht Save-Button + editable inputs", () => {
68
- vi.mocked(useShellUser).mockReturnValue({ id: "u1", roles: ["TenantAdmin"] });
60
+ // biome-ignore lint/suspicious/noExplicitAny: Bun mock function
61
+ (useShellUser as any).mockReturnValue({ id: "u1", roles: ["TenantAdmin"] });
69
62
  const Editor = getEditor();
70
63
  render(<Editor target={TARGET} onClose={() => {}} />, { wrapper: Wrapper });
71
64
 
72
- // Save-Button gerendert (canWrite=true → Button.type=submit)
73
65
  const saveButton = screen.getByRole("button", { name: /speichern/i });
74
66
  expect(saveButton).toBeTruthy();
75
67
  expect(saveButton.hasAttribute("disabled")).toBe(false);
76
68
 
77
- // Read-only-Banner darf NICHT erscheinen
78
69
  expect(screen.queryByText(/Read-only/)).toBeNull();
79
70
  });
80
71
 
81
72
  test("SystemAdmin sieht Save-Button (alternative write-role)", () => {
82
- vi.mocked(useShellUser).mockReturnValue({ id: "u1", roles: ["SystemAdmin"] });
73
+ // biome-ignore lint/suspicious/noExplicitAny: Bun mock function
74
+ (useShellUser as any).mockReturnValue({ id: "u1", roles: ["SystemAdmin"] });
83
75
  const Editor = getEditor();
84
76
  render(<Editor target={TARGET} onClose={() => {}} />, { wrapper: Wrapper });
85
77
 
@@ -88,11 +80,8 @@ describe("TextContentEditor — role-based write-access", () => {
88
80
  });
89
81
 
90
82
  test("Editor-Role sieht Read-only-Banner + KEIN Save-Button", () => {
91
- // Das ist der advisor-flagged Pfad bisher unverifiziert. Editor-
92
- // Role hat in publicstatus's Schema Zugriff auf visual-Workspace +
93
- // by-slug-query (read), aber NICHT auf set.write. UI muss das
94
- // explizit kommunizieren statt 403 erst beim save zu zeigen.
95
- vi.mocked(useShellUser).mockReturnValue({ id: "u1", roles: ["Editor"] });
83
+ // biome-ignore lint/suspicious/noExplicitAny: Bun mock function
84
+ (useShellUser as any).mockReturnValue({ id: "u1", roles: ["Editor"] });
96
85
  const Editor = getEditor();
97
86
  render(<Editor target={TARGET} onClose={() => {}} />, { wrapper: Wrapper });
98
87
 
@@ -101,12 +90,8 @@ describe("TextContentEditor — role-based write-access", () => {
101
90
  });
102
91
 
103
92
  test("Admin-Role (publicstatus-Convention, ohne TenantAdmin-dual-Tag) sieht Read-only-Banner", () => {
104
- // Apps die NUR `Admin` (ohne `TenantAdmin`) im JWT haben, kriegen
105
- // read-only. Dokumentiert das Dual-Role-Pattern aus publicstatus:
106
- // wer "Admin" allein hat (Memory `[Role-Naming-Drift]`), muss
107
- // explizit auch "TenantAdmin" im JWT tragen damit der Editor
108
- // schreiben darf.
109
- vi.mocked(useShellUser).mockReturnValue({ id: "u1", roles: ["Admin"] });
93
+ // biome-ignore lint/suspicious/noExplicitAny: Bun mock function
94
+ (useShellUser as any).mockReturnValue({ id: "u1", roles: ["Admin"] });
110
95
  const Editor = getEditor();
111
96
  render(<Editor target={TARGET} onClose={() => {}} />, { wrapper: Wrapper });
112
97
 
@@ -115,7 +100,8 @@ describe("TextContentEditor — role-based write-access", () => {
115
100
  });
116
101
 
117
102
  test("Logged-out (useShellUser=undefined) sieht Read-only-Banner", () => {
118
- vi.mocked(useShellUser).mockReturnValue(undefined);
103
+ // biome-ignore lint/suspicious/noExplicitAny: Bun mock function
104
+ (useShellUser as any).mockReturnValue(undefined);
119
105
  const Editor = getEditor();
120
106
  render(<Editor target={TARGET} onClose={() => {}} />, { wrapper: Wrapper });
121
107