@cosmicdrift/kumiko-samples 0.367.0 → 0.369.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 (406) hide show
  1. package/package.json +1 -1
  2. package/packages/bundled-features/package.json +16 -2
  3. package/packages/bundled-features/src/audit/handlers/details.query.ts +1 -1
  4. package/packages/bundled-features/src/audit/handlers/list.query.ts +1 -1
  5. package/packages/bundled-features/src/audit/run-escape-hatch-retention.ts +5 -5
  6. package/packages/bundled-features/src/auth-email-password/bootstrap.ts +1 -1
  7. package/packages/bundled-features/src/auth-email-password/handlers/invite-accept-with-login.write.ts +10 -3
  8. package/packages/bundled-features/src/auth-email-password/handlers/invite-accept.write.ts +10 -3
  9. package/packages/bundled-features/src/auth-email-password/handlers/invite-info.query.ts +1 -1
  10. package/packages/bundled-features/src/auth-email-password/handlers/invite-signup-complete.write.ts +6 -2
  11. package/packages/bundled-features/src/auth-email-password/handlers/signup-confirm.write.ts +1 -1
  12. package/packages/bundled-features/src/auth-email-password/seeding.ts +2 -2
  13. package/packages/bundled-features/src/auth-foundation/feature.ts +43 -34
  14. package/packages/bundled-features/src/auth-foundation/index.ts +1 -0
  15. package/packages/bundled-features/src/auth-mfa/db/queries.ts +5 -2
  16. package/packages/bundled-features/src/auth-mfa/handlers/enable-confirm-preauth.write.ts +1 -1
  17. package/packages/bundled-features/src/auth-mfa/handlers/enable-start-preauth.write.ts +1 -1
  18. package/packages/bundled-features/src/auth-mfa/handlers/reencrypt.job.ts +2 -2
  19. package/packages/bundled-features/src/auth-mfa/handlers/status.query.ts +1 -1
  20. package/packages/bundled-features/src/auth-mfa/handlers/verify.write.ts +1 -1
  21. package/packages/bundled-features/src/auth-mfa/mfa-code-verifier.ts +1 -1
  22. package/packages/bundled-features/src/auth-mfa/mfa-status-checker.ts +1 -1
  23. package/packages/bundled-features/src/auth-mfa-user-data/index.ts +19 -10
  24. package/packages/bundled-features/src/billing-foundation/billing-info-query.ts +1 -1
  25. package/packages/bundled-features/src/billing-foundation/feature.ts +138 -135
  26. package/packages/bundled-features/src/billing-foundation/handlers/create-portal-session.write.ts +1 -1
  27. package/packages/bundled-features/src/billing-foundation/projection.ts +3 -2
  28. package/packages/bundled-features/src/cap-counter/calendar-cap-usage-reservation.ts +52 -1
  29. package/packages/bundled-features/src/cap-counter/cap-reservation.ts +203 -15
  30. package/packages/bundled-features/src/cap-counter/changes.json +13 -0
  31. package/packages/bundled-features/src/cap-counter/feature.ts +84 -73
  32. package/packages/bundled-features/src/cap-counter/handlers/increment-rolling.write.ts +3 -4
  33. package/packages/bundled-features/src/cap-counter/index.ts +9 -1
  34. package/packages/bundled-features/src/cap-counter/tables.ts +10 -3
  35. package/packages/bundled-features/src/cap-overview/handlers/caps-usage.query.ts +1 -2
  36. package/packages/bundled-features/src/cap-overview/handlers/tenant-caps-list.query.ts +6 -2
  37. package/packages/bundled-features/src/cap-overview/handlers/tenant-options.query.ts +1 -1
  38. package/packages/bundled-features/src/channel-email/changes.json +7 -0
  39. package/packages/bundled-features/src/channel-email/email-channel.ts +39 -8
  40. package/packages/bundled-features/src/channel-email/feature.ts +1 -1
  41. package/packages/bundled-features/src/channel-email/index.ts +5 -1
  42. package/packages/bundled-features/src/channel-in-app/handlers/inbox.query.ts +1 -1
  43. package/packages/bundled-features/src/channel-in-app/handlers/mark-all-read.write.ts +1 -1
  44. package/packages/bundled-features/src/channel-in-app/handlers/mark-read.write.ts +1 -1
  45. package/packages/bundled-features/src/channel-in-app/handlers/unread-count.query.ts +1 -1
  46. package/packages/bundled-features/src/channel-in-app/tables.ts +1 -1
  47. package/packages/bundled-features/src/compliance-profiles/handlers/for-tenant.query.ts +1 -1
  48. package/packages/bundled-features/src/compliance-profiles/handlers/needs-profile.query.ts +1 -1
  49. package/packages/bundled-features/src/compliance-profiles/handlers/set-profile.write.ts +1 -2
  50. package/packages/bundled-features/src/compliance-profiles/resolve-for-tenant.ts +1 -1
  51. package/packages/bundled-features/src/compliance-profiles/seeding.ts +8 -4
  52. package/packages/bundled-features/src/compliance-profiles-ops/index.ts +26 -17
  53. package/packages/bundled-features/src/config/db/queries/resolver.ts +1 -1
  54. package/packages/bundled-features/src/config/handlers/reencrypt.job.ts +2 -2
  55. package/packages/bundled-features/src/config/resolver.ts +1 -1
  56. package/packages/bundled-features/src/config/web/i18n.ts +1 -2
  57. package/packages/bundled-features/src/config/write-helpers.ts +1 -1
  58. package/packages/bundled-features/src/crypto-shredding/changes.json +6 -0
  59. package/packages/bundled-features/src/crypto-shredding/handlers/forget-subject.write.ts +9 -7
  60. package/packages/bundled-features/src/custom-fields/db/queries/field-access.ts +1 -1
  61. package/packages/bundled-features/src/custom-fields/db/queries/projection.ts +1 -1
  62. package/packages/bundled-features/src/custom-fields/db/queries/quota.ts +1 -1
  63. package/packages/bundled-features/src/custom-fields/db/queries/retention.ts +6 -2
  64. package/packages/bundled-features/src/custom-fields/db/queries/user-data-rights.ts +1 -1
  65. package/packages/bundled-features/src/custom-fields/feature.ts +11 -14
  66. package/packages/bundled-features/src/custom-fields/handlers/clear-custom-field.write.ts +2 -2
  67. package/packages/bundled-features/src/custom-fields/handlers/delete-system-field.write.ts +2 -2
  68. package/packages/bundled-features/src/custom-fields/handlers/delete-tenant-field.write.ts +2 -2
  69. package/packages/bundled-features/src/custom-fields/handlers/set-custom-field.write.ts +2 -2
  70. package/packages/bundled-features/src/custom-fields/wire-for-entity.ts +4 -4
  71. package/packages/bundled-features/src/data-retention/resolve-for-tenant.ts +1 -1
  72. package/packages/bundled-features/src/data-retention/run-retention-cleanup.ts +16 -10
  73. package/packages/bundled-features/src/delivery/address-opt-out.ts +5 -2
  74. package/packages/bundled-features/src/delivery/attempt-log-retention.ts +1 -1
  75. package/packages/bundled-features/src/delivery/changes.json +6 -0
  76. package/packages/bundled-features/src/delivery/channel-context.ts +1 -1
  77. package/packages/bundled-features/src/delivery/db/queries/address-opt-outs.ts +1 -1
  78. package/packages/bundled-features/src/delivery/db/queries/preferences.ts +1 -1
  79. package/packages/bundled-features/src/delivery/delivery-service.ts +16 -6
  80. package/packages/bundled-features/src/delivery/feature.ts +2 -2
  81. package/packages/bundled-features/src/delivery/handlers/log.query.ts +3 -2
  82. package/packages/bundled-features/src/delivery/handlers/preferences.query.ts +1 -1
  83. package/packages/bundled-features/src/delivery/index.ts +8 -1
  84. package/packages/bundled-features/src/delivery/unsubscribe.ts +66 -37
  85. package/packages/bundled-features/src/delivery/upsert-preference.ts +5 -2
  86. package/packages/bundled-features/src/derivatives-sharp/feature.ts +21 -12
  87. package/packages/bundled-features/src/derivatives-sharp/index.ts +1 -1
  88. package/packages/bundled-features/src/document-ingest-foundation/feature.ts +112 -103
  89. package/packages/bundled-features/src/document-ingest-foundation/forget-extract-with-file-ref.ts +3 -3
  90. package/packages/bundled-features/src/document-ingest-foundation/index.ts +4 -1
  91. package/packages/bundled-features/src/feature-toggles/global-feature-state-table.ts +1 -1
  92. package/packages/bundled-features/src/feature-toggles/handlers/set.write.ts +3 -5
  93. package/packages/bundled-features/src/feature-toggles/toggle-runtime.ts +1 -1
  94. package/packages/bundled-features/src/file-derivatives/feature.ts +134 -129
  95. package/packages/bundled-features/src/file-derivatives/handlers/public-variant-by-file-ref.query.ts +1 -1
  96. package/packages/bundled-features/src/file-derivatives/handlers/public-variant.query.ts +1 -1
  97. package/packages/bundled-features/src/file-foundation/feature.ts +38 -29
  98. package/packages/bundled-features/src/file-foundation/index.ts +1 -0
  99. package/packages/bundled-features/src/file-foundation/read-file-ref-content.ts +1 -1
  100. package/packages/bundled-features/src/file-provider-inmemory/feature.ts +32 -21
  101. package/packages/bundled-features/src/file-provider-inmemory/index.ts +6 -1
  102. package/packages/bundled-features/src/file-provider-s3/changes.json +10 -1
  103. package/packages/bundled-features/src/file-provider-s3/env-feature.ts +62 -0
  104. package/packages/bundled-features/src/file-provider-s3/feature.ts +107 -73
  105. package/packages/bundled-features/src/file-provider-s3/index.ts +6 -1
  106. package/packages/bundled-features/src/file-provider-s3/lib/index.ts +3 -0
  107. package/packages/bundled-features/src/{files-provider-s3 → file-provider-s3/lib}/s3-provider.ts +19 -19
  108. package/packages/bundled-features/src/file-provider-s3-env/index.ts +7 -2
  109. package/packages/bundled-features/src/files-provider-s3/index.ts +11 -3
  110. package/packages/bundled-features/src/files-tenant-data/handlers/sweep-orphaned-derivatives.job.ts +1 -1
  111. package/packages/bundled-features/src/files-tenant-data/hooks.ts +1 -2
  112. package/packages/bundled-features/src/folders/feature.ts +8 -3
  113. package/packages/bundled-features/src/folders-user-data/index.ts +27 -18
  114. package/packages/bundled-features/src/form-draft/db/queries/cleanup.ts +1 -1
  115. package/packages/bundled-features/src/form-draft/db/queries/draft-count.ts +1 -1
  116. package/packages/bundled-features/src/form-draft/db/queries/owned-file-refs.ts +3 -2
  117. package/packages/bundled-features/src/form-draft/feature.ts +6 -1
  118. package/packages/bundled-features/src/form-draft/handlers/cleanup.job.ts +2 -2
  119. package/packages/bundled-features/src/form-draft/index.ts +1 -1
  120. package/packages/bundled-features/src/form-draft/lookup.ts +1 -1
  121. package/packages/bundled-features/src/form-draft-user-data/index.ts +19 -10
  122. package/packages/bundled-features/src/inbound-mail-foundation/feature.ts +165 -154
  123. package/packages/bundled-features/src/inbound-mail-foundation/handlers/ingest-message.write.ts +1 -2
  124. package/packages/bundled-features/src/inbound-mail-foundation/index.ts +1 -1
  125. package/packages/bundled-features/src/inbound-mail-foundation/oauth-access-token.ts +5 -2
  126. package/packages/bundled-features/src/inbound-mail-foundation/projection.ts +3 -2
  127. package/packages/bundled-features/src/inbound-mail-foundation/watch-supervisor.ts +1 -1
  128. package/packages/bundled-features/src/inbound-provider-imap/feature.ts +22 -13
  129. package/packages/bundled-features/src/inbound-provider-imap/index.ts +6 -1
  130. package/packages/bundled-features/src/inbound-provider-inmemory/feature.ts +20 -12
  131. package/packages/bundled-features/src/inbound-provider-inmemory/index.ts +1 -0
  132. package/packages/bundled-features/src/jobs/db/queries/retention.ts +1 -1
  133. package/packages/bundled-features/src/jobs/db/queries/stale-run-sweep.ts +1 -1
  134. package/packages/bundled-features/src/jobs/handlers/detail.query.ts +1 -1
  135. package/packages/bundled-features/src/jobs/handlers/list.query.ts +7 -2
  136. package/packages/bundled-features/src/jobs/handlers/retry.write.ts +1 -1
  137. package/packages/bundled-features/src/jobs/handlers/tenant-failures.query.ts +1 -1
  138. package/packages/bundled-features/src/jobs/handlers/tenant-runs.query.ts +1 -1
  139. package/packages/bundled-features/src/jobs/job-run-logger.ts +11 -10
  140. package/packages/bundled-features/src/jobs/tenant-job-failure-table.ts +1 -1
  141. package/packages/bundled-features/src/jobs/tenant-job-run-table.ts +1 -1
  142. package/packages/bundled-features/src/ledger/feature.ts +6 -3
  143. package/packages/bundled-features/src/mail-foundation/feature.ts +66 -46
  144. package/packages/bundled-features/src/mail-foundation/i18n.ts +8 -0
  145. package/packages/bundled-features/src/mail-foundation/index.ts +1 -0
  146. package/packages/bundled-features/src/mail-transport-inmemory/feature.ts +31 -17
  147. package/packages/bundled-features/src/mail-transport-inmemory/index.ts +6 -1
  148. package/packages/bundled-features/src/mail-transport-smtp/changes.json +7 -0
  149. package/packages/bundled-features/src/mail-transport-smtp/config-keys.ts +67 -0
  150. package/packages/bundled-features/src/mail-transport-smtp/feature.ts +67 -82
  151. package/packages/bundled-features/src/mail-transport-smtp/i18n.ts +28 -0
  152. package/packages/bundled-features/src/mail-transport-smtp/index.ts +1 -0
  153. package/packages/bundled-features/src/managed-pages/handlers/by-slug.query.ts +1 -1
  154. package/packages/bundled-features/src/managed-pages/handlers/by-tenant-published.query.ts +1 -2
  155. package/packages/bundled-features/src/managed-pages/handlers/set.write.ts +1 -2
  156. package/packages/bundled-features/src/managed-pages/seeding.ts +8 -4
  157. package/packages/bundled-features/src/metrics/compute.ts +1 -1
  158. package/packages/bundled-features/src/metrics/filters.ts +1 -1
  159. package/packages/bundled-features/src/metrics/validate.ts +1 -1
  160. package/packages/bundled-features/src/notes-history/feature.ts +6 -3
  161. package/packages/bundled-features/src/notes-history/handlers/add-note.write.ts +1 -1
  162. package/packages/bundled-features/src/notes-history-user-data/hooks.ts +1 -1
  163. package/packages/bundled-features/src/notes-history-user-data/index.ts +27 -18
  164. package/packages/bundled-features/src/personal-access-tokens/changes.json +6 -0
  165. package/packages/bundled-features/src/personal-access-tokens/feature.ts +17 -2
  166. package/packages/bundled-features/src/personal-access-tokens/handlers/create.write.ts +1 -1
  167. package/packages/bundled-features/src/personal-access-tokens/handlers/list.query.ts +2 -2
  168. package/packages/bundled-features/src/personal-access-tokens/handlers/revoke.write.ts +1 -1
  169. package/packages/bundled-features/src/personal-access-tokens/resolver.ts +1 -1
  170. package/packages/bundled-features/src/personal-access-tokens/revoke-for-user.ts +2 -2
  171. package/packages/bundled-features/src/personal-access-tokens/roles.ts +8 -6
  172. package/packages/bundled-features/src/personal-access-tokens/screens.ts +2 -0
  173. package/packages/bundled-features/src/readiness/feature.ts +25 -16
  174. package/packages/bundled-features/src/readiness/index.ts +1 -1
  175. package/packages/bundled-features/src/secrets/changes.json +6 -0
  176. package/packages/bundled-features/src/secrets/db/queries/read.ts +1 -1
  177. package/packages/bundled-features/src/secrets/feature.ts +26 -6
  178. package/packages/bundled-features/src/secrets/handlers/delete.write.ts +5 -2
  179. package/packages/bundled-features/src/secrets/handlers/rotate.job.ts +2 -2
  180. package/packages/bundled-features/src/secrets/handlers/set.write.ts +5 -2
  181. package/packages/bundled-features/src/secrets/secrets-context.ts +4 -3
  182. package/packages/bundled-features/src/sessions/db/queries/cleanup.ts +1 -1
  183. package/packages/bundled-features/src/sessions/handlers/detail.query.ts +1 -1
  184. package/packages/bundled-features/src/sessions/handlers/list.query.ts +2 -2
  185. package/packages/bundled-features/src/sessions/handlers/mine.query.ts +1 -1
  186. package/packages/bundled-features/src/sessions/handlers/revoke-all-for-user.write.ts +1 -1
  187. package/packages/bundled-features/src/sessions/handlers/revoke-all-others.write.ts +1 -1
  188. package/packages/bundled-features/src/sessions/handlers/revoke.write.ts +1 -1
  189. package/packages/bundled-features/src/sessions/session-callbacks.ts +14 -7
  190. package/packages/bundled-features/src/sessions/testing.ts +1 -1
  191. package/packages/bundled-features/src/tags/feature.ts +6 -3
  192. package/packages/bundled-features/src/template-resolver/api.ts +1 -1
  193. package/packages/bundled-features/src/template-resolver/constants.ts +0 -1
  194. package/packages/bundled-features/src/template-resolver/handlers/bundle-import.ts +1 -1
  195. package/packages/bundled-features/src/template-resolver/handlers/by-slug.query.ts +1 -1
  196. package/packages/bundled-features/src/template-resolver/handlers/by-tenant.query.ts +1 -2
  197. package/packages/bundled-features/src/template-resolver/handlers/collection-item.query.ts +1 -1
  198. package/packages/bundled-features/src/template-resolver/handlers/collection-list.query.ts +1 -2
  199. package/packages/bundled-features/src/template-resolver/handlers/collection-set.write.ts +1 -1
  200. package/packages/bundled-features/src/template-resolver/handlers/copy-locale.write.ts +1 -1
  201. package/packages/bundled-features/src/template-resolver/handlers/diff-to-system.query.ts +1 -1
  202. package/packages/bundled-features/src/template-resolver/handlers/export-bundle.query.ts +1 -1
  203. package/packages/bundled-features/src/template-resolver/handlers/find-by-id.query.ts +1 -1
  204. package/packages/bundled-features/src/template-resolver/handlers/layout-source.query.ts +1 -1
  205. package/packages/bundled-features/src/template-resolver/handlers/list.query.ts +1 -1
  206. package/packages/bundled-features/src/template-resolver/handlers/overview.query.ts +1 -1
  207. package/packages/bundled-features/src/template-resolver/handlers/reset-to-system.write.ts +1 -1
  208. package/packages/bundled-features/src/template-resolver/handlers/resource-remove.write.ts +1 -1
  209. package/packages/bundled-features/src/template-resolver/handlers/send-test.write.ts +4 -2
  210. package/packages/bundled-features/src/template-resolver/handlers/set.write.ts +1 -1
  211. package/packages/bundled-features/src/template-resolver/handlers/template-write.ts +1 -1
  212. package/packages/bundled-features/src/template-resolver/handlers/tenant-overrides.query.ts +1 -1
  213. package/packages/bundled-features/src/template-resolver/handlers/toggle-status.write.ts +1 -1
  214. package/packages/bundled-features/src/template-resolver/handlers/usage.query.ts +1 -1
  215. package/packages/bundled-features/src/template-resolver/handlers/validate.query.ts +1 -1
  216. package/packages/bundled-features/src/template-resolver/seeding.ts +3 -3
  217. package/packages/bundled-features/src/template-resolver/store-template-resource.ts +1 -1
  218. package/packages/bundled-features/src/template-resolver/template-preview.ts +1 -1
  219. package/packages/bundled-features/src/template-resolver/template-resources.ts +1 -1
  220. package/packages/bundled-features/src/template-resolver/testing.ts +1 -1
  221. package/packages/bundled-features/src/template-resolver-user-data/index.ts +23 -14
  222. package/packages/bundled-features/src/tenant/handlers/active-tenant-ids.query.ts +1 -1
  223. package/packages/bundled-features/src/tenant/handlers/add-member.write.ts +1 -2
  224. package/packages/bundled-features/src/tenant/handlers/cancel-invitation.write.ts +1 -1
  225. package/packages/bundled-features/src/tenant/handlers/invitations.query.ts +1 -1
  226. package/packages/bundled-features/src/tenant/handlers/me.query.ts +1 -1
  227. package/packages/bundled-features/src/tenant/handlers/member-directory.query.ts +1 -1
  228. package/packages/bundled-features/src/tenant/handlers/members.query.ts +1 -1
  229. package/packages/bundled-features/src/tenant/handlers/memberships.query.ts +1 -1
  230. package/packages/bundled-features/src/tenant/handlers/remove-member.write.ts +2 -1
  231. package/packages/bundled-features/src/tenant/handlers/resolve-user-ids.query.ts +1 -1
  232. package/packages/bundled-features/src/tenant/handlers/team-list.query.ts +1 -1
  233. package/packages/bundled-features/src/tenant/handlers/tenant-directory.query.ts +1 -1
  234. package/packages/bundled-features/src/tenant/handlers/update-member-roles.write.ts +1 -2
  235. package/packages/bundled-features/src/tenant/invited-membership.ts +2 -2
  236. package/packages/bundled-features/src/tenant/last-tenant-admin.ts +1 -2
  237. package/packages/bundled-features/src/tenant/seeding.ts +12 -12
  238. package/packages/bundled-features/src/tenant-lifecycle/handlers/cancel-destruction.write.ts +1 -2
  239. package/packages/bundled-features/src/tenant-lifecycle/handlers/request-destruction.write.ts +1 -2
  240. package/packages/bundled-features/src/tenant-lifecycle/lib/revoke-tenant-sessions.ts +1 -1
  241. package/packages/bundled-features/src/tenant-lifecycle/lifecycle-gate.ts +1 -1
  242. package/packages/bundled-features/src/tenant-lifecycle/run-tenant-destroy.ts +3 -3
  243. package/packages/bundled-features/src/tenant-lifecycle/stages.ts +10 -5
  244. package/packages/bundled-features/src/tenant-lifecycle/testing.ts +3 -3
  245. package/packages/bundled-features/src/tenant-settings/feature.ts +34 -29
  246. package/packages/bundled-features/src/tier-engine/feature.ts +426 -428
  247. package/packages/bundled-features/src/tier-engine/handlers/get-tenant-tier.query.ts +2 -3
  248. package/packages/bundled-features/src/tier-engine/handlers/set-tenant-tier.write.ts +5 -2
  249. package/packages/bundled-features/src/user/changes.json +6 -0
  250. package/packages/bundled-features/src/user/db/queries/stream-tenant-backfill.ts +1 -1
  251. package/packages/bundled-features/src/user/handlers/create.write.ts +1 -2
  252. package/packages/bundled-features/src/user/handlers/find-for-auth.query.ts +1 -1
  253. package/packages/bundled-features/src/user/handlers/list.query.ts +1 -1
  254. package/packages/bundled-features/src/user/handlers/update-roles.ts +2 -1
  255. package/packages/bundled-features/src/user/index.ts +5 -1
  256. package/packages/bundled-features/src/user/invited-global-roles.ts +2 -2
  257. package/packages/bundled-features/src/user/principal-status.ts +13 -3
  258. package/packages/bundled-features/src/user/resolve-display-names.ts +1 -1
  259. package/packages/bundled-features/src/user/seeding.ts +5 -5
  260. package/packages/bundled-features/src/user-data-rights/audit-download.ts +2 -2
  261. package/packages/bundled-features/src/user-data-rights/changes.json +6 -0
  262. package/packages/bundled-features/src/user-data-rights/db/queries/export-jobs.ts +1 -1
  263. package/packages/bundled-features/src/user-data-rights/db/queries/forget-cleanup.ts +1 -1
  264. package/packages/bundled-features/src/user-data-rights/feature.ts +7 -4
  265. package/packages/bundled-features/src/user-data-rights/handlers/download-by-job.query.ts +1 -1
  266. package/packages/bundled-features/src/user-data-rights/handlers/download-by-token.query.ts +1 -1
  267. package/packages/bundled-features/src/user-data-rights/handlers/export-status.query.ts +1 -1
  268. package/packages/bundled-features/src/user-data-rights/handlers/list-download-attempts.query.ts +1 -1
  269. package/packages/bundled-features/src/user-data-rights/handlers/my-audit-log.query.ts +1 -1
  270. package/packages/bundled-features/src/user-data-rights/handlers/request-export.write.ts +1 -2
  271. package/packages/bundled-features/src/user-data-rights/handlers/run-forget-cleanup.write.ts +3 -3
  272. package/packages/bundled-features/src/user-data-rights/lib/default-mailers.ts +40 -3
  273. package/packages/bundled-features/src/user-data-rights/lib/deny-if-target-outside-admin-tenant.ts +1 -1
  274. package/packages/bundled-features/src/user-data-rights/lib/update-user-lifecycle.ts +7 -2
  275. package/packages/bundled-features/src/user-data-rights/run-export-jobs.ts +7 -3
  276. package/packages/bundled-features/src/user-data-rights/run-forget-cleanup.ts +11 -5
  277. package/packages/bundled-features/src/user-data-rights/run-user-export.ts +10 -5
  278. package/packages/bundled-features/src/waitlist/handlers/invite.write.ts +1 -2
  279. package/packages/bundled-features/src/waitlist/handlers/reject.write.ts +1 -1
  280. package/packages/bundled-features/src/waitlist/lib.ts +5 -2
  281. package/packages/bundled-features/src/workflow-runner/event-subscriber.ts +1 -1
  282. package/packages/bundled-features/src/workflow-runner/feature.ts +58 -47
  283. package/packages/bundled-features/src/workflow-runner/handlers/resume-run.write.ts +1 -1
  284. package/packages/bundled-features/src/workflow-runner/index.ts +1 -1
  285. package/packages/bundled-features/src/workflow-runner/pending-projection.ts +1 -1
  286. package/samples/apps/admin-console/src/run-config.ts +2 -2
  287. package/samples/apps/cap-billing-demo/package.json +3 -0
  288. package/samples/apps/cap-billing-demo/src/feature.ts +2 -2
  289. package/samples/apps/cap-billing-demo/src/run-config.ts +8 -8
  290. package/samples/apps/marketing-demo/src/app/seed.ts +1 -1
  291. package/samples/apps/marketing-demo/src/db/queries/seed-counts.ts +1 -1
  292. package/samples/apps/showcase/package.json +3 -0
  293. package/samples/apps/showcase/src/app/seed-items.ts +1 -1
  294. package/samples/apps/styleguide/src/app/seed.ts +2 -2
  295. package/samples/apps/styleguide/src/features/ai-settings/seed.ts +2 -2
  296. package/samples/apps/ui-walkthrough/scripts/emit-e2e-data.ts +1 -1
  297. package/samples/apps/ui-walkthrough/src/app/auth-constants.ts +1 -1
  298. package/samples/apps/ui-walkthrough/src/app/seed-rental.ts +2 -2
  299. package/samples/apps/ui-walkthrough/src/app/seed-vehicles.ts +1 -1
  300. package/samples/apps/ui-walkthrough/src/app/seed.ts +1 -1
  301. package/samples/apps/ui-walkthrough/src/db/queries/row-counts.ts +1 -1
  302. package/samples/apps/ui-walkthrough/src/db/queries/task-counts.ts +1 -1
  303. package/samples/apps/use-all-bundled/feature-manifest.json +16 -16
  304. package/samples/apps/use-all-bundled/package.json +3 -0
  305. package/samples/apps/use-all-bundled/scripts/check-coverage.ts +2 -2
  306. package/samples/apps/use-all-bundled/src/app/auth-constants.ts +1 -1
  307. package/samples/apps/use-all-bundled/src/app/cap-overview-caps.ts +2 -2
  308. package/samples/apps/use-all-bundled/src/app/dev-app-options.ts +1 -1
  309. package/samples/apps/use-all-bundled/src/app/seed-documents.ts +1 -1
  310. package/samples/apps/use-all-bundled/src/app/seed.ts +11 -5
  311. package/samples/apps/use-all-bundled/src/run-config.ts +55 -56
  312. package/samples/apps/user-data-rights-demo/README.md +2 -2
  313. package/samples/apps/user-data-rights-demo/package.json +3 -0
  314. package/samples/apps/user-data-rights-demo/src/run-config.ts +7 -7
  315. package/samples/apps/workspaces/package.json +3 -0
  316. package/samples/recipes/access-control/package.json +3 -0
  317. package/samples/recipes/anonymous-access/package.json +3 -0
  318. package/samples/recipes/anonymous-access-multitenant/package.json +3 -0
  319. package/samples/recipes/apex-landing/package.json +1 -0
  320. package/samples/recipes/apex-surface-auth/package.json +3 -0
  321. package/samples/recipes/apex-surface-auth/src/feature.ts +2 -2
  322. package/samples/recipes/api-worker-split/package.json +3 -0
  323. package/samples/recipes/api-worker-split/src/feature.ts +1 -2
  324. package/samples/recipes/auth-claims/package.json +3 -0
  325. package/samples/recipes/auth-foundation-providers/src/feature.ts +1 -1
  326. package/samples/recipes/auth-mfa/package.json +3 -0
  327. package/samples/recipes/basic-entity/package.json +3 -0
  328. package/samples/recipes/chat-channels/package.json +3 -0
  329. package/samples/recipes/compliance-profiles-demo/package.json +3 -0
  330. package/samples/recipes/cross-feature-events/package.json +3 -1
  331. package/samples/recipes/cross-feature-events/src/feature.ts +1 -1
  332. package/samples/recipes/crypto-shredding-hr/package.json +3 -0
  333. package/samples/recipes/currencies-global/package.json +3 -0
  334. package/samples/recipes/currencies-per-tenant/package.json +3 -0
  335. package/samples/recipes/custom-fields-basic/package.json +3 -0
  336. package/samples/recipes/custom-handlers/package.json +3 -0
  337. package/samples/recipes/dashboard-stats/package.json +3 -0
  338. package/samples/recipes/delivery-notifications/package.json +3 -0
  339. package/samples/recipes/e2e-generator/package.json +3 -0
  340. package/samples/recipes/embedded/package.json +3 -0
  341. package/samples/recipes/embedded-entity-form/package.json +1 -0
  342. package/samples/recipes/encrypted-tenant-config/README.md +1 -1
  343. package/samples/recipes/encrypted-tenant-config/package.json +3 -0
  344. package/samples/recipes/error-contract/package.json +3 -0
  345. package/samples/recipes/event-sourcing/package.json +3 -1
  346. package/samples/recipes/event-sourcing/src/feature.ts +16 -20
  347. package/samples/recipes/event-sourcing/tsconfig.json +2 -6
  348. package/samples/recipes/feature-toggles/package.json +3 -0
  349. package/samples/recipes/field-access/package.json +3 -0
  350. package/samples/recipes/files-post-processing/package.json +1 -0
  351. package/samples/recipes/golden-path/README.md +30 -0
  352. package/samples/recipes/golden-path/package.json +11 -0
  353. package/samples/recipes/golden-path/src/feature.ts +70 -0
  354. package/samples/recipes/idempotency/package.json +3 -0
  355. package/samples/recipes/inbound-mail-triage/package.json +3 -0
  356. package/samples/recipes/lane-routing/package.json +3 -0
  357. package/samples/recipes/ledger-banking/package.json +3 -0
  358. package/samples/recipes/ledger-invoicing/package.json +3 -0
  359. package/samples/recipes/legal-pages/package.json +3 -0
  360. package/samples/recipes/lifecycle-hooks/package.json +3 -0
  361. package/samples/recipes/managed-config/package.json +3 -0
  362. package/samples/recipes/managed-pages/package.json +3 -0
  363. package/samples/recipes/notes-history-basic/package.json +3 -0
  364. package/samples/recipes/ownership/package.json +3 -0
  365. package/samples/recipes/pdf-audit-export/package.json +1 -0
  366. package/samples/recipes/pipeline-basics/package.json +3 -0
  367. package/samples/recipes/public-share-token/package.json +3 -0
  368. package/samples/recipes/public-share-token/src/feature.ts +1 -1
  369. package/samples/recipes/rate-limiting/package.json +3 -0
  370. package/samples/recipes/realtime-sse/package.json +3 -0
  371. package/samples/recipes/record-detail-layout/package.json +1 -0
  372. package/samples/recipes/reference-data/package.json +3 -0
  373. package/samples/recipes/relations/package.json +3 -0
  374. package/samples/recipes/screens-nav/package.json +3 -0
  375. package/samples/recipes/search/package.json +3 -0
  376. package/samples/recipes/secrets-demo/package.json +3 -0
  377. package/samples/recipes/security-baseline/src/feature.ts +2 -2
  378. package/samples/recipes/session-revocation/README.md +1 -1
  379. package/samples/recipes/session-revocation/package.json +3 -0
  380. package/samples/recipes/session-revocation/src/feature.ts +1 -1
  381. package/samples/recipes/state-machine/package.json +3 -1
  382. package/samples/recipes/state-machine/src/entities/invoice.ts +2 -2
  383. package/samples/recipes/state-machine/src/events.ts +19 -0
  384. package/samples/recipes/state-machine/src/feature.ts +25 -30
  385. package/samples/recipes/state-machine/src/handlers/_helpers.ts +13 -14
  386. package/samples/recipes/state-machine/src/handlers/create.ts +1 -1
  387. package/samples/recipes/state-machine/src/handlers/transitions.ts +129 -122
  388. package/samples/recipes/state-machine/src/reducer.ts +1 -1
  389. package/samples/recipes/state-machine/tsconfig.json +2 -6
  390. package/samples/recipes/tags-basic/package.json +3 -0
  391. package/samples/recipes/tenant-isolation/package.json +3 -0
  392. package/samples/recipes/tenant-settings/package.json +3 -0
  393. package/samples/recipes/tier-admin/package.json +3 -0
  394. package/samples/recipes/timezones/package.json +3 -0
  395. package/samples/recipes/tsconfig.base.json +1 -1
  396. package/samples/recipes/user-data-rights/package.json +3 -0
  397. package/samples/recipes/user-data-rights/src/feature.ts +6 -2
  398. package/samples/recipes/user-profile/package.json +3 -0
  399. package/samples/recipes/user-profile/src/feature.ts +2 -2
  400. package/samples/recipes/webhook-step/package.json +3 -0
  401. package/samples/recipes/wizard-form/package.json +1 -0
  402. package/samples/recipes/workflow-engine/package.json +3 -0
  403. package/samples/recipes/workflow-engine/src/db/queries/suspended-runs.ts +5 -2
  404. package/samples/recipes/writeform-section/package.json +1 -0
  405. package/packages/bundled-features/src/file-provider-s3-env/feature.ts +0 -58
  406. /package/packages/bundled-features/src/{files-provider-s3 → file-provider-s3/lib}/env-helper.ts +0 -0
