@cosmicdrift/kumiko-bundled-features 0.362.0 → 0.364.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 (468) hide show
  1. package/dist/admin-shell/feature.d.ts +3 -0
  2. package/dist/admin-shell/feature.js +6 -1
  3. package/dist/agent-tools/agent-manifest.js +2 -1
  4. package/dist/agent-tools/filter-ops.d.ts +1 -1
  5. package/dist/agent-tools/filter-ops.js +1 -3
  6. package/dist/agent-tools/tool-dispatch.js +2 -1
  7. package/dist/audit/handlers/list.query.js +9 -5
  8. package/dist/auth-email-password/bootstrap.d.ts +8 -5
  9. package/dist/auth-email-password/bootstrap.js +12 -4
  10. package/dist/auth-email-password/email-rate-limits.d.ts +4 -0
  11. package/dist/auth-email-password/email-rate-limits.js +13 -0
  12. package/dist/auth-email-password/email-templates.d.ts +4 -26
  13. package/dist/auth-email-password/email-templates.js +9 -101
  14. package/dist/auth-email-password/feature.js +24 -6
  15. package/dist/auth-email-password/handlers/change-password.write.d.ts +1 -1
  16. package/dist/auth-email-password/handlers/change-password.write.js +6 -2
  17. package/dist/auth-email-password/handlers/confirm-account-unlock.write.d.ts +1 -1
  18. package/dist/auth-email-password/handlers/invite-accept-with-login.write.d.ts +1 -1
  19. package/dist/auth-email-password/handlers/invite-accept.write.d.ts +1 -1
  20. package/dist/auth-email-password/handlers/invite-create.write.d.ts +2 -2
  21. package/dist/auth-email-password/handlers/invite-create.write.js +4 -4
  22. package/dist/auth-email-password/handlers/invite-signup-complete.write.d.ts +1 -1
  23. package/dist/auth-email-password/handlers/login.write.d.ts +1 -1
  24. package/dist/auth-email-password/handlers/logout.write.d.ts +1 -1
  25. package/dist/auth-email-password/handlers/logout.write.js +6 -2
  26. package/dist/auth-email-password/handlers/request-account-unlock.write.d.ts +1 -1
  27. package/dist/auth-email-password/handlers/request-account-unlock.write.js +2 -4
  28. package/dist/auth-email-password/handlers/request-email-verification.write.d.ts +1 -1
  29. package/dist/auth-email-password/handlers/request-email-verification.write.js +2 -4
  30. package/dist/auth-email-password/handlers/request-password-reset.write.d.ts +1 -1
  31. package/dist/auth-email-password/handlers/request-password-reset.write.js +2 -4
  32. package/dist/auth-email-password/handlers/reset-password.write.d.ts +1 -1
  33. package/dist/auth-email-password/handlers/signup-confirm.write.d.ts +1 -1
  34. package/dist/auth-email-password/handlers/signup-confirm.write.js +3 -4
  35. package/dist/auth-email-password/handlers/signup-request.write.d.ts +1 -1
  36. package/dist/auth-email-password/handlers/signup-request.write.js +2 -1
  37. package/dist/auth-email-password/handlers/switch-tenant-mfa-gate.write.d.ts +1 -1
  38. package/dist/auth-email-password/handlers/token-request-handler.d.ts +2 -3
  39. package/dist/auth-email-password/handlers/token-request-handler.js +2 -2
  40. package/dist/auth-email-password/handlers/verify-email.write.d.ts +1 -1
  41. package/dist/auth-email-password/index.d.ts +2 -2
  42. package/dist/auth-email-password/index.js +1 -1
  43. package/dist/auth-email-password/magic-link-mail.d.ts +3 -2
  44. package/dist/auth-email-password/magic-link-mail.js +19 -14
  45. package/dist/auth-email-password/mail-notifications.d.ts +32 -0
  46. package/dist/auth-email-password/mail-notifications.js +116 -0
  47. package/dist/auth-email-password/signup-activation.d.ts +1 -1
  48. package/dist/auth-email-password/signup-activation.js +2 -3
  49. package/dist/auth-email-password/web/auth-gate.js +16 -4
  50. package/dist/auth-foundation/types.js +2 -8
  51. package/dist/auth-mfa/feature.js +4 -4
  52. package/dist/auth-mfa/handlers/disable.write.d.ts +1 -1
  53. package/dist/auth-mfa/handlers/enable-confirm-preauth.write.d.ts +1 -1
  54. package/dist/auth-mfa/handlers/enable-confirm.write.d.ts +1 -1
  55. package/dist/auth-mfa/handlers/enable-start-preauth.write.d.ts +1 -1
  56. package/dist/auth-mfa/handlers/enable-start.write.d.ts +1 -1
  57. package/dist/auth-mfa/handlers/regenerate-recovery.write.d.ts +1 -1
  58. package/dist/auth-mfa/handlers/verify.write.d.ts +1 -1
  59. package/dist/auth-mfa/hmac-token-codec.js +4 -4
  60. package/dist/auth-mfa/mfa-challenge-token.js +3 -3
  61. package/dist/auth-mfa/mfa-preauth-setup-token.js +3 -3
  62. package/dist/auth-mfa/mfa-setup-token.js +8 -8
  63. package/dist/billing-foundation/constants.d.ts +3 -3
  64. package/dist/billing-foundation/constants.js +5 -4
  65. package/dist/billing-foundation/consumer-protection/confirmation-mail.d.ts +32 -14
  66. package/dist/billing-foundation/consumer-protection/confirmation-mail.js +74 -32
  67. package/dist/billing-foundation/consumer-protection/consent-gate.d.ts +3 -0
  68. package/dist/billing-foundation/consumer-protection/consent-gate.js +21 -2
  69. package/dist/billing-foundation/consumer-protection/issue-confirmation.js +3 -3
  70. package/dist/billing-foundation/consumer-protection/mail-notifications.d.ts +8 -0
  71. package/dist/billing-foundation/consumer-protection/mail-notifications.js +95 -0
  72. package/dist/billing-foundation/consumer-protection/register-consent.js +2 -0
  73. package/dist/billing-foundation/consumer-protection/register-termination.js +11 -1
  74. package/dist/billing-foundation/consumer-protection/termination-mail.d.ts +25 -13
  75. package/dist/billing-foundation/consumer-protection/termination-mail.js +11 -23
  76. package/dist/billing-foundation/consumer-protection/termination-notify.js +2 -2
  77. package/dist/billing-foundation/consumer-protection/termination-pages.js +1 -3
  78. package/dist/billing-foundation/consumer-protection/termination-receipt-limit.d.ts +6 -0
  79. package/dist/billing-foundation/consumer-protection/termination-receipt-limit.js +34 -0
  80. package/dist/billing-foundation/consumer-protection/termination-record.d.ts +2 -0
  81. package/dist/billing-foundation/consumer-protection/termination-record.js +7 -1
  82. package/dist/billing-foundation/consumer-protection/termination-request.js +31 -21
  83. package/dist/billing-foundation/consumer-protection/termination-texts.d.ts +1 -1
  84. package/dist/billing-foundation/consumer-protection/termination-texts.js +6 -0
  85. package/dist/billing-foundation/consumer-protection/withdrawal-period.d.ts +10 -0
  86. package/dist/billing-foundation/consumer-protection/withdrawal-period.js +26 -0
  87. package/dist/billing-foundation/handlers/create-checkout-session.write.js +11 -5
  88. package/dist/billing-foundation/handlers/process-event.write.js +12 -8
  89. package/dist/billing-foundation/handlers/process-payment-event.write.js +1 -1
  90. package/dist/billing-foundation/i18n.js +3 -0
  91. package/dist/billing-foundation/subscription-tier-sync.d.ts +7 -1
  92. package/dist/billing-foundation/subscription-tier-sync.js +16 -9
  93. package/dist/billing-foundation/types.d.ts +7 -2
  94. package/dist/cap-counter/calendar-cap-usage-reservation.js +2 -0
  95. package/dist/cap-counter/enforce-cap.d.ts +18 -1
  96. package/dist/cap-counter/enforce-cap.js +49 -6
  97. package/dist/cap-counter/index.d.ts +1 -1
  98. package/dist/cap-counter/index.js +1 -1
  99. package/dist/cap-counter/with-cap-enforcement.d.ts +2 -0
  100. package/dist/cap-overview/handlers/tenant-caps-list.query.js +2 -1
  101. package/dist/cap-overview/web/cap-usage-cell.js +5 -4
  102. package/dist/channel-email/email-channel.d.ts +2 -2
  103. package/dist/channel-email/email-channel.js +101 -26
  104. package/dist/channel-email/feature.js +3 -3
  105. package/dist/channel-email/index.d.ts +1 -1
  106. package/dist/channel-email/index.js +1 -1
  107. package/dist/channel-email/pii-guard.js +10 -0
  108. package/dist/channel-email/smtp-transport.js +13 -0
  109. package/dist/channel-email/types.d.ts +8 -0
  110. package/dist/channel-in-app/handlers/mark-all-read.write.d.ts +1 -1
  111. package/dist/channel-in-app/handlers/mark-read.write.d.ts +1 -1
  112. package/dist/compliance-profiles/handlers/set-profile.write.d.ts +1 -1
  113. package/dist/config/handlers/reset.write.d.ts +1 -1
  114. package/dist/config/handlers/set.write.d.ts +1 -1
  115. package/dist/config/handlers/set.write.js +3 -4
  116. package/dist/config/resolver.d.ts +1 -0
  117. package/dist/config/resolver.js +11 -14
  118. package/dist/config/write-helpers.d.ts +2 -0
  119. package/dist/config/write-helpers.js +19 -0
  120. package/dist/crypto-shredding/handlers/forget-subject.write.d.ts +1 -1
  121. package/dist/crypto-shredding/handlers/forget-subject.write.js +29 -19
  122. package/dist/custom-fields/handlers/update-tenant-field.write.js +0 -1
  123. package/dist/custom-fields/lib/value-schema.js +10 -11
  124. package/dist/custom-fields/wire-user-data-rights.js +7 -6
  125. package/dist/data-retention/index.d.ts +1 -0
  126. package/dist/data-retention/index.js +1 -0
  127. package/dist/data-retention/run-retention-cleanup.js +6 -6
  128. package/dist/delivery/attachment-ref.d.ts +41 -0
  129. package/dist/delivery/attachment-ref.js +78 -0
  130. package/dist/delivery/channel-context.d.ts +8 -4
  131. package/dist/delivery/channel-context.js +94 -2
  132. package/dist/delivery/constants.d.ts +1 -0
  133. package/dist/delivery/constants.js +1 -0
  134. package/dist/delivery/delivery-service.d.ts +4 -1
  135. package/dist/delivery/delivery-service.js +38 -32
  136. package/dist/delivery/feature.js +1 -1
  137. package/dist/delivery/handlers/resubscribe-address.write.d.ts +1 -1
  138. package/dist/delivery/handlers/resubscribe-user.write.d.ts +1 -1
  139. package/dist/delivery/handlers/set-preference.write.d.ts +1 -1
  140. package/dist/delivery/handlers/unsubscribe-address.write.d.ts +1 -1
  141. package/dist/delivery/handlers/unsubscribe-user.write.d.ts +1 -1
  142. package/dist/delivery/index.d.ts +5 -2
  143. package/dist/delivery/index.js +4 -1
  144. package/dist/delivery/jobs.js +66 -14
  145. package/dist/delivery/notify-factory.js +4 -1
  146. package/dist/delivery/testing.d.ts +4 -1
  147. package/dist/delivery/types.d.ts +26 -14
  148. package/dist/delivery/unsubscribe.js +4 -1
  149. package/dist/derivatives-sharp/render.js +1 -18
  150. package/dist/document-ingest-foundation/pages.js +4 -4
  151. package/dist/feature-toggles/handlers/set.write.d.ts +1 -1
  152. package/dist/feature-toggles/toggle-sync-signal.js +4 -4
  153. package/dist/file-derivatives/handlers/public-variant.query.js +1 -1
  154. package/dist/file-foundation/index.d.ts +1 -0
  155. package/dist/file-foundation/index.js +1 -0
  156. package/dist/file-foundation/read-file-ref-content.d.ts +27 -0
  157. package/dist/file-foundation/read-file-ref-content.js +51 -0
  158. package/dist/files-tenant-data/hooks.js +4 -0
  159. package/dist/folders/web/folder-manager.js +3 -2
  160. package/dist/form-draft/db/queries/cleanup.js +4 -4
  161. package/dist/form-draft/db/queries/draft-count.js +2 -2
  162. package/dist/form-draft/db/queries/owned-file-refs.js +2 -2
  163. package/dist/form-draft/handlers/discard.write.d.ts +1 -1
  164. package/dist/form-draft/handlers/save.write.d.ts +1 -1
  165. package/dist/form-draft/release-file-refs.js +3 -2
  166. package/dist/inbound-mail-foundation/handlers/connect-account.write.js +1 -1
  167. package/dist/inbound-mail-foundation/handlers/ingest-message.write.js +6 -6
  168. package/dist/inbound-mail-foundation/handlers/scope-visibility.d.ts +1 -1
  169. package/dist/inbound-mail-foundation/oauth-access-token.js +8 -3
  170. package/dist/inbound-mail-foundation/oauth-state.js +7 -7
  171. package/dist/inbound-mail-foundation/types.js +4 -4
  172. package/dist/inbound-mail-foundation/watch-supervisor.js +24 -13
  173. package/dist/inbound-provider-imap/imap-client.js +4 -3
  174. package/dist/jobs/db/queries/stale-run-sweep.d.ts +1 -1
  175. package/dist/jobs/db/queries/stale-run-sweep.js +12 -5
  176. package/dist/jobs/feature.js +5 -3
  177. package/dist/jobs/handlers/list.query.js +3 -1
  178. package/dist/jobs/handlers/retry.write.d.ts +1 -1
  179. package/dist/jobs/handlers/stale-run-sweep.job.d.ts +1 -1
  180. package/dist/jobs/handlers/stale-run-sweep.job.js +2 -2
  181. package/dist/jobs/handlers/trigger.write.d.ts +1 -1
  182. package/dist/jobs/job-run-logger.js +32 -10
  183. package/dist/jobs/tenant-job-run-table.js +3 -2
  184. package/dist/ledger/entity.js +3 -1
  185. package/dist/ledger/recurring.js +1 -3
  186. package/dist/managed-pages/branding.d.ts +2 -0
  187. package/dist/managed-pages/branding.js +24 -2
  188. package/dist/managed-pages/feature.js +1 -0
  189. package/dist/managed-pages/handlers/set.write.d.ts +1 -1
  190. package/dist/managed-pages/i18n.js +1 -0
  191. package/dist/managed-pages/screens/branding-screen.js +8 -1
  192. package/dist/page-render/branding.d.ts +1 -0
  193. package/dist/page-render/branding.js +1 -0
  194. package/dist/page-render/markdown.js +1 -1
  195. package/dist/personal-access-tokens/feature.js +1 -1
  196. package/dist/personal-access-tokens/handlers/create.write.d.ts +1 -1
  197. package/dist/personal-access-tokens/handlers/list.query.js +2 -1
  198. package/dist/personal-access-tokens/handlers/revoke.write.d.ts +1 -1
  199. package/dist/personal-access-tokens/revoke-for-user.js +2 -1
  200. package/dist/renderer-foundation/api.d.ts +13 -13
  201. package/dist/renderer-foundation/api.js +1 -1
  202. package/dist/renderer-foundation/constants.js +2 -4
  203. package/dist/renderer-foundation/feature.js +7 -7
  204. package/dist/renderer-foundation/index.d.ts +2 -1
  205. package/dist/renderer-foundation/index.js +1 -0
  206. package/dist/renderer-foundation/types.d.ts +94 -32
  207. package/dist/renderer-pdf/feature.js +7 -1
  208. package/dist/renderer-pdf/render-table-pdf.d.ts +27 -33
  209. package/dist/renderer-pdf/render-table-pdf.js +95 -34
  210. package/dist/secrets/feature.js +4 -3
  211. package/dist/secrets/handlers/delete.write.d.ts +1 -1
  212. package/dist/secrets/handlers/set.write.d.ts +1 -1
  213. package/dist/secrets/secrets-context.js +15 -3
  214. package/dist/sessions/db/queries/cleanup.js +2 -2
  215. package/dist/sessions/feature.js +1 -1
  216. package/dist/sessions/handlers/list.query.js +2 -1
  217. package/dist/sessions/handlers/revoke-all-for-user.write.d.ts +1 -1
  218. package/dist/sessions/handlers/revoke-all-others.write.d.ts +1 -1
  219. package/dist/sessions/handlers/revoke.write.d.ts +1 -1
  220. package/dist/shared/attachment-file-name.d.ts +3 -0
  221. package/dist/shared/attachment-file-name.js +35 -0
  222. package/dist/shared/chunked-entity-migration.d.ts +1 -0
  223. package/dist/shared/chunked-entity-migration.js +1 -1
  224. package/dist/shared/index.d.ts +3 -0
  225. package/dist/shared/index.js +3 -0
  226. package/dist/shared/is-tenant-db.js +1 -1
  227. package/dist/shared/mail-template-body.d.ts +7 -0
  228. package/dist/shared/mail-template-body.js +27 -0
  229. package/dist/shared/sharp-input-limits.d.ts +6 -0
  230. package/dist/shared/sharp-input-limits.js +22 -0
  231. package/dist/shared/signup-handover.js +2 -2
  232. package/dist/subscription-stripe/plugin-methods.js +18 -8
  233. package/dist/subscription-stripe/verify-webhook.d.ts +1 -1
  234. package/dist/tags/handlers/assign-tag.write.js +3 -1
  235. package/dist/tags/web/tag-chip.js +2 -0
  236. package/dist/template-resolver/api.d.ts +30 -2
  237. package/dist/template-resolver/api.js +42 -6
  238. package/dist/template-resolver/compile-document-body.d.ts +12 -0
  239. package/dist/template-resolver/compile-document-body.js +205 -0
  240. package/dist/template-resolver/constants.d.ts +13 -4
  241. package/dist/template-resolver/constants.js +32 -10
  242. package/dist/template-resolver/document-sanitizer.d.ts +32 -0
  243. package/dist/template-resolver/document-sanitizer.js +201 -0
  244. package/dist/template-resolver/document-schema.d.ts +21 -0
  245. package/dist/template-resolver/document-schema.js +316 -0
  246. package/dist/template-resolver/feature.d.ts +20 -0
  247. package/dist/template-resolver/feature.js +53 -6
  248. package/dist/template-resolver/handlers/bundle-import.d.ts +20 -0
  249. package/dist/template-resolver/handlers/bundle-import.js +147 -0
  250. package/dist/template-resolver/handlers/collection-set.write.d.ts +1 -1
  251. package/dist/template-resolver/handlers/collection-set.write.js +25 -2
  252. package/dist/template-resolver/handlers/copy-locale.write.d.ts +19 -0
  253. package/dist/template-resolver/handlers/copy-locale.write.js +134 -0
  254. package/dist/template-resolver/handlers/diff-to-system.query.d.ts +22 -0
  255. package/dist/template-resolver/handlers/diff-to-system.query.js +30 -0
  256. package/dist/template-resolver/handlers/discard-draft.write.d.ts +13 -0
  257. package/dist/template-resolver/handlers/discard-draft.write.js +37 -0
  258. package/dist/template-resolver/handlers/export-bundle.query.d.ts +35 -0
  259. package/dist/template-resolver/handlers/export-bundle.query.js +118 -0
  260. package/dist/template-resolver/handlers/find-by-id.query.d.ts +2 -0
  261. package/dist/template-resolver/handlers/find-by-id.query.js +9 -6
  262. package/dist/template-resolver/handlers/import-bundle-system.write.d.ts +39 -0
  263. package/dist/template-resolver/handlers/import-bundle-system.write.js +36 -0
  264. package/dist/template-resolver/handlers/import-bundle.write.d.ts +40 -0
  265. package/dist/template-resolver/handlers/import-bundle.write.js +19 -0
  266. package/dist/template-resolver/handlers/layout-source.query.d.ts +31 -0
  267. package/dist/template-resolver/handlers/layout-source.query.js +87 -0
  268. package/dist/template-resolver/handlers/list.query.d.ts +4 -1
  269. package/dist/template-resolver/handlers/list.query.js +9 -8
  270. package/dist/template-resolver/handlers/override-diff.d.ts +20 -0
  271. package/dist/template-resolver/handlers/override-diff.js +41 -0
  272. package/dist/template-resolver/handlers/overview.query.d.ts +65 -0
  273. package/dist/template-resolver/handlers/overview.query.js +205 -0
  274. package/dist/template-resolver/handlers/preview.query.d.ts +12 -0
  275. package/dist/template-resolver/handlers/preview.query.js +24 -0
  276. package/dist/template-resolver/handlers/reset-to-system.write.d.ts +8 -0
  277. package/dist/template-resolver/handlers/reset-to-system.write.js +50 -0
  278. package/dist/template-resolver/handlers/resource-link-pdf.write.d.ts +8 -0
  279. package/dist/template-resolver/handlers/resource-link-pdf.write.js +32 -0
  280. package/dist/template-resolver/handlers/resource-remove.write.d.ts +9 -0
  281. package/dist/template-resolver/handlers/resource-remove.write.js +44 -0
  282. package/dist/template-resolver/handlers/resource-shared.d.ts +9 -0
  283. package/dist/template-resolver/handlers/resource-shared.js +14 -0
  284. package/dist/template-resolver/handlers/resource-upload.write.d.ts +13 -0
  285. package/dist/template-resolver/handlers/resource-upload.write.js +98 -0
  286. package/dist/template-resolver/handlers/send-test.write.d.ts +13 -0
  287. package/dist/template-resolver/handlers/send-test.write.js +108 -0
  288. package/dist/template-resolver/handlers/set.write.d.ts +5 -2
  289. package/dist/template-resolver/handlers/set.write.js +25 -3
  290. package/dist/template-resolver/handlers/shared.d.ts +9 -5
  291. package/dist/template-resolver/handlers/shared.js +6 -2
  292. package/dist/template-resolver/handlers/template-gates.d.ts +36 -0
  293. package/dist/template-resolver/handlers/template-gates.js +142 -0
  294. package/dist/template-resolver/handlers/template-write.d.ts +45 -0
  295. package/dist/template-resolver/handlers/template-write.js +141 -0
  296. package/dist/template-resolver/handlers/tenant-overrides.query.d.ts +49 -0
  297. package/dist/template-resolver/handlers/tenant-overrides.query.js +112 -0
  298. package/dist/template-resolver/handlers/toggle-status.write.d.ts +27 -2
  299. package/dist/template-resolver/handlers/toggle-status.write.js +47 -15
  300. package/dist/template-resolver/handlers/upsert-system.write.d.ts +5 -8
  301. package/dist/template-resolver/handlers/upsert-system.write.js +14 -59
  302. package/dist/template-resolver/handlers/upsert-tenant.write.d.ts +5 -8
  303. package/dist/template-resolver/handlers/upsert-tenant.write.js +11 -62
  304. package/dist/template-resolver/handlers/usage.query.d.ts +17 -0
  305. package/dist/template-resolver/handlers/usage.query.js +24 -0
  306. package/dist/template-resolver/handlers/validate.query.d.ts +74 -0
  307. package/dist/template-resolver/handlers/validate.query.js +80 -0
  308. package/dist/template-resolver/index.d.ts +19 -1
  309. package/dist/template-resolver/index.js +17 -1
  310. package/dist/template-resolver/layout-appearance.d.ts +10 -0
  311. package/dist/template-resolver/layout-appearance.js +26 -0
  312. package/dist/template-resolver/layout-regions.d.ts +23 -0
  313. package/dist/template-resolver/layout-regions.js +125 -0
  314. package/dist/template-resolver/linked-resources.d.ts +17 -0
  315. package/dist/template-resolver/linked-resources.js +75 -0
  316. package/dist/template-resolver/pdf-constants.d.ts +7 -0
  317. package/dist/template-resolver/pdf-constants.js +8 -0
  318. package/dist/template-resolver/preview-display-html.d.ts +7 -0
  319. package/dist/template-resolver/preview-display-html.js +27 -0
  320. package/dist/template-resolver/print-document.d.ts +1 -0
  321. package/dist/template-resolver/print-document.js +55 -0
  322. package/dist/template-resolver/qualified-names.d.ts +34 -0
  323. package/dist/template-resolver/qualified-names.js +34 -0
  324. package/dist/template-resolver/record-mapping.d.ts +2 -0
  325. package/dist/template-resolver/record-mapping.js +21 -0
  326. package/dist/template-resolver/resource-image.d.ts +15 -0
  327. package/dist/template-resolver/resource-image.js +34 -0
  328. package/dist/template-resolver/seeding.d.ts +14 -1
  329. package/dist/template-resolver/seeding.js +125 -18
  330. package/dist/template-resolver/store-template-resource.d.ts +64 -0
  331. package/dist/template-resolver/store-template-resource.js +264 -0
  332. package/dist/template-resolver/table.d.ts +150 -8
  333. package/dist/template-resolver/table.js +20 -2
  334. package/dist/template-resolver/template-bundle.d.ts +36 -0
  335. package/dist/template-resolver/template-bundle.js +60 -0
  336. package/dist/template-resolver/template-diff.d.ts +7 -0
  337. package/dist/template-resolver/template-diff.js +68 -0
  338. package/dist/template-resolver/template-draft.d.ts +51 -0
  339. package/dist/template-resolver/template-draft.js +72 -0
  340. package/dist/template-resolver/template-meta.d.ts +85 -0
  341. package/dist/template-resolver/template-meta.js +145 -0
  342. package/dist/template-resolver/template-preview.d.ts +103 -0
  343. package/dist/template-resolver/template-preview.js +418 -0
  344. package/dist/template-resolver/template-resources.d.ts +19 -0
  345. package/dist/template-resolver/template-resources.js +73 -0
  346. package/dist/template-resolver/template-usage.d.ts +16 -0
  347. package/dist/template-resolver/template-usage.js +35 -0
  348. package/dist/template-resolver/template-validation.d.ts +22 -0
  349. package/dist/template-resolver/template-validation.js +173 -0
  350. package/dist/template-resolver/testing.js +5 -5
  351. package/dist/template-resolver/user-content-table.d.ts +14 -8
  352. package/dist/template-resolver/web/client-plugin.d.ts +1 -0
  353. package/dist/template-resolver/web/client-plugin.js +12 -7
  354. package/dist/template-resolver/web/document-editor-extensions.d.ts +18 -0
  355. package/dist/template-resolver/web/document-editor-extensions.js +145 -0
  356. package/dist/template-resolver/web/document-editor.d.ts +4 -0
  357. package/dist/template-resolver/web/document-editor.js +17 -0
  358. package/dist/template-resolver/web/document-tiptap-editor.d.ts +3 -0
  359. package/dist/template-resolver/web/document-tiptap-editor.js +492 -0
  360. package/dist/template-resolver/web/document-toolbar-blocks.d.ts +41 -0
  361. package/dist/template-resolver/web/document-toolbar-blocks.js +19 -0
  362. package/dist/template-resolver/web/editor-handle.d.ts +3 -0
  363. package/dist/template-resolver/web/editor-handle.js +24 -0
  364. package/dist/template-resolver/web/i18n.js +91 -0
  365. package/dist/template-resolver/web/index.d.ts +5 -0
  366. package/dist/template-resolver/web/index.js +5 -0
  367. package/dist/template-resolver/web/placeholder-line-editor-lazy.d.ts +4 -0
  368. package/dist/template-resolver/web/placeholder-line-editor-lazy.js +16 -0
  369. package/dist/template-resolver/web/placeholder-line-editor.d.ts +17 -0
  370. package/dist/template-resolver/web/placeholder-line-editor.js +94 -0
  371. package/dist/template-resolver/web/placeholder-text.d.ts +4 -0
  372. package/dist/template-resolver/web/placeholder-text.js +34 -0
  373. package/dist/template-resolver/web/use-stable-by-key.d.ts +1 -0
  374. package/dist/template-resolver/web/use-stable-by-key.js +13 -0
  375. package/dist/tenant/handlers/add-member.write.d.ts +1 -1
  376. package/dist/tenant/handlers/cancel-invitation.write.d.ts +1 -1
  377. package/dist/tenant/handlers/create.write.d.ts +1 -1
  378. package/dist/tenant/handlers/remove-member.write.d.ts +1 -1
  379. package/dist/tenant/handlers/team-list.query.js +3 -2
  380. package/dist/tenant/handlers/toggle-enabled.write.d.ts +2 -2
  381. package/dist/tenant/handlers/update-member-roles.write.d.ts +1 -1
  382. package/dist/tenant/handlers/update.write.d.ts +1 -1
  383. package/dist/tenant/seeding.js +31 -6
  384. package/dist/tenant-handover/handlers/claim.write.d.ts +1 -1
  385. package/dist/tenant-lifecycle/handlers/cancel-destruction.write.d.ts +2 -2
  386. package/dist/tenant-lifecycle/handlers/cancel-destruction.write.js +2 -1
  387. package/dist/tenant-lifecycle/handlers/request-destruction.write.d.ts +1 -1
  388. package/dist/tier-engine/feature.js +9 -3
  389. package/dist/tier-engine/handlers/set-tenant-tier.write.d.ts +1 -1
  390. package/dist/user/db/queries/stream-tenant-backfill.js +4 -4
  391. package/dist/user/handlers/create.write.d.ts +1 -1
  392. package/dist/user/handlers/detail.query.js +2 -1
  393. package/dist/user/handlers/update.write.d.ts +1 -1
  394. package/dist/user/resolve-display-names.js +5 -9
  395. package/dist/user/seeding.js +20 -1
  396. package/dist/user-data-rights/db/queries/forget-cleanup.d.ts +2 -2
  397. package/dist/user-data-rights/email-templates.d.ts +1 -29
  398. package/dist/user-data-rights/email-templates.js +5 -106
  399. package/dist/user-data-rights/feature.js +53 -35
  400. package/dist/user-data-rights/handlers/cancel-deletion.write.d.ts +2 -2
  401. package/dist/user-data-rights/handlers/cancel-deletion.write.js +2 -1
  402. package/dist/user-data-rights/handlers/confirm-deletion-by-token.write.d.ts +1 -1
  403. package/dist/user-data-rights/handlers/lift-restriction.write.d.ts +1 -1
  404. package/dist/user-data-rights/handlers/request-deletion-by-email.write.d.ts +1 -1
  405. package/dist/user-data-rights/handlers/request-deletion.write.d.ts +1 -1
  406. package/dist/user-data-rights/handlers/request-deletion.write.js +5 -7
  407. package/dist/user-data-rights/handlers/request-export.write.d.ts +1 -1
  408. package/dist/user-data-rights/handlers/restrict-account.write.d.ts +1 -1
  409. package/dist/user-data-rights/handlers/run-forget-cleanup.write.d.ts +1 -1
  410. package/dist/user-data-rights/lib/default-mailers.d.ts +15 -7
  411. package/dist/user-data-rights/lib/default-mailers.js +100 -48
  412. package/dist/user-data-rights/lib/update-user-lifecycle.js +1 -1
  413. package/dist/user-data-rights/mail-notifications.d.ts +13 -0
  414. package/dist/user-data-rights/mail-notifications.js +118 -0
  415. package/dist/user-data-rights/web/privacy-center-screen.js +2 -1
  416. package/dist/user-profile/handlers/change-email.write.d.ts +1 -1
  417. package/dist/user-profile/web/profile-screen.js +2 -1
  418. package/dist/waitlist/feature.js +10 -0
  419. package/dist/waitlist/handlers/invite.write.d.ts +1 -1
  420. package/dist/waitlist/handlers/reject.write.d.ts +1 -1
  421. package/dist/waitlist/handlers/submit.write.d.ts +1 -1
  422. package/dist/waitlist/handlers/submit.write.js +3 -4
  423. package/dist/waitlist/mail.d.ts +19 -6
  424. package/dist/waitlist/mail.js +50 -14
  425. package/dist/waitlist/options.js +1 -1
  426. package/dist/workflow-runner/event-trigger.js +2 -2
  427. package/dist/workflow-runner/handlers/resume-run.write.js +7 -2
  428. package/package.json +37 -17
  429. package/src/admin-shell/changes.json +6 -0
  430. package/src/audit/changes.json +6 -0
  431. package/src/auth-email-password/changes.json +51 -0
  432. package/src/billing-foundation/changes.json +43 -0
  433. package/src/cap-counter/changes.json +6 -0
  434. package/src/channel-email/changes.json +19 -0
  435. package/src/config/changes.json +6 -0
  436. package/src/crypto-shredding/changes.json +6 -0
  437. package/src/custom-fields/changes.json +6 -0
  438. package/src/data-retention/changes.json +6 -0
  439. package/src/delivery/changes.json +66 -0
  440. package/src/file-foundation/changes.json +6 -0
  441. package/src/files-tenant-data/changes.json +6 -0
  442. package/src/inbound-mail-foundation/changes.json +6 -0
  443. package/src/jobs/changes.json +19 -0
  444. package/src/ledger/changes.json +7 -0
  445. package/src/managed-pages/changes.json +6 -0
  446. package/src/renderer-foundation/changes.json +39 -0
  447. package/src/renderer-pdf/changes.json +12 -0
  448. package/src/secrets/changes.json +6 -0
  449. package/src/shared/changes.json +6 -0
  450. package/src/subscription-stripe/changes.json +6 -0
  451. package/src/template-resolver/changes.json +186 -0
  452. package/src/tier-engine/changes.json +6 -0
  453. package/src/user/changes.json +6 -0
  454. package/src/user-data-rights/changes.json +13 -0
  455. package/src/user-profile/changes.json +1 -1
  456. package/src/waitlist/changes.json +7 -0
  457. package/src/workflow-runner/changes.json +6 -0
  458. package/dist/renderer-simple/feature.d.ts +0 -10
  459. package/dist/renderer-simple/feature.js +0 -42
  460. package/dist/renderer-simple/index.d.ts +0 -3
  461. package/dist/renderer-simple/index.js +0 -2
  462. package/dist/renderer-simple/mail-blocks.d.ts +0 -21
  463. package/dist/renderer-simple/mail-blocks.js +0 -107
  464. package/dist/renderer-simple/resolve-variables.d.ts +0 -7
  465. package/dist/renderer-simple/resolve-variables.js +0 -47
  466. package/dist/renderer-simple/simple-renderer.d.ts +0 -24
  467. package/dist/renderer-simple/simple-renderer.js +0 -200
  468. package/src/renderer-simple/changes.json +0 -27
