@cosmicdrift/kumiko-bundled-features 0.363.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 (264) hide show
  1. package/dist/audit/handlers/list.query.js +9 -5
  2. package/dist/auth-email-password/bootstrap.d.ts +8 -5
  3. package/dist/auth-email-password/bootstrap.js +12 -4
  4. package/dist/auth-email-password/email-rate-limits.d.ts +4 -0
  5. package/dist/auth-email-password/email-rate-limits.js +13 -0
  6. package/dist/auth-email-password/handlers/change-password.write.d.ts +1 -1
  7. package/dist/auth-email-password/handlers/change-password.write.js +6 -2
  8. package/dist/auth-email-password/handlers/confirm-account-unlock.write.d.ts +1 -1
  9. package/dist/auth-email-password/handlers/invite-accept-with-login.write.d.ts +1 -1
  10. package/dist/auth-email-password/handlers/invite-accept.write.d.ts +1 -1
  11. package/dist/auth-email-password/handlers/invite-create.write.d.ts +2 -2
  12. package/dist/auth-email-password/handlers/invite-signup-complete.write.d.ts +1 -1
  13. package/dist/auth-email-password/handlers/login.write.d.ts +1 -1
  14. package/dist/auth-email-password/handlers/logout.write.d.ts +1 -1
  15. package/dist/auth-email-password/handlers/logout.write.js +6 -2
  16. package/dist/auth-email-password/handlers/request-account-unlock.write.d.ts +1 -1
  17. package/dist/auth-email-password/handlers/request-email-verification.write.d.ts +1 -1
  18. package/dist/auth-email-password/handlers/request-password-reset.write.d.ts +1 -1
  19. package/dist/auth-email-password/handlers/reset-password.write.d.ts +1 -1
  20. package/dist/auth-email-password/handlers/signup-confirm.write.d.ts +1 -1
  21. package/dist/auth-email-password/handlers/signup-request.write.d.ts +1 -1
  22. package/dist/auth-email-password/handlers/signup-request.write.js +2 -1
  23. package/dist/auth-email-password/handlers/switch-tenant-mfa-gate.write.d.ts +1 -1
  24. package/dist/auth-email-password/handlers/token-request-handler.d.ts +1 -1
  25. package/dist/auth-email-password/handlers/token-request-handler.js +2 -1
  26. package/dist/auth-email-password/handlers/verify-email.write.d.ts +1 -1
  27. package/dist/auth-email-password/web/auth-gate.js +16 -4
  28. package/dist/auth-mfa/handlers/disable.write.d.ts +1 -1
  29. package/dist/auth-mfa/handlers/enable-confirm-preauth.write.d.ts +1 -1
  30. package/dist/auth-mfa/handlers/enable-confirm.write.d.ts +1 -1
  31. package/dist/auth-mfa/handlers/enable-start-preauth.write.d.ts +1 -1
  32. package/dist/auth-mfa/handlers/enable-start.write.d.ts +1 -1
  33. package/dist/auth-mfa/handlers/regenerate-recovery.write.d.ts +1 -1
  34. package/dist/auth-mfa/handlers/verify.write.d.ts +1 -1
  35. package/dist/billing-foundation/consumer-protection/confirmation-mail.js +52 -5
  36. package/dist/billing-foundation/consumer-protection/consent-gate.d.ts +3 -0
  37. package/dist/billing-foundation/consumer-protection/consent-gate.js +21 -2
  38. package/dist/billing-foundation/consumer-protection/mail-notifications.js +1 -1
  39. package/dist/billing-foundation/consumer-protection/register-termination.js +11 -1
  40. package/dist/billing-foundation/consumer-protection/termination-mail.d.ts +5 -3
  41. package/dist/billing-foundation/consumer-protection/termination-mail.js +1 -2
  42. package/dist/billing-foundation/consumer-protection/termination-receipt-limit.d.ts +6 -0
  43. package/dist/billing-foundation/consumer-protection/termination-receipt-limit.js +34 -0
  44. package/dist/billing-foundation/consumer-protection/termination-record.d.ts +2 -0
  45. package/dist/billing-foundation/consumer-protection/termination-record.js +7 -1
  46. package/dist/billing-foundation/consumer-protection/termination-request.js +30 -20
  47. package/dist/billing-foundation/consumer-protection/termination-texts.d.ts +1 -1
  48. package/dist/billing-foundation/consumer-protection/termination-texts.js +6 -0
  49. package/dist/billing-foundation/consumer-protection/withdrawal-period.d.ts +10 -0
  50. package/dist/billing-foundation/consumer-protection/withdrawal-period.js +26 -0
  51. package/dist/billing-foundation/handlers/create-checkout-session.write.js +11 -5
  52. package/dist/billing-foundation/handlers/process-event.write.js +10 -6
  53. package/dist/billing-foundation/i18n.js +3 -0
  54. package/dist/billing-foundation/subscription-tier-sync.d.ts +7 -1
  55. package/dist/billing-foundation/subscription-tier-sync.js +13 -5
  56. package/dist/billing-foundation/types.d.ts +7 -2
  57. package/dist/cap-counter/calendar-cap-usage-reservation.js +2 -0
  58. package/dist/cap-counter/enforce-cap.d.ts +18 -1
  59. package/dist/cap-counter/enforce-cap.js +49 -6
  60. package/dist/cap-counter/index.d.ts +1 -1
  61. package/dist/cap-counter/index.js +1 -1
  62. package/dist/cap-counter/with-cap-enforcement.d.ts +2 -0
  63. package/dist/channel-email/email-channel.js +16 -4
  64. package/dist/channel-in-app/handlers/mark-all-read.write.d.ts +1 -1
  65. package/dist/channel-in-app/handlers/mark-read.write.d.ts +1 -1
  66. package/dist/compliance-profiles/handlers/set-profile.write.d.ts +1 -1
  67. package/dist/config/handlers/reset.write.d.ts +1 -1
  68. package/dist/config/handlers/set.write.d.ts +1 -1
  69. package/dist/config/handlers/set.write.js +2 -2
  70. package/dist/config/resolver.d.ts +1 -0
  71. package/dist/config/resolver.js +11 -14
  72. package/dist/config/write-helpers.d.ts +2 -0
  73. package/dist/config/write-helpers.js +19 -0
  74. package/dist/crypto-shredding/handlers/forget-subject.write.d.ts +1 -1
  75. package/dist/crypto-shredding/handlers/forget-subject.write.js +29 -19
  76. package/dist/custom-fields/handlers/update-tenant-field.write.js +0 -1
  77. package/dist/delivery/attachment-ref.d.ts +2 -0
  78. package/dist/delivery/attachment-ref.js +14 -0
  79. package/dist/delivery/channel-context.js +19 -1
  80. package/dist/delivery/handlers/resubscribe-address.write.d.ts +1 -1
  81. package/dist/delivery/handlers/resubscribe-user.write.d.ts +1 -1
  82. package/dist/delivery/handlers/set-preference.write.d.ts +1 -1
  83. package/dist/delivery/handlers/unsubscribe-address.write.d.ts +1 -1
  84. package/dist/delivery/handlers/unsubscribe-user.write.d.ts +1 -1
  85. package/dist/delivery/index.d.ts +1 -1
  86. package/dist/delivery/index.js +1 -1
  87. package/dist/delivery/jobs.js +43 -11
  88. package/dist/delivery/types.d.ts +7 -1
  89. package/dist/feature-toggles/handlers/set.write.d.ts +1 -1
  90. package/dist/file-derivatives/handlers/public-variant.query.js +1 -1
  91. package/dist/file-foundation/read-file-ref-content.d.ts +1 -1
  92. package/dist/file-foundation/read-file-ref-content.js +6 -4
  93. package/dist/files-tenant-data/hooks.js +4 -0
  94. package/dist/form-draft/handlers/discard.write.d.ts +1 -1
  95. package/dist/form-draft/handlers/save.write.d.ts +1 -1
  96. package/dist/inbound-mail-foundation/oauth-access-token.js +8 -3
  97. package/dist/inbound-mail-foundation/watch-supervisor.js +3 -1
  98. package/dist/jobs/db/queries/stale-run-sweep.d.ts +1 -1
  99. package/dist/jobs/db/queries/stale-run-sweep.js +12 -5
  100. package/dist/jobs/feature.js +5 -3
  101. package/dist/jobs/handlers/list.query.js +3 -1
  102. package/dist/jobs/handlers/retry.write.d.ts +1 -1
  103. package/dist/jobs/handlers/stale-run-sweep.job.d.ts +1 -1
  104. package/dist/jobs/handlers/stale-run-sweep.job.js +2 -2
  105. package/dist/jobs/handlers/trigger.write.d.ts +1 -1
  106. package/dist/jobs/job-run-logger.js +32 -10
  107. package/dist/jobs/tenant-job-run-table.js +3 -2
  108. package/dist/ledger/entity.js +3 -1
  109. package/dist/managed-pages/handlers/set.write.d.ts +1 -1
  110. package/dist/personal-access-tokens/handlers/create.write.d.ts +1 -1
  111. package/dist/personal-access-tokens/handlers/revoke.write.d.ts +1 -1
  112. package/dist/renderer-foundation/types.d.ts +2 -0
  113. package/dist/renderer-pdf/render-table-pdf.d.ts +27 -33
  114. package/dist/renderer-pdf/render-table-pdf.js +95 -34
  115. package/dist/secrets/feature.js +4 -3
  116. package/dist/secrets/handlers/delete.write.d.ts +1 -1
  117. package/dist/secrets/handlers/set.write.d.ts +1 -1
  118. package/dist/secrets/secrets-context.js +15 -3
  119. package/dist/sessions/handlers/revoke-all-for-user.write.d.ts +1 -1
  120. package/dist/sessions/handlers/revoke-all-others.write.d.ts +1 -1
  121. package/dist/sessions/handlers/revoke.write.d.ts +1 -1
  122. package/dist/shared/chunked-entity-migration.d.ts +1 -0
  123. package/dist/shared/chunked-entity-migration.js +1 -1
  124. package/dist/shared/mail-template-body.d.ts +3 -0
  125. package/dist/shared/mail-template-body.js +10 -0
  126. package/dist/subscription-stripe/plugin-methods.js +18 -8
  127. package/dist/subscription-stripe/verify-webhook.d.ts +1 -1
  128. package/dist/template-resolver/api.js +3 -2
  129. package/dist/template-resolver/compile-document-body.d.ts +4 -0
  130. package/dist/template-resolver/compile-document-body.js +58 -16
  131. package/dist/template-resolver/constants.d.ts +2 -2
  132. package/dist/template-resolver/constants.js +3 -1
  133. package/dist/template-resolver/document-schema.d.ts +1 -1
  134. package/dist/template-resolver/document-schema.js +23 -3
  135. package/dist/template-resolver/feature.d.ts +2 -0
  136. package/dist/template-resolver/feature.js +4 -1
  137. package/dist/template-resolver/handlers/collection-set.write.d.ts +1 -1
  138. package/dist/template-resolver/handlers/copy-locale.write.d.ts +1 -1
  139. package/dist/template-resolver/handlers/copy-locale.write.js +29 -11
  140. package/dist/template-resolver/handlers/diff-to-system.query.js +4 -38
  141. package/dist/template-resolver/handlers/discard-draft.write.d.ts +13 -0
  142. package/dist/template-resolver/handlers/discard-draft.write.js +37 -0
  143. package/dist/template-resolver/handlers/export-bundle.query.js +51 -1
  144. package/dist/template-resolver/handlers/find-by-id.query.d.ts +1 -0
  145. package/dist/template-resolver/handlers/find-by-id.query.js +6 -3
  146. package/dist/template-resolver/handlers/import-bundle-system.write.d.ts +1 -1
  147. package/dist/template-resolver/handlers/import-bundle.write.d.ts +1 -1
  148. package/dist/template-resolver/handlers/layout-source.query.d.ts +5 -0
  149. package/dist/template-resolver/handlers/layout-source.query.js +46 -23
  150. package/dist/template-resolver/handlers/list.query.js +4 -1
  151. package/dist/template-resolver/handlers/override-diff.d.ts +20 -0
  152. package/dist/template-resolver/handlers/override-diff.js +41 -0
  153. package/dist/template-resolver/handlers/overview.query.d.ts +6 -0
  154. package/dist/template-resolver/handlers/overview.query.js +51 -11
  155. package/dist/template-resolver/handlers/reset-to-system.write.d.ts +1 -1
  156. package/dist/template-resolver/handlers/resource-link-pdf.write.d.ts +1 -1
  157. package/dist/template-resolver/handlers/resource-remove.write.d.ts +1 -1
  158. package/dist/template-resolver/handlers/resource-upload.write.d.ts +1 -1
  159. package/dist/template-resolver/handlers/resource-upload.write.js +1 -1
  160. package/dist/template-resolver/handlers/send-test.write.d.ts +3 -1
  161. package/dist/template-resolver/handlers/send-test.write.js +37 -10
  162. package/dist/template-resolver/handlers/set.write.d.ts +1 -1
  163. package/dist/template-resolver/handlers/template-gates.js +13 -2
  164. package/dist/template-resolver/handlers/template-write.js +36 -4
  165. package/dist/template-resolver/handlers/tenant-overrides.query.d.ts +32 -0
  166. package/dist/template-resolver/handlers/tenant-overrides.query.js +80 -16
  167. package/dist/template-resolver/handlers/toggle-status.write.d.ts +17 -4
  168. package/dist/template-resolver/handlers/toggle-status.write.js +35 -19
  169. package/dist/template-resolver/handlers/upsert-system.write.d.ts +1 -1
  170. package/dist/template-resolver/handlers/upsert-tenant.write.d.ts +1 -1
  171. package/dist/template-resolver/handlers/upsert-tenant.write.js +1 -1
  172. package/dist/template-resolver/handlers/usage.query.js +11 -2
  173. package/dist/template-resolver/index.d.ts +4 -3
  174. package/dist/template-resolver/index.js +4 -3
  175. package/dist/template-resolver/layout-appearance.d.ts +10 -0
  176. package/dist/template-resolver/layout-appearance.js +26 -0
  177. package/dist/template-resolver/pdf-constants.d.ts +7 -0
  178. package/dist/template-resolver/pdf-constants.js +8 -0
  179. package/dist/template-resolver/qualified-names.d.ts +4 -0
  180. package/dist/template-resolver/qualified-names.js +4 -0
  181. package/dist/template-resolver/seeding.js +21 -4
  182. package/dist/template-resolver/store-template-resource.js +2 -2
  183. package/dist/template-resolver/table.d.ts +103 -0
  184. package/dist/template-resolver/table.js +9 -2
  185. package/dist/template-resolver/template-draft.d.ts +51 -0
  186. package/dist/template-resolver/template-draft.js +72 -0
  187. package/dist/template-resolver/template-meta.d.ts +30 -5
  188. package/dist/template-resolver/template-meta.js +60 -16
  189. package/dist/template-resolver/template-preview.d.ts +19 -2
  190. package/dist/template-resolver/template-preview.js +98 -54
  191. package/dist/template-resolver/template-resources.d.ts +5 -0
  192. package/dist/template-resolver/template-resources.js +35 -4
  193. package/dist/template-resolver/template-usage.d.ts +5 -1
  194. package/dist/template-resolver/template-usage.js +14 -9
  195. package/dist/template-resolver/template-validation.js +1 -0
  196. package/dist/template-resolver/web/client-plugin.js +4 -2
  197. package/dist/template-resolver/web/document-editor-extensions.d.ts +4 -1
  198. package/dist/template-resolver/web/document-editor-extensions.js +50 -12
  199. package/dist/template-resolver/web/document-editor.d.ts +2 -1
  200. package/dist/template-resolver/web/document-editor.js +8 -2
  201. package/dist/template-resolver/web/document-tiptap-editor.d.ts +1 -1
  202. package/dist/template-resolver/web/document-tiptap-editor.js +144 -28
  203. package/dist/template-resolver/web/document-toolbar-blocks.d.ts +8 -0
  204. package/dist/template-resolver/web/document-toolbar-blocks.js +4 -0
  205. package/dist/template-resolver/web/i18n.js +19 -4
  206. package/dist/template-resolver/web/index.d.ts +1 -0
  207. package/dist/template-resolver/web/index.js +1 -0
  208. package/dist/template-resolver/web/placeholder-line-editor.d.ts +3 -1
  209. package/dist/template-resolver/web/placeholder-line-editor.js +6 -6
  210. package/dist/tenant/handlers/add-member.write.d.ts +1 -1
  211. package/dist/tenant/handlers/cancel-invitation.write.d.ts +1 -1
  212. package/dist/tenant/handlers/create.write.d.ts +1 -1
  213. package/dist/tenant/handlers/remove-member.write.d.ts +1 -1
  214. package/dist/tenant/handlers/toggle-enabled.write.d.ts +2 -2
  215. package/dist/tenant/handlers/update-member-roles.write.d.ts +1 -1
  216. package/dist/tenant/handlers/update.write.d.ts +1 -1
  217. package/dist/tenant-handover/handlers/claim.write.d.ts +1 -1
  218. package/dist/tenant-lifecycle/handlers/cancel-destruction.write.d.ts +1 -1
  219. package/dist/tenant-lifecycle/handlers/request-destruction.write.d.ts +1 -1
  220. package/dist/tier-engine/feature.js +9 -3
  221. package/dist/tier-engine/handlers/set-tenant-tier.write.d.ts +1 -1
  222. package/dist/user/handlers/create.write.d.ts +1 -1
  223. package/dist/user/handlers/update.write.d.ts +1 -1
  224. package/dist/user/resolve-display-names.js +5 -9
  225. package/dist/user/seeding.js +20 -1
  226. package/dist/user-data-rights/handlers/cancel-deletion.write.d.ts +1 -1
  227. package/dist/user-data-rights/handlers/confirm-deletion-by-token.write.d.ts +1 -1
  228. package/dist/user-data-rights/handlers/lift-restriction.write.d.ts +1 -1
  229. package/dist/user-data-rights/handlers/request-deletion-by-email.write.d.ts +1 -1
  230. package/dist/user-data-rights/handlers/request-deletion.write.d.ts +1 -1
  231. package/dist/user-data-rights/handlers/request-export.write.d.ts +1 -1
  232. package/dist/user-data-rights/handlers/restrict-account.write.d.ts +1 -1
  233. package/dist/user-data-rights/handlers/run-forget-cleanup.write.d.ts +1 -1
  234. package/dist/user-profile/handlers/change-email.write.d.ts +1 -1
  235. package/dist/waitlist/handlers/invite.write.d.ts +1 -1
  236. package/dist/waitlist/handlers/reject.write.d.ts +1 -1
  237. package/dist/waitlist/handlers/submit.write.d.ts +1 -1
  238. package/dist/waitlist/options.js +1 -1
  239. package/dist/workflow-runner/event-trigger.js +2 -2
  240. package/dist/workflow-runner/handlers/resume-run.write.js +7 -2
  241. package/package.json +19 -10
  242. package/src/audit/changes.json +6 -0
  243. package/src/auth-email-password/changes.json +37 -0
  244. package/src/billing-foundation/changes.json +36 -0
  245. package/src/cap-counter/changes.json +6 -0
  246. package/src/config/changes.json +6 -0
  247. package/src/crypto-shredding/changes.json +6 -0
  248. package/src/custom-fields/changes.json +6 -0
  249. package/src/delivery/changes.json +18 -0
  250. package/src/file-foundation/changes.json +6 -0
  251. package/src/files-tenant-data/changes.json +6 -0
  252. package/src/inbound-mail-foundation/changes.json +6 -0
  253. package/src/jobs/changes.json +19 -0
  254. package/src/ledger/changes.json +7 -0
  255. package/src/renderer-pdf/changes.json +12 -0
  256. package/src/secrets/changes.json +6 -0
  257. package/src/shared/changes.json +6 -0
  258. package/src/subscription-stripe/changes.json +6 -0
  259. package/src/template-resolver/changes.json +68 -0
  260. package/src/tier-engine/changes.json +6 -0
  261. package/src/user/changes.json +6 -0
  262. package/src/user-data-rights/changes.json +6 -0
  263. package/src/user-profile/changes.json +1 -1
  264. package/src/workflow-runner/changes.json +6 -0
