@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
|
@@ -48,10 +48,10 @@ export type AuthProviderPlugin = {
|
|
|
48
48
|
|
|
49
49
|
export function isValidTokenShape(shape: unknown): shape is TokenShape {
|
|
50
50
|
if (typeof shape !== "object" || shape === null || !("kind" in shape)) return false;
|
|
51
|
-
const kind =
|
|
51
|
+
const kind = shape.kind;
|
|
52
52
|
if (kind === "jwt") return true;
|
|
53
53
|
if (kind === "prefix") {
|
|
54
|
-
const prefix = "prefix" in shape ?
|
|
54
|
+
const prefix = "prefix" in shape ? shape.prefix : undefined;
|
|
55
55
|
return typeof prefix === "string" && prefix.length > 0;
|
|
56
56
|
}
|
|
57
57
|
return false;
|
|
@@ -64,9 +64,9 @@ export function isAuthProviderPlugin(o: unknown): o is AuthProviderPlugin {
|
|
|
64
64
|
typeof o === "object" &&
|
|
65
65
|
o !== null &&
|
|
66
66
|
"build" in o &&
|
|
67
|
-
typeof
|
|
67
|
+
typeof o.build === "function" &&
|
|
68
68
|
"shape" in o &&
|
|
69
|
-
isValidTokenShape(
|
|
69
|
+
isValidTokenShape(o.shape)
|
|
70
70
|
);
|
|
71
71
|
}
|
|
72
72
|
|
|
@@ -105,12 +105,7 @@ export type SessionStoreProvider = {
|
|
|
105
105
|
};
|
|
106
106
|
|
|
107
107
|
export function isSessionStoreProvider(o: unknown): o is SessionStoreProvider {
|
|
108
|
-
return
|
|
109
|
-
typeof o === "object" &&
|
|
110
|
-
o !== null &&
|
|
111
|
-
"build" in o &&
|
|
112
|
-
typeof (o as { build: unknown }).build === "function"
|
|
113
|
-
);
|
|
108
|
+
return typeof o === "object" && o !== null && "build" in o && typeof o.build === "function";
|
|
114
109
|
}
|
|
115
110
|
|
|
116
111
|
// Tenant-resolution extension points (#1373). Both are single-provider and
|
|
@@ -154,19 +149,14 @@ export function isTenantResolverProvider(o: unknown): o is TenantResolverProvide
|
|
|
154
149
|
typeof o === "object" &&
|
|
155
150
|
o !== null &&
|
|
156
151
|
"build" in o &&
|
|
157
|
-
typeof
|
|
152
|
+
typeof o.build === "function" &&
|
|
158
153
|
"trust" in o &&
|
|
159
|
-
isTenantResolverTrust(
|
|
154
|
+
isTenantResolverTrust(o.trust)
|
|
160
155
|
);
|
|
161
156
|
}
|
|
162
157
|
|
|
163
158
|
export function isTenantExistenceProvider(o: unknown): o is TenantExistenceProvider {
|
|
164
|
-
return
|
|
165
|
-
typeof o === "object" &&
|
|
166
|
-
o !== null &&
|
|
167
|
-
"build" in o &&
|
|
168
|
-
typeof (o as { build: unknown }).build === "function"
|
|
169
|
-
);
|
|
159
|
+
return typeof o === "object" && o !== null && "build" in o && typeof o.build === "function";
|
|
170
160
|
}
|
|
171
161
|
|
|
172
162
|
// r.useExtension options-shape for auth-foundation's four extension points.
|
|
@@ -53,11 +53,9 @@ export function bindMfaRevokeAllOtherSessionsFromFeature(
|
|
|
53
53
|
): BindMfaRevokeAllOtherSessions | undefined {
|
|
54
54
|
const exports = feature.exports;
|
|
55
55
|
if (exports && typeof exports === "object" && "bindRevokeAllOtherSessions" in exports) {
|
|
56
|
-
const { bindRevokeAllOtherSessions } = exports
|
|
57
|
-
bindRevokeAllOtherSessions: unknown;
|
|
58
|
-
};
|
|
56
|
+
const { bindRevokeAllOtherSessions } = exports;
|
|
59
57
|
if (typeof bindRevokeAllOtherSessions === "function") {
|
|
60
|
-
// @cast-boundary exports
|
|
58
|
+
// @cast-boundary feature-exports — feature.exports is untyped by design
|
|
61
59
|
return bindRevokeAllOtherSessions as BindMfaRevokeAllOtherSessions;
|
|
62
60
|
}
|
|
63
61
|
}
|
|
@@ -71,9 +69,9 @@ export function mfaStatusCheckerFromFeature(
|
|
|
71
69
|
): MfaStatusChecker | undefined {
|
|
72
70
|
const exports = feature.exports;
|
|
73
71
|
if (exports && typeof exports === "object" && "checkMfaStatus" in exports) {
|
|
74
|
-
const { checkMfaStatus } = exports
|
|
72
|
+
const { checkMfaStatus } = exports;
|
|
75
73
|
if (typeof checkMfaStatus === "function") {
|
|
76
|
-
// @cast-boundary exports
|
|
74
|
+
// @cast-boundary feature-exports — feature.exports is untyped by design
|
|
77
75
|
return checkMfaStatus as MfaStatusChecker;
|
|
78
76
|
}
|
|
79
77
|
}
|
|
@@ -87,9 +85,9 @@ export function mfaStatusCheckerFromFeature(
|
|
|
87
85
|
export function mfaVerifierFromFeature(feature: FeatureDefinition): MfaCodeVerifier | undefined {
|
|
88
86
|
const exports = feature.exports;
|
|
89
87
|
if (exports && typeof exports === "object" && "verifyMfaCode" in exports) {
|
|
90
|
-
const { verifyMfaCode } = exports
|
|
88
|
+
const { verifyMfaCode } = exports;
|
|
91
89
|
if (typeof verifyMfaCode === "function") {
|
|
92
|
-
// @cast-boundary exports
|
|
90
|
+
// @cast-boundary feature-exports — feature.exports is untyped by design
|
|
93
91
|
return verifyMfaCode as MfaCodeVerifier;
|
|
94
92
|
}
|
|
95
93
|
}
|
|
@@ -107,9 +105,9 @@ export function bindRevokeAllPatTokensFromFeature(
|
|
|
107
105
|
): BindRevokeAllPatTokens | undefined {
|
|
108
106
|
const exports = feature.exports;
|
|
109
107
|
if (exports && typeof exports === "object" && "bindRevokeAllPatTokens" in exports) {
|
|
110
|
-
const { bindRevokeAllPatTokens } = exports
|
|
108
|
+
const { bindRevokeAllPatTokens } = exports;
|
|
111
109
|
if (typeof bindRevokeAllPatTokens === "function") {
|
|
112
|
-
// @cast-boundary exports
|
|
110
|
+
// @cast-boundary feature-exports — feature.exports is untyped by design
|
|
113
111
|
return bindRevokeAllPatTokens as BindRevokeAllPatTokens;
|
|
114
112
|
}
|
|
115
113
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { createHmac, timingSafeEqual } from "node:crypto";
|
|
2
2
|
import { Temporal } from "@cosmicdrift/kumiko-types/temporal";
|
|
3
|
+
import { isPlainObject } from "@cosmicdrift/kumiko-types/type-guards";
|
|
3
4
|
|
|
4
5
|
function hmacSign(input: string, secret: string): string {
|
|
5
6
|
return createHmac("sha256", secret).update(input).digest("base64url");
|
|
@@ -20,11 +21,10 @@ export function createHmacTokenCodec<
|
|
|
20
21
|
function splitEncodedBody(
|
|
21
22
|
value: unknown,
|
|
22
23
|
): { readonly payload: TPayload; readonly expiresAtMs: number } | null {
|
|
23
|
-
if (
|
|
24
|
-
const
|
|
25
|
-
const expiresAtMs = v["expiresAtMs"];
|
|
24
|
+
if (!isPlainObject(value)) return null;
|
|
25
|
+
const expiresAtMs = value["expiresAtMs"];
|
|
26
26
|
if (typeof expiresAtMs !== "number") return null;
|
|
27
|
-
const { expiresAtMs: _drop, ...rest } =
|
|
27
|
+
const { expiresAtMs: _drop, ...rest } = value;
|
|
28
28
|
return isPayload(rest) ? { payload: rest, expiresAtMs } : null;
|
|
29
29
|
}
|
|
30
30
|
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { TenantId } from "@cosmicdrift/kumiko-framework/engine";
|
|
2
2
|
import { Temporal } from "@cosmicdrift/kumiko-types/temporal";
|
|
3
|
+
import { isPlainObject } from "@cosmicdrift/kumiko-types/type-guards";
|
|
3
4
|
import { createHmacTokenCodec, type HmacTokenVerifyResult } from "./hmac-token-codec.js";
|
|
4
5
|
|
|
5
6
|
export type MfaChallengePayload = {
|
|
@@ -8,9 +9,8 @@ export type MfaChallengePayload = {
|
|
|
8
9
|
};
|
|
9
10
|
|
|
10
11
|
function isPayload(value: unknown): value is MfaChallengePayload {
|
|
11
|
-
if (
|
|
12
|
-
|
|
13
|
-
return typeof v["userId"] === "string" && typeof v["tenantId"] === "string";
|
|
12
|
+
if (!isPlainObject(value)) return false;
|
|
13
|
+
return typeof value["userId"] === "string" && typeof value["tenantId"] === "string";
|
|
14
14
|
}
|
|
15
15
|
|
|
16
16
|
// Domain-separated from mfa-preauth-setup (see hmac-token-codec). Single-use is
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { TenantId } from "@cosmicdrift/kumiko-framework/engine";
|
|
2
2
|
import { Temporal } from "@cosmicdrift/kumiko-types/temporal";
|
|
3
|
+
import { isPlainObject } from "@cosmicdrift/kumiko-types/type-guards";
|
|
3
4
|
import { createHmacTokenCodec, type HmacTokenVerifyResult } from "./hmac-token-codec.js";
|
|
4
5
|
|
|
5
6
|
export type MfaPreauthSetupPayload = {
|
|
@@ -8,9 +9,8 @@ export type MfaPreauthSetupPayload = {
|
|
|
8
9
|
};
|
|
9
10
|
|
|
10
11
|
function isPayload(value: unknown): value is MfaPreauthSetupPayload {
|
|
11
|
-
if (
|
|
12
|
-
|
|
13
|
-
return typeof v["userId"] === "string" && typeof v["tenantId"] === "string";
|
|
12
|
+
if (!isPlainObject(value)) return false;
|
|
13
|
+
return typeof value["userId"] === "string" && typeof value["tenantId"] === "string";
|
|
14
14
|
}
|
|
15
15
|
|
|
16
16
|
// Domain "mfa-preauth-setup" must never equal the challenge domain: a setup
|
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
// generated secret material, not just re-derive it from a lookup.
|
|
12
12
|
import { createHmac, timingSafeEqual } from "node:crypto";
|
|
13
13
|
import { Temporal } from "@cosmicdrift/kumiko-types/temporal";
|
|
14
|
+
import { isPlainObject } from "@cosmicdrift/kumiko-types/type-guards";
|
|
14
15
|
|
|
15
16
|
export type MfaSetupPayload = {
|
|
16
17
|
readonly userId: string;
|
|
@@ -47,15 +48,14 @@ export type VerifyMfaSetupResult =
|
|
|
47
48
|
| { readonly ok: false; readonly reason: "malformed" | "bad_signature" | "expired" };
|
|
48
49
|
|
|
49
50
|
function isEncodedBody(value: unknown): value is EncodedBody {
|
|
50
|
-
if (
|
|
51
|
-
const v = value as Record<string, unknown>;
|
|
51
|
+
if (!isPlainObject(value)) return false;
|
|
52
52
|
return (
|
|
53
|
-
typeof
|
|
54
|
-
typeof
|
|
55
|
-
Array.isArray(
|
|
56
|
-
|
|
57
|
-
typeof
|
|
58
|
-
(
|
|
53
|
+
typeof value["userId"] === "string" &&
|
|
54
|
+
typeof value["totpSecretBase32"] === "string" &&
|
|
55
|
+
Array.isArray(value["recoveryCodeHashes"]) &&
|
|
56
|
+
value["recoveryCodeHashes"].every((h) => typeof h === "string") &&
|
|
57
|
+
typeof value["expiresAtMs"] === "number" &&
|
|
58
|
+
(value["tenantId"] === undefined || typeof value["tenantId"] === "string")
|
|
59
59
|
);
|
|
60
60
|
}
|
|
61
61
|
|
|
@@ -1,4 +1,47 @@
|
|
|
1
1
|
[
|
|
2
|
+
{
|
|
3
|
+
"version": "0.364.0",
|
|
4
|
+
"type": "fix",
|
|
5
|
+
"title": "Stale subscription created events are ignored and manual-grant skips are logged",
|
|
6
|
+
"detail": "A created event for a subscription that is no longer current no longer replaces the active one while the current row blocks checkout, and the tier sync logs its own code when a manual grant keeps the tier."
|
|
7
|
+
},
|
|
8
|
+
{
|
|
9
|
+
"version": "0.364.0",
|
|
10
|
+
"type": "fix",
|
|
11
|
+
"title": "Contract confirmation mail renders the terms' Markdown as formatting",
|
|
12
|
+
"detail": "The terms rows of the contract confirmation mail were plain text, so `**Right to cancel.**` reached the buyer with its asterisks (0.363.0) or lost its emphasis. Each row of `termsParagraphs` is now a richText value: bold, italic and http(s)/mailto/tel links render as HTML, headings become bold rows. Author HTML and images in the terms stay escaped text, and links outside those schemes stay \"label (url)\". Tenant overrides of the template keep working, the variable name and row structure are unchanged."
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
"version": "0.364.0",
|
|
16
|
+
"type": "fix",
|
|
17
|
+
"title": "Terms in the contract confirmation mail no longer show raw Markdown",
|
|
18
|
+
"detail": "The terms Markdown is reduced to plain text rows: bold and italic markers are dropped, links become \"label (url)\", list items get their own row, and single line breaks inside a paragraph become spaces."
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
"version": "0.364.0",
|
|
22
|
+
"type": "fix",
|
|
23
|
+
"title": "The termination receipt rate limit keys Redis with a keyed, handler-bound digest instead of an unkeyed SHA-256 of the email",
|
|
24
|
+
"detail": "HandlerContext gains an optional rateLimitDigest(value), an HMAC bound to the running handler, so a handler can build rate-limit keys without plaintext identifiers and cannot reproduce the dispatcher's payload-bucket digests. normalizePayloadBucketValue is exported from the pipeline entry point. The receipt limit reuses it, and withholds the receipt mail (the declaration is still recorded) when no digest is wired. Payload-bucket normalization now applies Unicode NFKC first, so fullwidth lookalikes share a bucket. Existing receipt buckets restart empty; they expire after the 24 hour window."
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
"version": "0.364.0",
|
|
28
|
+
"type": "fix",
|
|
29
|
+
"title": "Checkout validates the one-off order label and price lookup",
|
|
30
|
+
"detail": "An invalid oneOffItemLabel or a provider without retrievePrices fails with a configuration error before the provider is called."
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
"version": "0.364.0",
|
|
34
|
+
"type": "fix",
|
|
35
|
+
"title": "Contract termination downgrades late withdrawals and caps receipt mails",
|
|
36
|
+
"detail": "A withdrawal declared after the 14-day period ends at period end (operator notice withdrawal_period_expired); the declaration is always accepted, receipt mails are capped at 3 per recipient per day and carry only enum fields, request id and time; purchaser roles come from the catalog."
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
"version": "0.363.0",
|
|
40
|
+
"type": "breaking",
|
|
41
|
+
"title": "Contract confirmation and termination mails use template variables",
|
|
42
|
+
"detail": "contract-confirmation, contract-termination-receipt and contract-termination-operator-notice are r.notification declarations with variables and localized templates (de, en). The notification types are qualified names now (billing-foundation:notify:<name>). The notify data is the template variable object (subject, details, contractLines, termsParagraphs, operatorEmail), not sections.",
|
|
43
|
+
"migration": "Update delivery preferences and tests that referenced the old type strings, and anything that listens to the notification data. An app that mounts it without includeBundled needs template-resolver, renderer-foundation and @cosmicdrift/kumiko-renderer-mail-html at boot (composeRendererStack() from dev-server mounts all of them), plus a delivery with the channel-email feature; apps without the template tables need a schema migration (`kumiko schema generate`)."
|
|
44
|
+
},
|
|
2
45
|
{
|
|
3
46
|
"version": "0.354.0",
|
|
4
47
|
"type": "breaking",
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
/// <reference types="temporal-polyfill/global" preserve="true" />
|
|
2
2
|
// @runtime client
|
|
3
3
|
import { Temporal } from "@cosmicdrift/kumiko-types/temporal";
|
|
4
|
+
import { isOneOf } from "@cosmicdrift/kumiko-types/type-guards";
|
|
4
5
|
|
|
5
6
|
// Feature name
|
|
6
7
|
export const BILLING_FOUNDATION_FEATURE = "billing-foundation" as const;
|
|
@@ -11,14 +12,14 @@ export const SUBSCRIPTION_PROVIDER_EXTENSION = "subscriptionProvider" as const;
|
|
|
11
12
|
|
|
12
13
|
/** delivery notification type of the § 312f contract confirmation mail. */
|
|
13
14
|
export const CONTRACT_CONFIRMATION_NOTIFICATION_TYPE =
|
|
14
|
-
"billing-foundation:contract-confirmation" as const;
|
|
15
|
+
"billing-foundation:notify:contract-confirmation" as const;
|
|
15
16
|
|
|
16
17
|
/** delivery notification types of the § 312k termination receipt and the
|
|
17
18
|
* operator notice. */
|
|
18
19
|
export const CONTRACT_TERMINATION_RECEIPT_NOTIFICATION_TYPE =
|
|
19
|
-
"billing-foundation:contract-termination-receipt" as const;
|
|
20
|
+
"billing-foundation:notify:contract-termination-receipt" as const;
|
|
20
21
|
export const CONTRACT_TERMINATION_OPERATOR_NOTIFICATION_TYPE =
|
|
21
|
-
"billing-foundation:contract-termination-operator-notice" as const;
|
|
22
|
+
"billing-foundation:notify:contract-termination-operator-notice" as const;
|
|
22
23
|
|
|
23
24
|
// Qualified write handler names (QN format: scope:type:name).
|
|
24
25
|
export const SubscriptionFoundationHandlers = {
|
|
@@ -230,5 +231,5 @@ export const TERMINATION_SCOPES = ["tenant-host", "platform"] as const;
|
|
|
230
231
|
export type TerminationScope = (typeof TERMINATION_SCOPES)[number];
|
|
231
232
|
|
|
232
233
|
export function isTerminationScope(value: unknown): value is TerminationScope {
|
|
233
|
-
return (TERMINATION_SCOPES
|
|
234
|
+
return isOneOf(TERMINATION_SCOPES, value);
|
|
234
235
|
}
|
package/packages/bundled-features/src/billing-foundation/consumer-protection/confirmation-mail.ts
CHANGED
|
@@ -1,6 +1,12 @@
|
|
|
1
|
+
import { escapeHtml, escapeHtmlAttr } from "@cosmicdrift/kumiko-headless";
|
|
1
2
|
import { Temporal } from "@cosmicdrift/kumiko-types/temporal";
|
|
3
|
+
import { Marked } from "marked";
|
|
4
|
+
import {
|
|
5
|
+
isAllowedDocumentLinkHref,
|
|
6
|
+
isDocumentPlaceholderHref,
|
|
7
|
+
} from "../../template-resolver/index.js";
|
|
2
8
|
import type { CheckoutConsentRecordedPayload } from "../events.js";
|
|
3
|
-
import {
|
|
9
|
+
import { type ConsentLocale, resolveConsentLocale } from "./consent-text.js";
|
|
4
10
|
|
|
5
11
|
// Inline de/en like the consent texts: the confirmation repeats the legal
|
|
6
12
|
// wording the buyer agreed to, so it cannot follow the en-only i18n convention.
|
|
@@ -22,7 +28,7 @@ type ConfirmationLabels = {
|
|
|
22
28
|
readonly every: string;
|
|
23
29
|
};
|
|
24
30
|
|
|
25
|
-
const
|
|
31
|
+
export const CONFIRMATION_LABELS: Readonly<Record<ConsentLocale, ConfirmationLabels>> = {
|
|
26
32
|
de: {
|
|
27
33
|
subject: "Vertragsbestätigung",
|
|
28
34
|
intro:
|
|
@@ -71,19 +77,17 @@ const LABELS: Readonly<Record<ConsentLocale, ConfirmationLabels>> = {
|
|
|
71
77
|
},
|
|
72
78
|
};
|
|
73
79
|
|
|
74
|
-
|
|
75
|
-
| { readonly text: string }
|
|
76
|
-
| { readonly heading: string }
|
|
77
|
-
| { readonly markdown: string };
|
|
80
|
+
type TextRow = { readonly text: string };
|
|
78
81
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
readonly
|
|
82
|
-
readonly
|
|
83
|
-
readonly
|
|
82
|
+
/** Variables of the contract confirmation template. */
|
|
83
|
+
export type ContractConfirmationVariables = {
|
|
84
|
+
readonly contractLines: readonly TextRow[];
|
|
85
|
+
readonly consentGivenAt: string;
|
|
86
|
+
readonly termsParagraphs: readonly TextRow[];
|
|
87
|
+
readonly operatorEmail: string;
|
|
84
88
|
};
|
|
85
89
|
|
|
86
|
-
export type
|
|
90
|
+
export type ContractConfirmationArgs = {
|
|
87
91
|
readonly consent: CheckoutConsentRecordedPayload;
|
|
88
92
|
readonly timeZone: string;
|
|
89
93
|
/** Translated plan name; falls back to the raw tier. */
|
|
@@ -99,7 +103,7 @@ export type RenderContractConfirmationArgs = {
|
|
|
99
103
|
};
|
|
100
104
|
|
|
101
105
|
function formatPrice(consent: CheckoutConsentRecordedPayload, locale: ConsentLocale): string {
|
|
102
|
-
const labels =
|
|
106
|
+
const labels = CONFIRMATION_LABELS[locale];
|
|
103
107
|
if (consent.unitAmount === null || consent.currency === null) return "-";
|
|
104
108
|
let amount: string;
|
|
105
109
|
try {
|
|
@@ -140,45 +144,87 @@ function formatDate(iso: string, locale: ConsentLocale, timeZone: string): strin
|
|
|
140
144
|
}).format(epochMilliseconds);
|
|
141
145
|
}
|
|
142
146
|
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
):
|
|
147
|
+
// One richText row of inline HTML per Markdown block. The sanitizer shows a rejected
|
|
148
|
+
// row as escaped text, so author HTML, images and refused links stay plain text.
|
|
149
|
+
const ROW_END = "\n\n";
|
|
150
|
+
|
|
151
|
+
function isMailLinkHref(href: string): boolean {
|
|
152
|
+
return isAllowedDocumentLinkHref(href) && !isDocumentPlaceholderHref(href);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
const inlineHtmlMarkdown = new Marked({ gfm: false, breaks: false });
|
|
156
|
+
inlineHtmlMarkdown.use({
|
|
157
|
+
renderer: {
|
|
158
|
+
heading({ tokens }) {
|
|
159
|
+
const headingHtml = this.parser.parseInline(tokens);
|
|
160
|
+
return `<strong>${headingHtml}</strong>${ROW_END}`;
|
|
161
|
+
},
|
|
162
|
+
paragraph({ tokens }) {
|
|
163
|
+
return `${this.parser.parseInline(tokens)}${ROW_END}`;
|
|
164
|
+
},
|
|
165
|
+
code: ({ text }) => `${escapeHtml(text)}${ROW_END}`,
|
|
166
|
+
blockquote({ tokens }) {
|
|
167
|
+
return this.parser.parse(tokens);
|
|
168
|
+
},
|
|
169
|
+
list({ items }) {
|
|
170
|
+
return items.map((item) => this.parser.parse(item.tokens)).join("");
|
|
171
|
+
},
|
|
172
|
+
hr: () => "",
|
|
173
|
+
space: () => "",
|
|
174
|
+
del({ tokens }) {
|
|
175
|
+
return this.parser.parseInline(tokens);
|
|
176
|
+
},
|
|
177
|
+
br: () => " ",
|
|
178
|
+
html: ({ text }) => escapeHtml(text),
|
|
179
|
+
image: ({ text }) => escapeHtml(text),
|
|
180
|
+
text(token) {
|
|
181
|
+
// Only block-level text (a list item's own text) carries inline child tokens.
|
|
182
|
+
return "tokens" in token && token.tokens
|
|
183
|
+
? `${this.parser.parseInline(token.tokens)}${ROW_END}`
|
|
184
|
+
: escapeHtml(token.text);
|
|
185
|
+
},
|
|
186
|
+
link({ href, tokens }) {
|
|
187
|
+
const labelHtml = this.parser.parseInline(tokens);
|
|
188
|
+
if (isMailLinkHref(href)) return `<a href="${escapeHtmlAttr(href)}">${labelHtml}</a>`;
|
|
189
|
+
return labelHtml === escapeHtml(href) ? labelHtml : `${labelHtml} (${escapeHtml(href)})`;
|
|
190
|
+
},
|
|
191
|
+
},
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
function termsParagraphsOf(markdown: string): readonly TextRow[] {
|
|
195
|
+
return inlineHtmlMarkdown
|
|
196
|
+
.parse(markdown, { async: false })
|
|
197
|
+
.split(/\n\s*\n/)
|
|
198
|
+
.map((row) => row.replace(/\s+/g, " ").trim())
|
|
199
|
+
.filter((row) => row.length > 0)
|
|
200
|
+
.map((text) => ({ text }));
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
export function contractConfirmationVariables(
|
|
204
|
+
args: ContractConfirmationArgs,
|
|
205
|
+
): ContractConfirmationVariables {
|
|
148
206
|
const locale = resolveConsentLocale(args.consent.locale);
|
|
149
|
-
const labels =
|
|
150
|
-
const
|
|
151
|
-
const { consent } = args;
|
|
207
|
+
const labels = CONFIRMATION_LABELS[locale];
|
|
208
|
+
const { consent, timeZone } = args;
|
|
152
209
|
|
|
153
|
-
const { timeZone } = args;
|
|
154
210
|
const subjectLine =
|
|
155
211
|
consent.mode === "payment"
|
|
156
212
|
? `${labels.item}: ${args.itemLabel ?? labels.oneOffPayment}`
|
|
157
213
|
: `${labels.plan}: ${args.tierLabel ?? consent.tier ?? labels.oneOffPayment}`;
|
|
158
|
-
const contractLines = [
|
|
159
|
-
subjectLine,
|
|
160
|
-
`${labels.price}: ${formatPrice(consent, locale)}`,
|
|
161
|
-
`${labels.contractStart}: ${formatDate(args.contractStartIso, locale, timeZone)}`,
|
|
162
|
-
...(args.currentPeriodEndIso !== undefined
|
|
163
|
-
? [`${labels.currentPeriodEnd}: ${formatDate(args.currentPeriodEndIso, locale, timeZone)}`]
|
|
164
|
-
: []),
|
|
165
|
-
];
|
|
166
214
|
const vatNote = args.vatNote[locale] ?? args.vatNote[resolveConsentLocale()];
|
|
167
215
|
|
|
168
216
|
return {
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
{
|
|
173
|
-
...
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
],
|
|
182
|
-
footer: `${labels.contact} ${args.operatorEmail}`,
|
|
217
|
+
contractLines: [
|
|
218
|
+
subjectLine,
|
|
219
|
+
`${labels.price}: ${formatPrice(consent, locale)}`,
|
|
220
|
+
`${labels.contractStart}: ${formatDate(args.contractStartIso, locale, timeZone)}`,
|
|
221
|
+
...(args.currentPeriodEndIso !== undefined
|
|
222
|
+
? [`${labels.currentPeriodEnd}: ${formatDate(args.currentPeriodEndIso, locale, timeZone)}`]
|
|
223
|
+
: []),
|
|
224
|
+
...(vatNote !== undefined ? [vatNote] : []),
|
|
225
|
+
].map((text) => ({ text })),
|
|
226
|
+
consentGivenAt: formatDate(args.consentGivenAtIso, locale, timeZone),
|
|
227
|
+
termsParagraphs: termsParagraphsOf(args.termsContent),
|
|
228
|
+
operatorEmail: args.operatorEmail,
|
|
183
229
|
};
|
|
184
230
|
}
|
package/packages/bundled-features/src/billing-foundation/consumer-protection/consent-gate.ts
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
|
|
4
4
|
import { createHash } from "node:crypto";
|
|
5
5
|
import { type HandlerContext, SYSTEM_TENANT_ID } from "@cosmicdrift/kumiko-framework/engine";
|
|
6
|
-
import { UnprocessableError } from "@cosmicdrift/kumiko-framework/errors";
|
|
6
|
+
import { InternalError, UnprocessableError } from "@cosmicdrift/kumiko-framework/errors";
|
|
7
7
|
import { generateId } from "@cosmicdrift/kumiko-framework/utils";
|
|
8
8
|
import * as z from "zod";
|
|
9
9
|
import {
|
|
@@ -15,6 +15,7 @@ import { paymentAggregateId, subscriptionAggregateId } from "../aggregate-id.js"
|
|
|
15
15
|
import {
|
|
16
16
|
CHECKOUT_CONSENT_RECORDED_EVENT_QN,
|
|
17
17
|
type CheckoutConsentRecordedPayload,
|
|
18
|
+
checkoutConsentRecordedPayloadSchema,
|
|
18
19
|
PAYMENT_AGGREGATE_TYPE,
|
|
19
20
|
SUBSCRIPTION_AGGREGATE_TYPE,
|
|
20
21
|
} from "../events.js";
|
|
@@ -50,6 +51,26 @@ export type OrderItem = {
|
|
|
50
51
|
readonly params?: Readonly<Record<string, string | number>>;
|
|
51
52
|
};
|
|
52
53
|
|
|
54
|
+
// Bounds come from the event schema the label ends up in, so an app label that
|
|
55
|
+
// passes here cannot fail the append after the provider session exists.
|
|
56
|
+
const orderItemSchema = z.object({
|
|
57
|
+
labelKey: checkoutConsentRecordedPayloadSchema.shape.itemLabelKey.unwrap(),
|
|
58
|
+
params: checkoutConsentRecordedPayloadSchema.shape.itemLabelParams,
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
/** Throws an InternalError (an app configuration fault, not a buyer error) for a
|
|
62
|
+
* label the consent event could not store. */
|
|
63
|
+
export function assertValidOrderItem(item: OrderItem | undefined, priceId: string): void {
|
|
64
|
+
// skip: subscription checkouts have no one-off order item
|
|
65
|
+
if (item === undefined) return;
|
|
66
|
+
const parsed = orderItemSchema.safeParse(item);
|
|
67
|
+
if (!parsed.success) {
|
|
68
|
+
throw new InternalError({
|
|
69
|
+
message: `billing-foundation: consumerProtection.oneOffItemLabel returned an invalid label for priceId "${priceId}": ${parsed.error.message}`,
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
53
74
|
export type ConsentPriceDetails = {
|
|
54
75
|
readonly tier: string | null;
|
|
55
76
|
readonly priceId: string;
|
package/packages/bundled-features/src/billing-foundation/consumer-protection/issue-confirmation.ts
CHANGED
|
@@ -27,7 +27,7 @@ import {
|
|
|
27
27
|
SUBSCRIPTION_AGGREGATE_TYPE,
|
|
28
28
|
} from "../events.js";
|
|
29
29
|
import type { ConsumerProtectionOptions, ResolvedBillingFoundationOptions } from "../types.js";
|
|
30
|
-
import {
|
|
30
|
+
import { contractConfirmationVariables } from "./confirmation-mail.js";
|
|
31
31
|
import { resolveConsentLocale } from "./consent-text.js";
|
|
32
32
|
|
|
33
33
|
export const issueContractConfirmationSchema = z
|
|
@@ -185,7 +185,7 @@ export function createIssueContractConfirmationHandler(
|
|
|
185
185
|
: undefined;
|
|
186
186
|
|
|
187
187
|
const issuedAtIso = options.now().toString();
|
|
188
|
-
const
|
|
188
|
+
const variables = contractConfirmationVariables({
|
|
189
189
|
consent,
|
|
190
190
|
timeZone: pickTimeZone(recipient.data.timezone, ctx.tz.tenant),
|
|
191
191
|
...(tierLabel !== undefined && { tierLabel }),
|
|
@@ -220,7 +220,7 @@ export function createIssueContractConfirmationHandler(
|
|
|
220
220
|
|
|
221
221
|
await ctx.notify(CONTRACT_CONFIRMATION_NOTIFICATION_TYPE, {
|
|
222
222
|
route: { email: recipient.data.email },
|
|
223
|
-
data:
|
|
223
|
+
data: variables,
|
|
224
224
|
locale,
|
|
225
225
|
priority: "critical",
|
|
226
226
|
});
|
package/packages/bundled-features/src/billing-foundation/consumer-protection/mail-notifications.ts
ADDED
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import {
|
|
2
|
+
type FeatureRegistrar,
|
|
3
|
+
type NotificationLocalizedTemplate,
|
|
4
|
+
type TemplateVariables,
|
|
5
|
+
templateVariable,
|
|
6
|
+
} from "@cosmicdrift/kumiko-framework/engine";
|
|
7
|
+
import { mailHeading, mailParagraph, mailRepeatedRows } from "../../shared/index.js";
|
|
8
|
+
import { CONFIRMATION_LABELS } from "./confirmation-mail.js";
|
|
9
|
+
import { CONSENT_LOCALES, CONSENT_TEXTS, type ConsentLocale } from "./consent-text.js";
|
|
10
|
+
import { TERMINATION_TEXTS } from "./termination-texts.js";
|
|
11
|
+
|
|
12
|
+
// Slot syntax for the translated texts: `{name}` becomes a template variable.
|
|
13
|
+
const OPERATOR_EMAIL_SLOT = "{operatorEmail}";
|
|
14
|
+
|
|
15
|
+
const textRows = (): TemplateVariables[string] =>
|
|
16
|
+
templateVariable.list({ text: templateVariable.text({ required: true }) }, { required: true });
|
|
17
|
+
|
|
18
|
+
export const TERMINATION_RECEIPT_VARIABLES: TemplateVariables = {
|
|
19
|
+
subject: templateVariable.text({ required: true }),
|
|
20
|
+
receivedAt: templateVariable.text({ required: true }),
|
|
21
|
+
requestId: templateVariable.text({ required: true }),
|
|
22
|
+
details: textRows(),
|
|
23
|
+
operatorEmail: templateVariable.text({ required: true }),
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export const OPERATOR_NOTICE_VARIABLES: TemplateVariables = {
|
|
27
|
+
subject: templateVariable.text({ required: true }),
|
|
28
|
+
lines: textRows(),
|
|
29
|
+
operatorEmail: templateVariable.text({ required: true }),
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
export const CONTRACT_CONFIRMATION_VARIABLES: TemplateVariables = {
|
|
33
|
+
contractLines: textRows(),
|
|
34
|
+
consentGivenAt: templateVariable.text({ required: true }),
|
|
35
|
+
termsParagraphs: templateVariable.list(
|
|
36
|
+
{ text: templateVariable.richText({ required: true }) },
|
|
37
|
+
{ required: true },
|
|
38
|
+
),
|
|
39
|
+
operatorEmail: templateVariable.text({ required: true }),
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
function perLocale(
|
|
43
|
+
build: (locale: ConsentLocale) => { body: string; subject: string },
|
|
44
|
+
): NotificationLocalizedTemplate {
|
|
45
|
+
return Object.fromEntries(
|
|
46
|
+
CONSENT_LOCALES.map((locale) => {
|
|
47
|
+
const { body, subject } = build(locale);
|
|
48
|
+
return [locale, { body, meta: { subject, category: "transactional" as const } }];
|
|
49
|
+
}),
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export function terminationReceiptTemplate(): NotificationLocalizedTemplate {
|
|
54
|
+
return perLocale((locale) => {
|
|
55
|
+
const texts = TERMINATION_TEXTS[locale];
|
|
56
|
+
return {
|
|
57
|
+
subject: "{{subject}}",
|
|
58
|
+
body: [
|
|
59
|
+
mailParagraph(texts.receiptGreeting),
|
|
60
|
+
mailParagraph(texts.receiptIntro),
|
|
61
|
+
mailParagraph(`${texts.receivedAt}: {receivedAt}`),
|
|
62
|
+
mailParagraph(`${texts.requestId}: {requestId}`),
|
|
63
|
+
mailRepeatedRows("details", "text"),
|
|
64
|
+
mailParagraph(texts.receiptNextSteps),
|
|
65
|
+
mailParagraph(texts.receiptRevoke(OPERATOR_EMAIL_SLOT)),
|
|
66
|
+
mailParagraph(`${texts.contact} {operatorEmail}`),
|
|
67
|
+
].join(""),
|
|
68
|
+
};
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export function operatorNoticeTemplate(): NotificationLocalizedTemplate {
|
|
73
|
+
return perLocale((locale) => ({
|
|
74
|
+
subject: "{{subject}}",
|
|
75
|
+
body: [
|
|
76
|
+
mailParagraph(TERMINATION_TEXTS[locale].operatorIntro),
|
|
77
|
+
mailRepeatedRows("lines", "text"),
|
|
78
|
+
mailParagraph("{operatorEmail}"),
|
|
79
|
+
].join(""),
|
|
80
|
+
}));
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export function contractConfirmationTemplate(): NotificationLocalizedTemplate {
|
|
84
|
+
return perLocale((locale) => {
|
|
85
|
+
const labels = CONFIRMATION_LABELS[locale];
|
|
86
|
+
const consent = CONSENT_TEXTS[locale];
|
|
87
|
+
return {
|
|
88
|
+
subject: labels.subject,
|
|
89
|
+
body: [
|
|
90
|
+
mailParagraph(labels.intro),
|
|
91
|
+
mailRepeatedRows("contractLines", "text"),
|
|
92
|
+
mailHeading(labels.consentHeading),
|
|
93
|
+
mailParagraph(consent.earlyPerformance),
|
|
94
|
+
mailParagraph(consent.withdrawalLoss),
|
|
95
|
+
mailParagraph(`${labels.consentGivenAt}: {consentGivenAt}`),
|
|
96
|
+
mailHeading(labels.termsHeading),
|
|
97
|
+
mailRepeatedRows("termsParagraphs", "text"),
|
|
98
|
+
mailParagraph(`${labels.contact} {operatorEmail}`),
|
|
99
|
+
].join(""),
|
|
100
|
+
};
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// The registered notification names are the tail of the QN types in constants.ts.
|
|
105
|
+
export function registerConsumerProtectionMails(r: FeatureRegistrar): void {
|
|
106
|
+
r.notification("contract-confirmation", {
|
|
107
|
+
variables: CONTRACT_CONFIRMATION_VARIABLES,
|
|
108
|
+
localizedTemplate: contractConfirmationTemplate,
|
|
109
|
+
});
|
|
110
|
+
r.notification("contract-termination-receipt", {
|
|
111
|
+
variables: TERMINATION_RECEIPT_VARIABLES,
|
|
112
|
+
localizedTemplate: terminationReceiptTemplate,
|
|
113
|
+
});
|
|
114
|
+
r.notification("contract-termination-operator-notice", {
|
|
115
|
+
variables: OPERATOR_NOTICE_VARIABLES,
|
|
116
|
+
localizedTemplate: operatorNoticeTemplate,
|
|
117
|
+
});
|
|
118
|
+
}
|