@cosmicdrift/kumiko-samples 0.362.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 (252) 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/pages.ts +6 -3
  75. package/packages/bundled-features/src/feature-toggles/toggle-sync-signal.ts +4 -4
  76. package/packages/bundled-features/src/file-foundation/index.ts +7 -0
  77. package/packages/bundled-features/src/file-foundation/read-file-ref-content.ts +86 -0
  78. package/packages/bundled-features/src/folders/web/folder-manager.tsx +5 -2
  79. package/packages/bundled-features/src/form-draft/db/queries/cleanup.ts +11 -11
  80. package/packages/bundled-features/src/form-draft/db/queries/draft-count.ts +2 -2
  81. package/packages/bundled-features/src/form-draft/db/queries/owned-file-refs.ts +2 -2
  82. package/packages/bundled-features/src/form-draft/release-file-refs.ts +3 -2
  83. package/packages/bundled-features/src/inbound-mail-foundation/handlers/connect-account.write.ts +1 -1
  84. package/packages/bundled-features/src/inbound-mail-foundation/handlers/ingest-message.write.ts +6 -6
  85. package/packages/bundled-features/src/inbound-mail-foundation/handlers/list-accounts.query.ts +1 -1
  86. package/packages/bundled-features/src/inbound-mail-foundation/handlers/list-messages.query.ts +1 -1
  87. package/packages/bundled-features/src/inbound-mail-foundation/handlers/scope-visibility.ts +2 -2
  88. package/packages/bundled-features/src/inbound-mail-foundation/oauth-state.ts +7 -7
  89. package/packages/bundled-features/src/inbound-mail-foundation/retention-sweep.ts +2 -6
  90. package/packages/bundled-features/src/inbound-mail-foundation/tenant-destroy-hook.ts +5 -13
  91. package/packages/bundled-features/src/inbound-mail-foundation/types.ts +4 -6
  92. package/packages/bundled-features/src/inbound-mail-foundation/watch-supervisor.ts +40 -25
  93. package/packages/bundled-features/src/inbound-provider-imap/imap-client.ts +4 -3
  94. package/packages/bundled-features/src/jobs/handlers/catalog.query.ts +1 -1
  95. package/packages/bundled-features/src/ledger/recurring.ts +1 -3
  96. package/packages/bundled-features/src/managed-pages/branding.ts +33 -9
  97. package/packages/bundled-features/src/managed-pages/changes.json +6 -0
  98. package/packages/bundled-features/src/managed-pages/feature.ts +1 -0
  99. package/packages/bundled-features/src/managed-pages/i18n.ts +1 -0
  100. package/packages/bundled-features/src/managed-pages/screens/branding-screen.ts +8 -0
  101. package/packages/bundled-features/src/page-render/branding.ts +3 -0
  102. package/packages/bundled-features/src/page-render/markdown.ts +1 -1
  103. package/packages/bundled-features/src/personal-access-tokens/feature.ts +2 -4
  104. package/packages/bundled-features/src/personal-access-tokens/handlers/list.query.ts +2 -1
  105. package/packages/bundled-features/src/personal-access-tokens/index.ts +1 -1
  106. package/packages/bundled-features/src/personal-access-tokens/revoke-for-user.ts +2 -1
  107. package/packages/bundled-features/src/renderer-foundation/README.md +11 -10
  108. package/packages/bundled-features/src/renderer-foundation/api.ts +14 -14
  109. package/packages/bundled-features/src/renderer-foundation/changes.json +39 -0
  110. package/packages/bundled-features/src/renderer-foundation/constants.ts +6 -8
  111. package/packages/bundled-features/src/renderer-foundation/feature.ts +7 -7
  112. package/packages/bundled-features/src/renderer-foundation/index.ts +13 -2
  113. package/packages/bundled-features/src/renderer-foundation/types.ts +132 -35
  114. package/packages/bundled-features/src/renderer-pdf/feature.ts +7 -1
  115. package/packages/bundled-features/src/sessions/db/queries/cleanup.ts +2 -2
  116. package/packages/bundled-features/src/sessions/feature.ts +2 -4
  117. package/packages/bundled-features/src/sessions/handlers/list.query.ts +2 -1
  118. package/packages/bundled-features/src/shared/attachment-file-name.ts +39 -0
  119. package/packages/bundled-features/src/shared/index.ts +6 -0
  120. package/packages/bundled-features/src/shared/is-tenant-db.ts +1 -1
  121. package/packages/bundled-features/src/shared/mail-template-body.ts +25 -0
  122. package/packages/bundled-features/src/shared/sharp-input-limits.ts +26 -0
  123. package/packages/bundled-features/src/shared/signup-handover.ts +4 -4
  124. package/packages/bundled-features/src/tags/handlers/assign-tag.write.ts +5 -1
  125. package/packages/bundled-features/src/tags/web/tag-chip.tsx +2 -1
  126. package/packages/bundled-features/src/template-resolver/README.md +63 -7
  127. package/packages/bundled-features/src/template-resolver/api.ts +83 -8
  128. package/packages/bundled-features/src/template-resolver/changes.json +118 -0
  129. package/packages/bundled-features/src/template-resolver/compile-document-body.ts +214 -0
  130. package/packages/bundled-features/src/template-resolver/constants.ts +39 -11
  131. package/packages/bundled-features/src/template-resolver/document-sanitizer.ts +335 -0
  132. package/packages/bundled-features/src/template-resolver/document-schema.ts +323 -0
  133. package/packages/bundled-features/src/template-resolver/feature.ts +56 -6
  134. package/packages/bundled-features/src/template-resolver/handlers/bundle-import.ts +188 -0
  135. package/packages/bundled-features/src/template-resolver/handlers/collection-set.write.ts +30 -2
  136. package/packages/bundled-features/src/template-resolver/handlers/copy-locale.write.ts +142 -0
  137. package/packages/bundled-features/src/template-resolver/handlers/diff-to-system.query.ts +72 -0
  138. package/packages/bundled-features/src/template-resolver/handlers/export-bundle.query.ts +83 -0
  139. package/packages/bundled-features/src/template-resolver/handlers/find-by-id.query.ts +3 -3
  140. package/packages/bundled-features/src/template-resolver/handlers/import-bundle-system.write.ts +42 -0
  141. package/packages/bundled-features/src/template-resolver/handlers/import-bundle.write.ts +22 -0
  142. package/packages/bundled-features/src/template-resolver/handlers/layout-source.query.ts +108 -0
  143. package/packages/bundled-features/src/template-resolver/handlers/list.query.ts +5 -7
  144. package/packages/bundled-features/src/template-resolver/handlers/overview.query.ts +250 -0
  145. package/packages/bundled-features/src/template-resolver/handlers/preview.query.ts +25 -0
  146. package/packages/bundled-features/src/template-resolver/handlers/reset-to-system.write.ts +58 -0
  147. package/packages/bundled-features/src/template-resolver/handlers/resource-link-pdf.write.ts +34 -0
  148. package/packages/bundled-features/src/template-resolver/handlers/resource-remove.write.ts +50 -0
  149. package/packages/bundled-features/src/template-resolver/handlers/resource-shared.ts +24 -0
  150. package/packages/bundled-features/src/template-resolver/handlers/resource-upload.write.ts +135 -0
  151. package/packages/bundled-features/src/template-resolver/handlers/send-test.write.ts +106 -0
  152. package/packages/bundled-features/src/template-resolver/handlers/set.write.ts +32 -3
  153. package/packages/bundled-features/src/template-resolver/handlers/shared.ts +7 -2
  154. package/packages/bundled-features/src/template-resolver/handlers/template-gates.ts +184 -0
  155. package/packages/bundled-features/src/template-resolver/handlers/template-write.ts +183 -0
  156. package/packages/bundled-features/src/template-resolver/handlers/tenant-overrides.query.ts +55 -0
  157. package/packages/bundled-features/src/template-resolver/handlers/toggle-status.write.ts +57 -10
  158. package/packages/bundled-features/src/template-resolver/handlers/upsert-system.write.ts +19 -71
  159. package/packages/bundled-features/src/template-resolver/handlers/upsert-tenant.write.ts +15 -76
  160. package/packages/bundled-features/src/template-resolver/handlers/usage.query.ts +17 -0
  161. package/packages/bundled-features/src/template-resolver/handlers/validate.query.ts +116 -0
  162. package/packages/bundled-features/src/template-resolver/index.ts +100 -0
  163. package/packages/bundled-features/src/template-resolver/layout-regions.ts +206 -0
  164. package/packages/bundled-features/src/template-resolver/linked-resources.ts +102 -0
  165. package/packages/bundled-features/src/template-resolver/preview-display-html.ts +36 -0
  166. package/packages/bundled-features/src/template-resolver/print-document.ts +58 -0
  167. package/packages/bundled-features/src/template-resolver/qualified-names.ts +30 -0
  168. package/packages/bundled-features/src/template-resolver/record-mapping.ts +26 -0
  169. package/packages/bundled-features/src/template-resolver/resource-image.ts +47 -0
  170. package/packages/bundled-features/src/template-resolver/seeding.ts +147 -17
  171. package/packages/bundled-features/src/template-resolver/store-template-resource.ts +416 -0
  172. package/packages/bundled-features/src/template-resolver/table.ts +14 -0
  173. package/packages/bundled-features/src/template-resolver/template-bundle.ts +79 -0
  174. package/packages/bundled-features/src/template-resolver/template-diff.ts +78 -0
  175. package/packages/bundled-features/src/template-resolver/template-meta.ts +129 -0
  176. package/packages/bundled-features/src/template-resolver/template-preview.ts +573 -0
  177. package/packages/bundled-features/src/template-resolver/template-resources.ts +74 -0
  178. package/packages/bundled-features/src/template-resolver/template-usage.ts +48 -0
  179. package/packages/bundled-features/src/template-resolver/template-validation.ts +229 -0
  180. package/packages/bundled-features/src/template-resolver/testing.ts +5 -5
  181. package/packages/bundled-features/src/template-resolver/web/client-plugin.tsx +9 -6
  182. package/packages/bundled-features/src/template-resolver/web/document-editor-extensions.ts +162 -0
  183. package/packages/bundled-features/src/template-resolver/web/document-editor.tsx +28 -0
  184. package/packages/bundled-features/src/template-resolver/web/document-tiptap-editor.tsx +850 -0
  185. package/packages/bundled-features/src/template-resolver/web/document-toolbar-blocks.ts +61 -0
  186. package/packages/bundled-features/src/template-resolver/web/editor-handle.ts +26 -0
  187. package/packages/bundled-features/src/template-resolver/web/i18n.ts +100 -0
  188. package/packages/bundled-features/src/template-resolver/web/index.ts +19 -0
  189. package/packages/bundled-features/src/template-resolver/web/placeholder-line-editor-lazy.tsx +36 -0
  190. package/packages/bundled-features/src/template-resolver/web/placeholder-line-editor.tsx +169 -0
  191. package/packages/bundled-features/src/template-resolver/web/placeholder-text.ts +37 -0
  192. package/packages/bundled-features/src/template-resolver/web/use-stable-by-key.ts +14 -0
  193. package/packages/bundled-features/src/tenant/handlers/team-list.query.ts +3 -2
  194. package/packages/bundled-features/src/tenant/seeding.ts +41 -7
  195. package/packages/bundled-features/src/tenant-lifecycle/boot-checks.ts +1 -1
  196. package/packages/bundled-features/src/tenant-lifecycle/handlers/cancel-destruction.write.ts +2 -1
  197. package/packages/bundled-features/src/user/db/queries/stream-tenant-backfill.ts +4 -4
  198. package/packages/bundled-features/src/user/handlers/detail.query.ts +3 -5
  199. package/packages/bundled-features/src/user/roles-input-schema.ts +2 -2
  200. package/packages/bundled-features/src/user-data-rights/changes.json +7 -0
  201. package/packages/bundled-features/src/user-data-rights/db/queries/forget-cleanup.ts +2 -2
  202. package/packages/bundled-features/src/user-data-rights/email-templates.ts +5 -149
  203. package/packages/bundled-features/src/user-data-rights/feature.ts +72 -35
  204. package/packages/bundled-features/src/user-data-rights/handlers/cancel-deletion.write.ts +2 -1
  205. package/packages/bundled-features/src/user-data-rights/handlers/request-deletion.write.ts +5 -11
  206. package/packages/bundled-features/src/user-data-rights/lib/default-mailers.ts +134 -53
  207. package/packages/bundled-features/src/user-data-rights/lib/update-user-lifecycle.ts +2 -2
  208. package/packages/bundled-features/src/user-data-rights/mail-notifications.ts +157 -0
  209. package/packages/bundled-features/src/user-data-rights/run-forget-cleanup.ts +1 -1
  210. package/packages/bundled-features/src/user-data-rights/web/privacy-center-screen.tsx +2 -1
  211. package/packages/bundled-features/src/user-profile/web/profile-screen.tsx +2 -1
  212. package/packages/bundled-features/src/waitlist/changes.json +7 -0
  213. package/packages/bundled-features/src/waitlist/feature.ts +17 -0
  214. package/packages/bundled-features/src/waitlist/handlers/submit.write.ts +4 -5
  215. package/packages/bundled-features/src/waitlist/mail.ts +69 -18
  216. package/packages/bundled-features/src/workflow-runner/event-trigger.ts +1 -2
  217. package/samples/apps/ui-walkthrough/package.json +2 -0
  218. package/samples/apps/ui-walkthrough/src/app/auth-constants.ts +9 -0
  219. package/samples/apps/ui-walkthrough/src/app/client.tsx +6 -0
  220. package/samples/apps/ui-walkthrough/src/app/seed-templates.ts +154 -0
  221. package/samples/apps/ui-walkthrough/src/app/server.ts +5 -1
  222. package/samples/apps/ui-walkthrough/src/app/shell.tsx +12 -1
  223. package/samples/apps/ui-walkthrough/src/app/template-seed-data.ts +354 -0
  224. package/samples/apps/ui-walkthrough/src/features/billing/feature.ts +68 -0
  225. package/samples/apps/ui-walkthrough/src/features/billing/i18n.ts +18 -0
  226. package/samples/apps/ui-walkthrough/src/features/billing/index.ts +2 -0
  227. package/samples/apps/ui-walkthrough/src/features/billing/web/index.ts +7 -0
  228. package/samples/apps/ui-walkthrough/src/run-config.ts +37 -1
  229. package/samples/apps/use-all-bundled/e2e-billing-gallery/gallery-features.ts +0 -2
  230. package/samples/apps/use-all-bundled/feature-manifest.json +115 -20
  231. package/samples/apps/use-all-bundled/kumiko/migrations/0045_template-resource-meta.rebuild.json +4 -0
  232. package/samples/apps/use-all-bundled/kumiko/migrations/0046_pdf-rendered.rebuild.json +4 -0
  233. package/samples/apps/use-all-bundled/package.json +2 -0
  234. package/samples/apps/use-all-bundled/playwright.config.ts +38 -3
  235. package/samples/apps/use-all-bundled/schema/generate.ts +13 -3
  236. package/samples/apps/use-all-bundled/schema/schema.generated.ts +3 -0
  237. package/samples/apps/use-all-bundled/scripts/check-coverage.ts +3 -0
  238. package/samples/apps/use-all-bundled/src/app/auth-constants.ts +3 -0
  239. package/samples/apps/use-all-bundled/src/app/client.tsx +4 -0
  240. package/samples/apps/use-all-bundled/src/app/seed-documents.ts +128 -0
  241. package/samples/apps/use-all-bundled/src/app/seed.ts +4 -0
  242. package/samples/apps/use-all-bundled/src/run-config.ts +11 -11
  243. package/samples/recipes/delivery-notifications/README.md +26 -25
  244. package/samples/recipes/delivery-notifications/package.json +2 -1
  245. package/samples/recipes/delivery-notifications/src/feature.ts +64 -58
  246. package/samples/recipes/seed-migration/README.md +1 -1
  247. package/packages/bundled-features/src/renderer-simple/changes.json +0 -27
  248. package/packages/bundled-features/src/renderer-simple/feature.ts +0 -66
  249. package/packages/bundled-features/src/renderer-simple/index.ts +0 -8
  250. package/packages/bundled-features/src/renderer-simple/mail-blocks.ts +0 -138
  251. package/packages/bundled-features/src/renderer-simple/resolve-variables.ts +0 -57
  252. package/packages/bundled-features/src/renderer-simple/simple-renderer.ts +0 -304