@@ -57,6 +57,9 @@ export declare function assertBelowHardCap(valueSeenByLastUnit: number, cap: {
57
57
  readonly capName: string;
58
58
  readonly limit: number;
59
59
  readonly profile: CapToleranceProfileName;
60
+ readonly periodStartIso?: string;
61
+ readonly periodEndIso?: string;
62
+ readonly i18nKey?: string;
60
63
  }): void;
61
64
  /**
62
65
  * Synchronous read-and-check of the calling tenant's counter for
@@ -85,6 +88,8 @@ export declare function enforceCap(ctx: CapCounterContext, options: {
85
88
  readonly limit: number;
86
89
  readonly profile: CapToleranceProfileName;
87
90
  readonly amount?: number;
91
+ readonly periodEndIso?: string;
92
+ readonly i18nKey?: string;
88
93
  }): Promise<EnforceCapResult>;
89
94
  /**
90
95
  * Synchronous read-and-check of the calling tenant's Rolling-Window-
@@ -120,6 +125,9 @@ export declare function enforceRollingCap(ctx: CapCounterContext, options: {
120
125
  * Thrown by enforceCap/enforceRollingCap when value ≥ hard-threshold.
121
126
  * Extends `KumikoError` — the dispatcher auto-maps this to HTTP 429
122
127
  * with `code: "cap_exceeded"`, no caller-side catch needed.
128
+ *
129
+ * Calendar caps carry `resetAt` (end of the period) and `retryAfterSeconds`, which the API
130
+ * answers as Retry-After. Rolling caps have no fixed reset instant, so both stay undefined.
123
131
  */
