@cosmicdrift/kumiko-samples 0.361.0 → 0.363.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 (265) hide show
  1. package/package.json +1 -1
  2. package/packages/bundled-features/package.json +17 -6
  3. package/packages/bundled-features/src/admin-shell/changes.json +6 -0
  4. package/packages/bundled-features/src/admin-shell/feature.ts +8 -1
  5. package/packages/bundled-features/src/agent-tools/agent-manifest.ts +3 -4
  6. package/packages/bundled-features/src/agent-tools/filter-ops.ts +1 -3
  7. package/packages/bundled-features/src/agent-tools/tool-catalog.ts +1 -3
  8. package/packages/bundled-features/src/agent-tools/tool-dispatch.ts +2 -1
  9. package/packages/bundled-features/src/auth-email-password/changes.json +14 -0
  10. package/packages/bundled-features/src/auth-email-password/email-templates.ts +12 -138
  11. package/packages/bundled-features/src/auth-email-password/feature.ts +30 -9
  12. package/packages/bundled-features/src/auth-email-password/handlers/invite-create.write.ts +4 -5
  13. package/packages/bundled-features/src/auth-email-password/handlers/request-account-unlock.write.ts +2 -5
  14. package/packages/bundled-features/src/auth-email-password/handlers/request-email-verification.write.ts +2 -5
  15. package/packages/bundled-features/src/auth-email-password/handlers/request-password-reset.write.ts +2 -5
  16. package/packages/bundled-features/src/auth-email-password/handlers/signup-confirm.write.ts +3 -4
  17. package/packages/bundled-features/src/auth-email-password/handlers/token-request-handler.ts +3 -12
  18. package/packages/bundled-features/src/auth-email-password/index.ts +2 -17
  19. package/packages/bundled-features/src/auth-email-password/magic-link-mail.ts +30 -22
  20. package/packages/bundled-features/src/auth-email-password/mail-notifications.ts +158 -0
  21. package/packages/bundled-features/src/auth-email-password/signup-activation.ts +2 -3
  22. package/packages/bundled-features/src/auth-foundation/types.ts +8 -18
  23. package/packages/bundled-features/src/auth-mfa/feature.ts +8 -10
  24. package/packages/bundled-features/src/auth-mfa/hmac-token-codec.ts +4 -4
  25. package/packages/bundled-features/src/auth-mfa/mfa-challenge-token.ts +3 -3
  26. package/packages/bundled-features/src/auth-mfa/mfa-preauth-setup-token.ts +3 -3
  27. package/packages/bundled-features/src/auth-mfa/mfa-setup-token.ts +8 -8
  28. package/packages/bundled-features/src/billing-foundation/changes.json +7 -0
  29. package/packages/bundled-features/src/billing-foundation/constants.ts +5 -4
  30. package/packages/bundled-features/src/billing-foundation/consumer-protection/confirmation-mail.ts +38 -44
  31. package/packages/bundled-features/src/billing-foundation/consumer-protection/issue-confirmation.ts +3 -3
  32. package/packages/bundled-features/src/billing-foundation/consumer-protection/mail-notifications.ts +115 -0
  33. package/packages/bundled-features/src/billing-foundation/consumer-protection/register-consent.ts +2 -0
  34. package/packages/bundled-features/src/billing-foundation/consumer-protection/termination-mail.ts +30 -30
  35. package/packages/bundled-features/src/billing-foundation/consumer-protection/termination-notify.ts +2 -2
  36. package/packages/bundled-features/src/billing-foundation/consumer-protection/termination-pages.ts +1 -4
  37. package/packages/bundled-features/src/billing-foundation/consumer-protection/termination-request.ts +2 -2
  38. package/packages/bundled-features/src/billing-foundation/get-subscription-for-tenant.ts +2 -2
  39. package/packages/bundled-features/src/billing-foundation/handlers/create-portal-session.write.ts +4 -9
  40. package/packages/bundled-features/src/billing-foundation/handlers/list-subscriptions.query.ts +1 -1
  41. package/packages/bundled-features/src/billing-foundation/handlers/process-event.write.ts +2 -2
  42. package/packages/bundled-features/src/billing-foundation/handlers/process-payment-event.write.ts +1 -1
  43. package/packages/bundled-features/src/billing-foundation/subscription-tier-sync.ts +3 -6
  44. package/packages/bundled-features/src/cap-overview/handlers/tenant-caps-list.query.ts +2 -1
  45. package/packages/bundled-features/src/cap-overview/web/cap-usage-cell.tsx +6 -4
  46. package/packages/bundled-features/src/channel-email/changes.json +19 -0
  47. package/packages/bundled-features/src/channel-email/email-channel.ts +122 -29
  48. package/packages/bundled-features/src/channel-email/feature.ts +6 -3
  49. package/packages/bundled-features/src/channel-email/index.ts +7 -1
  50. package/packages/bundled-features/src/channel-email/pii-guard.ts +13 -0
  51. package/packages/bundled-features/src/channel-email/smtp-transport.ts +15 -1
  52. package/packages/bundled-features/src/channel-email/types.ts +23 -0
  53. package/packages/bundled-features/src/channel-in-app/handlers/mark-read.write.ts +2 -2
  54. package/packages/bundled-features/src/config/handlers/set.write.ts +1 -5
  55. package/packages/bundled-features/src/custom-fields/db/queries/user-data-rights.ts +2 -2
  56. package/packages/bundled-features/src/custom-fields/lib/value-schema.ts +10 -11
  57. package/packages/bundled-features/src/custom-fields/wire-user-data-rights.ts +7 -6
  58. package/packages/bundled-features/src/data-retention/changes.json +6 -0
  59. package/packages/bundled-features/src/data-retention/index.ts +1 -0
  60. package/packages/bundled-features/src/data-retention/run-retention-cleanup.ts +8 -6
  61. package/packages/bundled-features/src/delivery/attachment-ref.ts +84 -0
  62. package/packages/bundled-features/src/delivery/changes.json +48 -0
  63. package/packages/bundled-features/src/delivery/channel-context.ts +129 -4
  64. package/packages/bundled-features/src/delivery/constants.ts +2 -0
  65. package/packages/bundled-features/src/delivery/delivery-service.ts +104 -26
  66. package/packages/bundled-features/src/delivery/feature.ts +1 -1
  67. package/packages/bundled-features/src/delivery/index.ts +17 -2
  68. package/packages/bundled-features/src/delivery/jobs.ts +48 -5
  69. package/packages/bundled-features/src/delivery/notify-factory.ts +14 -9
  70. package/packages/bundled-features/src/delivery/testing.ts +9 -1
  71. package/packages/bundled-features/src/delivery/types.ts +28 -18
  72. package/packages/bundled-features/src/delivery/unsubscribe.ts +5 -2
  73. package/packages/bundled-features/src/derivatives-sharp/render.ts +1 -22
  74. package/packages/bundled-features/src/document-ingest-foundation/aggregate-id.ts +10 -0
  75. package/packages/bundled-features/src/document-ingest-foundation/changes.json +6 -0
  76. package/packages/bundled-features/src/document-ingest-foundation/events.ts +3 -1
  77. package/packages/bundled-features/src/document-ingest-foundation/feature.ts +5 -3
  78. package/packages/bundled-features/src/document-ingest-foundation/index.ts +1 -0
  79. package/packages/bundled-features/src/document-ingest-foundation/pages.ts +6 -3
  80. package/packages/bundled-features/src/feature-toggles/toggle-sync-signal.ts +4 -4
  81. package/packages/bundled-features/src/file-foundation/index.ts +7 -0
  82. package/packages/bundled-features/src/file-foundation/read-file-ref-content.ts +86 -0
  83. package/packages/bundled-features/src/folders/web/folder-manager.tsx +5 -2
  84. package/packages/bundled-features/src/form-draft/db/queries/cleanup.ts +11 -11
  85. package/packages/bundled-features/src/form-draft/db/queries/draft-count.ts +2 -2
  86. package/packages/bundled-features/src/form-draft/db/queries/owned-file-refs.ts +2 -2
  87. package/packages/bundled-features/src/form-draft/release-file-refs.ts +3 -2
  88. package/packages/bundled-features/src/inbound-mail-foundation/handlers/connect-account.write.ts +1 -1
  89. package/packages/bundled-features/src/inbound-mail-foundation/handlers/ingest-message.write.ts +6 -6
  90. package/packages/bundled-features/src/inbound-mail-foundation/handlers/list-accounts.query.ts +1 -1
  91. package/packages/bundled-features/src/inbound-mail-foundation/handlers/list-messages.query.ts +1 -1
  92. package/packages/bundled-features/src/inbound-mail-foundation/handlers/scope-visibility.ts +2 -2
  93. package/packages/bundled-features/src/inbound-mail-foundation/oauth-state.ts +7 -7
  94. package/packages/bundled-features/src/inbound-mail-foundation/retention-sweep.ts +2 -6
  95. package/packages/bundled-features/src/inbound-mail-foundation/tenant-destroy-hook.ts +5 -13
  96. package/packages/bundled-features/src/inbound-mail-foundation/types.ts +4 -6
  97. package/packages/bundled-features/src/inbound-mail-foundation/watch-supervisor.ts +40 -25
  98. package/packages/bundled-features/src/inbound-provider-imap/imap-client.ts +4 -3
  99. package/packages/bundled-features/src/jobs/handlers/catalog.query.ts +1 -1
  100. package/packages/bundled-features/src/ledger/recurring.ts +1 -3
  101. package/packages/bundled-features/src/managed-pages/branding.ts +33 -9
  102. package/packages/bundled-features/src/managed-pages/changes.json +6 -0
  103. package/packages/bundled-features/src/managed-pages/feature.ts +1 -0
  104. package/packages/bundled-features/src/managed-pages/i18n.ts +1 -0
  105. package/packages/bundled-features/src/managed-pages/screens/branding-screen.ts +8 -0
  106. package/packages/bundled-features/src/page-render/branding.ts +3 -0
  107. package/packages/bundled-features/src/page-render/markdown.ts +1 -1
  108. package/packages/bundled-features/src/personal-access-tokens/feature.ts +2 -4
  109. package/packages/bundled-features/src/personal-access-tokens/handlers/list.query.ts +2 -1
  110. package/packages/bundled-features/src/personal-access-tokens/index.ts +1 -1
  111. package/packages/bundled-features/src/personal-access-tokens/revoke-for-user.ts +2 -1
  112. package/packages/bundled-features/src/renderer-foundation/README.md +11 -10
  113. package/packages/bundled-features/src/renderer-foundation/api.ts +14 -14
  114. package/packages/bundled-features/src/renderer-foundation/changes.json +39 -0
  115. package/packages/bundled-features/src/renderer-foundation/constants.ts +6 -8
  116. package/packages/bundled-features/src/renderer-foundation/feature.ts +7 -7
  117. package/packages/bundled-features/src/renderer-foundation/index.ts +13 -2
  118. package/packages/bundled-features/src/renderer-foundation/types.ts +132 -35
  119. package/packages/bundled-features/src/renderer-pdf/feature.ts +7 -1
  120. package/packages/bundled-features/src/sessions/db/queries/cleanup.ts +2 -2
  121. package/packages/bundled-features/src/sessions/feature.ts +2 -4
  122. package/packages/bundled-features/src/sessions/handlers/list.query.ts +2 -1
  123. package/packages/bundled-features/src/shared/attachment-file-name.ts +39 -0
  124. package/packages/bundled-features/src/shared/index.ts +6 -0
  125. package/packages/bundled-features/src/shared/is-tenant-db.ts +1 -1
  126. package/packages/bundled-features/src/shared/mail-template-body.ts +25 -0
  127. package/packages/bundled-features/src/shared/sharp-input-limits.ts +26 -0
  128. package/packages/bundled-features/src/shared/signup-handover.ts +4 -4
  129. package/packages/bundled-features/src/tags/handlers/assign-tag.write.ts +5 -1
  130. package/packages/bundled-features/src/tags/web/tag-chip.tsx +2 -1
  131. package/packages/bundled-features/src/template-resolver/README.md +63 -7
  132. package/packages/bundled-features/src/template-resolver/api.ts +83 -8
  133. package/packages/bundled-features/src/template-resolver/changes.json +118 -0
  134. package/packages/bundled-features/src/template-resolver/compile-document-body.ts +214 -0
  135. package/packages/bundled-features/src/template-resolver/constants.ts +39 -11
  136. package/packages/bundled-features/src/template-resolver/document-sanitizer.ts +335 -0
  137. package/packages/bundled-features/src/template-resolver/document-schema.ts +323 -0
  138. package/packages/bundled-features/src/template-resolver/feature.ts +56 -6
  139. package/packages/bundled-features/src/template-resolver/handlers/bundle-import.ts +188 -0
  140. package/packages/bundled-features/src/template-resolver/handlers/collection-set.write.ts +30 -2
  141. package/packages/bundled-features/src/template-resolver/handlers/copy-locale.write.ts +142 -0
  142. package/packages/bundled-features/src/template-resolver/handlers/diff-to-system.query.ts +72 -0
  143. package/packages/bundled-features/src/template-resolver/handlers/export-bundle.query.ts +83 -0
  144. package/packages/bundled-features/src/template-resolver/handlers/find-by-id.query.ts +3 -3
  145. package/packages/bundled-features/src/template-resolver/handlers/import-bundle-system.write.ts +42 -0
  146. package/packages/bundled-features/src/template-resolver/handlers/import-bundle.write.ts +22 -0
  147. package/packages/bundled-features/src/template-resolver/handlers/layout-source.query.ts +108 -0
  148. package/packages/bundled-features/src/template-resolver/handlers/list.query.ts +5 -7
  149. package/packages/bundled-features/src/template-resolver/handlers/overview.query.ts +250 -0
  150. package/packages/bundled-features/src/template-resolver/handlers/preview.query.ts +25 -0
  151. package/packages/bundled-features/src/template-resolver/handlers/reset-to-system.write.ts +58 -0
  152. package/packages/bundled-features/src/template-resolver/handlers/resource-link-pdf.write.ts +34 -0
  153. package/packages/bundled-features/src/template-resolver/handlers/resource-remove.write.ts +50 -0
  154. package/packages/bundled-features/src/template-resolver/handlers/resource-shared.ts +24 -0
  155. package/packages/bundled-features/src/template-resolver/handlers/resource-upload.write.ts +135 -0
  156. package/packages/bundled-features/src/template-resolver/handlers/send-test.write.ts +106 -0
  157. package/packages/bundled-features/src/template-resolver/handlers/set.write.ts +32 -3
  158. package/packages/bundled-features/src/template-resolver/handlers/shared.ts +7 -2
  159. package/packages/bundled-features/src/template-resolver/handlers/template-gates.ts +184 -0
  160. package/packages/bundled-features/src/template-resolver/handlers/template-write.ts +183 -0
  161. package/packages/bundled-features/src/template-resolver/handlers/tenant-overrides.query.ts +55 -0
  162. package/packages/bundled-features/src/template-resolver/handlers/toggle-status.write.ts +57 -10
  163. package/packages/bundled-features/src/template-resolver/handlers/upsert-system.write.ts +19 -71
  164. package/packages/bundled-features/src/template-resolver/handlers/upsert-tenant.write.ts +15 -76
  165. package/packages/bundled-features/src/template-resolver/handlers/usage.query.ts +17 -0
  166. package/packages/bundled-features/src/template-resolver/handlers/validate.query.ts +116 -0
  167. package/packages/bundled-features/src/template-resolver/index.ts +100 -0
  168. package/packages/bundled-features/src/template-resolver/layout-regions.ts +206 -0
  169. package/packages/bundled-features/src/template-resolver/linked-resources.ts +102 -0
  170. package/packages/bundled-features/src/template-resolver/preview-display-html.ts +36 -0
  171. package/packages/bundled-features/src/template-resolver/print-document.ts +58 -0
  172. package/packages/bundled-features/src/template-resolver/qualified-names.ts +30 -0
  173. package/packages/bundled-features/src/template-resolver/record-mapping.ts +26 -0
  174. package/packages/bundled-features/src/template-resolver/resource-image.ts +47 -0
  175. package/packages/bundled-features/src/template-resolver/seeding.ts +147 -17
  176. package/packages/bundled-features/src/template-resolver/store-template-resource.ts +416 -0
  177. package/packages/bundled-features/src/template-resolver/table.ts +14 -0
  178. package/packages/bundled-features/src/template-resolver/template-bundle.ts +79 -0
  179. package/packages/bundled-features/src/template-resolver/template-diff.ts +78 -0
  180. package/packages/bundled-features/src/template-resolver/template-meta.ts +129 -0
  181. package/packages/bundled-features/src/template-resolver/template-preview.ts +573 -0
  182. package/packages/bundled-features/src/template-resolver/template-resources.ts +74 -0
  183. package/packages/bundled-features/src/template-resolver/template-usage.ts +48 -0
  184. package/packages/bundled-features/src/template-resolver/template-validation.ts +229 -0
  185. package/packages/bundled-features/src/template-resolver/testing.ts +5 -5
  186. package/packages/bundled-features/src/template-resolver/web/client-plugin.tsx +9 -6
  187. package/packages/bundled-features/src/template-resolver/web/document-editor-extensions.ts +162 -0
  188. package/packages/bundled-features/src/template-resolver/web/document-editor.tsx +28 -0
  189. package/packages/bundled-features/src/template-resolver/web/document-tiptap-editor.tsx +850 -0
  190. package/packages/bundled-features/src/template-resolver/web/document-toolbar-blocks.ts +61 -0
  191. package/packages/bundled-features/src/template-resolver/web/editor-handle.ts +26 -0
  192. package/packages/bundled-features/src/template-resolver/web/i18n.ts +100 -0
  193. package/packages/bundled-features/src/template-resolver/web/index.ts +19 -0
  194. package/packages/bundled-features/src/template-resolver/web/placeholder-line-editor-lazy.tsx +36 -0
  195. package/packages/bundled-features/src/template-resolver/web/placeholder-line-editor.tsx +169 -0
  196. package/packages/bundled-features/src/template-resolver/web/placeholder-text.ts +37 -0
  197. package/packages/bundled-features/src/template-resolver/web/use-stable-by-key.ts +14 -0
  198. package/packages/bundled-features/src/tenant/handlers/team-list.query.ts +3 -2
  199. package/packages/bundled-features/src/tenant/seeding.ts +41 -7
  200. package/packages/bundled-features/src/tenant-lifecycle/boot-checks.ts +1 -1
  201. package/packages/bundled-features/src/tenant-lifecycle/handlers/cancel-destruction.write.ts +2 -1
  202. package/packages/bundled-features/src/user/db/queries/stream-tenant-backfill.ts +4 -4
  203. package/packages/bundled-features/src/user/handlers/detail.query.ts +3 -5
  204. package/packages/bundled-features/src/user/roles-input-schema.ts +2 -2
  205. package/packages/bundled-features/src/user-data-rights/changes.json +14 -0
  206. package/packages/bundled-features/src/user-data-rights/db/queries/forget-cleanup.ts +2 -2
  207. package/packages/bundled-features/src/user-data-rights/email-templates.ts +5 -149
  208. package/packages/bundled-features/src/user-data-rights/feature.ts +72 -35
  209. package/packages/bundled-features/src/user-data-rights/handlers/cancel-deletion.write.ts +2 -1
  210. package/packages/bundled-features/src/user-data-rights/handlers/download-by-token.query.ts +5 -1
  211. package/packages/bundled-features/src/user-data-rights/handlers/list-download-attempts.query.ts +13 -1
  212. package/packages/bundled-features/src/user-data-rights/handlers/request-deletion.write.ts +5 -11
  213. package/packages/bundled-features/src/user-data-rights/index.ts +1 -0
  214. package/packages/bundled-features/src/user-data-rights/lib/default-mailers.ts +134 -53
  215. package/packages/bundled-features/src/user-data-rights/lib/update-user-lifecycle.ts +2 -2
  216. package/packages/bundled-features/src/user-data-rights/mail-notifications.ts +157 -0
  217. package/packages/bundled-features/src/user-data-rights/run-forget-cleanup.ts +1 -1
  218. package/packages/bundled-features/src/user-data-rights/schema/download-attempt.ts +6 -12
  219. package/packages/bundled-features/src/user-data-rights/web/privacy-center-screen.tsx +2 -1
  220. package/packages/bundled-features/src/user-data-rights-defaults/feature.ts +10 -1
  221. package/packages/bundled-features/src/user-data-rights-defaults/hooks/download-attempt.userdata-hook.ts +58 -0
  222. package/packages/bundled-features/src/user-profile/web/profile-screen.tsx +2 -1
  223. package/packages/bundled-features/src/waitlist/changes.json +7 -0
  224. package/packages/bundled-features/src/waitlist/feature.ts +17 -0
  225. package/packages/bundled-features/src/waitlist/handlers/submit.write.ts +4 -5
  226. package/packages/bundled-features/src/waitlist/mail.ts +69 -18
  227. package/packages/bundled-features/src/workflow-runner/event-trigger.ts +1 -2
  228. package/samples/apps/ui-walkthrough/package.json +2 -0
  229. package/samples/apps/ui-walkthrough/src/app/auth-constants.ts +9 -0
  230. package/samples/apps/ui-walkthrough/src/app/client.tsx +6 -0
  231. package/samples/apps/ui-walkthrough/src/app/seed-templates.ts +154 -0
  232. package/samples/apps/ui-walkthrough/src/app/server.ts +5 -1
  233. package/samples/apps/ui-walkthrough/src/app/shell.tsx +12 -1
  234. package/samples/apps/ui-walkthrough/src/app/template-seed-data.ts +354 -0
  235. package/samples/apps/ui-walkthrough/src/features/billing/feature.ts +68 -0
  236. package/samples/apps/ui-walkthrough/src/features/billing/i18n.ts +18 -0
  237. package/samples/apps/ui-walkthrough/src/features/billing/index.ts +2 -0
  238. package/samples/apps/ui-walkthrough/src/features/billing/web/index.ts +7 -0
  239. package/samples/apps/ui-walkthrough/src/features/rental/screens.ts +1 -1
  240. package/samples/apps/ui-walkthrough/src/run-config.ts +37 -1
  241. package/samples/apps/use-all-bundled/e2e-billing-gallery/gallery-features.ts +0 -2
  242. package/samples/apps/use-all-bundled/feature-manifest.json +123 -22
  243. package/samples/apps/use-all-bundled/kumiko/migrations/0044_download-attempt-ip-bidx.rebuild.json +4 -0
  244. package/samples/apps/use-all-bundled/kumiko/migrations/0045_template-resource-meta.rebuild.json +4 -0
  245. package/samples/apps/use-all-bundled/kumiko/migrations/0046_pdf-rendered.rebuild.json +4 -0
  246. package/samples/apps/use-all-bundled/package.json +2 -0
  247. package/samples/apps/use-all-bundled/playwright.config.ts +38 -3
  248. package/samples/apps/use-all-bundled/schema/generate.ts +13 -3
  249. package/samples/apps/use-all-bundled/schema/schema.generated.ts +3 -0
  250. package/samples/apps/use-all-bundled/scripts/check-coverage.ts +3 -0
  251. package/samples/apps/use-all-bundled/src/app/auth-constants.ts +3 -0
  252. package/samples/apps/use-all-bundled/src/app/client.tsx +4 -0
  253. package/samples/apps/use-all-bundled/src/app/seed-documents.ts +128 -0
  254. package/samples/apps/use-all-bundled/src/app/seed.ts +4 -0
  255. package/samples/apps/use-all-bundled/src/run-config.ts +11 -11
  256. package/samples/recipes/delivery-notifications/README.md +26 -25
  257. package/samples/recipes/delivery-notifications/package.json +2 -1
  258. package/samples/recipes/delivery-notifications/src/feature.ts +64 -58
  259. package/samples/recipes/seed-migration/README.md +1 -1
  260. package/packages/bundled-features/src/renderer-simple/changes.json +0 -27
  261. package/packages/bundled-features/src/renderer-simple/feature.ts +0 -66
  262. package/packages/bundled-features/src/renderer-simple/index.ts +0 -8
  263. package/packages/bundled-features/src/renderer-simple/mail-blocks.ts +0 -138
  264. package/packages/bundled-features/src/renderer-simple/resolve-variables.ts +0 -57
  265. package/packages/bundled-features/src/renderer-simple/simple-renderer.ts +0 -304