@@ -1,4 +1,43 @@
1
1
  [
2
+ {
3
+ "version": "0.363.0",
4
+ "type": "breaking",
5
+ "title": "document-pdf requests name a template, the response carries fileName",
6
+ "detail": "The document-pdf payload is { slug, locale, variables } instead of ready HTML in content. The response has a fileName, RendererContext gets an optional filesForTenant (file access per tenant) and RendererError has the new code unavailable for a renderer backend that cannot be reached.",
7
+ "migration": "Callers that sent `payload: { content }` switch to a document-pdf template and pass slug, locale and variables (money-horse credit-schedule-pdf follows in #621); renderer-pdf callers pass slug and locale too. The enterprise package @cosmicdriftgamestudio/kumiko-renderer-puppeteer-client no longer builds against these types and is replaced by @cosmicdrift/kumiko-renderer-puppeteer-client."
8
+ },
9
+ {
10
+ "version": "0.363.0",
11
+ "type": "improvement",
12
+ "title": "renderer plugins can preview a document",
13
+ "detail": "RendererPlugin gets the optional capability previewDocument(req, ctx), which returns the filled-in body and layout regions without calling the print service. template-resolver:query:preview with kind document-pdf uses it, so the editor shows replaced placeholders and the resource and required-placeholder checks; without such a plugin the preview keeps returning the body and raw layout."
14
+ },
15
+ {
16
+ "version": "0.363.0",
17
+ "type": "improvement",
18
+ "title": "PdfOptions has landscape",
19
+ "detail": "The document-pdf options gain landscape, which the puppeteer-service passes to Chromium."
20
+ },
21
+ {
22
+ "version": "0.363.0",
23
+ "type": "breaking",
24
+ "title": "RenderRequest kind mail replaces mail-html",
25
+ "detail": "RenderRequest and RenderResponse use kind mail with a MailPayload ({ slug, locale, variables, unsubscribeUrl? }) and a response of { subject, html, text, previewText? }. The export MailHtmlPayload is removed; use MailPayload. The default plugin name for kind mail stays mail-html. RendererContext gains an optional config accessor that plugins use for tenant locale, currency and branding.",
26
+ "migration": "Replace kind mail-html with mail in createRendererForTenant and render calls, pass { slug, locale, variables } instead of content/contentFormat, and read the subject from the response instead of building it yourself. Pass ctx.config in the RendererContext to get tenant locale, currency and branding."
27
+ },
28
+ {
29
+ "version": "0.363.0",
30
+ "type": "breaking",
31
+ "title": "renderer-simple, MailBranding and renderer kind \"notification\" are removed",
32
+ "detail": "Every mail is a stored template rendered by renderer-mail-html now. renderer-simple (createRendererSimpleFeature, simpleRenderer, createSimpleRenderer, RendererSimpleOptions, mail blocks with LocalizedText, MailBadge, MailChip, MailStage, MailStageState and MailTone), the MailBranding type, the renderer kind \"notification\" and AuthMailContent are gone. NotificationRenderer and RendererInput are removed from delivery, and createChannelEmailFeature no longer takes a renderer option. The type NotificationPayload is removed from renderer-foundation. The template-resolver keeps \"notification\" only as a stored kind so legacy rows stay readable; RENDER_KINDS is mail, document-pdf and image-snapshot.",
33
+ "migration": "Remove createRendererSimpleFeature() and any renderer or mailBranding option from your feature list and createChannelEmailFeature call. An r.notification that is sent by email needs template + meta (or localizedTemplate) and variables; the sections/header/footer data it carried is replaced by template variables. Branding (logo, footer) now comes from the tenant branding config and the mail layout of renderer-mail-html. Mount template-resolver, renderer-foundation and @cosmicdrift/kumiko-renderer-mail-html (composeRendererStack() mounts all of them) and run seedNotificationTemplates at boot. Existing template rows with kind \"notification\" are no longer rendered; re-create them as kind \"mail\" or archive them."
34
+ },
35
+ {
36
+ "version": "0.363.0",
37
+ "type": "improvement",
38
+ "title": "MailPayload.draft renders an unsaved mail template; RendererContext.db also accepts a TenantDb",
39
+ "detail": "MailDraft (content, meta, variableSchema) replaces the stored body and subject of the template with the payload's slug and keeps its stored images; a slug without a stored template renders as a tenant draft. The renderer sanitizes the draft body and validates its meta again, and the response carries no template provenance. RendererContext.db is widened from DbConnection to DbRunner | TenantDb."
40
+ },
2
41
  {
3
42
  "version": "0.306.0",
4
43
  "type": "breaking",
@@ -1,11 +1,10 @@
1
- // Re-export aus template-resolver — gleiche RenderKind-Domäne. Beide
2
- // Bundles teilen sich die Enum, damit Plugins (renderer-mail-html etc.)
3
- // nicht zwei Quellen importieren müssen. Cross-feature-Imports gehen
4
- // über das Barrel (../template-resolver), nicht via deep-import.
1
+ // Re-export from template-resolver — same RenderKind domain. Both
2
+ // bundles share the enum so plugins (@cosmicdrift/kumiko-renderer-mail-html etc.)
3
+ // do not have to import two sources. Cross-feature imports go
4
+ // through the barrel (../template-resolver), not via deep import.
5
5
  import type { RenderKind as RenderKindLocal } from "../template-resolver/index.js";
6
6
 
7
- // Extension-point name for renderer plugins (renderer-simple,
8
- // renderer-mail-html, renderer-puppeteer-client, ...).
7
+ // Extension-point name for renderer plugins (@cosmicdrift/kumiko-renderer-mail-html, @cosmicdrift/kumiko-renderer-puppeteer-client, ...).
9
8
  export const RENDERER_EXTENSION = "renderer" as const;
10
9
 
11
10
  export {
@@ -18,8 +17,7 @@ export {
18
17
  // Standard-Default-Plugin pro Kind, wenn Tenant keine explizite Config
19
18
  // gesetzt hat. App-Bootstrap kann das via TenantConfigKey überschreiben.
20
19
  export const DEFAULT_PLUGIN_BY_KIND: Readonly<Record<RenderKindLocal, string>> = {
21
- notification: "simple",
22
- "mail-html": "mail-html",
20
+ mail: "mail-html",
23
21
  "document-pdf": "puppeteer",
24
22
  "image-snapshot": "puppeteer",
25
23
  };
@@ -2,18 +2,18 @@ import { defineFeature, type Registry } from "@cosmicdrift/kumiko-framework/engi
2
2
  import { RENDERER_EXTENSION } from "./constants.js";
3
3
  import { isRendererRegistrationPlugin, type RendererPlugin } from "./types.js";
4
4
 
5
- // renderer-foundation — Plugin-Foundation für Renderer (Notification,
6
- // HTML-Mail, PDF, Image). Plan-Doc:
5
+ // renderer-foundation — plugin foundation for renderers (HTML mail, PDF,
6
+ // image). Plan doc:
7
7
  // kumiko-platform/docs/plans/features/renderer-foundation.md
8
8
  //
9
- // Pattern symmetrisch zu ai-foundation: Foundation definiert den
10
- // Extension-Point `renderer`, Plugins (renderer-simple, renderer-mail-html,
11
- // renderer-puppeteer-client) registrieren sich via `r.useExtension`.
12
- // Konsumenten holen sich Plugin runtime via createRendererForTenant.
9
+ // Pattern symmetric to ai-foundation: the foundation defines the
10
+ // extension point `renderer`, plugins (@cosmicdrift/kumiko-renderer-mail-html,
11
+ // @cosmicdrift/kumiko-renderer-puppeteer-client) register via `r.useExtension`.
12
+ // Consumers get a plugin at runtime via createRendererForTenant.
13
13
  export function createRendererFoundationFeature() {
14
14
  return defineFeature("renderer-foundation", (r) => {
15
15
  r.describe(
16
- 'Plugin registry for content rendering (notification HTML, mail HTML, PDF, images): call `foundation.createRendererForTenant({ tenantId, kind })` at render time to get the right renderer plugin selected by kind, with tenant-level overrides via the `rendererPluginByKind` config key. Requires `template-resolver` (declared via `r.requires`). Low-level building block \u2014 add `renderer-simple` (or write a custom plugin via `r.useExtension("renderer", name, { kinds, render })`) rather than using this feature alone.',
16
+ 'Plugin registry for content rendering (mail HTML, PDF, images): call `foundation.createRendererForTenant({ tenantId, kind })` at render time to get the right renderer plugin selected by kind, with tenant-level overrides via the `rendererPluginByKind` config key. Requires `template-resolver` (declared via `r.requires`). Low-level building block \u2014 add `renderer-mail-html` (or write a custom plugin via `r.useExtension("renderer", name, { kinds, render })`) rather than using this feature alone.',
17
17
  );
18
18
  r.uiHints({
19
19
  displayLabel: "Renderer Foundation",
@@ -1,3 +1,4 @@
1
+ export { attachmentFileNameSchema } from "../shared/index.js";
1
2
  export {
2
3
  createRendererFoundationApi,
3
4
  type RendererFoundationApi,
@@ -14,15 +15,25 @@ export {
14
15
  export { collectRendererPlugins, createRendererFoundationFeature } from "./feature.js";
15
16
  export {
16
17
  type DocumentPayload,
18
+ type DocumentPdfDraft,
19
+ type DocumentPdfPayload,
20
+ type DocumentPreview,
21
+ type DocumentPreviewRequest,
22
+ type FileAttachmentRef,
17
23
  type ImageOptions,
24
+ type InlineAttachmentRef,
18
25
  isRendererRegistrationPlugin,
19
- type MailHtmlPayload,
20
- type NotificationPayload,
26
+ type MailAttachmentRef,
27
+ type MailDraft,
28
+ type MailPayload,
21
29
  type PdfOptions,
30
+ type RenderedPdfAttachmentRef,
31
+ type RenderedTemplateRef,
22
32
  type RendererContext,
23
33
  RendererError,
24
34
  type RendererPlugin,
25
35
  type RendererRegistrationPlugin,
36
+ type RenderProvenance,
26
37
  type RenderRequest,
27
38
  type RenderResponse,
28
39
  } from "./types.js";
@@ -1,43 +1,101 @@
1
- import type { DbConnection } from "@cosmicdrift/kumiko-framework/db";
2
- import type { Registry, TenantId } from "@cosmicdrift/kumiko-framework/engine";
1
+ import type { DbRunner, TenantDb } from "@cosmicdrift/kumiko-framework/db";
2
+ import type { ConfigAccessor, Registry, TenantId } from "@cosmicdrift/kumiko-framework/engine";
3
+ import type { FileContext } from "@cosmicdrift/kumiko-framework/files";
4
+ import type {
5
+ LinkedResourceEntry,
6
+ MailTemplateMeta,
7
+ PdfPageFormat,
8
+ PdfTemplateMeta,
9
+ TemplateScope,
10
+ } from "../template-resolver/index.js";
3
11
  import { type ContentFormat, RENDERER_EXTENSION, type RenderKind } from "./constants.js";
4
12
 
5
13
  // RenderRequest — Plugins erhalten das. Resource-Mode wählt der Caller
6
14
  // (renderer-foundation api) je nach kind oder explizit. Plugin selbst
7
15
  // entscheidet nicht — es bekommt eine schon aufgelöste Form.
8
16
  export type RenderRequest =
9
- | { kind: "notification"; payload: NotificationPayload }
10
- | { kind: "mail-html"; payload: MailHtmlPayload }
11
- | { kind: "document-pdf"; payload: DocumentPayload; options?: PdfOptions }
17
+ | { kind: "mail"; payload: MailPayload }
18
+ | { kind: "document-pdf"; payload: DocumentPdfPayload; options?: PdfOptions }
12
19
  | { kind: "image-snapshot"; payload: DocumentPayload; options?: ImageOptions };
13
20
 
14
- export type RenderResponse =
15
- | { kind: "notification"; html: string; text?: string }
16
- | { kind: "mail-html"; html: string; text: string }
17
- | { kind: "document-pdf"; pdfBytes: Uint8Array; pageCount: number; sizeBytes: number }
21
+ export type RenderedTemplateRef = {
22
+ readonly templateId: string;
23
+ readonly templateVersion: number;
24
+ readonly scope: TemplateScope;
25
+ };
26
+
27
+ export type RenderProvenance = {
28
+ readonly template?: RenderedTemplateRef;
29
+ readonly layout?: RenderedTemplateRef;
30
+ };
31
+
32
+ // Image a rendered mail embeds by Content-ID; the send job reads the bytes.
33
+ export type InlineAttachmentRef = {
34
+ readonly kind: "inline";
35
+ readonly cid: string;
36
+ readonly fileRefId: string;
37
+ };
38
+
39
+ // A stored file attached as-is; the send job reads the bytes.
40
+ export type FileAttachmentRef = {
41
+ readonly kind: "file";
42
+ readonly fileRefId: string;
43
+ readonly fileName: string;
44
+ };
45
+
46
+ // A PDF rendered from a stored document template when the mail is sent, so the job
47
+ // payload carries the variables instead of the bytes.
48
+ export type RenderedPdfAttachmentRef = {
49
+ readonly kind: "rendered-pdf";
50
+ readonly templateSlug: string;
51
+ readonly variables: Readonly<Record<string, unknown>>;
52
+ readonly fileName: string;
53
+ readonly locale?: string;
54
+ };
55
+
56
+ export type MailAttachmentRef = InlineAttachmentRef | FileAttachmentRef | RenderedPdfAttachmentRef;
57
+
58
+ export type RenderResponse = (
59
+ | {
60
+ kind: "mail";
61
+ subject: string;
62
+ html: string;
63
+ text: string;
64
+ previewText?: string;
65
+ attachments?: readonly MailAttachmentRef[];
66
+ }
67
+ | {
68
+ kind: "document-pdf";
69
+ pdfBytes: Uint8Array;
70
+ pageCount: number;
71
+ sizeBytes: number;
72
+ fileName: string;
73
+ }
18
74
  | {
19
75
  kind: "image-snapshot";
20
76
  imageBytes: Uint8Array;
21
77
  format: "png" | "jpg";
22
78
  width: number;
23
79
  height: number;
24
- };
80
+ }
81
+ ) &
82
+ RenderProvenance;
25
83
 
26
- export type NotificationPayload = {
27
- readonly template?: string;
28
- readonly content?: string;
29
- readonly contentFormat?: ContentFormat;
30
- readonly variables?: Readonly<Record<string, unknown>>;
31
- readonly locale?: string;
84
+ // An unsaved version of the mail template; rendering it never reads its stored body or subject.
85
+ export type MailDraft = {
86
+ readonly content: string;
87
+ readonly meta: MailTemplateMeta;
88
+ readonly variableSchema?: Readonly<Record<string, unknown>>;
89
+ /** Images and attachments of the row being edited; absent means those of the stored template. */
90
+ readonly linkedResources?: Readonly<Record<string, LinkedResourceEntry>>;
32
91
  };
33
92
 
34
- export type MailHtmlPayload = {
35
- readonly template?: string;
36
- readonly content?: string;
37
- readonly contentFormat?: ContentFormat;
38
- readonly variables?: Readonly<Record<string, unknown>>;
39
- readonly locale?: string;
40
- readonly subject?: string;
93
+ export type MailPayload = {
94
+ readonly slug: string;
95
+ readonly locale: string;
96
+ readonly variables: Readonly<Record<string, unknown>>;
97
+ readonly unsubscribeUrl?: string;
98
+ readonly draft?: MailDraft;
41
99
  };
42
100
 
43
101
  export type DocumentPayload = {
@@ -48,13 +106,46 @@ export type DocumentPayload = {
48
106
  readonly locale?: string;
49
107
  };
50
108
 
109
+ export type DocumentPdfPayload = {
110
+ readonly slug: string;
111
+ readonly locale: string;
112
+ readonly variables: Readonly<Record<string, unknown>>;
113
+ };
114
+
115
+ // An unsaved version of the document template, like MailDraft.
116
+ export type DocumentPdfDraft = {
117
+ readonly content: string;
118
+ readonly meta: PdfTemplateMeta | null;
119
+ readonly variableSchema?: Readonly<Record<string, unknown>>;
120
+ };
121
+
122
+ export type DocumentPreviewRequest = DocumentPdfPayload & { readonly draft?: DocumentPdfDraft };
123
+
124
+ // What an editor shows for a document: the printable HTML with the values filled in,
125
+ // without calling any print service.
126
+ export type DocumentPreview = {
127
+ readonly title: string;
128
+ readonly fileName: string;
129
+ readonly bodyHtml: string;
130
+ readonly layout: {
131
+ readonly slug: string;
132
+ readonly scope: TemplateScope;
133
+ readonly headerHtml: string;
134
+ readonly signatureHtml: string;
135
+ readonly footerHtml: string;
136
+ } | null;
137
+ /** Image keys the body or the layout asks for that no file is linked to. */
138
+ readonly missingResourceKeys: readonly string[];
139
+ };
140
+
51
141
  export type PdfOptions = {
52
- readonly format?: "A4" | "Letter";
142
+ readonly format?: PdfPageFormat;
53
143
  readonly marginMm?: { top?: number; right?: number; bottom?: number; left?: number };
54
144
  readonly headerTemplate?: string;
55
145
  readonly footerTemplate?: string;
56
146
  readonly printBackground?: boolean;
57
147
  readonly displayHeaderFooter?: boolean;
148
+ readonly landscape?: boolean;
58
149
  };
59
150
 
60
151
  export type ImageOptions = {
@@ -75,33 +166,39 @@ export type ImageOptions = {
75
166
  // const tplApi = createTemplateResolverApi(ctx.db);
76
167
  // const layout = await tplApi.resolveTemplate({ tenantId: ctx.tenantId, slug, kind });
77
168
  export type RendererContext = {
78
- readonly db: DbConnection;
169
+ readonly db: DbRunner | TenantDb;
79
170
  readonly registry: Registry;
80
171
  readonly tenantId: TenantId;
172
+ /** Tenant-scoped config for plugins that read the tenant currency or
173
+ * branding; plugins fall back to request defaults when absent. */
174
+ readonly config?: ConfigAccessor;
175
+ /** Only plugins that embed resource bytes (PDF) need it; without it images are dropped. */
176
+ readonly filesForTenant?: (tenantId: string) => FileContext | undefined;
81
177
  };
82
178
 
83
- // RendererPlugin-Contract — pro Plugin (renderer-simple, renderer-mail-html,
179
+ // RendererPlugin-Contract — pro Plugin (renderer-mail-html,
84
180
  // renderer-puppeteer-client). Plugin deklariert welche kinds es bedienen
85
181
  // kann; Foundation-Resolver wählt basierend auf Tenant-Config + kind.
86
182
  //
87
- // **Plugin-Invarianten:**
88
- // - `kinds` muss min. 1 Element haben (sonst nie ausgewählt)
89
- // - `render(req, ctx)`-Response.kind MUSS req.kind matchen
90
- // - Plugin ist zustandslos: kein internal-state zwischen render-Calls
91
- // - Plugin wirft `RendererError` für domain-Fehler (nicht bare Error)
92
- // - `ctx` ist required. Plugins ohne Service-Deps (z.B. renderer-simple)
93
- // ignorieren ihn einfach — TS function-arg variance erlaubt `(req) => ...`-
94
- // Implementations weiterhin (Implementation-Args ≤ Contract-Args ist OK).
183
+ // Plugin invariants:
184
+ // - `kinds` has at least one entry, otherwise the plugin is never picked.
185
+ // - The response kind of `render(req, ctx)` matches req.kind.
186
+ // - Plugins are stateless between render calls.
187
+ // - Domain failures throw `RendererError`, not a bare Error.
188
+ // - `ctx` is required; plugins without service deps ignore it. Function-arg
189
+ // variance still lets `(req) => ...` implementations satisfy the contract.
95
190
  export type RendererPlugin = {
96
191
  readonly name: string;
97
192
  readonly kinds: ReadonlyArray<RenderKind>;
98
193
  render(req: RenderRequest, ctx: RendererContext): Promise<RenderResponse>;
194
+ /** Optional: plugins that print documents offer a preview that never reaches the print service. */
195
+ previewDocument?(req: DocumentPreviewRequest, ctx: RendererContext): Promise<DocumentPreview>;
99
196
  };
100
197
 
101
198
  export class RendererError extends Error {
102
199
  constructor(
103
200
  message: string,
104
- public readonly code: "no_plugin_for_kind" | "invalid_payload" | "other",
201
+ public readonly code: "no_plugin_for_kind" | "invalid_payload" | "unavailable" | "other",
105
202
  ) {
106
203
  super(message);
107
204
  this.name = "RendererError";
@@ -25,7 +25,13 @@ export async function renderTablePdfRequest(req: RenderRequest): Promise<RenderR
25
25
  ...(req.options?.format && { format: req.options.format }),
26
26
  ...(req.options?.marginMm && { marginMm: req.options.marginMm }),
27
27
  });
28
- return { kind: "document-pdf", pdfBytes, pageCount, sizeBytes: pdfBytes.byteLength };
28
+ return {
29
+ kind: "document-pdf",
30
+ pdfBytes,
31
+ pageCount,
32
+ sizeBytes: pdfBytes.byteLength,
33
+ fileName: `${req.payload.slug}.pdf`,
34
+ };
29
35
  }
30
36
 
31
37
  export function createRendererPdfFeature(): FeatureDefinition {
@@ -6,7 +6,7 @@ export async function deleteStaleSessionsBatch(
6
6
  olderThanDays: number,
7
7
  batchSize: number,
8
8
  ): Promise<number> {
9
- const rows = (await asRawClient(db).unsafe(
9
+ const rows = await asRawClient(db).unsafe<{ id: string }>(
10
10
  `DELETE FROM "store_user_sessions"
11
11
  WHERE "id" IN (
12
12
  SELECT "id" FROM "store_user_sessions"
@@ -16,6 +16,6 @@ export async function deleteStaleSessionsBatch(
16
16
  )
17
17
  RETURNING "id"`,
18
18
  [olderThanDays, batchSize],
19
- )) as readonly { id: string }[];
19
+ );
20
20
  return rows.length;
21
21
  }
@@ -71,11 +71,9 @@ export function bindAutoRevokeFromFeature(
71
71
  ): BindAutoRevokeOnPasswordChange | undefined {
72
72
  const exports = feature.exports;
73
73
  if (exports && typeof exports === "object" && "bindAutoRevokeOnPasswordChange" in exports) {
74
- const { bindAutoRevokeOnPasswordChange } = exports as {
75
- bindAutoRevokeOnPasswordChange: unknown;
76
- };
74
+ const { bindAutoRevokeOnPasswordChange } = exports;
77
75
  if (typeof bindAutoRevokeOnPasswordChange === "function") {
78
- // @cast-boundary exports-walk — feature.exports is untyped by design
76
+ // @cast-boundary feature-exports — feature.exports is untyped by design
79
77
  return bindAutoRevokeOnPasswordChange as BindAutoRevokeOnPasswordChange;
80
78
  }
81
79
  }
@@ -4,6 +4,7 @@ import {
4
4
  definePagedQueryHandler,
5
5
  MAX_LIST_LIMIT,
6
6
  } from "@cosmicdrift/kumiko-framework/engine";
7
+ import { isOneOf } from "@cosmicdrift/kumiko-types/type-guards";
7
8
  import * as z from "zod";
8
9
  import { decryptStoredPii } from "../../shared/index.js";
9
10
  import { userSessionTable } from "../schema/user-session.js";
@@ -17,7 +18,7 @@ const SORTABLE_COLUMNS = ["id", "userId", "createdAt", "expiresAt", "revokedAt"]
17
18
  type SortableColumn = (typeof SORTABLE_COLUMNS)[number];
18
19
 
19
20
  function isSortableColumn(value: string): value is SortableColumn {
20
- return (SORTABLE_COLUMNS as readonly string[]).includes(value);
21
+ return isOneOf(SORTABLE_COLUMNS, value);
21
22
  }
22
23
 
23
24
  // Admin view of every session in the active tenant. ctx.db (TenantDb)
@@ -0,0 +1,39 @@
1
+ import * as z from "zod";
2
+
3
+ const MAX_FILE_NAME_LENGTH = 255;
4
+
5
+ function hasControlCharacter(value: string): boolean {
6
+ return value.split("").some((char) => {
7
+ const code = char.charCodeAt(0);
8
+ return code <= 0x1f || code === 0x7f;
9
+ });
10
+ }
11
+
12
+ export const attachmentFileNameSchema = z
13
+ .string()
14
+ .min(1)
15
+ .max(MAX_FILE_NAME_LENGTH)
16
+ .refine((name) => !name.includes("/") && !name.includes("\\") && !hasControlCharacter(name), {
17
+ message: "file name must not contain path separators or control characters",
18
+ });
19
+
20
+ const PDF_FILE_NAME_MAX_LENGTH = 200;
21
+ const PDF_EXTENSION = ".pdf";
22
+
23
+ // Shared by everything that names a rendered PDF, so a mail attachment and a download of the same
24
+ // template get the same name. The cut leaves room for the extension: the attachment schema caps
25
+ // rendered-pdf names, and a name that only fits without ".pdf" would fail it.
26
+ export function sanitizePdfAttachmentFileName(raw: string, templateSlug: string): string {
27
+ const cleaned = raw
28
+ .split("")
29
+ .filter((char) => !hasControlCharacter(char))
30
+ .join("")
31
+ .replace(/[/\\";]/g, "-")
32
+ .replaceAll("..", "-")
33
+ .trim()
34
+ .slice(0, PDF_FILE_NAME_MAX_LENGTH - PDF_EXTENSION.length)
35
+ .trim();
36
+ const lastSegment = templateSlug.split(":").pop() ?? templateSlug;
37
+ if (cleaned === "") return `${lastSegment}${PDF_EXTENSION}`;
38
+ return cleaned.toLowerCase().endsWith(PDF_EXTENSION) ? cleaned : `${cleaned}${PDF_EXTENSION}`;
39
+ }
@@ -1,5 +1,9 @@
1
1
  export { collectErasureFailure, throwIfErasureFailed } from "./assert-erased.js";
2
2
  export { assertNoWhereRuleInOwnershipWrite } from "./assert-no-where-rule-in-ownership-write.js";
3
+ export {
4
+ attachmentFileNameSchema,
5
+ sanitizePdfAttachmentFileName,
6
+ } from "./attachment-file-name.js";
3
7
  export {
4
8
  type ChunkedMigrationOptions,
5
9
  type ChunkedMigrationResult,
@@ -18,6 +22,7 @@ export { isWithinGracePeriod } from "./grace-period.js";
18
22
  export { isIdentityV3Hash, verifyIdentityV3Hash } from "./identity-v3-hash.js";
19
23
  export { isTenantDb } from "./is-tenant-db.js";
20
24
  export { createLockoutCounter, type LockoutCounterState } from "./lockout-counter.js";
25
+ export { mailHeading, mailParagraph, mailRepeatedRows } from "./mail-template-body.js";
21
26
  export { mapWithConcurrency } from "./map-with-concurrency.js";
22
27
  export {
23
28
  denyUnlessJoinRowParentVisible,
@@ -43,6 +48,7 @@ export {
43
48
  export { runInSubTransaction } from "./run-in-sub-transaction.js";
44
49
  export { registrationLocaleFromRequest, sessionLocaleField } from "./session-locale-field.js";
45
50
  export { sessionTimezoneField } from "./session-timezone-field.js";
51
+ export { assertNotSvg, MAX_INPUT_PIXELS, SHARP_INPUT_OPTIONS } from "./sharp-input-limits.js";
46
52
  export {
47
53
  peekTokenSubject,
48
54
  signToken,
@@ -5,5 +5,5 @@ import type { DbRunner, TenantDb } from "@cosmicdrift/kumiko-framework/db";
5
5
  // Shared so retention/compliance-profile resolvers that accept either (bulk cron
6
6
  // vs. per-hook TenantDb) branch through one predicate instead of duplicating it.
7
7
  export function isTenantDb(db: DbRunner | TenantDb): db is TenantDb {
8
- return typeof (db as Partial<TenantDb>).unsafeRaw === "function";
8
+ return "unsafeRaw" in db && typeof db.unsafeRaw === "function";
9
9
  }
@@ -0,0 +1,25 @@
1
+ import { escapeHtml, escapeHtmlAttr } from "@cosmicdrift/kumiko-headless";
2
+
3
+ const SLOT_PATTERN = /\{([A-Za-z_][A-Za-z0-9_]*)\}/g;
4
+
5
+ // Author text uses `{name}` slots. The text is escaped first and the slots are
6
+ // swapped for variable nodes afterwards, so a slot can never smuggle markup.
7
+ export function mailTextWithSlotsHtml(text: string): string {
8
+ return escapeHtml(text).replace(
9
+ SLOT_PATTERN,
10
+ (_, name: string) => `<span data-variable="${escapeHtmlAttr(name)}"></span>`,
11
+ );
12
+ }
13
+
14
+ export function mailParagraph(text: string): string {
15
+ return `<p>${mailTextWithSlotsHtml(text)}</p>`;
16
+ }
17
+
18
+ export function mailHeading(text: string): string {
19
+ return `<h2>${mailTextWithSlotsHtml(text)}</h2>`;
20
+ }
21
+
22
+ // One table row per list entry; the cell shows the entry's `field`.
23
+ export function mailRepeatedRows(listName: string, field: string): string {
24
+ return `<table><tbody><tr data-each="${escapeHtmlAttr(listName)}"><td><p><span data-variable="${escapeHtmlAttr(field)}"></span></p></td></tr></tbody></table>`;
25
+ }
@@ -0,0 +1,26 @@
1
+ // Sharp-free so callers can import the limits without loading the native binding.
2
+
3
+ // Above this, a decoded pixel buffer runs into hundreds of MB before any
4
+ // resize even happens — sharp's own default (~268MP) is still large enough
5
+ // to be an OOM primitive for a single request.
6
+ export const MAX_INPUT_PIXELS = 100_000_000;
7
+ // sequentialRead trades random pixel access for lower peak memory on
8
+ // streamed formats — applied at every decode entry point below since none
9
+ // of them need non-sequential access to the *original* bytes (extract runs
10
+ // against a single already-buffered region per call).
11
+ export const SHARP_INPUT_OPTIONS = {
12
+ limitInputPixels: MAX_INPUT_PIXELS,
13
+ sequentialRead: true,
14
+ } as const;
15
+
16
+ // Shared trust boundary for every image byte source this renderer decodes —
17
+ // the original upload (renderImage) and an overlay layer's bytes (applyOverlays)
18
+ // alike: a base64 string in a field declaration isn't more trustworthy than
19
+ // an upload just because it came from app code.
20
+ export function assertNotSvg(format: string | undefined): void {
21
+ if (format === "svg") {
22
+ throw new Error(
23
+ "derivatives-sharp: SVG is not supported — rendering untrusted SVG through libvips is a trust boundary this renderer doesn't need to cross.",
24
+ );
25
+ }
26
+ }
@@ -13,6 +13,7 @@
13
13
 
14
14
  import type { DbRunner } from "@cosmicdrift/kumiko-framework/db";
15
15
  import type { Registry } from "@cosmicdrift/kumiko-framework/engine";
16
+ import { isPlainObject } from "@cosmicdrift/kumiko-types/type-guards";
16
17
 
17
18
  export const EXT_SIGNUP_HANDOVER = "signupHandover" as const;
18
19
 
@@ -46,10 +47,9 @@ export type SignupHandoverProvider = {
46
47
 
47
48
  export function isSignupHandoverProvider(options: unknown): options is SignupHandoverProvider {
48
49
  return (
49
- typeof options === "object" &&
50
- options !== null &&
51
- typeof (options as Record<string, unknown>)["verifyGrant"] === "function" &&
52
- typeof (options as Record<string, unknown>)["mintClaim"] === "function"
50
+ isPlainObject(options) &&
51
+ typeof options["verifyGrant"] === "function" &&
52
+ typeof options["mintClaim"] === "function"
53
53
  );
54
54
  }
55
55
 
@@ -1,5 +1,6 @@
1
1
  import type { AccessRule, WriteHandlerDef } from "@cosmicdrift/kumiko-framework/engine";
2
2
  import { NotFoundError, writeFailure } from "@cosmicdrift/kumiko-framework/errors";
3
+ import { isPlainObject } from "@cosmicdrift/kumiko-types/type-guards";
3
4
  import { denyUnlessJoinRowParentVisible } from "../../shared/index.js";
4
5
  import { tagAssignmentAggregateId } from "../aggregate-id.js";
5
6
  import { DEFAULT_TAG_ACCESS } from "../constants.js";
@@ -67,7 +68,10 @@ export function createAssignTagHandler(
67
68
  // above and this restore() call, so restore() correctly refuses with
68
69
  // "not_deleted" — but the desired end state is already true. Converge
69
70
  // like the create() race below instead of surfacing a spurious error.
70
- if ((restored.error.details as { reason?: string } | undefined)?.reason === "not_deleted") {
71
+ if (
72
+ isPlainObject(restored.error.details) &&
73
+ restored.error.details["reason"] === "not_deleted"
74
+ ) {
71
75
  return { isSuccess: true as const, data: { id } };
72
76
  }
73
77
  if (restored.error.code !== "not_found") return restored;
@@ -21,7 +21,8 @@ export function contrastText(color: string): "#000000" | "#ffffff" | null {
21
21
  function hexToRgb(color: string): { r: number; g: number; b: number } | null {
22
22
  const match = /^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/.exec(color.trim());
23
23
  if (match === null) return null;
24
- const short = match[1] as string;
24
+ const short = match[1];
25
+ if (short === undefined) return null;
25
26
  const hex =
26
27
  short.length === 3
27
28
  ? `${short[0]}${short[0]}${short[1]}${short[1]}${short[2]}${short[2]}`