124
132
  export declare class CapExceededError extends KumikoError {
125
133
  readonly capName: string;
@@ -128,7 +136,13 @@ export declare class CapExceededError extends KumikoError {
128
136
  readonly tolerance: CapToleranceProfile;
129
137
  readonly code: "cap_exceeded";
130
138
  readonly httpStatus = 429;
131
- constructor(capName: string, limit: number, currentValue: number, tolerance: CapToleranceProfile);
139
+ readonly resetAt: string | undefined;
140
+ constructor(capName: string, limit: number, currentValue: number, tolerance: CapToleranceProfile, options?: {
141
+ readonly resetAt?: string;
142
+ readonly i18nKey?: string;
143
+ readonly cause?: Error;
144
+ readonly now?: Temporal.Instant;
145
+ });
132
146
  }
133
147
  /**
134
148
  * Calendar-month period start in UTC. Use this for monthly caps
@@ -137,6 +151,7 @@ export declare class CapExceededError extends KumikoError {
137
151
  * Returns ISO string for the 1st of the current month at 00:00 UTC.
138
152
  */
139
153
  export declare function currentCalendarMonthStartIso(now?: Temporal.Instant): string;
154
+ export declare function calendarMonthPeriodEndIso(periodStartIso: string): string;
140
155
  /**
141
156
  * Soft-hit-notifier callback. Caller liefert die Funktion die ein
142
157
  * delivery-event emittet (z.B. `delivery.send({to, template, payload})`).
@@ -169,6 +184,8 @@ export declare function enforceCapAndMaybeNotify(ctx: CapCounterContext, options
169
184
  readonly profile: CapToleranceProfileName;
170
185
  readonly notify: SoftHitNotifier;
171
186
  readonly amount?: number;
187
+ readonly periodEndIso?: string;
188
+ readonly i18nKey?: string;
172
189
  readonly markSoftWarnedOutsideTransaction?: boolean;
173
190
  }): Promise<EnforceCapResult>;
174
191
  /**
@@ -17,8 +17,24 @@ export const CAP_TOLERANCES = {
17
17
  export function assertBelowHardCap(valueSeenByLastUnit, cap) {
18
18
  const tolerance = CAP_TOLERANCES[cap.profile];
19
19
  if (valueSeenByLastUnit >= cap.limit * tolerance.hard) {
20
- throw new CapExceededError(cap.capName, cap.limit, valueSeenByLastUnit, tolerance);
20
+ const resetAt = resolvePeriodEndIso(cap.periodStartIso, cap.periodEndIso);
21
+ throw new CapExceededError(cap.capName, cap.limit, valueSeenByLastUnit, tolerance, {
22
+ ...(resetAt !== undefined && { resetAt }),
23
+ ...(cap.i18nKey !== undefined && { i18nKey: cap.i18nKey }),
24
+ });
25
+ }
26
+ }
27
+ function resolvePeriodEndIso(periodStartIso, periodEndIso) {
28
+ if (periodStartIso === undefined)
29
+ return periodEndIso;
30
+ if (periodEndIso === undefined)
31
+ return calendarMonthPeriodEndIso(periodStartIso);
32
+ const start = Temporal.Instant.from(periodStartIso);
33
+ const end = Temporal.Instant.from(periodEndIso);
34
+ if (Temporal.Instant.compare(end, start) <= 0) {
35
+ throw new Error(`cap-counter: periodEndIso (${periodEndIso}) must be after periodStartIso (${periodStartIso})`);
21
36
  }
37
+ return end.toString();
22
38
  }
23
39
  // =============================================================================
24
40
  // Enforce-Cap helper
@@ -115,6 +131,9 @@ export async function enforceRollingCap(ctx, options) {
115
131
  * Thrown by enforceCap/enforceRollingCap when value ≥ hard-threshold.
116
132
  * Extends `KumikoError` — the dispatcher auto-maps this to HTTP 429
117
133
  * with `code: "cap_exceeded"`, no caller-side catch needed.
134
+ *
135
+ * Calendar caps carry `resetAt` (end of the period) and `retryAfterSeconds`, which the API
136
+ * answers as Retry-After. Rolling caps have no fixed reset instant, so both stay undefined.
118
137
  */
119
138
  export class CapExceededError extends KumikoError {
120
139
  capName;
@@ -123,17 +142,32 @@ export class CapExceededError extends KumikoError {
123
142
  tolerance;
124
143
  code = "cap_exceeded";
125
144
  httpStatus = 429;
126
- constructor(capName, limit, currentValue, tolerance) {
145
+ resetAt;
146
+ constructor(capName, limit, currentValue, tolerance, options) {
147
+ const resetAt = options?.resetAt;
148
+ const retryAfterSeconds = resetAt === undefined
149
+ ? undefined
150
+ : Math.max(0, Math.ceil(Temporal.Instant.from(resetAt).epochMilliseconds / 1000 -
151
+ (options?.now ?? Temporal.Now.instant()).epochMilliseconds / 1000));
127
152
  super({
128
- message: `Cap "${capName}" exceeded: current=${currentValue}, limit=${limit}, hard-threshold=${limit * tolerance.hard}. Upgrade tier or wait for next period reset.`,
129
- i18nKey: "errors.cap.exceeded",
130
- i18nParams: { capName, limit, currentValue },
131
- details: { capName, limit, currentValue, tolerance },
153
+ message: `Cap "${capName}" exceeded: current=${currentValue}, limit=${limit}, hard-threshold=${limit * tolerance.hard}. Upgrade tier or wait for next period reset.${resetAt === undefined ? "" : ` Resets at ${resetAt}.`}`,
154
+ i18nKey: options?.i18nKey ?? "errors.cap.exceeded",
155
+ i18nParams: { capName, limit, currentValue, ...(resetAt !== undefined && { resetAt }) },
156
+ details: {
157
+ capName,
158
+ limit,
159
+ currentValue,
160
+ tolerance,
161
+ ...(resetAt !== undefined && { resetAt, retryAfterSeconds }),
162
+ },
163
+ ...(retryAfterSeconds !== undefined && { retryAfterSeconds }),
164
+ ...(options?.cause && { cause: options.cause }),
132
165
  });
133
166
  this.capName = capName;
134
167
  this.limit = limit;
135
168
  this.currentValue = currentValue;
136
169
  this.tolerance = tolerance;
170
+ this.resetAt = resetAt;
137
171
  }
138
172
  }
139
173
  // =============================================================================
@@ -158,6 +192,13 @@ export function currentCalendarMonthStartIso(now = Temporal.Now.instant()) {
158
192
  });
159
193
  return start.toInstant().toString();
160
194
  }
195
+ export function calendarMonthPeriodEndIso(periodStartIso) {
196
+ return Temporal.Instant.from(periodStartIso)
197
+ .toZonedDateTimeISO("UTC")
198
+ .add({ months: 1 })
199
+ .toInstant()
200
+ .toString();
201
+ }
161
202
  /**
162
203
  * Calendar-Period-enforcement + automatische soft-hit-Notification.
163
204
  * Ruft `enforceCap`, bei `crossed: true` den Notifier UND
@@ -175,6 +216,8 @@ export async function enforceCapAndMaybeNotify(ctx, options) {
175
216
  limit: options.limit,
176
217
  profile: options.profile,
177
218
  ...(options.amount !== undefined && { amount: options.amount }),
219
+ ...(options.periodEndIso !== undefined && { periodEndIso: options.periodEndIso }),
220
+ ...(options.i18nKey !== undefined && { i18nKey: options.i18nKey }),
178
221
  });
179
222
  if (result.state === "soft-hit" && result.crossed) {
180
223
  await options.notify({
@@ -2,7 +2,7 @@ export { capCounterAggregateId, rollingCapAggregateId } from "./aggregate-id.js"
2
2
  export { type BookCapUsageOptions, bookCapUsage, type MarkCapSoftWarnedOptions, markCapSoftWarned, type ReadRollingCapUsageOptions, readRollingCapUsage, } from "./book-cap-usage.js";
3
3
  export { type CalendarCapUsageReservation, type CalendarCapUsageReservationSpec, reserveCalendarCapUsage, } from "./calendar-cap-usage-reservation.js";
4
4
  export { CAP_COUNTER_FEATURE, CAP_COUNTER_ROLLING_AGGREGATE_TYPE, CapCounterHandlers, CapCounterQueries, ROLLING_INCREMENTED_EVENT_QN, ROLLING_INCREMENTED_EVENT_SHORT, ROLLING_RELEASED_EVENT_QN, ROLLING_RELEASED_EVENT_SHORT, } from "./constants.js";
5
- export { CAP_TOLERANCES, CapExceededError, type CapToleranceProfile, type CapToleranceProfileName, currentCalendarMonthStartIso, type EnforceCapResult, enforceCap, enforceCapAndMaybeNotify, enforceRollingCap, enforceRollingCapAndMaybeNotify, enforceStockCap, type SoftHitNotifier, type StockCapResult, } from "./enforce-cap.js";
5
+ export { CAP_TOLERANCES, CapExceededError, type CapToleranceProfile, type CapToleranceProfileName, calendarMonthPeriodEndIso, currentCalendarMonthStartIso, type EnforceCapResult, enforceCap, enforceCapAndMaybeNotify, enforceRollingCap, enforceRollingCapAndMaybeNotify, enforceStockCap, type SoftHitNotifier, type StockCapResult, } from "./enforce-cap.js";
6
6
  export { capCounterEntity } from "./entity.js";
7
7
  export { capCounterFeature } from "./feature.js";
8
8
  export { readPayloadField } from "./payload-field.js";
@@ -3,7 +3,7 @@ export { capCounterAggregateId, rollingCapAggregateId } from "./aggregate-id.js"
3
3
  export { bookCapUsage, markCapSoftWarned, readRollingCapUsage, } from "./book-cap-usage.js";
4
4
  export { reserveCalendarCapUsage, } from "./calendar-cap-usage-reservation.js";
5
5
  export { CAP_COUNTER_FEATURE, CAP_COUNTER_ROLLING_AGGREGATE_TYPE, CapCounterHandlers, CapCounterQueries, ROLLING_INCREMENTED_EVENT_QN, ROLLING_INCREMENTED_EVENT_SHORT, ROLLING_RELEASED_EVENT_QN, ROLLING_RELEASED_EVENT_SHORT, } from "./constants.js";
6
- export { CAP_TOLERANCES, CapExceededError, currentCalendarMonthStartIso, enforceCap, enforceCapAndMaybeNotify, enforceRollingCap, enforceRollingCapAndMaybeNotify, enforceStockCap, } from "./enforce-cap.js";
6
+ export { CAP_TOLERANCES, CapExceededError, calendarMonthPeriodEndIso, currentCalendarMonthStartIso, enforceCap, enforceCapAndMaybeNotify, enforceRollingCap, enforceRollingCapAndMaybeNotify, enforceStockCap, } from "./enforce-cap.js";
7
7
  export { capCounterEntity } from "./entity.js";
8
8
  export { capCounterFeature } from "./feature.js";
9
9
  export { readPayloadField } from "./payload-field.js";
@@ -10,6 +10,8 @@ import { type CapToleranceProfileName, type SoftHitNotifier } from "./enforce-ca
10
10
  export type CalendarCapDef = {
11
11
  readonly capName: string;
12
12
  readonly periodStartIso: string;
13
+ readonly periodEndIso?: string;
14
+ readonly i18nKey?: string;
13
15
  readonly limit: number;
14
16
  readonly profile: CapToleranceProfileName;
15
17
  /** Increment-amount post-success. Default 1. */
@@ -2,6 +2,7 @@ import { selectMany } from "@cosmicdrift/kumiko-framework/bun-db";
2
2
  import { buildEntityTable, decodeCursor, encodeCursor } from "@cosmicdrift/kumiko-framework/db";
3
3
  import { definePagedQueryHandler, MAX_LIST_LIMIT, } from "@cosmicdrift/kumiko-framework/engine";
4
4
  import { InternalError, ValidationError } from "@cosmicdrift/kumiko-framework/errors";
5
+ import { isOneOf } from "@cosmicdrift/kumiko-types/type-guards";
5
6
  import * as z from "zod";
6
7
  import { subscriptionsProjectionTable } from "../../billing-foundation/index.js";
7
8
  import { decryptTenantNames, tenantTable } from "../../tenant/index.js";
@@ -34,7 +35,7 @@ async function resolveLimitsByTenantAndCap(caps, tenants, configFor) {
34
35
  return new Map(entries);
35
36
  }
36
37
  function isSortableField(field) {
37
- return SORTABLE_FIELDS.includes(field);
38
+ return isOneOf(SORTABLE_FIELDS, field);
38
39
  }
39
40
  // tier is a plain string with no defined ordering — lt/gt have no meaning
40
41
  // on it and are rejected rather than silently treated as "no filter".
@@ -1,13 +1,14 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { isPlainObject } from "@cosmicdrift/kumiko-types/type-guards";
2
3
  import { CapUsageBar } from "./cap-usage-bar.js";
3
4
  function isCapUsage(value) {
4
- if (typeof value !== "object" || value === null)
5
+ if (!isPlainObject(value))
5
6
  return false;
6
- const used = value.used;
7
- const limit = value.limit;
7
+ const used = value["used"];
8
+ const limit = value["limit"];
8
9
  return ((typeof used === "number" || used === null) &&
9
10
  (typeof limit === "number" || limit === null) &&
10
- typeof value.fraction === "number");
11
+ typeof value["fraction"] === "number");
11
12
  }