@@ -1,54 +1,48 @@
1
1
  import * as z from "zod";
2
- export declare const pdfTableDocumentSchema: z.ZodObject<{
2
+ import type { PdfPageFormat } from "../template-resolver/index.js";
3
+ declare const pdfTableColumnSchema: z.ZodReadonly<z.ZodObject<{
4
+ header: z.ZodString;
5
+ weight: z.ZodOptional<z.ZodNumber>;
6
+ align: z.ZodOptional<z.ZodEnum<{
7
+ left: "left";
8
+ right: "right";
9
+ }>>;
10
+ }, z.core.$strip>>;
11
+ export declare const pdfTableDocumentSchema: z.ZodReadonly<z.ZodObject<{
3
12
  title: z.ZodString;
4
13
  subtitle: z.ZodOptional<z.ZodString>;
5
- meta: z.ZodOptional<z.ZodArray<z.ZodObject<{
14
+ meta: z.ZodOptional<z.ZodReadonly<z.ZodArray<z.ZodReadonly<z.ZodObject<{
6
15
  label: z.ZodString;
7
16
  value: z.ZodString;
8
- }, z.core.$strip>>>;
9
- columns: z.ZodArray<z.ZodObject<{
17
+ }, z.core.$strip>>>>>;
18
+ columns: z.ZodReadonly<z.ZodArray<z.ZodReadonly<z.ZodObject<{
10
19
  header: z.ZodString;
11
20
  weight: z.ZodOptional<z.ZodNumber>;
12
21
  align: z.ZodOptional<z.ZodEnum<{
13
22
  left: "left";
14
23
  right: "right";
15
24
  }>>;
16
- }, z.core.$strip>>;
17
- rows: z.ZodArray<z.ZodArray<z.ZodString>>;
25
+ }, z.core.$strip>>>>;
26
+ rows: z.ZodReadonly<z.ZodArray<z.ZodReadonly<z.ZodArray<z.ZodString>>>>;
18
27
  emptyText: z.ZodOptional<z.ZodString>;
19
28
  footerText: z.ZodOptional<z.ZodString>;
20
29
  pageLabel: z.ZodOptional<z.ZodString>;
21
- }, z.core.$strip>;
22
- export type PdfTableColumn = {
23
- readonly header: string;
24
- readonly weight?: number;
25
- readonly align?: "left" | "right";
26
- };
27
- export type PdfTableDocument = {
28
- readonly title: string;
29
- readonly subtitle?: string;
30
- readonly meta?: readonly {
31
- readonly label: string;
32
- readonly value: string;
33
- }[];
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
- };
30
+ }, z.core.$strip>>;
31
+ export type PdfTableColumn = z.infer<typeof pdfTableColumnSchema>;
32
+ export type PdfTableDocument = z.infer<typeof pdfTableDocumentSchema>;
33
+ declare const marginMmSchema: z.ZodReadonly<z.ZodObject<{
34
+ top: z.ZodOptional<z.ZodNumber>;
35
+ right: z.ZodOptional<z.ZodNumber>;
36
+ bottom: z.ZodOptional<z.ZodNumber>;
37
+ left: z.ZodOptional<z.ZodNumber>;
38
+ }, z.core.$strip>>;
41
39
  export type PdfTableOptions = {
42
- readonly format?: "A4" | "Letter";
43
- readonly marginMm?: {
44
- readonly top?: number;
45
- readonly right?: number;
46
- readonly bottom?: number;
47
- readonly left?: number;
48
- };
40
+ readonly format?: PdfPageFormat;
41
+ readonly marginMm?: z.input<typeof marginMmSchema>;
49
42
  };