@@ -1,304 +0,0 @@
1
- import { escapeHtml, escapeHtmlAttr } from "@cosmicdrift/kumiko-headless";
2
- import type { NotificationRenderer } from "../delivery/index.js";
3
- import { renderSafeMarkdown } from "../page-render/index.js";
4
- import {
5
- badgeText,
6
- chipsText,
7
- type MailBadge,
8
- type MailChip,
9
- type MailStage,
10
- parseBadge,
11
- renderBadge,
12
- renderChips,
13
- renderStages,
14
- stagesText,
15
- } from "./mail-blocks.js";
16
-
17
- export type MailBranding = {
18
- /** Shown as text when there is no logo, and as the logo alt text. */
19
- readonly productName?: string;
20
- /** Hex color (#rgb, #rrggbb, #rrggbbaa); used for buttons and the header accent. */
21
- readonly primaryColor?: string;
22
- /** Absolute http(s) URL; anything else is dropped. Mutually exclusive with `logoPath`. */
23
- readonly logoUrl?: string;
24
- /** Absolute path on `baseUrl` (e.g. "/logo.png"); use PNG/JPEG, mail clients block SVG. */
25
- readonly logoPath?: string;
26
- /** Absolute http(s) origin that `logoPath` is resolved against; same base as `auth.mail.baseUrl`. */
27
- readonly baseUrl?: string;
28
- /** Locale tried after the exact and language-part match when a localized field has no entry. */
29
- readonly defaultLocale?: string;
30
- readonly footerText?: LocalizedText;
31
- /** Absolute http(s) URLs only; string URLs that are not are dropped, localized ones fail at boot. */
32
- readonly footerLinks?: readonly {
33
- readonly label: LocalizedText;
34
- readonly url: LocalizedText;
35
- }[];
36
- };
37
-
38
- export type LocalizedText = string | Readonly<Record<string, string>>;
39
-
40
- const DEFAULT_PRIMARY_COLOR = "#2563eb";
41
- const HEX_COLOR_PATTERN = /^#(?:[0-9a-fA-F]{3,4}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})$/;
42
-
43
- function isHttpUrl(value: string): boolean {
44
- try {
45
- const { protocol } = new URL(value);
46
- return protocol === "http:" || protocol === "https:";
47
- } catch {
48
- return false;
49
- }
50
- }
51
-
52
- function languagePart(locale: string): string {
53
- return locale.split("-")[0] ?? locale;
54
- }
55
-
56
- function pickByLocale(
57
- entries: Readonly<Record<string, string>>,
58
- locale: string,
59
- ): string | undefined {
60
- // Own keys only: a locale like "constructor" must not hit Object.prototype.
61
- const ownEntry = (key: string): string | undefined =>
62
- Object.hasOwn(entries, key) ? entries[key] : undefined;
63
- return ownEntry(locale) ?? ownEntry(languagePart(locale));
64
- }
65
-
66
- function resolveLocalized(
67
- value: LocalizedText | undefined,
68
- locale: string | undefined,
69
- defaultLocale: string | undefined,
70
- ): string | undefined {
71
- if (value === undefined || typeof value === "string") return value;
72
- return (
73
- (locale !== undefined ? pickByLocale(value, locale) : undefined) ??
74
- (defaultLocale !== undefined ? pickByLocale(value, defaultLocale) : undefined) ??
75
- Object.values(value)[0]
76
- );
77
- }
78
-
79
- function assertLocalizedUrlsAreHttp(branding: MailBranding): void {
80
- for (const link of branding.footerLinks ?? []) {
81
- if (typeof link.url === "string") continue;
82
- for (const [locale, url] of Object.entries(link.url)) {
83
- if (!isHttpUrl(url)) {
84
- throw new Error(
85
- `createSimpleRenderer: footerLinks url for locale "${locale}" must be an absolute http(s) URL`,
86
- );
87
- }
88
- }
89
- }
90
- }
91
-
92
- function resolveLogoUrl(branding: MailBranding | undefined): string | undefined {
93
- if (!branding) return undefined;
94
- const { logoUrl, logoPath, baseUrl } = branding;
95
- if (logoPath === undefined) {
96
- return logoUrl && isHttpUrl(logoUrl) ? logoUrl : undefined;
97
- }
98
- if (logoUrl !== undefined) {
99
- throw new Error("createSimpleRenderer: logoPath and logoUrl are mutually exclusive");
100
- }
101
- if (baseUrl === undefined || !isHttpUrl(baseUrl)) {
102
- throw new Error("createSimpleRenderer: logoPath requires an absolute http(s) baseUrl");
103
- }
104
- if (!/^\/(?![/\\])/.test(logoPath) || logoPath.includes("\\")) {
105
- throw new Error('createSimpleRenderer: logoPath must start with a single "/"');
106
- }
107
- const resolved = new URL(logoPath, baseUrl);
108
- if (resolved.origin !== new URL(baseUrl).origin) {
109
- throw new Error("createSimpleRenderer: logoPath must stay on the baseUrl origin");
110
- }
111
- return resolved.href;
112
- }
113
-
114
- function resolvePrimaryColor(branding: MailBranding | undefined): string {
115
- const color = branding?.primaryColor;
116
- return color && HEX_COLOR_PATTERN.test(color) ? color : DEFAULT_PRIMARY_COLOR;
117
- }
118
-
119
- function renderBrandingHeader(
120
- branding: MailBranding | undefined,
121
- primaryColor: string,
122
- logoUrl: string | undefined,
123
- ): string {
124
- if (!branding) return "";
125
- if (!logoUrl && !branding.productName) return "";
126
- const logo = logoUrl
127
- ? `<img src="${escapeHtmlAttr(logoUrl)}" alt="${escapeHtmlAttr(branding.productName ?? "")}" style="display:block;max-height:40px;max-width:200px;border:0" />`
128
- : `<span style="font-size:18px;font-weight:700;color:${escapeHtmlAttr(primaryColor)}">${escapeHtml(branding.productName ?? "")}</span>`;
129
- return `<div style="margin:0 0 24px;padding:0 0 16px;border-bottom:3px solid ${escapeHtmlAttr(primaryColor)}">${logo}</div>`;
130
- }
131
-
132
- type ResolvedBrandingFooter = {
133
- readonly footerText: string | undefined;
134
- readonly links: readonly { readonly label: string; readonly url: string }[];
135
- };
136
-
137
- function resolveBrandingFooter(
138
- branding: MailBranding,
139
- locale: string | undefined,
140
- ): ResolvedBrandingFooter {
141
- const pick = (value: LocalizedText | undefined): string | undefined =>
142
- resolveLocalized(value, locale, branding.defaultLocale);
143
- const links = (branding.footerLinks ?? []).flatMap((link) => {
144
- const url = pick(link.url);
145
- if (url === undefined || !isHttpUrl(url)) return [];
146
- return [{ label: pick(link.label) ?? "", url }];
147
- });
148
- return { footerText: pick(branding.footerText), links };
149
- }
150
-
151
- function renderBrandingFooter(
152
- branding: MailBranding | undefined,
153
- locale: string | undefined,
154
- ): string {
155
- if (!branding) return "";
156
- const { footerText, links: resolvedLinks } = resolveBrandingFooter(branding, locale);
157
- const links = resolvedLinks.map(
158
- (link) =>
159
- `<a href="${escapeHtmlAttr(link.url)}" style="color:#999">${escapeHtml(link.label)}</a>`,
160
- );
161
- const footerPartsHtml = [footerText ? escapeHtml(footerText) : "", links.join(" · ")].filter(
162
- (part) => part !== "",
163
- );
164
- if (footerPartsHtml.length === 0) return "";
165
- const footerHtml = footerPartsHtml.join("<br />");
166
- return `<p style="margin:16px 0 0;color:#999;font-size:12px">${footerHtml}</p>`;
167
- }
168
-
169
- function brandingFooterLines(
170
- branding: MailBranding | undefined,
171
- locale: string | undefined,
172
- ): readonly string[] {
173
- if (!branding) return [];
174
- const { footerText, links } = resolveBrandingFooter(branding, locale);
175
- return [
176
- ...(footerText ? [footerText] : []),
177
- ...links.map((link) => (link.label ? `${link.label}: ${link.url}` : link.url)),
178
- ];
179
- }
180
-
181
- type Section =
182
- | { readonly text: string }
183
- | { readonly heading: string }
184
- | { readonly markdown: string }
185
- | { readonly button: { readonly label: string; readonly url: string } }
186
- | { readonly stages: readonly MailStage[] }
187
- | { readonly chips: readonly MailChip[] };
188
-
189
- type EmailTemplateData = {
190
- // Preferred: structured email data
191
- readonly badge?: MailBadge;
192
- readonly header?: string;
193
- readonly sections?: readonly Section[];
194
- readonly footer?: string;
195
- // Fallback: plain title + body (used when no structured template is defined)
196
- readonly title?: string;
197
- readonly body?: string;
198
- };
199
-
200
- function templateContent(variables: Readonly<Record<string, unknown>>): {
201
- readonly badge: MailBadge | undefined;
202
- readonly header: string | undefined;
203
- readonly sections: readonly Section[] | undefined;
204
- readonly footer: string | undefined;
205
- } {
206
- const data = variables as EmailTemplateData; // @cast-boundary render-helper
207
- // Without structured fields, title + body become the header and a single text section.
208
- return {
209
- badge: parseBadge(data.badge),
210
- header: data.header ?? data.title,
211
- sections: data.sections ?? (data.body ? [{ text: data.body }] : undefined),
212
- footer: data.footer,
213
- };
214
- }
215
-
216
- // Markdown stays as written: its source is already readable plain text.
217
- function sectionText(section: Section): string {
218
- if ("text" in section) return section.text;
219
- if ("heading" in section) return section.heading;
220
- if ("markdown" in section) return section.markdown;
221
- if ("button" in section) return `${section.button.label}: ${section.button.url}`;
222
- if ("stages" in section) return stagesText(section.stages);
223
- if ("chips" in section) return chipsText(section.chips);
224
- return "";
225
- }
226
-
227
- function renderSection(section: Section, primaryColor: string): string {
228
- if ("text" in section) {
229
- return `<p style="margin:0 0 16px;color:#333;font-size:14px;line-height:1.5">${escapeHtml(section.text)}</p>`;
230
- }
231
- if ("heading" in section) {
232
- return `<h2 style="margin:24px 0 12px;color:#111;font-size:16px;font-weight:600">${escapeHtml(section.heading)}</h2>`;
233
- }
234
- if ("markdown" in section) {
235
- const markdownHtml = renderSafeMarkdown(section.markdown);
236
- return `<div style="color:#333;font-size:14px;line-height:1.5">${markdownHtml}</div>`;
237
- }
238
- if ("button" in section) {
239
- return `<p style="margin:0 0 16px"><a href="${escapeHtml(section.button.url)}" style="display:inline-block;padding:10px 24px;background:${escapeHtmlAttr(primaryColor)};color:#fff;text-decoration:none;border-radius:4px;font-size:14px">${escapeHtml(section.button.label)}</a></p>`;
240
- }
241
- if ("stages" in section) return renderStages(section.stages, primaryColor);
242
- if ("chips" in section) return renderChips(section.chips);
243
- return "";
244
- }
245
-
246
- // Simple Renderer: turns structured email template data into HTML with inline CSS.
247
- // No external dependencies, no template engine — just string concatenation.
248
- export function createSimpleRenderer(branding?: MailBranding): NotificationRenderer {
249
- const primaryColor = resolvePrimaryColor(branding);
250
- const logoUrl = resolveLogoUrl(branding);
251
- if (branding) assertLocalizedUrlsAreHttp(branding);
252
- return {
253
- name: "simple",
254
-
255
- async render(input) {
256
- const { badge, header, sections, footer } = templateContent(input.variables);
257
-
258
- const parts: string[] = [];
259
- parts.push('<!DOCTYPE html><html><body style="margin:0;padding:0;font-family:sans-serif">');
260
- parts.push('<div style="max-width:600px;margin:0 auto;padding:24px">');
261
- parts.push(renderBrandingHeader(branding, primaryColor, logoUrl));
262
-
263
- if (badge) parts.push(renderBadge(badge));
264
-
265
- if (header) {
266
- parts.push(
267
- `<h1 style="margin:0 0 24px;color:#111;font-size:20px;font-weight:600">${escapeHtml(header)}</h1>`,
268
- );
269
- }
270
-
271
- if (sections) {
272
- for (const section of sections) {
273
- parts.push(renderSection(section, primaryColor));
274
- }
275
- }
276
-
277
- if (footer) {
278
- parts.push(
279
- `<p style="margin:24px 0 0;color:#999;font-size:12px;border-top:1px solid #eee;padding-top:16px">${escapeHtml(footer)}</p>`,
280
- );
281
- }
282
-
283
- parts.push(renderBrandingFooter(branding, input.locale));
284
- parts.push("</div></body></html>");
285
- return parts.join("");
286
- },
287
-
288
- async renderText(input) {
289
- const { badge, header, sections, footer } = templateContent(input.variables);
290
- const brandingFooter = brandingFooterLines(branding, input.locale).join("\n");
291
- return [
292
- badge ? badgeText(badge) : undefined,
293
- header,
294
- ...(sections ?? []).map(sectionText),
295
- footer,
296
- brandingFooter,
297
- ]
298
- .filter((block): block is string => block !== undefined && block !== "")
299
- .join("\n\n");
300
- },
301
- };
302
- }
303
-
304
- export const simpleRenderer: NotificationRenderer = createSimpleRenderer();