@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.
- package/package.json +1 -1
- package/packages/bundled-features/package.json +23 -6
- package/packages/bundled-features/src/admin-shell/changes.json +6 -0
- package/packages/bundled-features/src/admin-shell/feature.ts +8 -1
- package/packages/bundled-features/src/agent-tools/agent-manifest.ts +3 -4
- package/packages/bundled-features/src/agent-tools/filter-ops.ts +1 -3
- package/packages/bundled-features/src/agent-tools/tool-catalog.ts +1 -3
- package/packages/bundled-features/src/agent-tools/tool-dispatch.ts +2 -1
- package/packages/bundled-features/src/audit/changes.json +6 -0
- package/packages/bundled-features/src/audit/handlers/list.query.ts +10 -5
- package/packages/bundled-features/src/auth-email-password/bootstrap.ts +29 -8
- package/packages/bundled-features/src/auth-email-password/changes.json +51 -0
- package/packages/bundled-features/src/auth-email-password/email-rate-limits.ts +16 -0
- package/packages/bundled-features/src/auth-email-password/email-templates.ts +12 -138
- package/packages/bundled-features/src/auth-email-password/feature.ts +30 -9
- package/packages/bundled-features/src/auth-email-password/handlers/change-password.write.ts +6 -2
- package/packages/bundled-features/src/auth-email-password/handlers/invite-create.write.ts +4 -5
- package/packages/bundled-features/src/auth-email-password/handlers/logout.write.ts +7 -2
- package/packages/bundled-features/src/auth-email-password/handlers/request-account-unlock.write.ts +2 -5
- package/packages/bundled-features/src/auth-email-password/handlers/request-email-verification.write.ts +2 -5
- package/packages/bundled-features/src/auth-email-password/handlers/request-password-reset.write.ts +2 -5
- package/packages/bundled-features/src/auth-email-password/handlers/signup-confirm.write.ts +3 -4
- package/packages/bundled-features/src/auth-email-password/handlers/signup-request.write.ts +2 -1
- package/packages/bundled-features/src/auth-email-password/handlers/token-request-handler.ts +5 -13
- package/packages/bundled-features/src/auth-email-password/index.ts +2 -17
- package/packages/bundled-features/src/auth-email-password/magic-link-mail.ts +30 -22
- package/packages/bundled-features/src/auth-email-password/mail-notifications.ts +158 -0
- package/packages/bundled-features/src/auth-email-password/signup-activation.ts +2 -3
- package/packages/bundled-features/src/auth-email-password/web/auth-gate.tsx +24 -5
- package/packages/bundled-features/src/auth-foundation/types.ts +8 -18
- package/packages/bundled-features/src/auth-mfa/feature.ts +8 -10
- package/packages/bundled-features/src/auth-mfa/hmac-token-codec.ts +4 -4
- package/packages/bundled-features/src/auth-mfa/mfa-challenge-token.ts +3 -3
- package/packages/bundled-features/src/auth-mfa/mfa-preauth-setup-token.ts +3 -3
- package/packages/bundled-features/src/auth-mfa/mfa-setup-token.ts +8 -8
- package/packages/bundled-features/src/billing-foundation/changes.json +43 -0
- package/packages/bundled-features/src/billing-foundation/constants.ts +5 -4
- package/packages/bundled-features/src/billing-foundation/consumer-protection/confirmation-mail.ts +90 -44
- package/packages/bundled-features/src/billing-foundation/consumer-protection/consent-gate.ts +22 -1
- package/packages/bundled-features/src/billing-foundation/consumer-protection/issue-confirmation.ts +3 -3
- package/packages/bundled-features/src/billing-foundation/consumer-protection/mail-notifications.ts +118 -0
- package/packages/bundled-features/src/billing-foundation/consumer-protection/register-consent.ts +2 -0
- package/packages/bundled-features/src/billing-foundation/consumer-protection/register-termination.ts +11 -1
- package/packages/bundled-features/src/billing-foundation/consumer-protection/termination-mail.ts +39 -33
- package/packages/bundled-features/src/billing-foundation/consumer-protection/termination-notify.ts +2 -2
- package/packages/bundled-features/src/billing-foundation/consumer-protection/termination-pages.ts +1 -4
- package/packages/bundled-features/src/billing-foundation/consumer-protection/termination-receipt-limit.ts +41 -0
- package/packages/bundled-features/src/billing-foundation/consumer-protection/termination-record.ts +10 -1
- package/packages/bundled-features/src/billing-foundation/consumer-protection/termination-request.ts +36 -20
- package/packages/bundled-features/src/billing-foundation/consumer-protection/termination-texts.ts +9 -0
- package/packages/bundled-features/src/billing-foundation/consumer-protection/withdrawal-period.ts +35 -0
- package/packages/bundled-features/src/billing-foundation/get-subscription-for-tenant.ts +2 -2
- package/packages/bundled-features/src/billing-foundation/handlers/create-checkout-session.write.ts +11 -3
- package/packages/bundled-features/src/billing-foundation/handlers/create-portal-session.write.ts +4 -9
- package/packages/bundled-features/src/billing-foundation/handlers/list-subscriptions.query.ts +1 -1
- package/packages/bundled-features/src/billing-foundation/handlers/process-event.write.ts +12 -6
- package/packages/bundled-features/src/billing-foundation/handlers/process-payment-event.write.ts +1 -1
- package/packages/bundled-features/src/billing-foundation/i18n.ts +3 -0
- package/packages/bundled-features/src/billing-foundation/subscription-tier-sync.ts +20 -11
- package/packages/bundled-features/src/billing-foundation/types.ts +7 -2
- package/packages/bundled-features/src/cap-counter/calendar-cap-usage-reservation.ts +2 -0
- package/packages/bundled-features/src/cap-counter/changes.json +6 -0
- package/packages/bundled-features/src/cap-counter/enforce-cap.ts +75 -5
- package/packages/bundled-features/src/cap-counter/index.ts +1 -0
- package/packages/bundled-features/src/cap-counter/with-cap-enforcement.ts +3 -0
- package/packages/bundled-features/src/cap-overview/handlers/tenant-caps-list.query.ts +2 -1
- package/packages/bundled-features/src/cap-overview/web/cap-usage-cell.tsx +6 -4
- package/packages/bundled-features/src/channel-email/changes.json +19 -0
- package/packages/bundled-features/src/channel-email/email-channel.ts +135 -29
- package/packages/bundled-features/src/channel-email/feature.ts +6 -3
- package/packages/bundled-features/src/channel-email/index.ts +7 -1
- package/packages/bundled-features/src/channel-email/pii-guard.ts +13 -0
- package/packages/bundled-features/src/channel-email/smtp-transport.ts +15 -1
- package/packages/bundled-features/src/channel-email/types.ts +23 -0
- package/packages/bundled-features/src/channel-in-app/handlers/mark-read.write.ts +2 -2
- package/packages/bundled-features/src/config/changes.json +6 -0
- package/packages/bundled-features/src/config/handlers/set.write.ts +3 -7
- package/packages/bundled-features/src/config/resolver.ts +15 -12
- package/packages/bundled-features/src/config/write-helpers.ts +23 -0
- package/packages/bundled-features/src/crypto-shredding/changes.json +6 -0
- package/packages/bundled-features/src/crypto-shredding/handlers/forget-subject.write.ts +48 -27
- package/packages/bundled-features/src/custom-fields/changes.json +6 -0
- package/packages/bundled-features/src/custom-fields/db/queries/user-data-rights.ts +2 -2
- package/packages/bundled-features/src/custom-fields/handlers/update-tenant-field.write.ts +0 -1
- package/packages/bundled-features/src/custom-fields/lib/value-schema.ts +10 -11
- package/packages/bundled-features/src/custom-fields/wire-user-data-rights.ts +7 -6
- package/packages/bundled-features/src/data-retention/changes.json +6 -0
- package/packages/bundled-features/src/data-retention/index.ts +1 -0
- package/packages/bundled-features/src/data-retention/run-retention-cleanup.ts +8 -6
- package/packages/bundled-features/src/delivery/attachment-ref.ts +105 -0
- package/packages/bundled-features/src/delivery/changes.json +66 -0
- package/packages/bundled-features/src/delivery/channel-context.ts +152 -4
- package/packages/bundled-features/src/delivery/constants.ts +2 -0
- package/packages/bundled-features/src/delivery/delivery-service.ts +104 -26
- package/packages/bundled-features/src/delivery/feature.ts +1 -1
- package/packages/bundled-features/src/delivery/index.ts +19 -2
- package/packages/bundled-features/src/delivery/jobs.ts +97 -17
- package/packages/bundled-features/src/delivery/notify-factory.ts +14 -9
- package/packages/bundled-features/src/delivery/testing.ts +9 -1
- package/packages/bundled-features/src/delivery/types.ts +34 -18
- package/packages/bundled-features/src/delivery/unsubscribe.ts +5 -2
- package/packages/bundled-features/src/derivatives-sharp/render.ts +1 -22
- package/packages/bundled-features/src/document-ingest-foundation/pages.ts +6 -3
- package/packages/bundled-features/src/feature-toggles/toggle-sync-signal.ts +4 -4
- package/packages/bundled-features/src/file-derivatives/handlers/public-variant.query.ts +1 -1
- package/packages/bundled-features/src/file-foundation/changes.json +6 -0
- package/packages/bundled-features/src/file-foundation/index.ts +7 -0
- package/packages/bundled-features/src/file-foundation/read-file-ref-content.ts +89 -0
- package/packages/bundled-features/src/files-tenant-data/changes.json +6 -0
- package/packages/bundled-features/src/files-tenant-data/hooks.ts +4 -0
- package/packages/bundled-features/src/folders/web/folder-manager.tsx +5 -2
- package/packages/bundled-features/src/form-draft/db/queries/cleanup.ts +11 -11
- package/packages/bundled-features/src/form-draft/db/queries/draft-count.ts +2 -2
- package/packages/bundled-features/src/form-draft/db/queries/owned-file-refs.ts +2 -2
- package/packages/bundled-features/src/form-draft/release-file-refs.ts +3 -2
- package/packages/bundled-features/src/inbound-mail-foundation/changes.json +6 -0
- package/packages/bundled-features/src/inbound-mail-foundation/handlers/connect-account.write.ts +1 -1
- package/packages/bundled-features/src/inbound-mail-foundation/handlers/ingest-message.write.ts +6 -6
- package/packages/bundled-features/src/inbound-mail-foundation/handlers/list-accounts.query.ts +1 -1
- package/packages/bundled-features/src/inbound-mail-foundation/handlers/list-messages.query.ts +1 -1
- package/packages/bundled-features/src/inbound-mail-foundation/handlers/scope-visibility.ts +2 -2
- package/packages/bundled-features/src/inbound-mail-foundation/oauth-access-token.ts +13 -8
- package/packages/bundled-features/src/inbound-mail-foundation/oauth-state.ts +7 -7
- package/packages/bundled-features/src/inbound-mail-foundation/retention-sweep.ts +2 -6
- package/packages/bundled-features/src/inbound-mail-foundation/tenant-destroy-hook.ts +5 -13
- package/packages/bundled-features/src/inbound-mail-foundation/types.ts +4 -6
- package/packages/bundled-features/src/inbound-mail-foundation/watch-supervisor.ts +42 -25
- package/packages/bundled-features/src/inbound-provider-imap/imap-client.ts +4 -3
- package/packages/bundled-features/src/jobs/changes.json +19 -0
- package/packages/bundled-features/src/jobs/db/queries/stale-run-sweep.ts +12 -4
- package/packages/bundled-features/src/jobs/feature.ts +7 -4
- package/packages/bundled-features/src/jobs/handlers/catalog.query.ts +1 -1
- package/packages/bundled-features/src/jobs/handlers/list.query.ts +3 -1
- package/packages/bundled-features/src/jobs/handlers/stale-run-sweep.job.ts +5 -2
- package/packages/bundled-features/src/jobs/job-run-logger.ts +52 -17
- package/packages/bundled-features/src/jobs/tenant-job-run-table.ts +7 -2
- package/packages/bundled-features/src/ledger/changes.json +7 -0
- package/packages/bundled-features/src/ledger/entity.ts +3 -1
- package/packages/bundled-features/src/ledger/recurring.ts +1 -3
- package/packages/bundled-features/src/managed-pages/branding.ts +33 -9
- package/packages/bundled-features/src/managed-pages/changes.json +6 -0
- package/packages/bundled-features/src/managed-pages/feature.ts +1 -0
- package/packages/bundled-features/src/managed-pages/i18n.ts +1 -0
- package/packages/bundled-features/src/managed-pages/screens/branding-screen.ts +8 -0
- package/packages/bundled-features/src/page-render/branding.ts +3 -0
- package/packages/bundled-features/src/page-render/markdown.ts +1 -1
- package/packages/bundled-features/src/personal-access-tokens/feature.ts +2 -4
- package/packages/bundled-features/src/personal-access-tokens/handlers/list.query.ts +2 -1
- package/packages/bundled-features/src/personal-access-tokens/index.ts +1 -1
- package/packages/bundled-features/src/personal-access-tokens/revoke-for-user.ts +2 -1
- package/packages/bundled-features/src/renderer-foundation/README.md +11 -10
- package/packages/bundled-features/src/renderer-foundation/api.ts +14 -14
- package/packages/bundled-features/src/renderer-foundation/changes.json +39 -0
- package/packages/bundled-features/src/renderer-foundation/constants.ts +6 -8
- package/packages/bundled-features/src/renderer-foundation/feature.ts +7 -7
- package/packages/bundled-features/src/renderer-foundation/index.ts +13 -2
- package/packages/bundled-features/src/renderer-foundation/types.ts +134 -35
- package/packages/bundled-features/src/renderer-pdf/changes.json +12 -0
- package/packages/bundled-features/src/renderer-pdf/feature.ts +7 -1
- package/packages/bundled-features/src/renderer-pdf/render-table-pdf.ts +162 -82
- package/packages/bundled-features/src/secrets/changes.json +6 -0
- package/packages/bundled-features/src/secrets/feature.ts +4 -3
- package/packages/bundled-features/src/secrets/secrets-context.ts +23 -3
- package/packages/bundled-features/src/sessions/db/queries/cleanup.ts +2 -2
- package/packages/bundled-features/src/sessions/feature.ts +2 -4
- package/packages/bundled-features/src/sessions/handlers/list.query.ts +2 -1
- package/packages/bundled-features/src/shared/attachment-file-name.ts +39 -0
- package/packages/bundled-features/src/shared/changes.json +6 -0
- package/packages/bundled-features/src/shared/chunked-entity-migration.ts +4 -1
- package/packages/bundled-features/src/shared/index.ts +6 -0
- package/packages/bundled-features/src/shared/is-tenant-db.ts +1 -1
- package/packages/bundled-features/src/shared/mail-template-body.ts +42 -0
- package/packages/bundled-features/src/shared/sharp-input-limits.ts +26 -0
- package/packages/bundled-features/src/shared/signup-handover.ts +4 -4
- package/packages/bundled-features/src/subscription-stripe/changes.json +6 -0
- package/packages/bundled-features/src/subscription-stripe/plugin-methods.ts +19 -11
- package/packages/bundled-features/src/subscription-stripe/verify-webhook.ts +3 -1
- package/packages/bundled-features/src/tags/handlers/assign-tag.write.ts +5 -1
- package/packages/bundled-features/src/tags/web/tag-chip.tsx +2 -1
- package/packages/bundled-features/src/template-resolver/README.md +66 -8
- package/packages/bundled-features/src/template-resolver/api.ts +85 -9
- package/packages/bundled-features/src/template-resolver/changes.json +186 -0
- package/packages/bundled-features/src/template-resolver/compile-document-body.ts +278 -0
- package/packages/bundled-features/src/template-resolver/constants.ts +49 -11
- package/packages/bundled-features/src/template-resolver/document-sanitizer.ts +335 -0
- package/packages/bundled-features/src/template-resolver/document-schema.ts +345 -0
- package/packages/bundled-features/src/template-resolver/feature.ts +59 -6
- package/packages/bundled-features/src/template-resolver/handlers/bundle-import.ts +188 -0
- package/packages/bundled-features/src/template-resolver/handlers/collection-set.write.ts +30 -2
- package/packages/bundled-features/src/template-resolver/handlers/copy-locale.write.ts +160 -0
- package/packages/bundled-features/src/template-resolver/handlers/diff-to-system.query.ts +33 -0
- package/packages/bundled-features/src/template-resolver/handlers/discard-draft.write.ts +52 -0
- package/packages/bundled-features/src/template-resolver/handlers/export-bundle.query.ts +138 -0
- package/packages/bundled-features/src/template-resolver/handlers/find-by-id.query.ts +9 -6
- package/packages/bundled-features/src/template-resolver/handlers/import-bundle-system.write.ts +42 -0
- package/packages/bundled-features/src/template-resolver/handlers/import-bundle.write.ts +22 -0
- package/packages/bundled-features/src/template-resolver/handlers/layout-source.query.ts +151 -0
- package/packages/bundled-features/src/template-resolver/handlers/list.query.ts +19 -18
- package/packages/bundled-features/src/template-resolver/handlers/override-diff.ts +49 -0
- package/packages/bundled-features/src/template-resolver/handlers/overview.query.ts +323 -0
- package/packages/bundled-features/src/template-resolver/handlers/preview.query.ts +25 -0
- package/packages/bundled-features/src/template-resolver/handlers/reset-to-system.write.ts +58 -0
- package/packages/bundled-features/src/template-resolver/handlers/resource-link-pdf.write.ts +34 -0
- package/packages/bundled-features/src/template-resolver/handlers/resource-remove.write.ts +50 -0
- package/packages/bundled-features/src/template-resolver/handlers/resource-shared.ts +24 -0
- package/packages/bundled-features/src/template-resolver/handlers/resource-upload.write.ts +135 -0
- package/packages/bundled-features/src/template-resolver/handlers/send-test.write.ts +135 -0
- package/packages/bundled-features/src/template-resolver/handlers/set.write.ts +32 -3
- package/packages/bundled-features/src/template-resolver/handlers/shared.ts +7 -2
- package/packages/bundled-features/src/template-resolver/handlers/template-gates.ts +196 -0
- package/packages/bundled-features/src/template-resolver/handlers/template-write.ts +233 -0
- package/packages/bundled-features/src/template-resolver/handlers/tenant-overrides.query.ts +137 -0
- package/packages/bundled-features/src/template-resolver/handlers/toggle-status.write.ts +91 -17
- package/packages/bundled-features/src/template-resolver/handlers/upsert-system.write.ts +19 -71
- package/packages/bundled-features/src/template-resolver/handlers/upsert-tenant.write.ts +16 -77
- package/packages/bundled-features/src/template-resolver/handlers/usage.query.ts +31 -0
- package/packages/bundled-features/src/template-resolver/handlers/validate.query.ts +116 -0
- package/packages/bundled-features/src/template-resolver/index.ts +111 -0
- package/packages/bundled-features/src/template-resolver/layout-appearance.ts +40 -0
- package/packages/bundled-features/src/template-resolver/layout-regions.ts +206 -0
- package/packages/bundled-features/src/template-resolver/linked-resources.ts +102 -0
- package/packages/bundled-features/src/template-resolver/pdf-constants.ts +12 -0
- package/packages/bundled-features/src/template-resolver/preview-display-html.ts +36 -0
- package/packages/bundled-features/src/template-resolver/print-document.ts +58 -0
- package/packages/bundled-features/src/template-resolver/qualified-names.ts +34 -0
- package/packages/bundled-features/src/template-resolver/record-mapping.ts +26 -0
- package/packages/bundled-features/src/template-resolver/resource-image.ts +47 -0
- package/packages/bundled-features/src/template-resolver/seeding.ts +171 -20
- package/packages/bundled-features/src/template-resolver/store-template-resource.ts +416 -0
- package/packages/bundled-features/src/template-resolver/table.ts +27 -1
- package/packages/bundled-features/src/template-resolver/template-bundle.ts +79 -0
- package/packages/bundled-features/src/template-resolver/template-diff.ts +78 -0
- package/packages/bundled-features/src/template-resolver/template-draft.ts +106 -0
- package/packages/bundled-features/src/template-resolver/template-meta.ts +192 -0
- package/packages/bundled-features/src/template-resolver/template-preview.ts +676 -0
- package/packages/bundled-features/src/template-resolver/template-resources.ts +114 -0
- package/packages/bundled-features/src/template-resolver/template-usage.ts +68 -0
- package/packages/bundled-features/src/template-resolver/template-validation.ts +230 -0
- package/packages/bundled-features/src/template-resolver/testing.ts +5 -5
- package/packages/bundled-features/src/template-resolver/web/client-plugin.tsx +20 -13
- package/packages/bundled-features/src/template-resolver/web/document-editor-extensions.ts +215 -0
- package/packages/bundled-features/src/template-resolver/web/document-editor.tsx +37 -0
- package/packages/bundled-features/src/template-resolver/web/document-tiptap-editor.tsx +1065 -0
- package/packages/bundled-features/src/template-resolver/web/document-toolbar-blocks.ts +72 -0
- package/packages/bundled-features/src/template-resolver/web/editor-handle.ts +26 -0
- package/packages/bundled-features/src/template-resolver/web/i18n.ts +121 -0
- package/packages/bundled-features/src/template-resolver/web/index.ts +26 -0
- package/packages/bundled-features/src/template-resolver/web/placeholder-line-editor-lazy.tsx +36 -0
- package/packages/bundled-features/src/template-resolver/web/placeholder-line-editor.tsx +176 -0
- package/packages/bundled-features/src/template-resolver/web/placeholder-text.ts +37 -0
- package/packages/bundled-features/src/template-resolver/web/use-stable-by-key.ts +14 -0
- package/packages/bundled-features/src/tenant/handlers/team-list.query.ts +3 -2
- package/packages/bundled-features/src/tenant/seeding.ts +41 -7
- package/packages/bundled-features/src/tenant-lifecycle/boot-checks.ts +1 -1
- package/packages/bundled-features/src/tenant-lifecycle/handlers/cancel-destruction.write.ts +2 -1
- package/packages/bundled-features/src/tier-engine/changes.json +6 -0
- package/packages/bundled-features/src/tier-engine/feature.ts +11 -3
- package/packages/bundled-features/src/user/changes.json +6 -0
- package/packages/bundled-features/src/user/db/queries/stream-tenant-backfill.ts +4 -4
- package/packages/bundled-features/src/user/handlers/detail.query.ts +3 -5
- package/packages/bundled-features/src/user/resolve-display-names.ts +8 -15
- package/packages/bundled-features/src/user/roles-input-schema.ts +2 -2
- package/packages/bundled-features/src/user/seeding.ts +19 -5
- package/packages/bundled-features/src/user-data-rights/changes.json +13 -0
- package/packages/bundled-features/src/user-data-rights/db/queries/forget-cleanup.ts +2 -2
- package/packages/bundled-features/src/user-data-rights/email-templates.ts +5 -149
- package/packages/bundled-features/src/user-data-rights/feature.ts +72 -35
- package/packages/bundled-features/src/user-data-rights/handlers/cancel-deletion.write.ts +2 -1
- package/packages/bundled-features/src/user-data-rights/handlers/request-deletion.write.ts +5 -11
- package/packages/bundled-features/src/user-data-rights/lib/default-mailers.ts +134 -53
- package/packages/bundled-features/src/user-data-rights/lib/update-user-lifecycle.ts +2 -2
- package/packages/bundled-features/src/user-data-rights/mail-notifications.ts +157 -0
- package/packages/bundled-features/src/user-data-rights/run-forget-cleanup.ts +1 -1
- package/packages/bundled-features/src/user-data-rights/web/privacy-center-screen.tsx +2 -1
- package/packages/bundled-features/src/user-profile/changes.json +1 -1
- package/packages/bundled-features/src/user-profile/web/profile-screen.tsx +2 -1
- package/packages/bundled-features/src/waitlist/changes.json +7 -0
- package/packages/bundled-features/src/waitlist/feature.ts +17 -0
- package/packages/bundled-features/src/waitlist/handlers/submit.write.ts +4 -5
- package/packages/bundled-features/src/waitlist/mail.ts +69 -18
- package/packages/bundled-features/src/waitlist/options.ts +1 -1
- package/packages/bundled-features/src/workflow-runner/changes.json +6 -0
- package/packages/bundled-features/src/workflow-runner/event-trigger.ts +3 -3
- package/packages/bundled-features/src/workflow-runner/handlers/resume-run.write.ts +7 -1
- package/samples/apps/ui-walkthrough/package.json +11 -0
- package/samples/apps/ui-walkthrough/playwright.config.ts +40 -7
- package/samples/apps/ui-walkthrough/src/app/auth-constants.ts +9 -0
- package/samples/apps/ui-walkthrough/src/app/client.tsx +6 -0
- package/samples/apps/ui-walkthrough/src/app/seed-templates.ts +239 -0
- package/samples/apps/ui-walkthrough/src/app/server.ts +5 -1
- package/samples/apps/ui-walkthrough/src/app/shell.tsx +12 -1
- package/samples/apps/ui-walkthrough/src/app/template-seed-data.ts +570 -0
- package/samples/apps/ui-walkthrough/src/features/billing/feature.ts +72 -0
- package/samples/apps/ui-walkthrough/src/features/billing/i18n.ts +18 -0
- package/samples/apps/ui-walkthrough/src/features/billing/index.ts +2 -0
- package/samples/apps/ui-walkthrough/src/features/billing/web/index.ts +7 -0
- package/samples/apps/ui-walkthrough/src/run-config.ts +41 -1
- package/samples/apps/use-all-bundled/e2e-billing-gallery/gallery-features.ts +0 -2
- package/samples/apps/use-all-bundled/feature-manifest.json +118 -21
- package/samples/apps/use-all-bundled/kumiko/migrations/0045_template-resource-meta.rebuild.json +4 -0
- package/samples/apps/use-all-bundled/kumiko/migrations/0046_pdf-rendered.rebuild.json +4 -0
- package/samples/apps/use-all-bundled/package.json +2 -0
- package/samples/apps/use-all-bundled/playwright.config.ts +38 -3
- package/samples/apps/use-all-bundled/schema/generate.ts +13 -3
- package/samples/apps/use-all-bundled/schema/schema.generated.ts +3 -0
- package/samples/apps/use-all-bundled/scripts/check-coverage.ts +3 -0
- package/samples/apps/use-all-bundled/src/app/auth-constants.ts +3 -0
- package/samples/apps/use-all-bundled/src/app/client.tsx +4 -0
- package/samples/apps/use-all-bundled/src/app/seed-documents.ts +128 -0
- package/samples/apps/use-all-bundled/src/app/seed.ts +4 -0
- package/samples/apps/use-all-bundled/src/run-config.ts +11 -11
- package/samples/recipes/delivery-notifications/README.md +26 -25
- package/samples/recipes/delivery-notifications/package.json +2 -1
- package/samples/recipes/delivery-notifications/src/feature.ts +64 -58
- package/samples/recipes/seed-migration/README.md +1 -1
- package/packages/bundled-features/src/renderer-simple/changes.json +0 -27
- package/packages/bundled-features/src/renderer-simple/feature.ts +0 -66
- package/packages/bundled-features/src/renderer-simple/index.ts +0 -8
- package/packages/bundled-features/src/renderer-simple/mail-blocks.ts +0 -138
- package/packages/bundled-features/src/renderer-simple/resolve-variables.ts +0 -57
- 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 —
|
|
6
|
-
//
|
|
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
|
|
10
|
-
//
|
|
11
|
-
// renderer-puppeteer-client)
|
|
12
|
-
//
|
|
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 (
|
|
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
|
|
20
|
-
type
|
|
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 {
|
|
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: "
|
|
10
|
-
| { kind: "
|
|
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
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
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
|
-
|
|
27
|
-
|
|
28
|
-
readonly content
|
|
29
|
-
readonly
|
|
30
|
-
readonly
|
|
31
|
-
|
|
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
|
|
35
|
-
readonly
|
|
36
|
-
readonly
|
|
37
|
-
readonly
|
|
38
|
-
readonly
|
|
39
|
-
readonly
|
|
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?:
|
|
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:
|
|
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-
|
|
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
|
-
//
|
|
88
|
-
// - `kinds`
|
|
89
|
-
// - `render(req, ctx)
|
|
90
|
-
// -
|
|
91
|
-
// -
|
|
92
|
-
// - `ctx`
|
|
93
|
-
//
|
|
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 {
|
|
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
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
readonly
|
|
38
|
-
|
|
39
|
-
|
|
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?:
|
|
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
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
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
|
-
|
|
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: (
|
|
179
|
-
right: (
|
|
180
|
-
bottom: (
|
|
181
|
-
left: (
|
|
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
|
-
|
|
232
|
-
|
|
233
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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",
|