12
13
  export function CapUsageCell({ value }) {
13
14
  if (!isCapUsage(value))
@@ -1,9 +1,9 @@
1
1
  import type { TenantId } from "@cosmicdrift/kumiko-framework/engine";
2
- import { type DeliveryChannel, type NotificationRenderer } from "../delivery/index.js";
2
+ import { type DeliveryChannel, type EmailAttachmentRef } from "../delivery/index.js";
3
3
  import type { EmailTransport } from "./types.js";
4
+ export declare function fileAttachmentsFrom(data: Readonly<Record<string, unknown>> | undefined, messageLocale?: string): readonly EmailAttachmentRef[];
4
5
  export type EmailChannelOptions = {
5
6
  readonly transport: EmailTransport;
6
- readonly renderer: NotificationRenderer;
7
7
  readonly resolveEmail: (userId: string, ctx: {
8
8
  db: unknown;
9
9
  tenantId: TenantId;
@@ -1,5 +1,6 @@
1
1
  import { PII_ERASED_SENTINEL } from "@cosmicdrift/kumiko-framework/crypto";
2
- import { DELIVERY_UNSUBSCRIBE_ONE_CLICK_HEADER_VALUE, DELIVERY_UNSUBSCRIBE_PATH, } from "../delivery/index.js";
2
+ import * as z from "zod";
3
+ import { DELIVERY_UNSUBSCRIBE_ONE_CLICK_HEADER_VALUE, DELIVERY_UNSUBSCRIBE_PATH, EMAIL_CHANNEL_NAME, fileAttachmentRefSchema, MAX_EMAIL_ATTACHMENTS, renderedPdfAttachmentRefSchema, } from "../delivery/index.js";
3
4
  import { decryptStoredPii } from "../shared/index.js";
4
5
  import { guardEmailMessage } from "./pii-guard.js";
5
6
  // Envelope (From / Reply-To / threading headers) rides on the channel data —
@@ -65,31 +66,95 @@ function emailEnvelopeFrom(data) {
65
66
  ...(headers && { headers }),
66
67
  };
67
68
  }
69
+ const fileAttachmentsSchema = z
70
+ .array(z.discriminatedUnion("kind", [fileAttachmentRefSchema, renderedPdfAttachmentRefSchema]))
71
+ .max(MAX_EMAIL_ATTACHMENTS);
72
+ // Only kinds "file" and "rendered-pdf" may come from notify data; inline images are created by the renderer alone.
73
+ // Invalid input throws instead of dropping the attachment silently.
74
+ export function fileAttachmentsFrom(data, messageLocale) {
75
+ const raw = data?.["attachments"];
76
+ if (raw === undefined)
77
+ return [];
78
+ return fileAttachmentsSchema
79
+ .parse(raw)
80
+ .map((ref) => ref.kind === "rendered-pdf" && ref.locale === undefined && messageLocale !== undefined
81
+ ? { ...ref, locale: messageLocale }
82
+ : ref);
83
+ }
84
+ const INLINE_EXTENSION_BY_CONTENT_TYPE = {
85
+ "image/png": "png",
86
+ "image/jpeg": "jpg",
87
+ "image/gif": "gif",
88
+ "image/webp": "webp",
89
+ };
90
+ async function resolveAttachments(sources, ctx) {
91
+ if (sources.length === 0)
92
+ return [];
93
+ if (ctx.readAttachment === undefined) {
94
+ throw new Error("email channel: the message has attachments but the channel context cannot read files");
95
+ }
96
+ const resolved = [];
97
+ for (const { ref, origin } of sources) {
98
+ const found = await ctx.readAttachment(ref, origin);
99
+ if (ref.kind === "rendered-pdf") {
100
+ if (found === null) {
101
+ throw new Error(`email channel: rendering attachment "${ref.fileName}" returned nothing`);
102
+ }
103
+ resolved.push({
104
+ kind: "rendered-pdf",
105
+ templateSlug: ref.templateSlug,
106
+ fileName: ref.fileName,
107
+ content: found.content,
108
+ contentType: found.contentType,
109
+ });
110
+ continue;
111
+ }
112
+ if (found === null) {
113
+ // A missing inline image only degrades the look; a missing file is content the recipient expects.
114
+ if (ref.kind === "inline")
115
+ continue;
116
+ throw new Error(`email channel: attachment file ${ref.fileRefId} was not found`);
117
+ }
118
+ const fileName = ref.kind === "file"
119
+ ? ref.fileName
120
+ : INLINE_EXTENSION_BY_CONTENT_TYPE[found.contentType]
121
+ ? `${ref.cid}.${INLINE_EXTENSION_BY_CONTENT_TYPE[found.contentType]}`
122
+ : ref.cid;
123
+ resolved.push({ ...ref, fileName, content: found.content, contentType: found.contentType });
124
+ }
125
+ return resolved;
126
+ }
68
127
  export function createEmailChannel(options) {
69
- const { transport, renderer, resolveEmail } = options;
70
- // Render is the expensive step (template engine, possibly a remote service)
71
- // and runs in the delivery.render job, decoupled from the SMTP send so each
72
- // can retry independently. Extracted so the inline fallback (no job runner)
128
+ const { transport, resolveEmail } = options;
129
+ // Render runs in the delivery.render job, decoupled from the SMTP send so
130
+ // each can retry independently. Extracted so the inline path (no job runner)
73
131
  // can reuse it without going through the channel's own render() indirection.
74
- async function renderMessage(message) {
75
- // Build renderer input: per-channel template data (if any) or fall back
76
- // to title/body from the message. Renderer handles both cases.
77
- const variables = message.data ?? {
78
- title: message.title,
79
- body: message.body,
80
- };
81
- const rendererInput = {
82
- template: message.notificationType,
83
- variables,
84
- locale: message.locale,
132
+ async function renderMessage(message, ctx) {
133
+ const notificationType = message.notificationType;
134
+ if (ctx.registry.getAllNotifications().get(notificationType)?.template === undefined) {
135
+ throw new Error(`email channel: notification "${notificationType}" has no mail template, declare template + meta on r.notification`);
136
+ }
137
+ if (ctx.renderMail === undefined) {
138
+ throw new Error(`email channel: notification "${notificationType}" has a mail template but the channel context carries no renderMail`);
139
+ }
140
+ const unsubscribeUrl = message.data?.["unsubscribeUrl"];
141
+ const { subject, html, text, attachments, fromName, replyTo } = await ctx.renderMail({
142
+ slug: notificationType,
143
+ locale: message.locale ?? "en",
144
+ variables: message.data ?? {},
145
+ ...(typeof unsubscribeUrl === "string" && { unsubscribeUrl }),
146
+ });
147
+ return {
148
+ html,
149
+ subject,
150
+ text,
151
+ ...(fromName !== undefined && { fromName }),
152
+ ...(replyTo !== undefined && { replyTo }),
153
+ ...(attachments !== undefined && { attachments }),
85
154
  };
86
- const html = await renderer.render(rendererInput);
87
- const text = renderer.renderText ? await renderer.renderText(rendererInput) : undefined;
88
- const subject = variables["subject"] ?? message.title; // @cast-boundary dynamic-key
89
- return { html, subject, ...(text !== undefined && { text }) };
90
155
  }
91
156
  return {
92
- name: "email",
157
+ name: EMAIL_CHANNEL_NAME,
93
158
  mode: "queued",
94
159
  // resolveEmail typically returns the raw stored user.email, which is ciphertext under an active KMS.
95
160
  async resolve(userId, ctx) {
@@ -99,18 +164,28 @@ export function createEmailChannel(options) {
99
164
  const address = await decryptStoredPii(stored, "email", "channel-email:resolve");
100
165
  return address === PII_ERASED_SENTINEL ? null : address;
101
166
  },
102
- render(message, _ctx) {
103
- return renderMessage(message);
167
+ render(message, ctx) {
168
+ return renderMessage(message, ctx);
104
169
  },
105
- async send(address, message, _ctx, rendered) {
106
- const { html, subject, text } = rendered ?? (await renderMessage(message));
107
- const envelope = emailEnvelopeFrom(message.data);
170
+ async send(address, message, ctx, rendered) {
171
+ const { html, subject, text, attachments, fromName, replyTo } = rendered ?? (await renderMessage(message, ctx));
172
+ // Template-level sender defaults; notify data wins.
173
+ const envelope = {
174
+ ...(fromName && { fromName }),
175
+ ...(replyTo && { replyTo }),
176
+ ...emailEnvelopeFrom(message.data),
177
+ };
178
+ const resolvedAttachments = await resolveAttachments([
179
+ ...(attachments ?? []).map((ref) => ({ ref, origin: "template" })),
180
+ ...fileAttachmentsFrom(message.data, message.locale).map((ref) => ({ ref, origin: "message" })),
181
+ ], ctx);
108
182
  await transport.send(guardEmailMessage({
109
183
  to: address,
110
184
  subject,
111
185
  html,
112
186
  ...(text !== undefined && { text }),
113
187
  ...envelope,
188
+ ...(resolvedAttachments.length > 0 && { attachments: resolvedAttachments }),
114
189
  }));
115
190
  return { status: "sent", address };
116
191
  },
@@ -1,10 +1,10 @@
1
- import { DELIVERY_CHANNEL_EXTENSION } from "@cosmicdrift/kumiko-bundled-features/delivery";
1
+ import { DELIVERY_CHANNEL_EXTENSION, EMAIL_CHANNEL_NAME, } from "@cosmicdrift/kumiko-bundled-features/delivery";
2
2
  import { defineFeature } from "@cosmicdrift/kumiko-framework/engine";
3
3
  import { createEmailChannel } from "./email-channel.js";
4
4
  export function createChannelEmailFeature(options) {
5
5
  const channel = createEmailChannel(options);
6
6
  return defineFeature("channel-email", (r) => {
7
- r.describe("Wires an `EmailTransport` (typically `mail-transport-smtp` in production, `createInMemoryTransport()` in tests) into the delivery system as the `email` channel. Requires `delivery`; pass an `EmailChannelOptions` with a `transport`, a `renderer: NotificationRenderer` (e.g. backed by `renderer-simple`), and a `resolveEmail` function that maps a user ID to their email address.");
7
+ r.describe("Wires an `EmailTransport` (typically `mail-transport-smtp` in production, `createInMemoryTransport()` in tests) into the delivery system as the `email` channel. Requires `delivery`; pass an `EmailChannelOptions` with a `transport` and a `resolveEmail` function that maps a user ID to their email address. Mails render from stored templates (`renderer-mail-html`): a notification routed to this channel needs `template` + `meta` on `r.notification`, otherwise its delivery fails.");
8
8
  r.uiHints({
9
9
  displayLabel: "Email Channel",
10
10
  category: "notifications",
@@ -12,7 +12,7 @@ export function createChannelEmailFeature(options) {
12
12
  });
13
13
  r.requires("delivery");
14
14
  r.translations({ keys: { "delivery.channel.email": { en: "Email" } } });
15
- r.useExtension(DELIVERY_CHANNEL_EXTENSION, "email", {
15
+ r.useExtension(DELIVERY_CHANNEL_EXTENSION, EMAIL_CHANNEL_NAME, {
16
16
  mode: channel.mode,
17
17
  resolve: channel.resolve,
18
18
  render: channel.render,
@@ -2,4 +2,4 @@ export { createEmailChannel, type EmailChannelOptions } from "./email-channel.js
2
2
  export { createChannelEmailFeature } from "./feature.js";
3
3
  export { guardEmailMessage, withPiiCiphertextGuard } from "./pii-guard.js";
4
4
  export { createSmtpTransport, createSmtpTransportFromEnv, type SmtpEnv, type SmtpTransportOptions, } from "./smtp-transport.js";
5
- export { createInMemoryTransport, type EmailMessage, type EmailTransport } from "./types.js";
5
+ export { createInMemoryTransport, type EmailAttachment, type EmailAttachmentRef, type EmailMessage, type EmailTransport, } from "./types.js";
@@ -2,4 +2,4 @@ export { createEmailChannel } from "./email-channel.js";
2
2
  export { createChannelEmailFeature } from "./feature.js";
3
3
  export { guardEmailMessage, withPiiCiphertextGuard } from "./pii-guard.js";
4
4
  export { createSmtpTransport, createSmtpTransportFromEnv, } from "./smtp-transport.js";
5
- export { createInMemoryTransport } from "./types.js";
5
+ export { createInMemoryTransport, } from "./types.js";
@@ -8,6 +8,15 @@ const CIPHERTEXT_RE = /kumiko-pii:v\d+:[^"\s<>\\]*/g;
8
8
  // is always refused (the address is garbage — better no mail than a leaked
9
9
  // blob); ciphertext in subject/body fails loud in dev and is redacted+logged
10
10
  // in prod.
11
+ function assertAttachmentNamesArePlain(message) {
12
+ for (const attachment of message.attachments ?? []) {
13
+ const fields = [attachment.fileName, attachment.kind === "inline" ? attachment.cid : undefined];
14
+ if (fields.some((field) => field?.includes(CIPHERTEXT_MARKER))) {
15
+ throw new Error(`[channel-email] refusing to send: attachment file name is a PII ciphertext ` +
16
+ `("${PII_CIPHERTEXT_PREFIX}…") — decrypt the stored value before mailing (decryptStoredPii).`);
17
+ }
18
+ }
19
+ }
11
20
  export function guardEmailMessage(message) {
12
21
  // Every envelope address, not just `to`: a ciphertext From, From-Name or Reply-To is
13
22
  // the same garbage as a ciphertext recipient — refuse rather than mail it.
@@ -28,6 +37,7 @@ export function guardEmailMessage(message) {
28
37
  `("${PII_CIPHERTEXT_PREFIX}…") — decrypt the stored value before mailing (decryptStoredPii).`);
29
38
  }
30
39
  }
40
+ assertAttachmentNamesArePlain(message);
31
41
  const leaking = message.subject.includes(CIPHERTEXT_MARKER) ||
32
42
  message.html.includes(CIPHERTEXT_MARKER) ||
33
43
  message.text?.includes(CIPHERTEXT_MARKER) === true;
@@ -37,6 +37,16 @@ function fromHeader(address, fromName) {
37
37
  const [parsed] = addressparser(address, { flatten: true });
38
38
  return parsed?.address ? { name, address: parsed.address } : address;
39
39
  }
40
+ function toNodemailerAttachment(attachment) {
41
+ const base = {
42
+ filename: attachment.fileName,
43
+ content: Buffer.from(attachment.content),
44
+ contentType: attachment.contentType,
45
+ };
46
+ return attachment.kind === "inline"
47
+ ? { ...base, cid: attachment.cid, contentDisposition: "inline" }
48
+ : base;
49
+ }
40
50
  export function createSmtpTransport(options) {
41
51
  const transporter = createTransport({
42
52
  host: options.host,
@@ -57,6 +67,9 @@ export function createSmtpTransport(options) {
57
67
  ...(message.text !== undefined && { text: message.text }),
58
68
  ...(message.replyTo && { replyTo: message.replyTo }),
59
69
  ...(message.headers && { headers: message.headers }),
70
+ ...(message.attachments && {
71
+ attachments: message.attachments.map(toNodemailerAttachment),
72
+ }),
60
73
  });
61
74
  },
62
75
  };
@@ -1,3 +1,10 @@
1
+ import type { EmailAttachmentRef, FileAttachmentRef, InlineAttachmentRef, RenderedPdfAttachmentRef } from "../delivery/index.js";
2
+ export type { EmailAttachmentRef };
3
+ export type EmailAttachment = (InlineAttachmentRef | FileAttachmentRef | Pick<RenderedPdfAttachmentRef, "kind" | "templateSlug">) & {
4
+ readonly content: Uint8Array;
5
+ readonly contentType: string;
6
+ readonly fileName: string;
7
+ };
1
8
  export type EmailMessage = {
2
9
  readonly to: string;
3
10
  readonly subject: string;
@@ -7,6 +14,7 @@ export type EmailMessage = {
7
14
  readonly fromName?: string;
8
15
  readonly replyTo?: string;
9
16
  readonly headers?: Readonly<Record<string, string>>;
17
+ readonly attachments?: readonly EmailAttachment[];
10
18
  };
11
19
  export type EmailTransport = {
12
20
  send(message: EmailMessage): Promise<void>;
@@ -1,4 +1,4 @@
1
1
  import * as z from "zod";
2
2
  export declare const markAllReadWrite: import("@cosmicdrift/kumiko-types/define-handler").WriteHandlerDefinition<"markAllRead", z.ZodObject<{}, z.core.$strip>, {
3
3
  marked: number;
4
- }, import("@cosmicdrift/kumiko-types/event-type-map").KumikoEventTypeMap>;
4
+ }, import("@cosmicdrift/kumiko-types/event-type-map").KumikoEventTypeMap, undefined>;
@@ -3,4 +3,4 @@ export declare const markReadWrite: import("@cosmicdrift/kumiko-types/define-han
3
3
  id: z.ZodNumber;
4
4
  }, z.core.$strip>, {
5
5
  id: number | undefined;
6
- }, import("@cosmicdrift/kumiko-types/event-type-map").KumikoEventTypeMap>;
6
+ }, import("@cosmicdrift/kumiko-types/event-type-map").KumikoEventTypeMap, undefined>;
@@ -10,4 +10,4 @@ export declare const setProfileWrite: import("@cosmicdrift/kumiko-types/define-h
10
10
  }, z.core.$strip>, {
11
11
  profileKey: "de-hr-dsgvo-hgb" | "eu-dsgvo" | "swiss-dsg";
12
12
  isNew: boolean;
13
- }, import("@cosmicdrift/kumiko-types/event-type-map").KumikoEventTypeMap>;
13
+ }, import("@cosmicdrift/kumiko-types/event-type-map").KumikoEventTypeMap, undefined>;
@@ -9,4 +9,4 @@ export declare const resetWrite: import("@cosmicdrift/kumiko-types/define-handle
9
9
  }, z.core.$strip>, {
10
10
  key: string;
11
11
  scope: import("@cosmicdrift/kumiko-types/config-scope").ConfigScope;
12
- }, import("@cosmicdrift/kumiko-types/event-type-map").KumikoEventTypeMap>;
12
+ }, import("@cosmicdrift/kumiko-types/event-type-map").KumikoEventTypeMap, undefined>;
@@ -11,4 +11,4 @@ export declare const setWrite: import("@cosmicdrift/kumiko-types/define-handler"
11
11
  key: string;
12
12
  value: string | number | boolean;
13
13
  scope: import("@cosmicdrift/kumiko-types/config-scope").ConfigScope;
14
- }, import("@cosmicdrift/kumiko-types/event-type-map").KumikoEventTypeMap>;
14
+ }, import("@cosmicdrift/kumiko-types/event-type-map").KumikoEventTypeMap, undefined>;
@@ -1,6 +1,6 @@
1
1
  import { requestContext } from "@cosmicdrift/kumiko-framework/api";
2
2
  import { configuredPiiSubjectKms, encryptPiiValueForSubject, } from "@cosmicdrift/kumiko-framework/crypto";
3
- import { createEventStoreExecutor } from "@cosmicdrift/kumiko-framework/db";
3
+ import { createEventStoreExecutor, scopeTenantDbToTenant } from "@cosmicdrift/kumiko-framework/db";
4
4
  import { ConfigScopes, defineWriteHandler, isEncryptedAtRest, SYSTEM_TENANT_ID, } from "@cosmicdrift/kumiko-framework/engine";
5
5
  import { InternalError, isKumikoError, UnprocessableError, writeFailure, } from "@cosmicdrift/kumiko-framework/errors";
6
6
  import * as z from "zod";
@@ -74,7 +74,7 @@ export const setWrite = defineWriteHandler({
74
74
  scope,
75
75
  tenantId,
76
76
  userId,
77
- db,
77
+ db: scopeTenantDbToTenant(db, tenantId),
78
78
  });
79
79
  }
80
80
  catch (error) {
@@ -120,8 +120,7 @@ export const setWrite = defineWriteHandler({
120
120
  });
121
121
  }
122
122
  // scope="system" was already rejected above — narrow accordingly.
123
- const subject = resolvePiiSubject(scope, // @cast-boundary runtime-checked above
124
- tenantId, userId);
123
+ const subject = resolvePiiSubject(scope, tenantId, userId);
125
124
  const kmsCtx = {
126
125
  requestId: requestContext.get()?.requestId ?? "config:write:set",
127
126
  tenantId,
@@ -7,6 +7,7 @@ export type ConfigResolverOptions = {
7
7
  cipher?: EnvelopeCipher;
8
8
  appOverrides?: AppConfigOverrides;
9
9
  };
10
+ export declare function decryptStoredValue(raw: string, keyDef: ConfigKeyDefinition, cipher: EnvelopeCipher | undefined, qualifiedKey: string): Promise<string>;
10
11
  export declare function createConfigResolver(options?: ConfigResolverOptions): ConfigResolver;
11
12
  export declare function validateAppOverrides(registry: {
12
13
  getConfigKey: (key: string) => import("@cosmicdrift/kumiko-framework/engine").ConfigKeyDefinition | undefined;
@@ -57,6 +57,15 @@ async function decryptPiiEncrypted(raw, qualifiedKey) {
57
57
  const kmsCtx = { requestId: requestContext.get()?.requestId ?? "config:resolver" };
58
58
  return decryptPiiValueForSubject(kms, raw, kmsCtx, qualifiedKey);
59
59
  }
60
+ // Plaintext form of a stored config_values.value for every read path (resolver,
61
+ // bootstrap idempotency check).
62
+ export async function decryptStoredValue(raw, keyDef, cipher, qualifiedKey) {
63
+ if (keyDef.encrypted)
64
+ return decryptEncrypted(cipher, raw, qualifiedKey);
65
+ if (keyDef.piiEncrypted)
66
+ return decryptPiiEncrypted(raw, qualifiedKey);
67
+ return raw;
68
+ }
60
69
  // backing="secrets" keys store their value in the secrets store (flat per
61
70
  // (tenant,key) at SYSTEM_TENANT_ID), not in config_values. Both read paths
62
71
  // (getWithSource + buildCascade) route the system rung here. Missing reader =
@@ -142,13 +151,7 @@ async function buildCascade(qualifiedKey, keyDef, tenantId, userId, db, fetchRow
142
151
  }
143
152
  const row = await fetchRow(lookup.tenantId, lookup.userId);
144
153
  if (row?.value !== null && row?.value !== undefined) {
145
- let raw = row.value;
146
- if (keyDef.encrypted) {
147
- raw = await decryptEncrypted(cipher, raw, qualifiedKey);
148
- }
149
- else if (keyDef.piiEncrypted) {
150
- raw = await decryptPiiEncrypted(raw, qualifiedKey);
151
- }
154
+ const raw = await decryptStoredValue(row.value, keyDef, cipher, qualifiedKey);
152
155
  if (activeIndex === -1)
153
156
  activeIndex = levels.length;
154
157
  levels.push({
@@ -296,13 +299,7 @@ export function createConfigResolver(options = {}) {
296
299
  for (const lookup of lookups) {
297
300
  const row = await findRow(qualifiedKey, lookup.tenantId, lookup.userId, db);
298
301
  if (row?.value !== null && row?.value !== undefined) {
299
- let raw = row.value;
300
- if (keyDef.encrypted) {
301
- raw = await decryptEncrypted(cipher, raw, qualifiedKey);
302
- }
303
- else if (keyDef.piiEncrypted) {
304
- raw = await decryptPiiEncrypted(raw, qualifiedKey);
305
- }
302
+ const raw = await decryptStoredValue(row.value, keyDef, cipher, qualifiedKey);
306
303
  return { value: deserializeValue(raw, keyDef.type), source: lookup.source };
307
304
  }
308
305
  }