@cosmicdrift/kumiko-samples 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 (321) hide show
  1. package/package.json +1 -1
  2. package/packages/bundled-features/package.json +23 -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/audit/changes.json +6 -0
  10. package/packages/bundled-features/src/audit/handlers/list.query.ts +10 -5
  11. package/packages/bundled-features/src/auth-email-password/bootstrap.ts +29 -8
  12. package/packages/bundled-features/src/auth-email-password/changes.json +51 -0
  13. package/packages/bundled-features/src/auth-email-password/email-rate-limits.ts +16 -0
  14. package/packages/bundled-features/src/auth-email-password/email-templates.ts +12 -138
  15. package/packages/bundled-features/src/auth-email-password/feature.ts +30 -9
  16. package/packages/bundled-features/src/auth-email-password/handlers/change-password.write.ts +6 -2
  17. package/packages/bundled-features/src/auth-email-password/handlers/invite-create.write.ts +4 -5
  18. package/packages/bundled-features/src/auth-email-password/handlers/logout.write.ts +7 -2
  19. package/packages/bundled-features/src/auth-email-password/handlers/request-account-unlock.write.ts +2 -5
  20. package/packages/bundled-features/src/auth-email-password/handlers/request-email-verification.write.ts +2 -5
  21. package/packages/bundled-features/src/auth-email-password/handlers/request-password-reset.write.ts +2 -5
  22. package/packages/bundled-features/src/auth-email-password/handlers/signup-confirm.write.ts +3 -4
  23. package/packages/bundled-features/src/auth-email-password/handlers/signup-request.write.ts +2 -1
  24. package/packages/bundled-features/src/auth-email-password/handlers/token-request-handler.ts +5 -13
  25. package/packages/bundled-features/src/auth-email-password/index.ts +2 -17
  26. package/packages/bundled-features/src/auth-email-password/magic-link-mail.ts +30 -22
  27. package/packages/bundled-features/src/auth-email-password/mail-notifications.ts +158 -0
  28. package/packages/bundled-features/src/auth-email-password/signup-activation.ts +2 -3
  29. package/packages/bundled-features/src/auth-email-password/web/auth-gate.tsx +24 -5
  30. package/packages/bundled-features/src/auth-foundation/types.ts +8 -18
  31. package/packages/bundled-features/src/auth-mfa/feature.ts +8 -10
  32. package/packages/bundled-features/src/auth-mfa/hmac-token-codec.ts +4 -4
  33. package/packages/bundled-features/src/auth-mfa/mfa-challenge-token.ts +3 -3
  34. package/packages/bundled-features/src/auth-mfa/mfa-preauth-setup-token.ts +3 -3
  35. package/packages/bundled-features/src/auth-mfa/mfa-setup-token.ts +8 -8
  36. package/packages/bundled-features/src/billing-foundation/changes.json +43 -0
  37. package/packages/bundled-features/src/billing-foundation/constants.ts +5 -4
  38. package/packages/bundled-features/src/billing-foundation/consumer-protection/confirmation-mail.ts +90 -44
  39. package/packages/bundled-features/src/billing-foundation/consumer-protection/consent-gate.ts +22 -1
  40. package/packages/bundled-features/src/billing-foundation/consumer-protection/issue-confirmation.ts +3 -3
  41. package/packages/bundled-features/src/billing-foundation/consumer-protection/mail-notifications.ts +118 -0
  42. package/packages/bundled-features/src/billing-foundation/consumer-protection/register-consent.ts +2 -0
  43. package/packages/bundled-features/src/billing-foundation/consumer-protection/register-termination.ts +11 -1
  44. package/packages/bundled-features/src/billing-foundation/consumer-protection/termination-mail.ts +39 -33
  45. package/packages/bundled-features/src/billing-foundation/consumer-protection/termination-notify.ts +2 -2
  46. package/packages/bundled-features/src/billing-foundation/consumer-protection/termination-pages.ts +1 -4
  47. package/packages/bundled-features/src/billing-foundation/consumer-protection/termination-receipt-limit.ts +41 -0
  48. package/packages/bundled-features/src/billing-foundation/consumer-protection/termination-record.ts +10 -1
  49. package/packages/bundled-features/src/billing-foundation/consumer-protection/termination-request.ts +36 -20
  50. package/packages/bundled-features/src/billing-foundation/consumer-protection/termination-texts.ts +9 -0
  51. package/packages/bundled-features/src/billing-foundation/consumer-protection/withdrawal-period.ts +35 -0
  52. package/packages/bundled-features/src/billing-foundation/get-subscription-for-tenant.ts +2 -2
  53. package/packages/bundled-features/src/billing-foundation/handlers/create-checkout-session.write.ts +11 -3
  54. package/packages/bundled-features/src/billing-foundation/handlers/create-portal-session.write.ts +4 -9
  55. package/packages/bundled-features/src/billing-foundation/handlers/list-subscriptions.query.ts +1 -1
  56. package/packages/bundled-features/src/billing-foundation/handlers/process-event.write.ts +12 -6
  57. package/packages/bundled-features/src/billing-foundation/handlers/process-payment-event.write.ts +1 -1
  58. package/packages/bundled-features/src/billing-foundation/i18n.ts +3 -0
  59. package/packages/bundled-features/src/billing-foundation/subscription-tier-sync.ts +20 -11
  60. package/packages/bundled-features/src/billing-foundation/types.ts +7 -2
  61. package/packages/bundled-features/src/cap-counter/calendar-cap-usage-reservation.ts +2 -0
  62. package/packages/bundled-features/src/cap-counter/changes.json +6 -0
  63. package/packages/bundled-features/src/cap-counter/enforce-cap.ts +75 -5
  64. package/packages/bundled-features/src/cap-counter/index.ts +1 -0
  65. package/packages/bundled-features/src/cap-counter/with-cap-enforcement.ts +3 -0
  66. package/packages/bundled-features/src/cap-overview/handlers/tenant-caps-list.query.ts +2 -1
  67. package/packages/bundled-features/src/cap-overview/web/cap-usage-cell.tsx +6 -4
  68. package/packages/bundled-features/src/channel-email/changes.json +19 -0
  69. package/packages/bundled-features/src/channel-email/email-channel.ts +135 -29
  70. package/packages/bundled-features/src/channel-email/feature.ts +6 -3
  71. package/packages/bundled-features/src/channel-email/index.ts +7 -1
  72. package/packages/bundled-features/src/channel-email/pii-guard.ts +13 -0
  73. package/packages/bundled-features/src/channel-email/smtp-transport.ts +15 -1
  74. package/packages/bundled-features/src/channel-email/types.ts +23 -0
  75. package/packages/bundled-features/src/channel-in-app/handlers/mark-read.write.ts +2 -2
  76. package/packages/bundled-features/src/config/changes.json +6 -0
  77. package/packages/bundled-features/src/config/handlers/set.write.ts +3 -7
  78. package/packages/bundled-features/src/config/resolver.ts +15 -12
  79. package/packages/bundled-features/src/config/write-helpers.ts +23 -0
  80. package/packages/bundled-features/src/crypto-shredding/changes.json +6 -0
  81. package/packages/bundled-features/src/crypto-shredding/handlers/forget-subject.write.ts +48 -27
  82. package/packages/bundled-features/src/custom-fields/changes.json +6 -0
  83. package/packages/bundled-features/src/custom-fields/db/queries/user-data-rights.ts +2 -2
  84. package/packages/bundled-features/src/custom-fields/handlers/update-tenant-field.write.ts +0 -1
  85. package/packages/bundled-features/src/custom-fields/lib/value-schema.ts +10 -11
  86. package/packages/bundled-features/src/custom-fields/wire-user-data-rights.ts +7 -6
  87. package/packages/bundled-features/src/data-retention/changes.json +6 -0
  88. package/packages/bundled-features/src/data-retention/index.ts +1 -0
  89. package/packages/bundled-features/src/data-retention/run-retention-cleanup.ts +8 -6
  90. package/packages/bundled-features/src/delivery/attachment-ref.ts +105 -0
  91. package/packages/bundled-features/src/delivery/changes.json +66 -0
  92. package/packages/bundled-features/src/delivery/channel-context.ts +152 -4
  93. package/packages/bundled-features/src/delivery/constants.ts +2 -0
  94. package/packages/bundled-features/src/delivery/delivery-service.ts +104 -26
  95. package/packages/bundled-features/src/delivery/feature.ts +1 -1
  96. package/packages/bundled-features/src/delivery/index.ts +19 -2
  97. package/packages/bundled-features/src/delivery/jobs.ts +97 -17
  98. package/packages/bundled-features/src/delivery/notify-factory.ts +14 -9
  99. package/packages/bundled-features/src/delivery/testing.ts +9 -1
  100. package/packages/bundled-features/src/delivery/types.ts +34 -18
  101. package/packages/bundled-features/src/delivery/unsubscribe.ts +5 -2
  102. package/packages/bundled-features/src/derivatives-sharp/render.ts +1 -22
  103. package/packages/bundled-features/src/document-ingest-foundation/pages.ts +6 -3
  104. package/packages/bundled-features/src/feature-toggles/toggle-sync-signal.ts +4 -4
  105. package/packages/bundled-features/src/file-derivatives/handlers/public-variant.query.ts +1 -1
  106. package/packages/bundled-features/src/file-foundation/changes.json +6 -0
  107. package/packages/bundled-features/src/file-foundation/index.ts +7 -0
  108. package/packages/bundled-features/src/file-foundation/read-file-ref-content.ts +89 -0
  109. package/packages/bundled-features/src/files-tenant-data/changes.json +6 -0
  110. package/packages/bundled-features/src/files-tenant-data/hooks.ts +4 -0
  111. package/packages/bundled-features/src/folders/web/folder-manager.tsx +5 -2
  112. package/packages/bundled-features/src/form-draft/db/queries/cleanup.ts +11 -11
  113. package/packages/bundled-features/src/form-draft/db/queries/draft-count.ts +2 -2
  114. package/packages/bundled-features/src/form-draft/db/queries/owned-file-refs.ts +2 -2
  115. package/packages/bundled-features/src/form-draft/release-file-refs.ts +3 -2
  116. package/packages/bundled-features/src/inbound-mail-foundation/changes.json +6 -0
  117. package/packages/bundled-features/src/inbound-mail-foundation/handlers/connect-account.write.ts +1 -1
  118. package/packages/bundled-features/src/inbound-mail-foundation/handlers/ingest-message.write.ts +6 -6
  119. package/packages/bundled-features/src/inbound-mail-foundation/handlers/list-accounts.query.ts +1 -1
  120. package/packages/bundled-features/src/inbound-mail-foundation/handlers/list-messages.query.ts +1 -1
  121. package/packages/bundled-features/src/inbound-mail-foundation/handlers/scope-visibility.ts +2 -2
  122. package/packages/bundled-features/src/inbound-mail-foundation/oauth-access-token.ts +13 -8
  123. package/packages/bundled-features/src/inbound-mail-foundation/oauth-state.ts +7 -7
  124. package/packages/bundled-features/src/inbound-mail-foundation/retention-sweep.ts +2 -6
  125. package/packages/bundled-features/src/inbound-mail-foundation/tenant-destroy-hook.ts +5 -13
  126. package/packages/bundled-features/src/inbound-mail-foundation/types.ts +4 -6
  127. package/packages/bundled-features/src/inbound-mail-foundation/watch-supervisor.ts +42 -25
  128. package/packages/bundled-features/src/inbound-provider-imap/imap-client.ts +4 -3
  129. package/packages/bundled-features/src/jobs/changes.json +19 -0
  130. package/packages/bundled-features/src/jobs/db/queries/stale-run-sweep.ts +12 -4
  131. package/packages/bundled-features/src/jobs/feature.ts +7 -4
  132. package/packages/bundled-features/src/jobs/handlers/catalog.query.ts +1 -1
  133. package/packages/bundled-features/src/jobs/handlers/list.query.ts +3 -1
  134. package/packages/bundled-features/src/jobs/handlers/stale-run-sweep.job.ts +5 -2
  135. package/packages/bundled-features/src/jobs/job-run-logger.ts +52 -17
  136. package/packages/bundled-features/src/jobs/tenant-job-run-table.ts +7 -2
  137. package/packages/bundled-features/src/ledger/changes.json +7 -0
  138. package/packages/bundled-features/src/ledger/entity.ts +3 -1
  139. package/packages/bundled-features/src/ledger/recurring.ts +1 -3
  140. package/packages/bundled-features/src/managed-pages/branding.ts +33 -9
  141. package/packages/bundled-features/src/managed-pages/changes.json +6 -0
  142. package/packages/bundled-features/src/managed-pages/feature.ts +1 -0
  143. package/packages/bundled-features/src/managed-pages/i18n.ts +1 -0
  144. package/packages/bundled-features/src/managed-pages/screens/branding-screen.ts +8 -0
  145. package/packages/bundled-features/src/page-render/branding.ts +3 -0
  146. package/packages/bundled-features/src/page-render/markdown.ts +1 -1
  147. package/packages/bundled-features/src/personal-access-tokens/feature.ts +2 -4
  148. package/packages/bundled-features/src/personal-access-tokens/handlers/list.query.ts +2 -1
  149. package/packages/bundled-features/src/personal-access-tokens/index.ts +1 -1
  150. package/packages/bundled-features/src/personal-access-tokens/revoke-for-user.ts +2 -1
  151. package/packages/bundled-features/src/renderer-foundation/README.md +11 -10
  152. package/packages/bundled-features/src/renderer-foundation/api.ts +14 -14
  153. package/packages/bundled-features/src/renderer-foundation/changes.json +39 -0
  154. package/packages/bundled-features/src/renderer-foundation/constants.ts +6 -8
  155. package/packages/bundled-features/src/renderer-foundation/feature.ts +7 -7
  156. package/packages/bundled-features/src/renderer-foundation/index.ts +13 -2
  157. package/packages/bundled-features/src/renderer-foundation/types.ts +134 -35
  158. package/packages/bundled-features/src/renderer-pdf/changes.json +12 -0
  159. package/packages/bundled-features/src/renderer-pdf/feature.ts +7 -1
  160. package/packages/bundled-features/src/renderer-pdf/render-table-pdf.ts +162 -82
  161. package/packages/bundled-features/src/secrets/changes.json +6 -0
  162. package/packages/bundled-features/src/secrets/feature.ts +4 -3
  163. package/packages/bundled-features/src/secrets/secrets-context.ts +23 -3
  164. package/packages/bundled-features/src/sessions/db/queries/cleanup.ts +2 -2
  165. package/packages/bundled-features/src/sessions/feature.ts +2 -4
  166. package/packages/bundled-features/src/sessions/handlers/list.query.ts +2 -1
  167. package/packages/bundled-features/src/shared/attachment-file-name.ts +39 -0
  168. package/packages/bundled-features/src/shared/changes.json +6 -0
  169. package/packages/bundled-features/src/shared/chunked-entity-migration.ts +4 -1
  170. package/packages/bundled-features/src/shared/index.ts +6 -0
  171. package/packages/bundled-features/src/shared/is-tenant-db.ts +1 -1
  172. package/packages/bundled-features/src/shared/mail-template-body.ts +42 -0
  173. package/packages/bundled-features/src/shared/sharp-input-limits.ts +26 -0
  174. package/packages/bundled-features/src/shared/signup-handover.ts +4 -4
  175. package/packages/bundled-features/src/subscription-stripe/changes.json +6 -0
  176. package/packages/bundled-features/src/subscription-stripe/plugin-methods.ts +19 -11
  177. package/packages/bundled-features/src/subscription-stripe/verify-webhook.ts +3 -1
  178. package/packages/bundled-features/src/tags/handlers/assign-tag.write.ts +5 -1
  179. package/packages/bundled-features/src/tags/web/tag-chip.tsx +2 -1
  180. package/packages/bundled-features/src/template-resolver/README.md +66 -8
  181. package/packages/bundled-features/src/template-resolver/api.ts +85 -9
  182. package/packages/bundled-features/src/template-resolver/changes.json +186 -0
  183. package/packages/bundled-features/src/template-resolver/compile-document-body.ts +278 -0
  184. package/packages/bundled-features/src/template-resolver/constants.ts +49 -11
  185. package/packages/bundled-features/src/template-resolver/document-sanitizer.ts +335 -0
  186. package/packages/bundled-features/src/template-resolver/document-schema.ts +345 -0
  187. package/packages/bundled-features/src/template-resolver/feature.ts +59 -6
  188. package/packages/bundled-features/src/template-resolver/handlers/bundle-import.ts +188 -0
  189. package/packages/bundled-features/src/template-resolver/handlers/collection-set.write.ts +30 -2
  190. package/packages/bundled-features/src/template-resolver/handlers/copy-locale.write.ts +160 -0
  191. package/packages/bundled-features/src/template-resolver/handlers/diff-to-system.query.ts +33 -0
  192. package/packages/bundled-features/src/template-resolver/handlers/discard-draft.write.ts +52 -0
  193. package/packages/bundled-features/src/template-resolver/handlers/export-bundle.query.ts +138 -0
  194. package/packages/bundled-features/src/template-resolver/handlers/find-by-id.query.ts +9 -6
  195. package/packages/bundled-features/src/template-resolver/handlers/import-bundle-system.write.ts +42 -0
  196. package/packages/bundled-features/src/template-resolver/handlers/import-bundle.write.ts +22 -0
  197. package/packages/bundled-features/src/template-resolver/handlers/layout-source.query.ts +151 -0
  198. package/packages/bundled-features/src/template-resolver/handlers/list.query.ts +19 -18
  199. package/packages/bundled-features/src/template-resolver/handlers/override-diff.ts +49 -0
  200. package/packages/bundled-features/src/template-resolver/handlers/overview.query.ts +323 -0
  201. package/packages/bundled-features/src/template-resolver/handlers/preview.query.ts +25 -0
  202. package/packages/bundled-features/src/template-resolver/handlers/reset-to-system.write.ts +58 -0
  203. package/packages/bundled-features/src/template-resolver/handlers/resource-link-pdf.write.ts +34 -0
  204. package/packages/bundled-features/src/template-resolver/handlers/resource-remove.write.ts +50 -0
  205. package/packages/bundled-features/src/template-resolver/handlers/resource-shared.ts +24 -0
  206. package/packages/bundled-features/src/template-resolver/handlers/resource-upload.write.ts +135 -0
  207. package/packages/bundled-features/src/template-resolver/handlers/send-test.write.ts +135 -0
  208. package/packages/bundled-features/src/template-resolver/handlers/set.write.ts +32 -3
  209. package/packages/bundled-features/src/template-resolver/handlers/shared.ts +7 -2
  210. package/packages/bundled-features/src/template-resolver/handlers/template-gates.ts +196 -0
  211. package/packages/bundled-features/src/template-resolver/handlers/template-write.ts +233 -0
  212. package/packages/bundled-features/src/template-resolver/handlers/tenant-overrides.query.ts +137 -0
  213. package/packages/bundled-features/src/template-resolver/handlers/toggle-status.write.ts +91 -17
  214. package/packages/bundled-features/src/template-resolver/handlers/upsert-system.write.ts +19 -71
  215. package/packages/bundled-features/src/template-resolver/handlers/upsert-tenant.write.ts +16 -77
  216. package/packages/bundled-features/src/template-resolver/handlers/usage.query.ts +31 -0
  217. package/packages/bundled-features/src/template-resolver/handlers/validate.query.ts +116 -0
  218. package/packages/bundled-features/src/template-resolver/index.ts +111 -0
  219. package/packages/bundled-features/src/template-resolver/layout-appearance.ts +40 -0
  220. package/packages/bundled-features/src/template-resolver/layout-regions.ts +206 -0
  221. package/packages/bundled-features/src/template-resolver/linked-resources.ts +102 -0
  222. package/packages/bundled-features/src/template-resolver/pdf-constants.ts +12 -0
  223. package/packages/bundled-features/src/template-resolver/preview-display-html.ts +36 -0
  224. package/packages/bundled-features/src/template-resolver/print-document.ts +58 -0
  225. package/packages/bundled-features/src/template-resolver/qualified-names.ts +34 -0
  226. package/packages/bundled-features/src/template-resolver/record-mapping.ts +26 -0
  227. package/packages/bundled-features/src/template-resolver/resource-image.ts +47 -0
  228. package/packages/bundled-features/src/template-resolver/seeding.ts +171 -20
  229. package/packages/bundled-features/src/template-resolver/store-template-resource.ts +416 -0
  230. package/packages/bundled-features/src/template-resolver/table.ts +27 -1
  231. package/packages/bundled-features/src/template-resolver/template-bundle.ts +79 -0
  232. package/packages/bundled-features/src/template-resolver/template-diff.ts +78 -0
  233. package/packages/bundled-features/src/template-resolver/template-draft.ts +106 -0
  234. package/packages/bundled-features/src/template-resolver/template-meta.ts +192 -0
  235. package/packages/bundled-features/src/template-resolver/template-preview.ts +676 -0
  236. package/packages/bundled-features/src/template-resolver/template-resources.ts +114 -0
  237. package/packages/bundled-features/src/template-resolver/template-usage.ts +68 -0
  238. package/packages/bundled-features/src/template-resolver/template-validation.ts +230 -0
  239. package/packages/bundled-features/src/template-resolver/testing.ts +5 -5
  240. package/packages/bundled-features/src/template-resolver/web/client-plugin.tsx +20 -13
  241. package/packages/bundled-features/src/template-resolver/web/document-editor-extensions.ts +215 -0
  242. package/packages/bundled-features/src/template-resolver/web/document-editor.tsx +37 -0
  243. package/packages/bundled-features/src/template-resolver/web/document-tiptap-editor.tsx +1065 -0
  244. package/packages/bundled-features/src/template-resolver/web/document-toolbar-blocks.ts +72 -0
  245. package/packages/bundled-features/src/template-resolver/web/editor-handle.ts +26 -0
  246. package/packages/bundled-features/src/template-resolver/web/i18n.ts +121 -0
  247. package/packages/bundled-features/src/template-resolver/web/index.ts +26 -0
  248. package/packages/bundled-features/src/template-resolver/web/placeholder-line-editor-lazy.tsx +36 -0
  249. package/packages/bundled-features/src/template-resolver/web/placeholder-line-editor.tsx +176 -0
  250. package/packages/bundled-features/src/template-resolver/web/placeholder-text.ts +37 -0
  251. package/packages/bundled-features/src/template-resolver/web/use-stable-by-key.ts +14 -0
  252. package/packages/bundled-features/src/tenant/handlers/team-list.query.ts +3 -2
  253. package/packages/bundled-features/src/tenant/seeding.ts +41 -7
  254. package/packages/bundled-features/src/tenant-lifecycle/boot-checks.ts +1 -1
  255. package/packages/bundled-features/src/tenant-lifecycle/handlers/cancel-destruction.write.ts +2 -1
  256. package/packages/bundled-features/src/tier-engine/changes.json +6 -0
  257. package/packages/bundled-features/src/tier-engine/feature.ts +11 -3
  258. package/packages/bundled-features/src/user/changes.json +6 -0
  259. package/packages/bundled-features/src/user/db/queries/stream-tenant-backfill.ts +4 -4
  260. package/packages/bundled-features/src/user/handlers/detail.query.ts +3 -5
  261. package/packages/bundled-features/src/user/resolve-display-names.ts +8 -15
  262. package/packages/bundled-features/src/user/roles-input-schema.ts +2 -2
  263. package/packages/bundled-features/src/user/seeding.ts +19 -5
  264. package/packages/bundled-features/src/user-data-rights/changes.json +13 -0
  265. package/packages/bundled-features/src/user-data-rights/db/queries/forget-cleanup.ts +2 -2
  266. package/packages/bundled-features/src/user-data-rights/email-templates.ts +5 -149
  267. package/packages/bundled-features/src/user-data-rights/feature.ts +72 -35
  268. package/packages/bundled-features/src/user-data-rights/handlers/cancel-deletion.write.ts +2 -1
  269. package/packages/bundled-features/src/user-data-rights/handlers/request-deletion.write.ts +5 -11
  270. package/packages/bundled-features/src/user-data-rights/lib/default-mailers.ts +134 -53
  271. package/packages/bundled-features/src/user-data-rights/lib/update-user-lifecycle.ts +2 -2
  272. package/packages/bundled-features/src/user-data-rights/mail-notifications.ts +157 -0
  273. package/packages/bundled-features/src/user-data-rights/run-forget-cleanup.ts +1 -1
  274. package/packages/bundled-features/src/user-data-rights/web/privacy-center-screen.tsx +2 -1
  275. package/packages/bundled-features/src/user-profile/changes.json +1 -1
  276. package/packages/bundled-features/src/user-profile/web/profile-screen.tsx +2 -1
  277. package/packages/bundled-features/src/waitlist/changes.json +7 -0
  278. package/packages/bundled-features/src/waitlist/feature.ts +17 -0
  279. package/packages/bundled-features/src/waitlist/handlers/submit.write.ts +4 -5
  280. package/packages/bundled-features/src/waitlist/mail.ts +69 -18
  281. package/packages/bundled-features/src/waitlist/options.ts +1 -1
  282. package/packages/bundled-features/src/workflow-runner/changes.json +6 -0
  283. package/packages/bundled-features/src/workflow-runner/event-trigger.ts +3 -3
  284. package/packages/bundled-features/src/workflow-runner/handlers/resume-run.write.ts +7 -1
  285. package/samples/apps/ui-walkthrough/package.json +11 -0
  286. package/samples/apps/ui-walkthrough/playwright.config.ts +40 -7
  287. package/samples/apps/ui-walkthrough/src/app/auth-constants.ts +9 -0
  288. package/samples/apps/ui-walkthrough/src/app/client.tsx +6 -0
  289. package/samples/apps/ui-walkthrough/src/app/seed-templates.ts +239 -0
  290. package/samples/apps/ui-walkthrough/src/app/server.ts +5 -1
  291. package/samples/apps/ui-walkthrough/src/app/shell.tsx +12 -1
  292. package/samples/apps/ui-walkthrough/src/app/template-seed-data.ts +570 -0
  293. package/samples/apps/ui-walkthrough/src/features/billing/feature.ts +72 -0
  294. package/samples/apps/ui-walkthrough/src/features/billing/i18n.ts +18 -0
  295. package/samples/apps/ui-walkthrough/src/features/billing/index.ts +2 -0
  296. package/samples/apps/ui-walkthrough/src/features/billing/web/index.ts +7 -0
  297. package/samples/apps/ui-walkthrough/src/run-config.ts +41 -1
  298. package/samples/apps/use-all-bundled/e2e-billing-gallery/gallery-features.ts +0 -2
  299. package/samples/apps/use-all-bundled/feature-manifest.json +118 -21
  300. package/samples/apps/use-all-bundled/kumiko/migrations/0045_template-resource-meta.rebuild.json +4 -0
  301. package/samples/apps/use-all-bundled/kumiko/migrations/0046_pdf-rendered.rebuild.json +4 -0
  302. package/samples/apps/use-all-bundled/package.json +2 -0
  303. package/samples/apps/use-all-bundled/playwright.config.ts +38 -3
  304. package/samples/apps/use-all-bundled/schema/generate.ts +13 -3
  305. package/samples/apps/use-all-bundled/schema/schema.generated.ts +3 -0
  306. package/samples/apps/use-all-bundled/scripts/check-coverage.ts +3 -0
  307. package/samples/apps/use-all-bundled/src/app/auth-constants.ts +3 -0
  308. package/samples/apps/use-all-bundled/src/app/client.tsx +4 -0
  309. package/samples/apps/use-all-bundled/src/app/seed-documents.ts +128 -0
  310. package/samples/apps/use-all-bundled/src/app/seed.ts +4 -0
  311. package/samples/apps/use-all-bundled/src/run-config.ts +11 -11
  312. package/samples/recipes/delivery-notifications/README.md +26 -25
  313. package/samples/recipes/delivery-notifications/package.json +2 -1
  314. package/samples/recipes/delivery-notifications/src/feature.ts +64 -58
  315. package/samples/recipes/seed-migration/README.md +1 -1
  316. package/packages/bundled-features/src/renderer-simple/changes.json +0 -27
  317. package/packages/bundled-features/src/renderer-simple/feature.ts +0 -66
  318. package/packages/bundled-features/src/renderer-simple/index.ts +0 -8
  319. package/packages/bundled-features/src/renderer-simple/mail-blocks.ts +0 -138
  320. package/packages/bundled-features/src/renderer-simple/resolve-variables.ts +0 -57
  321. package/packages/bundled-features/src/renderer-simple/simple-renderer.ts +0 -304