@@ -38,10 +38,10 @@
38
38
  // Rows weiter (reference bleibt alt), deshalb id-Cursor-Paging statt einer
39
39
  // einzelnen Page — sonst verstopfen erledigte Rows das batchLimit-Fenster.
40
40
 
41
- import { selectMany } from "@cosmicdrift/kumiko-framework/bun-db";
42
41
  import {
43
42
  collectPiiSubjectFields,
44
43
  configuredPiiSubjectKms,
44
+ isRecordKeyedField,
45
45
  isSelfPiiField,
46
46
  type KmsAdapter,
47
47
  resolveSubjectForField,
@@ -53,6 +53,7 @@ import {
53
53
  createTenantDb,
54
54
  type DbRunner,
55
55
  nullBlindIndexesForSubject,
56
+ selectMany,
56
57
  type TenantDb,
57
58
  type WhereObject,
58
59
  WritePurposes,
@@ -160,10 +161,17 @@ async function purgeMatchingRows(
160
161
  // (purgeMatchingRows's hardDelete select) is enough; the with-files path's
161
162
  // full row works the same way.
162
163
  //
163
- // userOwned/tenantOwned are explicitly excluded: hardDelete only destroys
164
+ // userOwned/tenantOwned are explicitly excluded (except a whenAbsent: "self" field
165
+ // of an ownerless row, which resolves to this row's own record key): hardDelete only destroys
164
166
  // THIS row's own data, but the owning user's/tenant's key may still protect
165
167
  // OTHER live rows that reference the same subject — erasing it here would
166
168
  // be a silent cross-row data-loss bug, not a retention fix.
169
+ function mayResolveToOwnRecordKey(field: EntityDefinition["fields"][string]): boolean {
170
+ if ("tenantOwned" in field && field.tenantOwned === true) return false;
171
+ if ("userOwned" in field && field.userOwned !== undefined) return isRecordKeyedField(field);
172
+ return ("recordOwned" in field && field.recordOwned === true) || isSelfPiiField(field);
173
+ }
174
+
167
175
  function collectHardDeleteEraseSubjects(
168
176
  entity: EntityDefinition,
169
177
  entityName: string,
@@ -173,13 +181,11 @@ function collectHardDeleteEraseSubjects(
173
181
  const subjectsByKey = new Map<string, SubjectId>();
174
182
  for (const fieldName of collectPiiSubjectFields(entity)) {
175
183
  const field = entity.fields[fieldName];
176
- if (!field) continue;
177
- const isRecordOwned = "recordOwned" in field && field.recordOwned === true;
178
- const isUserOwned = "userOwned" in field && field.userOwned !== undefined;
179
- const isTenantOwned = "tenantOwned" in field && field.tenantOwned === true;
180
- if (isUserOwned || isTenantOwned) continue; // never erase — see header comment
181
- if (!isRecordOwned && !isSelfPiiField(field)) continue;
184
+ if (!field || !mayResolveToOwnRecordKey(field)) continue;
182
185
  const subject = resolveSubjectForField(entity, fieldName, row, { tenantId, entityName });
186
+ // whenAbsent: "self" resolves to the owner's key while the row has an owner — never erased here
187
+ const isUserOwned = "userOwned" in field && field.userOwned !== undefined;
188
+ if (isUserOwned && subject?.kind !== "record") continue;
183
189
  if (subject) subjectsByKey.set(subjectIdToKey(subject), subject);
184
190
  }
185
191
  return Array.from(subjectsByKey.values());
@@ -236,7 +242,7 @@ async function forgetRowAndEraseKeys(args: {
236
242
  readonly registry: Registry;
237
243
  }): Promise<{ readonly isSuccess: boolean }> {
238
244
  return runInSubTransaction(args.db, async (sp) => {
239
- const tdb = createTenantDb(sp, args.tenantId, "system");
245
+ const tdb = createTenantDb({ db: sp, tenantId: args.tenantId, mode: "system" });
240
246
  const result = await args.executor.forget(
241
247
  { id: args.row["id"] as EntityId },
242
248
  args.systemUser,
@@ -666,7 +672,7 @@ export async function runRetentionCleanup(
666
672
  // the daily cron converges (hardDelete removes rows, softDelete's isDeleted
667
673
  // filter shrinks the set). A single batched forget/delete event is a follow-up.
668
674
  const systemUser = createSystemUser(tenantId);
669
- const tdb = createTenantDb(db, tenantId, "system");
675
+ const tdb = createTenantDb({ db, tenantId, mode: "system" });
670
676
  const fileHolders = collectSingleFileHolders(registry);
671
677
 
672
678
  // Pre-load every override row for this tenant ONCE — N entities × M
@@ -1,6 +1,9 @@
1
- import { fetchOne } from "@cosmicdrift/kumiko-framework/bun-db";
2
1
  import { computeBlindIndex, configuredBlindIndexKey } from "@cosmicdrift/kumiko-framework/crypto";
3
- import { createEventStoreExecutor, type TenantDb } from "@cosmicdrift/kumiko-framework/db";
2
+ import {
3
+ createEventStoreExecutor,
4
+ fetchOne,
5
+ type TenantDb,
6
+ } from "@cosmicdrift/kumiko-framework/db";
4
7
  import type { SessionUser, TenantId, WriteResult } from "@cosmicdrift/kumiko-framework/engine";
5
8
  import { ConflictError, writeFailure } from "@cosmicdrift/kumiko-framework/errors";
6
9
  import { generateDeterministicId } from "@cosmicdrift/kumiko-framework/utils";
@@ -1,5 +1,5 @@
1
- import { deleteManyBatched } from "@cosmicdrift/kumiko-framework/bun-db";
2
1
  import type { DbConnection } from "@cosmicdrift/kumiko-framework/db";
2
+ import { deleteManyBatched } from "@cosmicdrift/kumiko-framework/db";
3
3
  import { type PruneEventsOptions, pruneEvents } from "@cosmicdrift/kumiko-framework/pipeline";
4
4
  import { deliveryAttemptsTable } from "./tables.js";
5
5
 
@@ -1,4 +1,10 @@
1
1
  [
2
+ {
3
+ "version": "0.369.0",
4
+ "type": "improvement",
5
+ "title": "Unsubscribe links in dev and a reusable result page",
6
+ "detail": "runDevApp accepts unsubscribe: { secret, tenantOrigin? } like runProdApp, mounts the unsubscribe routes and signs the links (base URL from auth.mail.baseUrl or APP_BASE_URL). Both share resolveUnsubscribeWiring from server-runtime. delivery exports renderUnsubscribeResultPage({ kind: \"unsubscribed\" | \"resubscribed\" | \"failed\", locale, resubscribeToken? }), the page the unsubscribe routes answer with, for apps that serve their own (e.g. legacy) unsubscribe links; without a resubscribeToken it offers no undo button."
7
+ },
2
8
  {
3
9
  "version": "0.367.0",
4
10
  "type": "improvement",
@@ -148,7 +148,7 @@ export function buildChannelContext(
148
148
  filesForTenant?: (tenantId: string) => FileContext | undefined,
149
149
  ): ChannelContext {
150
150
  return {
151
- db: createTenantDb(db, tenantId),
151
+ db: createTenantDb({ db, tenantId }),
152
152
  registry,
153
153
  sseBroker,
154
154
  tenantId,
@@ -1,5 +1,5 @@
1
- import { selectMany } from "@cosmicdrift/kumiko-framework/bun-db";
2
1
  import type { DbConnection } from "@cosmicdrift/kumiko-framework/db";
2
+ import { selectMany } from "@cosmicdrift/kumiko-framework/db";
3
3
  import type { TenantId } from "@cosmicdrift/kumiko-framework/engine";
4
4
  import { notificationAddressOptOutsTable } from "../../tables.js";
5
5
 
@@ -1,5 +1,5 @@
1
- import { unsafeReadRetrying } from "@cosmicdrift/kumiko-framework/bun-db";
2
1
  import type { DbConnection } from "@cosmicdrift/kumiko-framework/db";
2
+ import { unsafeReadRetrying } from "@cosmicdrift/kumiko-framework/db";
3
3
  import type { TenantId } from "@cosmicdrift/kumiko-framework/engine";
4
4
 
5
5
  export type NotificationPreferenceRow = {
@@ -15,6 +15,7 @@ import type {
15
15
  } from "@cosmicdrift/kumiko-framework/engine";
16
16
  import {
17
17
  assertNotificationVariables,
18
+ bridgeStub,
18
19
  createSystemUser,
19
20
  } from "@cosmicdrift/kumiko-framework/engine";
20
21
  import type { FileContext } from "@cosmicdrift/kumiko-framework/files";
@@ -24,7 +25,6 @@ import {
24
25
  createEscapeHatchReporter,
25
26
  } from "@cosmicdrift/kumiko-framework/pipeline";
26
27
  import type { SecretsContext } from "@cosmicdrift/kumiko-framework/secrets";
27
- import { bridgeStub } from "@cosmicdrift/kumiko-framework/testing/handler-context";
28
28
  import { generateId } from "@cosmicdrift/kumiko-framework/utils";
29
29
  import type { Redis } from "ioredis";
30
30
  import { fileContextFromResolver } from "../file-foundation/index.js";
@@ -273,17 +273,27 @@ export function createDeliveryService(options: DeliveryServiceOptions): Delivery
273
273
  // path a cross-tenant read the dispatcher never allows. Cross-tenant reach only
274
274
  // exists through the grant-carrying systemDb view below.
275
275
  const isSystem = registry.isHandlerSystemScoped(tenantUserIdsQuery);
276
- const tenantDb = createTenantDb(run.db, tenantId, "tenant", undefined, undefined, undefined, {
277
- report,
276
+ const tenantDb = createTenantDb({
277
+ db: run.db,
278
+ tenantId,
279
+ mode: "tenant",
280
+ grants: {
281
+ report,
282
+ },
278
283
  });
279
284
  // The dispatcher grants a systemScope handler's ctx.systemDb.unsafeRaw() ungated —
280
285
  // the grant comes from the handler's own r.systemScope() registration, not from this
281
286
  // caller. Only the systemDb view's own source TenantDb carries the grant, so it
282
287
  // can't leak onto ctx.db.
283
288
  const systemDbSourceTenantDb = isSystem
284
- ? createTenantDb(run.db, tenantId, "system", undefined, undefined, undefined, {
285
- report,
286
- unsafeRaw: { reason: `r.systemScope() handler "${tenantUserIdsQuery}"` },
289
+ ? createTenantDb({
290
+ db: run.db,
291
+ tenantId,
292
+ mode: "system",
293
+ grants: {
294
+ report,
295
+ unsafeRaw: { reason: `r.systemScope() handler "${tenantUserIdsQuery}"` },
296
+ },
287
297
  })
288
298
  : undefined;
289
299
  // Hand-built context, not routed through the dispatcher — this needs both
@@ -1,5 +1,5 @@
1
- import { updateMany, upsertByPk } from "@cosmicdrift/kumiko-framework/bun-db";
2
1
  import type { DbConnection } from "@cosmicdrift/kumiko-framework/db";
2
+ import { updateMany, upsertByPk } from "@cosmicdrift/kumiko-framework/db";
3
3
  import {
4
4
  access,
5
5
  defineFeature,
@@ -61,7 +61,7 @@ export function createDeliveryFeature(options?: DeliveryFeatureOptions): Feature
61
61
  const attemptLogRetentionDays = resolveAttemptLogRetentionDays(options?.attemptLogRetentionDays);
62
62
  return defineFeature("delivery", (r) => {
63
63
  r.describe(
64
- "The notification dispatch core: call `ctx.notify(notificationType, { to, route, data, priority, idempotencyKey })` from any handler to fan out a notification across all registered channels (email, in-app, push). Inside a write handler the delivery runs after the write has committed (the call returns `{ kind: \"deferred\" }`, a rolled-back write sends nothing); `immediate: true` delivers synchronously and returns `{ kind: \"dispatched\", deliveries }`. It stores per-user channel preferences in the `notification-preference` entity, opt-outs for no-account recipient addresses in `notification-address-opt-out` (keyed by a blind-index hash, never the plaintext address), logs every attempt to `store_delivery_attempts`, and enforces idempotency and rate-limiting \u2014 add `channel-email`, `channel-in-app`, or `channel-push` on top to actually send anything. Unsubscribe/resubscribe links are served by `createUnsubscribeRoutes({ secret })` mounted via the app's `extraRoutes` at `/api/delivery/unsubscribe` and `/api/delivery/resubscribe`: `GET /unsubscribe?token=` renders a confirmation page (no write), `POST /unsubscribe` performs the opt-out (RFC 8058 one-click, token from the form body or query), `POST /resubscribe` undoes it (JSON `{token}` or form body only, never the query — reachable by a mail-client link prefetcher) — sign links with `signUnsubscribeToken` / `signAddressUnsubscribeToken` using the same secret. `channel-email` sets `List-Unsubscribe` / `List-Unsubscribe-Post` automatically when a message's `data.unsubscribeUrl` points at the unsubscribe route. A notification declared with `unsubscribe: true` gets that link from delivery itself when the service has `unsubscribeLinks` (`runProdApp` option `unsubscribe: { secret }` mounts the routes and wires it; base URL from `auth.publicOrigin`, `auth.mail.baseUrl` or `APP_BASE_URL`): user recipients get a user token, `route` recipients an address token (needs a blind-index key, otherwise no link); a `data.unsubscribeUrl` from the caller wins. `unsubscribe.tenantOrigin: (ctx) => origin | null` points the link at the tenant's own host (subdomain or custom domain, must serve this app's `/api`); null, a throw or a non-https value fall back to the app origin. The token carries the mail's locale, so the confirmation and result pages render in the mail's language. To react to a no-account opt-out (e.g. remove the app's own subscriber row), declare `r.job(name, { trigger: { on: DeliveryHandlers.unsubscribeAddress } }, handler)` on a lane the app consumes: it runs after each committed opt-out, also an idempotent repeat, with `unsubscribeAddressPayloadSchema` (`addressHash`, `notificationType`, `channel`) as payload and the tenant in `ctx.triggeredBy.tenantId`; match the subscriber via `hashUnsubscribeAddress(email)`. Declare `unsubscribeUrl` as an optional template variable if the mail body shows it.",
64
+ "The notification dispatch core: call `ctx.notify(notificationType, { to, route, data, priority, idempotencyKey })` from any handler to fan out a notification across all registered channels (email, in-app, push). Inside a write handler the delivery runs after the write has committed (the call returns `{ kind: \"deferred\" }`, a rolled-back write sends nothing); `immediate: true` delivers synchronously and returns `{ kind: \"dispatched\", deliveries }`. It stores per-user channel preferences in the `notification-preference` entity, opt-outs for no-account recipient addresses in `notification-address-opt-out` (keyed by a blind-index hash, never the plaintext address), logs every attempt to `store_delivery_attempts`, and enforces idempotency and rate-limiting \u2014 add `channel-email`, `channel-in-app`, or `channel-push` on top to actually send anything. Unsubscribe/resubscribe links are served by `createUnsubscribeRoutes({ secret })` mounted via the app's `extraRoutes` at `/api/delivery/unsubscribe` and `/api/delivery/resubscribe`: `GET /unsubscribe?token=` renders a confirmation page (no write), `POST /unsubscribe` performs the opt-out (RFC 8058 one-click, token from the form body or query), `POST /resubscribe` undoes it (JSON `{token}` or form body only, never the query — reachable by a mail-client link prefetcher) — sign links with `signUnsubscribeToken` / `signAddressUnsubscribeToken` using the same secret. `channel-email` sets `List-Unsubscribe` / `List-Unsubscribe-Post` automatically when a message's `data.unsubscribeUrl` points at the unsubscribe route. A notification declared with `unsubscribe: true` gets that link from delivery itself when the service has `unsubscribeLinks` (the `runProdApp` and `runDevApp` option `unsubscribe: { secret }` mounts the routes and wires it; base URL from `auth.publicOrigin`, `auth.mail.baseUrl` or `APP_BASE_URL`): user recipients get a user token, `route` recipients an address token (needs a blind-index key, otherwise no link); a `data.unsubscribeUrl` from the caller wins. `unsubscribe.tenantOrigin: (ctx) => origin | null` points the link at the tenant's own host (subdomain or custom domain, must serve this app's `/api`); null, a throw or a non-https value fall back to the app origin. The token carries the mail's locale, so the confirmation and result pages render in the mail's language. An app serving its own (e.g. legacy) unsubscribe links renders the same result page with `renderUnsubscribeResultPage({ kind, locale })`. To react to a no-account opt-out (e.g. remove the app's own subscriber row), declare `r.job(name, { trigger: { on: DeliveryHandlers.unsubscribeAddress } }, handler)` on a lane the app consumes: it runs after each committed opt-out, also an idempotent repeat, with `unsubscribeAddressPayloadSchema` (`addressHash`, `notificationType`, `channel`) as payload and the tenant in `ctx.triggeredBy.tenantId`; match the subscriber via `hashUnsubscribeAddress(email)`. Declare `unsubscribeUrl` as an optional template variable if the mail body shows it.",
65
65
  );
66
66
  r.uiHints({
67
67
  displayLabel: "Notifications \u00b7 Dispatch Core",
@@ -1,8 +1,9 @@
1
- import { selectMany, type WhereObject } from "@cosmicdrift/kumiko-framework/bun-db";
2
1
  import {
3
2
  decodeKeysetCursor,
4
3
  encodeKeysetCursor,
4
+ selectMany,
5
5
  type TenantDb,
6
+ type WhereObject,
6
7
  } from "@cosmicdrift/kumiko-framework/db";
7
8
  import {
8
9
  access,
@@ -66,7 +67,7 @@ function isDeliveryLogSortField(value: string): value is DeliveryLogSortField {
66
67
  type DeliveryLogSortColumn = (typeof DELIVERY_LOG_SORT_COLUMNS)[DeliveryLogSortField];
67
68
 
68
69
  // createdAt round-trips through Temporal.Instant (matches how the WHERE
69
- // builder coerces timestamptz values — see bun-db/query.ts prepareValue);
70
+ // builder coerces timestamptz values — see db/query.ts prepareValue);
70
71
  // every other whitelisted column is a plain text column. Keyed by the
71
72
  // PHYSICAL column, not the display alias — row only has the DB field names
72
73
  // (e.g. notificationType), never the alias (e.g. "type").
@@ -1,4 +1,4 @@
1
- import { selectMany } from "@cosmicdrift/kumiko-framework/bun-db";
1
+ import { selectMany } from "@cosmicdrift/kumiko-framework/db";
2
2
  import { defineQueryHandler } from "@cosmicdrift/kumiko-framework/engine";
3
3
  import { InternalError } from "@cosmicdrift/kumiko-framework/errors";
4
4
  import * as z from "zod";
@@ -20,7 +20,11 @@ export {
20
20
  DEFAULT_ATTEMPT_LOG_RETENTION_DAYS,
21
21
  runAttemptLogRetention,
22
22
  } from "./attempt-log-retention.js";
23
- export { createEmailAttachmentReader, createTemplateMailRenderer } from "./channel-context.js";
23
+ export {
24
+ buildChannelContext,
25
+ createEmailAttachmentReader,
26
+ createTemplateMailRenderer,
27
+ } from "./channel-context.js";
24
28
  export {
25
29
  type ChatWebhookChannelOptions,
26
30
  chatMessageText,
@@ -67,6 +71,7 @@ export {
67
71
  type UnsubscribeAddressPayload,
68
72
  unsubscribeAddressPayloadSchema,
69
73
  } from "./handlers/unsubscribe-address.write.js";
74
+ export { currentNotificationType } from "./notification-type-aliases.js";
70
75
  export { createDeliveryNotifyFactory } from "./notify-factory.js";
71
76
  export {
72
77
  deliveryAttemptsTable,
@@ -93,8 +98,10 @@ export {
93
98
  type AddressUnsubscribeTokenPayload,
94
99
  createUnsubscribeRoutes,
95
100
  DELIVERY_UNSUBSCRIBE_ONE_CLICK_HEADER_VALUE,
101
+ renderUnsubscribeResultPage,
96
102
  signAddressUnsubscribeToken,
97
103
  signUnsubscribeToken,
104
+ type UnsubscribeResultPage,
98
105
  type UnsubscribeRouteOptions,
99
106
  type UnsubscribeTokenPayload,
100
107
  } from "./unsubscribe.js";
@@ -415,33 +415,48 @@ function confirmationPage(token: string, locale: string | undefined): string {
415
415
  );
416
416
  }
417
417
 
418
- function unsubscribedPage(resubscribeToken: string, locale: string | undefined): string {
419
- const lang = pageLocale(locale);
420
- return htmlPage(
421
- lang,
422
- mailT(lang, "mail.unsubscribe.doneTitle"),
423
- `<p>${escapeHtmlAttr(mailT(lang, "mail.unsubscribe.doneMessage"))}</p>
418
+ export type UnsubscribeResultPage =
419
+ // Without a resubscribeToken the page offers no undo button.
420
+ | { readonly kind: "unsubscribed"; readonly resubscribeToken?: string }
421
+ | { readonly kind: "resubscribed" }
422
+ | { readonly kind: "failed" };
423
+
424
+ /** The plain result page the unsubscribe routes answer with, for apps serving their own
425
+ * (e.g. legacy) unsubscribe links. Texts come from the mail translations of `locale`. */
426
+ export function renderUnsubscribeResultPage(
427
+ page: UnsubscribeResultPage & { readonly locale?: string | undefined },
428
+ ): string {
429
+ const lang = pageLocale(page.locale);
430
+ switch (page.kind) {
431
+ case "unsubscribed": {
432
+ const undoForm =
433
+ page.resubscribeToken === undefined
434
+ ? ""
435
+ : `
424
436
  <form method="post" action="${DELIVERY_RESUBSCRIBE_PATH}">
425
- <input type="hidden" name="token" value="${escapeHtmlAttr(resubscribeToken)}">
437
+ <input type="hidden" name="token" value="${escapeHtmlAttr(page.resubscribeToken)}">
426
438
  <button type="submit">${escapeHtmlAttr(mailT(lang, "mail.unsubscribe.undoButton"))}</button>
427
- </form>`,
428
- );
429
- }
430
-
431
- function resubscribedPage(locale: string | undefined): string {
432
- const lang = pageLocale(locale);
433
- return htmlPage(
434
- lang,
435
- mailT(lang, "mail.unsubscribe.resubscribedTitle"),
436
- `<p>${escapeHtmlAttr(mailT(lang, "mail.unsubscribe.resubscribedMessage"))}</p>`,
437
- );
439
+ </form>`;
440
+ return htmlPage(
441
+ lang,
442
+ mailT(lang, "mail.unsubscribe.doneTitle"),
443
+ `<p>${escapeHtmlAttr(mailT(lang, "mail.unsubscribe.doneMessage"))}</p>${undoForm}`,
444
+ );
445
+ }
446
+ case "resubscribed":
447
+ return htmlPage(
448
+ lang,
449
+ mailT(lang, "mail.unsubscribe.resubscribedTitle"),
450
+ `<p>${escapeHtmlAttr(mailT(lang, "mail.unsubscribe.resubscribedMessage"))}</p>`,
451
+ );
452
+ case "failed": {
453
+ const message = mailT(lang, "mail.unsubscribe.failed");
454
+ return htmlPage(lang, message, `<p>${escapeHtmlAttr(message)}</p>`);
455
+ }
456
+ }
438
457
  }
439
458
 
440
- function failedPage(locale: string | undefined): string {
441
- const lang = pageLocale(locale);
442
- const message = mailT(lang, "mail.unsubscribe.failed");
443
- return htmlPage(lang, message, `<p>${escapeHtmlAttr(message)}</p>`);
444
- }
459
+ const NO_STORE_HEADERS = { "Cache-Control": "no-store" } as const;
445
460
 
446
461
  function isFormRequest(contentType: string | undefined): boolean {
447
462
  return contentType?.includes("application/x-www-form-urlencoded") === true;
@@ -516,12 +531,20 @@ export function createUnsubscribeRoutes(
516
531
  // (e.g. events-table missing, DB down) behind a misleading 400.
517
532
  const succeeded = await dispatchUnsubscribeWrite(verified, deps);
518
533
  if (!succeeded) {
519
- return c.html(failedPage(verified.locale), 500, { "Cache-Control": "no-store" });
534
+ return c.html(
535
+ renderUnsubscribeResultPage({ kind: "failed", locale: verified.locale }),
536
+ 500,
537
+ NO_STORE_HEADERS,
538
+ );
520
539
  }
521
540
  return c.html(
522
- unsubscribedPage(await signResubscribeToken(verified, encodedSecret), verified.locale),
541
+ renderUnsubscribeResultPage({
542
+ kind: "unsubscribed",
543
+ resubscribeToken: await signResubscribeToken(verified, encodedSecret),
544
+ locale: verified.locale,
545
+ }),
523
546
  200,
524
- { "Cache-Control": "no-store" },
547
+ NO_STORE_HEADERS,
525
548
  );
526
549
  },
527
550
  });
@@ -540,22 +563,28 @@ export function createUnsubscribeRoutes(
540
563
  // The undo button on the unsubscribed page posts a form; a browser should land on a page.
541
564
  if (isFormRequest(c.req.header("content-type"))) {
542
565
  return outcome === "success"
543
- ? c.html(resubscribedPage(verified.locale), 200, { "Cache-Control": "no-store" })
544
- : c.html(failedPage(verified.locale), outcome === "limit_reached" ? 409 : 500, {
545
- "Cache-Control": "no-store",
546
- });
566
+ ? c.html(
567
+ renderUnsubscribeResultPage({ kind: "resubscribed", locale: verified.locale }),
568
+ 200,
569
+ NO_STORE_HEADERS,
570
+ )
571
+ : c.html(
572
+ renderUnsubscribeResultPage({ kind: "failed", locale: verified.locale }),
573
+ outcome === "limit_reached" ? 409 : 500,
574
+ NO_STORE_HEADERS,
575
+ );
547
576
  }
548
577
  if (outcome === "limit_reached") {
549
- return c.json({ error: { code: DeliveryErrors.resubscribeLimitReached } }, 409, {
550
- "Cache-Control": "no-store",
551
- });
578
+ return c.json(
579
+ { error: { code: DeliveryErrors.resubscribeLimitReached } },
580
+ 409,
581
+ NO_STORE_HEADERS,
582
+ );
552
583
  }
553
584
  if (outcome === "failed") {
554
- return c.json({ error: { code: "resubscribe_failed" } }, 500, {
555
- "Cache-Control": "no-store",
556
- });
585
+ return c.json({ error: { code: "resubscribe_failed" } }, 500, NO_STORE_HEADERS);
557
586
  }
558
- return c.json({ isSuccess: true }, 200, { "Cache-Control": "no-store" });
587
+ return c.json({ isSuccess: true }, 200, NO_STORE_HEADERS);
559
588
  },
560
589
  });
561
590
 
@@ -13,8 +13,11 @@
13
13
  // fall through to update, or no-op if the winner already set the target
14
14
  // `enabled` state.
15
15
 
16
- import { fetchOne } from "@cosmicdrift/kumiko-framework/bun-db";
17
- import { createEventStoreExecutor, type TenantDb } from "@cosmicdrift/kumiko-framework/db";
16
+ import {
17
+ createEventStoreExecutor,
18
+ fetchOne,
19
+ type TenantDb,
20
+ } from "@cosmicdrift/kumiko-framework/db";
18
21
  import type { SessionUser, TenantId, WriteResult } from "@cosmicdrift/kumiko-framework/engine";
19
22
  import { generateDeterministicId } from "@cosmicdrift/kumiko-framework/utils";
20
23
  import { notificationPreferenceEntity, notificationPreferencesTable } from "./tables.js";
@@ -6,17 +6,26 @@ import { renderImage } from "./render.js";
6
6
 
7
7
  const FEATURE_NAME = "derivatives-sharp";
8
8
 
9
- export const derivativesSharpFeature = defineFeature(FEATURE_NAME, (r) => {
10
- r.describe(
11
- "Registers an `image/*` renderer against the `derivativeRenderer` extension point declared by `file-derivatives`, backed by sharp. Supports resize/fit (cover, inside, contain), format conversion with quality (webp, avif, jpeg), whole-image blur, and blurring individual regions burned in before resize (see BlurRegion). EXIF orientation is applied and then normalized away, and all other EXIF (including GPS) is dropped. Server-only: sharp is a native binding — never import this from client code.",
9
+ export function createDerivativesSharpFeature() {
10
+ return defineFeature(
11
+ FEATURE_NAME,
12
+ (r) => {
13
+ r.describe(
14
+ "Registers an `image/*` renderer against the `derivativeRenderer` extension point declared by `file-derivatives`, backed by sharp. Supports resize/fit (cover, inside, contain), format conversion with quality (webp, avif, jpeg), whole-image blur, and blurring individual regions burned in before resize (see BlurRegion). EXIF orientation is applied and then normalized away, and all other EXIF (including GPS) is dropped. Server-only: sharp is a native binding — never import this from client code.",
15
+ );
16
+ r.uiHints({
17
+ displayLabel: "File Derivatives · Sharp",
18
+ category: "storage",
19
+ recommended: false,
20
+ });
21
+ r.requires({ features: ["file-derivatives"] });
22
+
23
+ const plugin: DerivativeRendererPlugin = { render: renderImage };
24
+ r.useExtension(EXT_DERIVATIVE_RENDERER, "image/*", plugin);
25
+ },
26
+ { dedupeOptions: {} },
12
27
  );
13
- r.uiHints({
14
- displayLabel: "File Derivatives · Sharp",
15
- category: "storage",
16
- recommended: false,
17
- });
18
- r.requires({ features: ["file-derivatives"] });
28
+ }
19
29
 
20
- const plugin: DerivativeRendererPlugin = { render: renderImage };
21
- r.useExtension(EXT_DERIVATIVE_RENDERER, "image/*", plugin);
22
- });
30
+ /** @deprecated Use createDerivativesSharpFeature(). Run `bun kumiko-upgrade --apply`. Removed in the first release after 2026-10-29. */
31
+ export const derivativesSharpFeature = createDerivativesSharpFeature();
@@ -1,2 +1,2 @@
1
- export { derivativesSharpFeature } from "./feature.js";
1
+ export { createDerivativesSharpFeature, derivativesSharpFeature } from "./feature.js";
2
2
  export { imageMetadata } from "./render.js";