50
43
  export type PdfTableResult = {
51
44
  readonly pdfBytes: Uint8Array;
52
45
  readonly pageCount: number;
53
46
  };
54
47
  export declare function renderTablePdf(doc: PdfTableDocument, options?: PdfTableOptions): Promise<PdfTableResult>;
48
+ export {};
@@ -1,22 +1,44 @@
1
1
  import { PDFDocument, rgb, StandardFonts } from "pdf-lib";
2
2
  import * as z from "zod";
3
3
  import { RendererError } from "../renderer-foundation/index.js";
4
- export const pdfTableDocumentSchema = z.object({
4
+ // Upper bounds keep a hostile or runaway payload from exhausting memory and CPU in layout.
5
+ const PDF_TABLE_MAX_ROWS = 10_000;
6
+ const PDF_TABLE_MAX_COLUMNS = 30;
7
+ const PDF_TABLE_MAX_CELL_LENGTH = 5_000;
8
+ const pdfTableColumnSchema = z
9
+ .object({
10
+ header: z.string(),
11
+ weight: z.number().positive().optional(),
12
+ align: z.enum(["left", "right"]).optional(),
13
+ })
14
+ .readonly();
15
+ export const pdfTableDocumentSchema = z
16
+ .object({
5
17
  title: z.string(),
6
18
  subtitle: z.string().optional(),
7
- meta: z.array(z.object({ label: z.string(), value: z.string() })).optional(),
8
- columns: z
9
- .array(z.object({
10
- header: z.string(),
11
- weight: z.number().positive().optional(),
12
- align: z.enum(["left", "right"]).optional(),
13
- }))
14
- .min(1),
15
- rows: z.array(z.array(z.string())),
19
+ meta: z
20
+ .array(z.object({ label: z.string(), value: z.string() }).readonly())
21
+ .readonly()
22
+ .optional(),
23
+ columns: z.array(pdfTableColumnSchema).min(1).max(PDF_TABLE_MAX_COLUMNS).readonly(),
24
+ rows: z
25
+ .array(z.array(z.string().max(PDF_TABLE_MAX_CELL_LENGTH)).readonly())
26
+ .max(PDF_TABLE_MAX_ROWS)
27
+ .readonly(),
16
28
  emptyText: z.string().optional(),
17
29
  footerText: z.string().optional(),
30
+ // Template with `{page}` and `{pages}`; default `"{page} / {pages}"`.
18
31
  pageLabel: z.string().optional(),
19
- });
32
+ })
33
+ .readonly();
34
+ const marginMmSchema = z
35
+ .object({
36
+ top: z.number().nonnegative().optional(),
37
+ right: z.number().nonnegative().optional(),
38
+ bottom: z.number().nonnegative().optional(),
39
+ left: z.number().nonnegative().optional(),
40
+ })
41
+ .readonly();
20
42
  const POINTS_PER_MM = 72 / 25.4;