@@ -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,103 @@
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
+ fromName?: string;
66
+ replyTo?: string;
67
+ attachments?: readonly MailAttachmentRef[];
68
+ }
69
+ | {
70
+ kind: "document-pdf";
71
+ pdfBytes: Uint8Array;
72
+ pageCount: number;
73
+ sizeBytes: number;
74
+ fileName: string;
75
+ }
18
76
  | {
19
77
  kind: "image-snapshot";
20
78
  imageBytes: Uint8Array;
21
79
  format: "png" | "jpg";
22
80
  width: number;
23
81
  height: number;
24
- };
82
+ }
83
+ ) &
84
+ RenderProvenance;
25
85
 
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;
86
+ // An unsaved version of the mail template; rendering it never reads its stored body or subject.
87
+ export type MailDraft = {
88
+ readonly content: string;
89
+ readonly meta: MailTemplateMeta;
90
+ readonly variableSchema?: Readonly<Record<string, unknown>>;
91
+ /** Images and attachments of the row being edited; absent means those of the stored template. */
92
+ readonly linkedResources?: Readonly<Record<string, LinkedResourceEntry>>;
32
93
  };
33
94
 
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;
95
+ export type MailPayload = {
96
+ readonly slug: string;
97
+ readonly locale: string;
98
+ readonly variables: Readonly<Record<string, unknown>>;
99
+ readonly unsubscribeUrl?: string;
100
+ readonly draft?: MailDraft;
41
101
  };