21
43
  const PAGE_SIZE_POINTS = {
22
44
  A4: { width: 595.28, height: 841.89 },
@@ -31,6 +53,7 @@ const LINE_HEIGHT_FACTOR = 1.3;
31
53
  const CELL_PADDING_X = 4;
32
54
  const CELL_PADDING_Y = 3;
33
55
  const FOOTER_RESERVE = 16;
56
+ const MIN_COLUMN_WIDTH = 20;
34
57
  const META_LABEL_GAP = 10;
35
58
  const BLOCK_GAP = 10;
36
59
  const REPLACEMENT_CHAR = "?";
@@ -46,7 +69,7 @@ function createEncodableText(font) {
46
69
  const supported = new Set(font.getCharacterSet());
47
70
  return (text) => {
48
71
  let result = "";
49
- for (const char of text.replace(/\t/g, " ")) {
72
+ for (const char of text.replace(/\r\n?/g, "\n").replace(/\t/g, " ")) {
50
73
  const codePoint = char.codePointAt(0) ?? 0;
51
74
  result += codePoint === 0x0a || supported.has(codePoint) ? char : REPLACEMENT_CHAR;
52
75
  }
@@ -111,25 +134,22 @@ function assertRowShape(doc) {
111
134
  }
112
135
  });
113
136
  }
114
- export async function renderTablePdf(doc, options) {
115
- if (doc.columns.length === 0) {
116
- throw new RendererError("table needs at least one column", "invalid_payload");
137
+ function resolveTableLayout(doc, options, fonts, clean) {
138
+ const marginMm = marginMmSchema.safeParse(options?.marginMm ?? {});
139
+ if (!marginMm.success) {
140
+ throw new RendererError("margins must be non-negative numbers", "invalid_payload");
117
141
  }
118
- assertRowShape(doc);
119
- const pdf = await PDFDocument.create({ updateMetadata: false });
120
- const fonts = {
121
- regular: await pdf.embedFont(StandardFonts.Helvetica),
122
- bold: await pdf.embedFont(StandardFonts.HelveticaBold),
123
- };
124
- const clean = createEncodableText(fonts.regular);
125
142
  const pageSize = PAGE_SIZE_POINTS[options?.format ?? "A4"];
126
143
  const margin = {
127
- top: (options?.marginMm?.top ?? DEFAULT_MARGIN_MM.top) * POINTS_PER_MM,
128
- right: (options?.marginMm?.right ?? DEFAULT_MARGIN_MM.right) * POINTS_PER_MM,
129
- bottom: (options?.marginMm?.bottom ?? DEFAULT_MARGIN_MM.bottom) * POINTS_PER_MM,
130
- left: (options?.marginMm?.left ?? DEFAULT_MARGIN_MM.left) * POINTS_PER_MM,
144
+ top: (marginMm.data.top ?? DEFAULT_MARGIN_MM.top) * POINTS_PER_MM,
145
+ right: (marginMm.data.right ?? DEFAULT_MARGIN_MM.right) * POINTS_PER_MM,
146
+ bottom: (marginMm.data.bottom ?? DEFAULT_MARGIN_MM.bottom) * POINTS_PER_MM,
147
+ left: (marginMm.data.left ?? DEFAULT_MARGIN_MM.left) * POINTS_PER_MM,
131
148
  };
132
149
  const contentWidth = pageSize.width - margin.left - margin.right;
150
+ if (contentWidth < MIN_COLUMN_WIDTH * doc.columns.length) {
151
+ throw new RendererError("margins leave no room for table columns", "invalid_payload");
152
+ }
133
153
  const contentBottom = margin.bottom + FOOTER_RESERVE;
134
154
  const columnWidths = resolveColumnWidths(doc.columns, contentWidth);
135
155
  const bodyLineHeight = lineHeightFor(BODY_SIZE);
@@ -139,6 +159,33 @@ export async function renderTablePdf(doc, options) {
139
159
  }
140
160
  const headerLines = doc.columns.map((column, index) => wrapText(clean(column.header), fonts.bold, BODY_SIZE, (columnWidths[index] ?? contentWidth) - 2 * CELL_PADDING_X));
141
161
  const headerHeight = Math.max(...headerLines.map((lines) => lines.length)) * bodyLineHeight + 2 * CELL_PADDING_Y;
162
+ // A header this tall would leave every continuation page without room for a row.
163
+ if (rowSpaceOnEmptyPage - headerHeight < bodyLineHeight + 2 * CELL_PADDING_Y) {
164
+ throw new RendererError("table header leaves no room for rows", "invalid_payload");
165
+ }
166
+ return {
167
+ pageSize,
168
+ margin,
169
+ contentWidth,
170
+ contentBottom,
171
+ columnWidths,
172
+ bodyLineHeight,
173
+ headerLines,
174
+ headerHeight,
175
+ };
176
+ }
177
+ export async function renderTablePdf(doc, options) {
178
+ if (doc.columns.length === 0) {
179
+ throw new RendererError("table needs at least one column", "invalid_payload");
180
+ }
181
+ assertRowShape(doc);
182
+ const pdf = await PDFDocument.create({ updateMetadata: false });
183
+ const fonts = {
184
+ regular: await pdf.embedFont(StandardFonts.Helvetica),
185
+ bold: await pdf.embedFont(StandardFonts.HelveticaBold),
186
+ };
187
+ const clean = createEncodableText(fonts.regular);
188
+ const { pageSize, margin, contentWidth, contentBottom, columnWidths, bodyLineHeight, headerLines, headerHeight, } = resolveTableLayout(doc, options, fonts, clean);
142
189
  let page = pdf.addPage([pageSize.width, pageSize.height]);
143
190
  let cursorY = pageSize.height - margin.top;
144
191
  function drawLines(lines, font, size, left, width, topY, align, color = TEXT_COLOR) {
@@ -154,14 +201,27 @@ export async function renderTablePdf(doc, options) {
154
201
  });
155
202
  });
156
203
  }
204
+ function startBlankPage() {
205
+ page = pdf.addPage([pageSize.width, pageSize.height]);
206
+ cursorY = pageSize.height - margin.top;
207
+ }
208
+ function ensureRoom(height) {
209
+ if (cursorY - height < contentBottom)
210
+ startBlankPage();
211
+ }
212
+ // Line by line, so a block taller than one page continues on the next.
213
+ function flowLines(lines, font, size, left, width, color = TEXT_COLOR) {
214
+ const lineHeight = lineHeightFor(size);
215
+ for (const line of lines) {
216
+ ensureRoom(lineHeight);
217
+ drawLines([line], font, size, left, width, cursorY, "left", color);
218
+ cursorY -= lineHeight;
219
+ }
220
+ }
157
221
  function drawIntroBlock() {
158
- const titleLines = wrapText(clean(doc.title), fonts.bold, TITLE_SIZE, contentWidth);
159
- drawLines(titleLines, fonts.bold, TITLE_SIZE, margin.left, contentWidth, cursorY, "left");
160
- cursorY -= titleLines.length * lineHeightFor(TITLE_SIZE);
222
+ flowLines(wrapText(clean(doc.title), fonts.bold, TITLE_SIZE, contentWidth), fonts.bold, TITLE_SIZE, margin.left, contentWidth);
161
223
  if (doc.subtitle !== undefined) {
162
- const subtitleLines = wrapText(clean(doc.subtitle), fonts.regular, SUBTITLE_SIZE, contentWidth);
163
- drawLines(subtitleLines, fonts.regular, SUBTITLE_SIZE, margin.left, contentWidth, cursorY, "left", MUTED_COLOR);
164
- cursorY -= subtitleLines.length * lineHeightFor(SUBTITLE_SIZE);
224
+ flowLines(wrapText(clean(doc.subtitle), fonts.regular, SUBTITLE_SIZE, contentWidth), fonts.regular, SUBTITLE_SIZE, margin.left, contentWidth, MUTED_COLOR);
165
225
  }
166
226
  const meta = doc.meta ?? [];
167
227
  if (meta.length > 0) {
@@ -171,9 +231,9 @@ export async function renderTablePdf(doc, options) {
171
231
  META_LABEL_GAP;
172
232
  meta.forEach((entry, index) => {
173
233
  const valueLines = wrapText(clean(entry.value), fonts.regular, BODY_SIZE, contentWidth - labelWidth);
234
+ ensureRoom(bodyLineHeight);
174
235
  drawLines([labels[index] ?? ""], fonts.bold, BODY_SIZE, margin.left, labelWidth, cursorY, "left");
175
- drawLines(valueLines, fonts.regular, BODY_SIZE, margin.left + labelWidth, contentWidth - labelWidth, cursorY, "left");
176
- cursorY -= valueLines.length * bodyLineHeight;
236
+ flowLines(valueLines, fonts.regular, BODY_SIZE, margin.left + labelWidth, contentWidth - labelWidth);
177
237
  });
178
238
  }
179
239
  cursorY -= BLOCK_GAP;
@@ -235,6 +295,7 @@ export async function renderTablePdf(doc, options) {
235
295
  }
236
296
  }
237
297
  drawIntroBlock();
298
+ ensureRoom(headerHeight + bodyLineHeight + 2 * CELL_PADDING_Y);
238
299
  drawTableHeader();
239
300
  if (doc.rows.length === 0 && doc.emptyText !== undefined) {
240
301
  drawRow([
@@ -60,20 +60,21 @@ export function requireSecretsContext(ctx, handlerName) {
60
60
  "createSecretsContext({db, masterKeyProvider, registry}) via extraContext.secrets at boot.",
61
61
  });
62
62
  }
63
- const raw = ctx.secrets;
64
63
  const userId = ctx._userId;
65
64
  if (!userId) {
66
65
  throw new InternalError({
67
66
  message: `[${handlerName}] ctx._userId missing — cannot audit secret reads without a caller identity.`,
68
67
  });
69
68
  }
70
- return {
69
+ const withAudit = (raw) => ({
70
+ withRunner: (runner) => withAudit(raw.withRunner(runner)),
71
71
  get: (tenantId, key, overrideAudit) => raw.get(tenantId, key, overrideAudit ?? { userId, handlerName }),
72
72
  // No audit injection: has() is metadata-only and never logs a read.
73
73
  has: raw.has.bind(raw),
74
74
  set: raw.set.bind(raw),
75
75
  delete: raw.delete.bind(raw),
76
- };
76
+ });
77
+ return withAudit(ctx.secrets);
77
78
  }
78
79
  function resolveSecretsAccess(opts) {
79
80
  if (opts.access !== undefined && opts.roles !== undefined) {
@@ -4,4 +4,4 @@ export declare function createDeleteHandler(access?: AccessRule): import("@cosmi
4
4
  key: z.ZodString;
5
5
  }, z.core.$strip>, {
6
6
  key: string;
7
- }, import("@cosmicdrift/kumiko-types/event-type-map").KumikoEventTypeMap>;
7
+ }, import("@cosmicdrift/kumiko-types/event-type-map").KumikoEventTypeMap, undefined>;
@@ -8,4 +8,4 @@ export declare function createSetHandler(access?: AccessRule): import("@cosmicdr
8
8
  }, z.core.$strip>, {
9
9
  key: string;
10
10
  redactedPreview: string;
11
- }, import("@cosmicdrift/kumiko-types/event-type-map").KumikoEventTypeMap>;
11
+ }, import("@cosmicdrift/kumiko-types/event-type-map").KumikoEventTypeMap, undefined>;
@@ -44,12 +44,21 @@ function resolveKey(keyOrHandle) {
44
44
  return typeof keyOrHandle === "string" ? keyOrHandle : keyOrHandle.name;
45
45
  }
46
46
  export function createSecretsContext(opts) {
47
- const { db, masterKeyProvider } = opts;
47
+ const { masterKeyProvider } = opts;
48
48
  const provider = withDekCache(masterKeyProvider, opts.dekCache ?? createDekCache());
49
+ return buildSecretsContext(opts, provider, opts.db, false);
50
+ }
51
+ // `runnerIsTransaction`: the runner is a caller-owned transaction, so audited reads join it
52
+ // instead of opening a second connection (which would deadlock a small pool).
53
+ function buildSecretsContext(opts, provider, db, runnerIsTransaction) {
54
+ const { masterKeyProvider } = opts;
49
55
  async function lookup(tenantId, key) {
50
56
  return fetchOne(db, tenantSecretsTable, { tenantId, key });
51
57
  }
52
58
  return {
59
+ withRunner(runner) {
60
+ return buildSecretsContext(opts, provider, runner, true);
61
+ },
53
62
  async get(tenantId, keyOrHandle, auditCtx) {
54
63
  const key = resolveKey(keyOrHandle);
55
64
  // Atomic audit + read: a decrypt that "escaped" the audit trail
@@ -65,7 +74,7 @@ export function createSecretsContext(opts) {
65
74
  const plaintext = await decryptValue(decodeStoredEnvelope(existing.envelope), provider);
66
75
  return createSecret(plaintext);
67
76
  }
68
- const plaintext = await transaction(db, async (tx) => {
77
+ const readAndAudit = async (tx) => {
69
78
  // Inline select inside the TX via raw client — fetchOne's connection
70
79
  // type doesn't widen to the transaction object cleanly.
71
80
  const envelope = await selectTenantSecretEnvelope(tx, tenantId, key);
@@ -99,7 +108,10 @@ export function createSecretsContext(opts) {
99
108
  metadata,
100
109
  });
101
110
  return pt;
102
- });
111
+ };
112
+ const plaintext = runnerIsTransaction
113
+ ? await readAndAudit(db)
114
+ : await transaction(db, readAndAudit);
103
115
  if (plaintext === undefined)
104
116
  return undefined;
105
117
  // Brand the plaintext only after audit committed. The response
@@ -6,4 +6,4 @@ export declare const revokeAllForUserWrite: import("@cosmicdrift/kumiko-types/de
6
6
  }, z.core.$strip>, {
7
7
  count: number;
8
8
  userId: string;
9
- }, import("@cosmicdrift/kumiko-types/event-type-map").KumikoEventTypeMap>;
9
+ }, import("@cosmicdrift/kumiko-types/event-type-map").KumikoEventTypeMap, undefined>;
@@ -1,4 +1,4 @@
1
1
  import * as z from "zod";
2
2
  export declare const revokeAllOthersWrite: import("@cosmicdrift/kumiko-types/define-handler").WriteHandlerDefinition<"user-session:revoke-all-others", z.ZodObject<{}, z.core.$strip>, {
3
3
  count: number;
4
- }, import("@cosmicdrift/kumiko-types/event-type-map").KumikoEventTypeMap>;
4
+ }, import("@cosmicdrift/kumiko-types/event-type-map").KumikoEventTypeMap, undefined>;
@@ -3,4 +3,4 @@ export declare const revokeWrite: import("@cosmicdrift/kumiko-types/define-handl
3
3
  id: z.ZodUUID;
4
4
  }, z.core.$strip>, {
5
5
  id: string;
6
- }, import("@cosmicdrift/kumiko-types/event-type-map").KumikoEventTypeMap>;
6
+ }, import("@cosmicdrift/kumiko-types/event-type-map").KumikoEventTypeMap, undefined>;
@@ -12,6 +12,7 @@ export type ChunkedMigrationOptions<Row> = {
12
12
  readonly migrateRow: (row: Row) => Promise<MigrationRowOutcome>;
13
13
  readonly maxFailures: number;
14
14
  readonly deadlineAt: number;
15
+ readonly minBatches?: number;
15
16
  readonly signal?: AbortSignal | undefined;
16
17
  readonly onRowError?: (row: Row, err: unknown) => void;
17
18
  };
@@ -25,7 +25,7 @@ export async function runChunkedMigration(opts) {
25
25
  stoppedReason = "signal";
26
26
  break;
27
27
  }
28
- if (Date.now() >= opts.deadlineAt) {
28
+ if (batchesProcessed >= (opts.minBatches ?? 0) && Date.now() >= opts.deadlineAt) {
29
29
  stoppedReason = "timeout";
30
30
  break;
31
31
  }
@@ -1,4 +1,7 @@
1
1
  export declare function mailTextWithSlotsHtml(text: string): string;
2
2
  export declare function mailParagraph(text: string): string;
3
3
  export declare function mailHeading(text: string): string;
4
+ export declare function mailButton(label: string, linkVariable: string): string;
5
+ export declare function mailParagraphWithLink(text: string, linkLabel: string, linkVariable: string): string;
4
6
  export declare function mailRepeatedRows(listName: string, field: string): string;
7
+ export declare function mailSubject(text: string): string;
@@ -11,7 +11,17 @@ export function mailParagraph(text) {
11
11
  export function mailHeading(text) {
12
12
  return `<h2>${mailTextWithSlotsHtml(text)}</h2>`;
13
13
  }
14
+ export function mailButton(label, linkVariable) {
15
+ return `<p><a data-button href="{{${escapeHtmlAttr(linkVariable)}}}">${escapeHtml(label)}</a></p>`;
16
+ }
17
+ export function mailParagraphWithLink(text, linkLabel, linkVariable) {
18
+ return `<p>${mailTextWithSlotsHtml(text)} <a href="{{${escapeHtmlAttr(linkVariable)}}}">${escapeHtml(linkLabel)}</a></p>`;
19
+ }
14
20
  // One table row per list entry; the cell shows the entry's `field`.
15
21
  export function mailRepeatedRows(listName, field) {
16
22
  return `<table><tbody><tr data-each="${escapeHtmlAttr(listName)}"><td><p><span data-variable="${escapeHtmlAttr(field)}"></span></p></td></tr></tbody></table>`;
17
23
  }
24
+ // Subjects are Handlebars text, so the same `{name}` slots become `{{name}}`.
25
+ export function mailSubject(text) {
26
+ return text.replace(SLOT_PATTERN, "{{$1}}");
27
+ }
@@ -75,9 +75,7 @@ export function mapAppLocaleToStripeLocale(locale) {
75
75
  return knownLocales.find((known) => known === language) ?? "auto";
76
76
  }
77
77
  function isUnknownCustomerStripeError(error) {
78
- return (isResourceMissingStripeError(error) &&
79
- error instanceof Stripe.errors.StripeInvalidRequestError &&
80
- error.param === "customer");
78
+ return isResourceMissingStripeError(error) && error.param === "customer";
81
79
  }
82
80
  export function createStripeCheckoutSession(runtime, { paymentInvoiceCreation = true } = {}) {
83
81
  return async (ctx, options) => {
@@ -147,11 +145,23 @@ export function createStripeCheckoutSession(runtime, { paymentInvoiceCreation =
147
145
  export function createStripePortalSession(runtime) {
148
146
  return async (ctx, options) => {
149
147
  const stripe = await runtime.clientForCtx(ctx);
150
- const session = await stripe.billingPortal.sessions.create({
151
- customer: options.providerCustomerId,
152
- return_url: options.returnUrl,
153
- });
154
- return { url: session.url };
148
+ try {
149
+ const session = await stripe.billingPortal.sessions.create({
150
+ customer: options.providerCustomerId,
151
+ return_url: options.returnUrl,
152
+ });
153
+ return { url: session.url };
154
+ }
155
+ catch (error) {
156
+ // A portal needs an existing customer, so unlike checkout there is no retry without one.
157
+ if (!isUnknownCustomerStripeError(error))
158
+ throw error;
159
+ throw new UnprocessableError("provider_customer_unknown", {
160
+ i18nKey: "billing-foundation.errors.providerCustomerUnknown",
161
+ message: `subscription-stripe: Stripe does not know customer "${options.providerCustomerId}" (account switch or customer deleted).`,
162
+ cause: error,
163
+ });
164
+ }
155
165
  };
156
166
  }
157
167
  export function createStripeCancelSubscription(runtime) {
@@ -3,7 +3,7 @@ import { type SubscriptionEventType, type SubscriptionStatus } from "@cosmicdrif
3
3
  import type { SecretsContext } from "@cosmicdrift/kumiko-framework/secrets";
4
4
  import Stripe from "stripe";
5
5
  import type { StripeWebhookRuntime } from "./runtime.js";
6
- export declare function isResourceMissingStripeError(error: unknown): boolean;
6
+ export declare function isResourceMissingStripeError(error: unknown): error is Stripe.errors.StripeInvalidRequestError;
7
7
  export type StripeWebhookOptions = {
8
8
  /** Price-to-tier-Map. Plugin liest die price-id aus dem event und
9
9
  * mapped auf tier-name. Fehlt die price-id im Mapping → null. App-
@@ -6,6 +6,7 @@ import { InternalError } from "@cosmicdrift/kumiko-framework/errors";
6
6
  import { FALLBACK_LOCALE, SYSTEM_TENANT_ID } from "./constants.js";
7
7
  import { fileAttachmentsOf, imageLinksOf, parseLinkedResourceEntries, pdfAttachmentLinksOf, } from "./linked-resources.js";
8
8
  import { templateResourcesTable } from "./table.js";
9
+ import { activeVersionOf, servedVersionOf } from "./template-draft.js";
9
10
  import { parseTemplateMeta } from "./template-meta.js";
10
11
  export function createTemplateResolverApi(db) {
11
12
  return {
@@ -51,7 +52,7 @@ export function createTemplateResolverApi(db) {
51
52
  function pickActiveLayoutRow(rows, locale) {
52
53
  const active = rows
53
54
  .filter((row) => row.status === "active")
54
- .sort((a, b) => b.version - a.version || a.locale.localeCompare(b.locale));
55
+ .sort((a, b) => activeVersionOf(b) - activeVersionOf(a) || a.locale.localeCompare(b.locale));
55
56
  return (active.find((row) => row.locale === locale) ??
56
57
  active.find((row) => row.locale === FALLBACK_LOCALE) ??
57
58
  active[0] ??
@@ -77,7 +78,7 @@ function toPublic(row) {
77
78
  const linkedEntries = parseLinkedResourceEntries(row.linkedResources);
78
79
  return {
79
80
  id: String(row.id),
80
- version: row.version,
81
+ version: servedVersionOf(row),
81
82
  tenantId: row.tenantId,
82
83
  slug: row.slug,
83
84
  kind,
@@ -1,7 +1,11 @@
1
1
  import type { TemplateVariables } from "@cosmicdrift/kumiko-framework/engine";
2
2
  export type CompileDocumentBodyOptions = {
3
3
  readonly resourceSrc?: (key: string) => string | undefined;
4
+ readonly resourceImageMaxWidthPx?: number;
4
5
  readonly alignmentAsInlineStyle?: boolean;
6
+ readonly buttonAsInlineStyle?: boolean;
7
+ readonly tableAsInlineStyle?: boolean;
8
+ readonly buttonInlineStyle?: string;
5
9
  };
6
10
  export declare function collectDocumentResourceKeys(html: string): readonly string[];
7
11
  export declare function toResourceDataUri(contentType: string, bytes: Uint8Array): string;
@@ -37,6 +37,8 @@ function valueExpression(path, def) {
37
37
  return `{{money ${path}}}`;
38
38
  if (def?.type === "date")
39
39
  return `{{date ${path}}}`;
40
+ if (def?.type === "richText")
41
+ return `{{richText ${path}}}`;
40
42
  return `{{${path}}}`;
41
43
  }
42
44
  function rewriteVariable(element, state) {
@@ -88,6 +90,16 @@ function stripActiveContent(element) {
88
90
  function neutralizeBraces(html) {
89
91
  return html.replace(/[{}]/g, (brace) => (brace === "{" ? "&#123;" : "&#125;"));
90
92
  }
93
+ const MAIL_BUTTON_STYLE = "display:inline-block;padding:10px 20px;border-radius:6px;background-color:#1f5468;color:#ffffff;font-weight:600;text-decoration:none";
94
+ const MAIL_TABLE_STYLE = "width:100%;border-collapse:collapse";
95
+ const MAIL_TABLE_CELL_STYLE = "padding:6px 12px;vertical-align:top;border-bottom:1px solid #e5e5e5";
96
+ const MAIL_TABLE_HEAD_STYLE = "font-size:12px;font-weight:600;color:#6b7280;text-align:left";
97
+ const MAIL_SMALL_STYLE = "font-size:12px;color:#6b7280";
98
+ // Appended after an existing style (data-align) so the alignment survives.
99
+ function appendStyle(element, style) {
100
+ const existing = element.getAttribute("style");
101
+ element.setAttribute("style", existing === null ? style : `${existing};${style}`);
102
+ }
91
103
  const RESOURCE_IMAGE_CONTENT_TYPES = new Set([
92
104
  "image/png",
93
105
  "image/jpeg",
@@ -118,7 +130,7 @@ function rewriteAlignment(element) {
118
130
  if (isOneOf(DOCUMENT_TEXT_ALIGNMENTS, align))
119
131
  element.setAttribute("style", `text-align:${align}`);
120
132
  }
121
- function rewriteResourceImage(element, resourceSrc) {
133
+ function rewriteResourceImage(element, resourceSrc, maxWidthPx) {
122
134
  const key = element.getAttribute("data-resource") ?? "";
123
135
  const src = isDocumentResourceKey(key) ? resourceSrc?.(key) : undefined;
124
136
  if (src === undefined) {
@@ -128,6 +140,45 @@ function rewriteResourceImage(element, resourceSrc) {
128
140
  }
129
141
  element.setAttribute("src", src);
130
142
  element.removeAttribute("data-resource");
143
+ if (maxWidthPx !== undefined)
144
+ element.setAttribute("style", `max-width:${maxWidthPx}px;height:auto`);
145
+ }
146
+ function stripScriptsAndActiveContent(root, options) {
147
+ for (const element of Array.from(root.querySelectorAll("script")))
148
+ element.remove();
149
+ for (const element of Array.from(root.querySelectorAll("img[data-resource]"))) {
150
+ rewriteResourceImage(element, options.resourceSrc, options.resourceImageMaxWidthPx);
151
+ }
152
+ for (const element of Array.from(root.querySelectorAll("*")))
153
+ stripActiveContent(element);
154
+ }
155
+ function applyAlignmentInlineStyles(root) {
156
+ for (const element of Array.from(root.querySelectorAll("[data-align]"))) {
157
+ rewriteAlignment(element);
158
+ }
159
+ for (const element of Array.from(root.querySelectorAll("[data-size]"))) {
160
+ element.removeAttribute("data-size");
161
+ appendStyle(element, MAIL_SMALL_STYLE);
162
+ }
163
+ }
164
+ function applyTableInlineStyles(root) {
165
+ for (const table of Array.from(root.querySelectorAll("table"))) {
166
+ appendStyle(table, MAIL_TABLE_STYLE);
167
+ }
168
+ for (const cell of Array.from(root.querySelectorAll("td, th"))) {
169
+ appendStyle(cell, MAIL_TABLE_CELL_STYLE);
170
+ }
171
+ for (const head of Array.from(root.querySelectorAll("th"))) {
172
+ appendStyle(head, MAIL_TABLE_HEAD_STYLE);
173
+ }
174
+ }
175
+ function rewriteLinks(root, state, options) {
176
+ for (const element of Array.from(root.querySelectorAll("a[href]"))) {
177
+ rewriteLink(element, state);
178
+ if (options.buttonAsInlineStyle === true && element.hasAttribute("data-button")) {
179
+ element.setAttribute("style", options.buttonInlineStyle ?? MAIL_BUTTON_STYLE);
180
+ }
181
+ }
131
182
  }
132
183
  // Turns a sanitized document body into Handlebars source. Rows and links are
133
184
  // rewritten in document order; list rows last so their placeholders still see
@@ -136,24 +187,15 @@ export function compileDocumentBodyToHandlebars(html, variables, options = {}) {
136
187
  const state = { variables, expressions: [] };
137
188
  const scrubbed = html.replaceAll(EXPRESSION_OPEN, "").replaceAll(EXPRESSION_CLOSE, "");
138
189
  const compiled = withParsedDocument(scrubbed, (root) => {
139
- for (const element of Array.from(root.querySelectorAll("script")))
140
- element.remove();
141
- for (const element of Array.from(root.querySelectorAll("img[data-resource]"))) {
142
- rewriteResourceImage(element, options.resourceSrc);
143
- }
144
- for (const element of Array.from(root.querySelectorAll("*")))
145
- stripActiveContent(element);
146
- if (options.alignmentAsInlineStyle === true) {
147
- for (const element of Array.from(root.querySelectorAll("[data-align]"))) {
148
- rewriteAlignment(element);
149
- }
150
- }
190
+ stripScriptsAndActiveContent(root, options);
191
+ if (options.alignmentAsInlineStyle === true)
192
+ applyAlignmentInlineStyles(root);
193
+ if (options.tableAsInlineStyle === true)
194
+ applyTableInlineStyles(root);
151
195
  for (const element of Array.from(root.querySelectorAll("span[data-variable]"))) {
152
196
  rewriteVariable(element, state);
153
197
  }
154
- for (const element of Array.from(root.querySelectorAll("a[href]"))) {
155
- rewriteLink(element, state);
156
- }
198
+ rewriteLinks(root, state, options);
157
199
  for (const element of Array.from(root.querySelectorAll("tr[data-each]"))) {
158
200
  rewriteRepeatRow(element, state);
159
201
  }
@@ -16,8 +16,8 @@ export declare const TEMPLATE_SCOPES: readonly ["system", "tenant"];
16
16
  export type TemplateScope = (typeof TEMPLATE_SCOPES)[number];
17
17
  export declare const TEMPLATE_STATUSES: readonly ["draft", "active", "archived"];
18
18
  export type TemplateStatus = (typeof TEMPLATE_STATUSES)[number];
19
- export declare const PDF_PAGE_FORMATS: readonly ["A4", "Letter"];
20
- export type PdfPageFormat = (typeof PDF_PAGE_FORMATS)[number];
19
+ export { PDF_DEFAULT_MARGIN_MM, PDF_FOOTER_MARGIN_MM, PDF_HEADER_MARGIN_MM, PDF_ORIENTATIONS, PDF_PAGE_FORMATS, type PdfOrientation, type PdfPageFormat, } from "./pdf-constants.js";
20
+ export declare const HEX_COLOR_PATTERN: RegExp;
21
21
  export declare const DEFAULT_MAIL_LAYOUT_SLUG = "default";
22
22
  export declare const MAIL_PREVIEW_MAX_BYTES: number;
23
23
  export declare const TEST_MAIL_NOTIFICATION_TYPE = "template-resolver:test-mail";