42
102
 
43
103
  export type DocumentPayload = {
@@ -48,13 +108,46 @@ export type DocumentPayload = {
48
108
  readonly locale?: string;
49
109
  };
50
110
 
111
+ export type DocumentPdfPayload = {
112
+ readonly slug: string;
113
+ readonly locale: string;
114
+ readonly variables: Readonly<Record<string, unknown>>;
115
+ };
116
+
117
+ // An unsaved version of the document template, like MailDraft.
118
+ export type DocumentPdfDraft = {
119
+ readonly content: string;
120
+ readonly meta: PdfTemplateMeta | null;
121
+ readonly variableSchema?: Readonly<Record<string, unknown>>;
122
+ };
123
+
124
+ export type DocumentPreviewRequest = DocumentPdfPayload & { readonly draft?: DocumentPdfDraft };
125
+
126
+ // What an editor shows for a document: the printable HTML with the values filled in,
127
+ // without calling any print service.
128
+ export type DocumentPreview = {
129
+ readonly title: string;
130
+ readonly fileName: string;
131
+ readonly bodyHtml: string;
132
+ readonly layout: {
133
+ readonly slug: string;
134
+ readonly scope: TemplateScope;
135
+ readonly headerHtml: string;
136
+ readonly signatureHtml: string;
137
+ readonly footerHtml: string;
138
+ } | null;
139
+ /** Image keys the body or the layout asks for that no file is linked to. */
140
+ readonly missingResourceKeys: readonly string[];
141
+ };
142
+
51
143
  export type PdfOptions = {
52
- readonly format?: "A4" | "Letter";
144
+ readonly format?: PdfPageFormat;
53
145
  readonly marginMm?: { top?: number; right?: number; bottom?: number; left?: number };
54
146
  readonly headerTemplate?: string;
55
147
  readonly footerTemplate?: string;
56
148
  readonly printBackground?: boolean;
57
149
  readonly displayHeaderFooter?: boolean;
150
+ readonly landscape?: boolean;
58
151
  };
59
152
 
60
153
  export type ImageOptions = {
@@ -75,33 +168,39 @@ export type ImageOptions = {
75
168
  // const tplApi = createTemplateResolverApi(ctx.db);
76
169
  // const layout = await tplApi.resolveTemplate({ tenantId: ctx.tenantId, slug, kind });
77
170
  export type RendererContext = {
78
- readonly db: DbConnection;
171
+ readonly db: DbRunner | TenantDb;
79
172
  readonly registry: Registry;
80
173
  readonly tenantId: TenantId;
174
+ /** Tenant-scoped config for plugins that read the tenant currency or
175
+ * branding; plugins fall back to request defaults when absent. */
176
+ readonly config?: ConfigAccessor;
177
+ /** Only plugins that embed resource bytes (PDF) need it; without it images are dropped. */
178
+ readonly filesForTenant?: (tenantId: string) => FileContext | undefined;
81
179
  };
82
180
 
83
- // RendererPlugin-Contract — pro Plugin (renderer-simple, renderer-mail-html,
181
+ // RendererPlugin-Contract — pro Plugin (renderer-mail-html,
84
182
  // renderer-puppeteer-client). Plugin deklariert welche kinds es bedienen
85
183
  // kann; Foundation-Resolver wählt basierend auf Tenant-Config + kind.
86
184
  //
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).
185
+ // Plugin invariants:
186
+ // - `kinds` has at least one entry, otherwise the plugin is never picked.
187
+ // - The response kind of `render(req, ctx)` matches req.kind.
188
+ // - Plugins are stateless between render calls.
189
+ // - Domain failures throw `RendererError`, not a bare Error.
190
+ // - `ctx` is required; plugins without service deps ignore it. Function-arg
191
+ // variance still lets `(req) => ...` implementations satisfy the contract.
95
192
  export type RendererPlugin = {
96
193
  readonly name: string;
97
194
  readonly kinds: ReadonlyArray<RenderKind>;
98
195
  render(req: RenderRequest, ctx: RendererContext): Promise<RenderResponse>;
196
+ /** Optional: plugins that print documents offer a preview that never reaches the print service. */
197
+ previewDocument?(req: DocumentPreviewRequest, ctx: RendererContext): Promise<DocumentPreview>;
99
198
  };
100
199
 
101
200
  export class RendererError extends Error {
102
201
  constructor(
103
202
  message: string,
104
- public readonly code: "no_plugin_for_kind" | "invalid_payload" | "other",
203
+ public readonly code: "no_plugin_for_kind" | "invalid_payload" | "unavailable" | "other",
105
204
  ) {
106
205
  super(message);
107
206
  this.name = "RendererError";
@@ -1,4 +1,16 @@
1
1
  [
2
+ {
3
+ "version": "0.364.0",
4
+ "type": "fix",
5
+ "title": "renderTablePdf rejects tables above 10000 rows, 30 columns or 5000 characters per cell",
6
+ "detail": "The table document schema now caps rows at 10000, columns at 30 and each cell string at 5000 characters. Larger input fails with invalid_payload instead of exhausting memory during layout."
7
+ },
8
+ {
9
+ "version": "0.364.0",
10
+ "type": "fix",
11
+ "title": "render-table-pdf validates input and paginates its intro",
12
+ "detail": "Table documents reject negative margins and unusable column widths, normalize CRLF and paginate the intro text and the first header row."
13
+ },
2
14
  {
3
15
  "version": "0.360.0",
4
16
  "type": "improvement",
@@ -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 {
@@ -1,52 +1,56 @@
1
1
  import { PDFDocument, type PDFFont, type PDFPage, rgb, StandardFonts } from "pdf-lib";
2
2
  import * as z from "zod";
3
3
  import { RendererError } from "../renderer-foundation/index.js";
4
-
5
- export const pdfTableDocumentSchema = z.object({
6
- title: z.string(),
7
- subtitle: z.string().optional(),
8
- meta: z.array(z.object({ label: z.string(), value: z.string() })).optional(),
9
- columns: z
10
- .array(
11
- z.object({
12
- header: z.string(),
13
- weight: z.number().positive().optional(),
14
- align: z.enum(["left", "right"]).optional(),
15
- }),
16
- )
17
- .min(1),
18
- rows: z.array(z.array(z.string())),
19
- emptyText: z.string().optional(),
20
- footerText: z.string().optional(),
21
- pageLabel: z.string().optional(),
22
- });
23
-
24
- export type PdfTableColumn = {
25
- readonly header: string;
26
- readonly weight?: number;
27
- readonly align?: "left" | "right";
28
- };
29
-
30
- export type PdfTableDocument = {
31
- readonly title: string;
32
- readonly subtitle?: string;
33
- readonly meta?: readonly { readonly label: string; readonly value: string }[];
34
- readonly columns: readonly PdfTableColumn[];
35
- readonly rows: readonly (readonly string[])[];
36
- readonly emptyText?: string;
37
- readonly footerText?: string;
38
- /** Template with `{page}` and `{pages}`; default `"{page} / {pages}"`. */
39
- readonly pageLabel?: string;
40
- };
4
+ import type { PdfPageFormat } from "../template-resolver/index.js";
5
+
6
+ // Upper bounds keep a hostile or runaway payload from exhausting memory and CPU in layout.
7
+ const PDF_TABLE_MAX_ROWS = 10_000;
8
+ const PDF_TABLE_MAX_COLUMNS = 30;
9
+ const PDF_TABLE_MAX_CELL_LENGTH = 5_000;
10
+
11
+ const pdfTableColumnSchema = z
12
+ .object({
13
+ header: z.string(),
14
+ weight: z.number().positive().optional(),
15
+ align: z.enum(["left", "right"]).optional(),
16
+ })
17
+ .readonly();
18
+
19
+ export const pdfTableDocumentSchema = z
20
+ .object({
21
+ title: z.string(),
22
+ subtitle: z.string().optional(),
23
+ meta: z
24
+ .array(z.object({ label: z.string(), value: z.string() }).readonly())
25
+ .readonly()
26
+ .optional(),
27
+ columns: z.array(pdfTableColumnSchema).min(1).max(PDF_TABLE_MAX_COLUMNS).readonly(),
28
+ rows: z
29
+ .array(z.array(z.string().max(PDF_TABLE_MAX_CELL_LENGTH)).readonly())
30
+ .max(PDF_TABLE_MAX_ROWS)
31
+ .readonly(),
32
+ emptyText: z.string().optional(),
33
+ footerText: z.string().optional(),
34
+ // Template with `{page}` and `{pages}`; default `"{page} / {pages}"`.
35
+ pageLabel: z.string().optional(),
36
+ })
37
+ .readonly();
38
+
39
+ export type PdfTableColumn = z.infer<typeof pdfTableColumnSchema>;
40
+ export type PdfTableDocument = z.infer<typeof pdfTableDocumentSchema>;
41
+
42
+ const marginMmSchema = z
43
+ .object({
44
+ top: z.number().nonnegative().optional(),
45
+ right: z.number().nonnegative().optional(),
46
+ bottom: z.number().nonnegative().optional(),
47
+ left: z.number().nonnegative().optional(),
48
+ })
49
+ .readonly();
41
50
 
42
51
  export type PdfTableOptions = {
43
- readonly format?: "A4" | "Letter";
44
- readonly marginMm?: {
45
- readonly top?: number;
46
- readonly right?: number;
47
- readonly bottom?: number;
48
- readonly left?: number;
49
- };
52
+ readonly format?: PdfPageFormat;
53
+ readonly marginMm?: z.input<typeof marginMmSchema>;
50
54
  };
51
55
 
52
56
  export type PdfTableResult = { readonly pdfBytes: Uint8Array; readonly pageCount: number };
@@ -55,7 +59,7 @@ const POINTS_PER_MM = 72 / 25.4;
55
59
  const PAGE_SIZE_POINTS = {
56
60
  A4: { width: 595.28, height: 841.89 },
57
61
  Letter: { width: 612, height: 792 },
58
- } as const;
62
+ } as const satisfies Record<PdfPageFormat, { readonly width: number; readonly height: number }>;
59
63
  const DEFAULT_MARGIN_MM = { top: 18, right: 15, bottom: 18, left: 15 } as const;
60
64
 
61
65
  const TITLE_SIZE = 16;
@@ -66,6 +70,7 @@ const LINE_HEIGHT_FACTOR = 1.3;
66
70
  const CELL_PADDING_X = 4;
67
71
  const CELL_PADDING_Y = 3;
68
72
  const FOOTER_RESERVE = 16;
73
+ const MIN_COLUMN_WIDTH = 20;
69
74
  const META_LABEL_GAP = 10;
70
75
  const BLOCK_GAP = 10;
71
76
  const REPLACEMENT_CHAR = "?";
@@ -86,7 +91,7 @@ function createEncodableText(font: PDFFont): (text: string) => string {
86
91
  const supported = new Set(font.getCharacterSet());
87
92
  return (text) => {
88
93
  let result = "";
89
- for (const char of text.replace(/\t/g, " ")) {
94
+ for (const char of text.replace(/\r\n?/g, "\n").replace(/\t/g, " ")) {
90
95
  const codePoint = char.codePointAt(0) ?? 0;
91
96
  result += codePoint === 0x0a || supported.has(codePoint) ? char : REPLACEMENT_CHAR;
92
97
  }
@@ -157,30 +162,43 @@ function assertRowShape(doc: PdfTableDocument): void {
157
162
  });
158
163
  }
159
164
 
160
- export async function renderTablePdf(
161
- doc: PdfTableDocument,
162
- options?: PdfTableOptions,
163
- ): Promise<PdfTableResult> {
164
- if (doc.columns.length === 0) {
165
- throw new RendererError("table needs at least one column", "invalid_payload");
166
- }
167
- assertRowShape(doc);
168
-
169
- const pdf = await PDFDocument.create({ updateMetadata: false });
170
- const fonts: Fonts = {
171
- regular: await pdf.embedFont(StandardFonts.Helvetica),
172
- bold: await pdf.embedFont(StandardFonts.HelveticaBold),
165
+ type TableLayout = {
166
+ readonly pageSize: { readonly width: number; readonly height: number };
167
+ readonly margin: {
168
+ readonly top: number;
169
+ readonly right: number;
170
+ readonly bottom: number;
171
+ readonly left: number;
173
172
  };
174
- const clean = createEncodableText(fonts.regular);
173
+ readonly contentWidth: number;
174
+ readonly contentBottom: number;
175
+ readonly columnWidths: readonly number[];
176
+ readonly bodyLineHeight: number;
177
+ readonly headerLines: readonly (readonly string[])[];
178
+ readonly headerHeight: number;
179
+ };
175
180
 
181
+ function resolveTableLayout(
182
+ doc: PdfTableDocument,
183
+ options: PdfTableOptions | undefined,
184
+ fonts: Fonts,
185
+ clean: (text: string) => string,
186
+ ): TableLayout {
187
+ const marginMm = marginMmSchema.safeParse(options?.marginMm ?? {});
188
+ if (!marginMm.success) {
189
+ throw new RendererError("margins must be non-negative numbers", "invalid_payload");
190
+ }
176
191
  const pageSize = PAGE_SIZE_POINTS[options?.format ?? "A4"];
177
192
  const margin = {
178
- top: (options?.marginMm?.top ?? DEFAULT_MARGIN_MM.top) * POINTS_PER_MM,
179
- right: (options?.marginMm?.right ?? DEFAULT_MARGIN_MM.right) * POINTS_PER_MM,
180
- bottom: (options?.marginMm?.bottom ?? DEFAULT_MARGIN_MM.bottom) * POINTS_PER_MM,
181
- left: (options?.marginMm?.left ?? DEFAULT_MARGIN_MM.left) * POINTS_PER_MM,
193
+ top: (marginMm.data.top ?? DEFAULT_MARGIN_MM.top) * POINTS_PER_MM,
194
+ right: (marginMm.data.right ?? DEFAULT_MARGIN_MM.right) * POINTS_PER_MM,
195
+ bottom: (marginMm.data.bottom ?? DEFAULT_MARGIN_MM.bottom) * POINTS_PER_MM,
196
+ left: (marginMm.data.left ?? DEFAULT_MARGIN_MM.left) * POINTS_PER_MM,
182
197
  };
183
198
  const contentWidth = pageSize.width - margin.left - margin.right;
199
+ if (contentWidth < MIN_COLUMN_WIDTH * doc.columns.length) {
200
+ throw new RendererError("margins leave no room for table columns", "invalid_payload");
201
+ }
184
202
  const contentBottom = margin.bottom + FOOTER_RESERVE;
185
203
  const columnWidths = resolveColumnWidths(doc.columns, contentWidth);
186
204
  const bodyLineHeight = lineHeightFor(BODY_SIZE);
@@ -200,6 +218,48 @@ export async function renderTablePdf(
200
218
  );
201
219
  const headerHeight =
202
220
  Math.max(...headerLines.map((lines) => lines.length)) * bodyLineHeight + 2 * CELL_PADDING_Y;
221
+ // A header this tall would leave every continuation page without room for a row.
222
+ if (rowSpaceOnEmptyPage - headerHeight < bodyLineHeight + 2 * CELL_PADDING_Y) {
223
+ throw new RendererError("table header leaves no room for rows", "invalid_payload");
224
+ }
225
+ return {
226
+ pageSize,
227
+ margin,
228
+ contentWidth,
229
+ contentBottom,
230
+ columnWidths,
231
+ bodyLineHeight,
232
+ headerLines,
233
+ headerHeight,
234
+ };
235
+ }
236
+
237
+ export async function renderTablePdf(
238
+ doc: PdfTableDocument,
239
+ options?: PdfTableOptions,
240
+ ): Promise<PdfTableResult> {
241
+ if (doc.columns.length === 0) {
242
+ throw new RendererError("table needs at least one column", "invalid_payload");
243
+ }
244
+ assertRowShape(doc);
245
+
246
+ const pdf = await PDFDocument.create({ updateMetadata: false });
247
+ const fonts: Fonts = {
248
+ regular: await pdf.embedFont(StandardFonts.Helvetica),
249
+ bold: await pdf.embedFont(StandardFonts.HelveticaBold),
250
+ };
251
+ const clean = createEncodableText(fonts.regular);
252
+
253
+ const {
254
+ pageSize,
255
+ margin,
256
+ contentWidth,
257
+ contentBottom,
258
+ columnWidths,
259
+ bodyLineHeight,
260
+ headerLines,
261
+ headerHeight,
262
+ } = resolveTableLayout(doc, options, fonts, clean);
203
263
 
204
264
  let page: PDFPage = pdf.addPage([pageSize.width, pageSize.height]);
205
265
  let cursorY = pageSize.height - margin.top;
@@ -227,29 +287,50 @@ export async function renderTablePdf(
227
287
  });
228
288
  }
229
289
 
290
+ function startBlankPage(): void {
291
+ page = pdf.addPage([pageSize.width, pageSize.height]);
292
+ cursorY = pageSize.height - margin.top;
293
+ }
294
+
295
+ function ensureRoom(height: number): void {
296
+ if (cursorY - height < contentBottom) startBlankPage();
297
+ }
298
+
299
+ // Line by line, so a block taller than one page continues on the next.
300
+ function flowLines(
301
+ lines: readonly string[],
302
+ font: PDFFont,
303
+ size: number,
304
+ left: number,
305
+ width: number,
306
+ color = TEXT_COLOR,
307
+ ): void {
308
+ const lineHeight = lineHeightFor(size);
309
+ for (const line of lines) {
310
+ ensureRoom(lineHeight);
311
+ drawLines([line], font, size, left, width, cursorY, "left", color);
312
+ cursorY -= lineHeight;
313
+ }
314
+ }
315
+
230
316
  function drawIntroBlock(): void {
231
- const titleLines = wrapText(clean(doc.title), fonts.bold, TITLE_SIZE, contentWidth);
232
- drawLines(titleLines, fonts.bold, TITLE_SIZE, margin.left, contentWidth, cursorY, "left");
233
- cursorY -= titleLines.length * lineHeightFor(TITLE_SIZE);
317
+ flowLines(
318
+ wrapText(clean(doc.title), fonts.bold, TITLE_SIZE, contentWidth),
319
+ fonts.bold,
320
+ TITLE_SIZE,
321
+ margin.left,
322
+ contentWidth,
323
+ );
234
324
 
235
325
  if (doc.subtitle !== undefined) {
236
- const subtitleLines = wrapText(
237
- clean(doc.subtitle),
238
- fonts.regular,
239
- SUBTITLE_SIZE,
240
- contentWidth,
241
- );
242
- drawLines(
243
- subtitleLines,
326
+ flowLines(
327
+ wrapText(clean(doc.subtitle), fonts.regular, SUBTITLE_SIZE, contentWidth),
244
328
  fonts.regular,
245
329
  SUBTITLE_SIZE,
246
330
  margin.left,
247
331
  contentWidth,
248
- cursorY,
249
- "left",
250
332
  MUTED_COLOR,
251
333
  );
252
- cursorY -= subtitleLines.length * lineHeightFor(SUBTITLE_SIZE);
253
334
  }
254
335
 
255
336
  const meta = doc.meta ?? [];
@@ -266,6 +347,7 @@ export async function renderTablePdf(
266
347
  BODY_SIZE,
267
348
  contentWidth - labelWidth,
268
349
  );
350
+ ensureRoom(bodyLineHeight);
269
351
  drawLines(
270
352
  [labels[index] ?? ""],
271
353
  fonts.bold,
@@ -275,16 +357,13 @@ export async function renderTablePdf(
275
357
  cursorY,
276
358
  "left",
277
359
  );
278
- drawLines(
360
+ flowLines(
279
361
  valueLines,
280
362
  fonts.regular,
281
363
  BODY_SIZE,
282
364
  margin.left + labelWidth,
283
365
  contentWidth - labelWidth,
284
- cursorY,
285
- "left",
286
366
  );
287
- cursorY -= valueLines.length * bodyLineHeight;
288
367
  });
289
368
  }
290
369
  cursorY -= BLOCK_GAP;
@@ -372,6 +451,7 @@ export async function renderTablePdf(
372
451
  }
373
452
 
374
453
  drawIntroBlock();
454
+ ensureRoom(headerHeight + bodyLineHeight + 2 * CELL_PADDING_Y);
375
455
  drawTableHeader();
376
456
 
377
457
  if (doc.rows.length === 0 && doc.emptyText !== undefined) {
@@ -1,4 +1,10 @@
1
1
  [
2
+ {
3
+ "version": "0.364.0",
4
+ "type": "improvement",
5
+ "title": "SecretsContext.withRunner(tx) runs secret access on a given transaction",
6
+ "detail": "The OAuth refresh now reads and writes the refresh token on its advisory-lock transaction, so a refresh holds one pool connection instead of two and parallel refreshes cannot starve the pool. Custom SecretsContext implementations need a `withRunner` method."
7
+ },
2
8
  {
3
9
  "version": "0.343.0",
4
10
  "type": "improvement",