@cosmicdrift/kumiko-bundled-features 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/dist/admin-shell/feature.d.ts +3 -0
- package/dist/admin-shell/feature.js +6 -1
- package/dist/agent-tools/agent-manifest.js +2 -1
- package/dist/agent-tools/filter-ops.d.ts +1 -1
- package/dist/agent-tools/filter-ops.js +1 -3
- package/dist/agent-tools/tool-dispatch.js +2 -1
- package/dist/audit/handlers/list.query.js +9 -5
- package/dist/auth-email-password/bootstrap.d.ts +8 -5
- package/dist/auth-email-password/bootstrap.js +12 -4
- package/dist/auth-email-password/email-rate-limits.d.ts +4 -0
- package/dist/auth-email-password/email-rate-limits.js +13 -0
- package/dist/auth-email-password/email-templates.d.ts +4 -26
- package/dist/auth-email-password/email-templates.js +9 -101
- package/dist/auth-email-password/feature.js +24 -6
- package/dist/auth-email-password/handlers/change-password.write.d.ts +1 -1
- package/dist/auth-email-password/handlers/change-password.write.js +6 -2
- package/dist/auth-email-password/handlers/confirm-account-unlock.write.d.ts +1 -1
- package/dist/auth-email-password/handlers/invite-accept-with-login.write.d.ts +1 -1
- package/dist/auth-email-password/handlers/invite-accept.write.d.ts +1 -1
- package/dist/auth-email-password/handlers/invite-create.write.d.ts +2 -2
- package/dist/auth-email-password/handlers/invite-create.write.js +4 -4
- package/dist/auth-email-password/handlers/invite-signup-complete.write.d.ts +1 -1
- package/dist/auth-email-password/handlers/login.write.d.ts +1 -1
- package/dist/auth-email-password/handlers/logout.write.d.ts +1 -1
- package/dist/auth-email-password/handlers/logout.write.js +6 -2
- package/dist/auth-email-password/handlers/request-account-unlock.write.d.ts +1 -1
- package/dist/auth-email-password/handlers/request-account-unlock.write.js +2 -4
- package/dist/auth-email-password/handlers/request-email-verification.write.d.ts +1 -1
- package/dist/auth-email-password/handlers/request-email-verification.write.js +2 -4
- package/dist/auth-email-password/handlers/request-password-reset.write.d.ts +1 -1
- package/dist/auth-email-password/handlers/request-password-reset.write.js +2 -4
- package/dist/auth-email-password/handlers/reset-password.write.d.ts +1 -1
- package/dist/auth-email-password/handlers/signup-confirm.write.d.ts +1 -1
- package/dist/auth-email-password/handlers/signup-confirm.write.js +3 -4
- package/dist/auth-email-password/handlers/signup-request.write.d.ts +1 -1
- package/dist/auth-email-password/handlers/signup-request.write.js +2 -1
- package/dist/auth-email-password/handlers/switch-tenant-mfa-gate.write.d.ts +1 -1
- package/dist/auth-email-password/handlers/token-request-handler.d.ts +2 -3
- package/dist/auth-email-password/handlers/token-request-handler.js +2 -2
- package/dist/auth-email-password/handlers/verify-email.write.d.ts +1 -1
- package/dist/auth-email-password/index.d.ts +2 -2
- package/dist/auth-email-password/index.js +1 -1
- package/dist/auth-email-password/magic-link-mail.d.ts +3 -2
- package/dist/auth-email-password/magic-link-mail.js +19 -14
- package/dist/auth-email-password/mail-notifications.d.ts +32 -0
- package/dist/auth-email-password/mail-notifications.js +116 -0
- package/dist/auth-email-password/signup-activation.d.ts +1 -1
- package/dist/auth-email-password/signup-activation.js +2 -3
- package/dist/auth-email-password/web/auth-gate.js +16 -4
- package/dist/auth-foundation/types.js +2 -8
- package/dist/auth-mfa/feature.js +4 -4
- package/dist/auth-mfa/handlers/disable.write.d.ts +1 -1
- package/dist/auth-mfa/handlers/enable-confirm-preauth.write.d.ts +1 -1
- package/dist/auth-mfa/handlers/enable-confirm.write.d.ts +1 -1
- package/dist/auth-mfa/handlers/enable-start-preauth.write.d.ts +1 -1
- package/dist/auth-mfa/handlers/enable-start.write.d.ts +1 -1
- package/dist/auth-mfa/handlers/regenerate-recovery.write.d.ts +1 -1
- package/dist/auth-mfa/handlers/verify.write.d.ts +1 -1
- package/dist/auth-mfa/hmac-token-codec.js +4 -4
- package/dist/auth-mfa/mfa-challenge-token.js +3 -3
- package/dist/auth-mfa/mfa-preauth-setup-token.js +3 -3
- package/dist/auth-mfa/mfa-setup-token.js +8 -8
- package/dist/billing-foundation/constants.d.ts +3 -3
- package/dist/billing-foundation/constants.js +5 -4
- package/dist/billing-foundation/consumer-protection/confirmation-mail.d.ts +32 -14
- package/dist/billing-foundation/consumer-protection/confirmation-mail.js +74 -32
- package/dist/billing-foundation/consumer-protection/consent-gate.d.ts +3 -0
- package/dist/billing-foundation/consumer-protection/consent-gate.js +21 -2
- package/dist/billing-foundation/consumer-protection/issue-confirmation.js +3 -3
- package/dist/billing-foundation/consumer-protection/mail-notifications.d.ts +8 -0
- package/dist/billing-foundation/consumer-protection/mail-notifications.js +95 -0
- package/dist/billing-foundation/consumer-protection/register-consent.js +2 -0
- package/dist/billing-foundation/consumer-protection/register-termination.js +11 -1
- package/dist/billing-foundation/consumer-protection/termination-mail.d.ts +25 -13
- package/dist/billing-foundation/consumer-protection/termination-mail.js +11 -23
- package/dist/billing-foundation/consumer-protection/termination-notify.js +2 -2
- package/dist/billing-foundation/consumer-protection/termination-pages.js +1 -3
- package/dist/billing-foundation/consumer-protection/termination-receipt-limit.d.ts +6 -0
- package/dist/billing-foundation/consumer-protection/termination-receipt-limit.js +34 -0
- package/dist/billing-foundation/consumer-protection/termination-record.d.ts +2 -0
- package/dist/billing-foundation/consumer-protection/termination-record.js +7 -1
- package/dist/billing-foundation/consumer-protection/termination-request.js +31 -21
- package/dist/billing-foundation/consumer-protection/termination-texts.d.ts +1 -1
- package/dist/billing-foundation/consumer-protection/termination-texts.js +6 -0
- package/dist/billing-foundation/consumer-protection/withdrawal-period.d.ts +10 -0
- package/dist/billing-foundation/consumer-protection/withdrawal-period.js +26 -0
- package/dist/billing-foundation/handlers/create-checkout-session.write.js +11 -5
- package/dist/billing-foundation/handlers/process-event.write.js +12 -8
- package/dist/billing-foundation/handlers/process-payment-event.write.js +1 -1
- package/dist/billing-foundation/i18n.js +3 -0
- package/dist/billing-foundation/subscription-tier-sync.d.ts +7 -1
- package/dist/billing-foundation/subscription-tier-sync.js +16 -9
- package/dist/billing-foundation/types.d.ts +7 -2
- package/dist/cap-counter/calendar-cap-usage-reservation.js +2 -0
- package/dist/cap-counter/enforce-cap.d.ts +18 -1
- package/dist/cap-counter/enforce-cap.js +49 -6
- package/dist/cap-counter/index.d.ts +1 -1
- package/dist/cap-counter/index.js +1 -1
- package/dist/cap-counter/with-cap-enforcement.d.ts +2 -0
- package/dist/cap-overview/handlers/tenant-caps-list.query.js +2 -1
- package/dist/cap-overview/web/cap-usage-cell.js +5 -4
- package/dist/channel-email/email-channel.d.ts +2 -2
- package/dist/channel-email/email-channel.js +101 -26
- package/dist/channel-email/feature.js +3 -3
- package/dist/channel-email/index.d.ts +1 -1
- package/dist/channel-email/index.js +1 -1
- package/dist/channel-email/pii-guard.js +10 -0
- package/dist/channel-email/smtp-transport.js +13 -0
- package/dist/channel-email/types.d.ts +8 -0
- package/dist/channel-in-app/handlers/mark-all-read.write.d.ts +1 -1
- package/dist/channel-in-app/handlers/mark-read.write.d.ts +1 -1
- package/dist/compliance-profiles/handlers/set-profile.write.d.ts +1 -1
- package/dist/config/handlers/reset.write.d.ts +1 -1
- package/dist/config/handlers/set.write.d.ts +1 -1
- package/dist/config/handlers/set.write.js +3 -4
- package/dist/config/resolver.d.ts +1 -0
- package/dist/config/resolver.js +11 -14
- package/dist/config/write-helpers.d.ts +2 -0
- package/dist/config/write-helpers.js +19 -0
- package/dist/crypto-shredding/handlers/forget-subject.write.d.ts +1 -1
- package/dist/crypto-shredding/handlers/forget-subject.write.js +29 -19
- package/dist/custom-fields/handlers/update-tenant-field.write.js +0 -1
- package/dist/custom-fields/lib/value-schema.js +10 -11
- package/dist/custom-fields/wire-user-data-rights.js +7 -6
- package/dist/data-retention/index.d.ts +1 -0
- package/dist/data-retention/index.js +1 -0
- package/dist/data-retention/run-retention-cleanup.js +6 -6
- package/dist/delivery/attachment-ref.d.ts +41 -0
- package/dist/delivery/attachment-ref.js +78 -0
- package/dist/delivery/channel-context.d.ts +8 -4
- package/dist/delivery/channel-context.js +94 -2
- package/dist/delivery/constants.d.ts +1 -0
- package/dist/delivery/constants.js +1 -0
- package/dist/delivery/delivery-service.d.ts +4 -1
- package/dist/delivery/delivery-service.js +38 -32
- package/dist/delivery/feature.js +1 -1
- package/dist/delivery/handlers/resubscribe-address.write.d.ts +1 -1
- package/dist/delivery/handlers/resubscribe-user.write.d.ts +1 -1
- package/dist/delivery/handlers/set-preference.write.d.ts +1 -1
- package/dist/delivery/handlers/unsubscribe-address.write.d.ts +1 -1
- package/dist/delivery/handlers/unsubscribe-user.write.d.ts +1 -1
- package/dist/delivery/index.d.ts +5 -2
- package/dist/delivery/index.js +4 -1
- package/dist/delivery/jobs.js +66 -14
- package/dist/delivery/notify-factory.js +4 -1
- package/dist/delivery/testing.d.ts +4 -1
- package/dist/delivery/types.d.ts +26 -14
- package/dist/delivery/unsubscribe.js +4 -1
- package/dist/derivatives-sharp/render.js +1 -18
- package/dist/document-ingest-foundation/pages.js +4 -4
- package/dist/feature-toggles/handlers/set.write.d.ts +1 -1
- package/dist/feature-toggles/toggle-sync-signal.js +4 -4
- package/dist/file-derivatives/handlers/public-variant.query.js +1 -1
- package/dist/file-foundation/index.d.ts +1 -0
- package/dist/file-foundation/index.js +1 -0
- package/dist/file-foundation/read-file-ref-content.d.ts +27 -0
- package/dist/file-foundation/read-file-ref-content.js +51 -0
- package/dist/files-tenant-data/hooks.js +4 -0
- package/dist/folders/web/folder-manager.js +3 -2
- package/dist/form-draft/db/queries/cleanup.js +4 -4
- package/dist/form-draft/db/queries/draft-count.js +2 -2
- package/dist/form-draft/db/queries/owned-file-refs.js +2 -2
- package/dist/form-draft/handlers/discard.write.d.ts +1 -1
- package/dist/form-draft/handlers/save.write.d.ts +1 -1
- package/dist/form-draft/release-file-refs.js +3 -2
- package/dist/inbound-mail-foundation/handlers/connect-account.write.js +1 -1
- package/dist/inbound-mail-foundation/handlers/ingest-message.write.js +6 -6
- package/dist/inbound-mail-foundation/handlers/scope-visibility.d.ts +1 -1
- package/dist/inbound-mail-foundation/oauth-access-token.js +8 -3
- package/dist/inbound-mail-foundation/oauth-state.js +7 -7
- package/dist/inbound-mail-foundation/types.js +4 -4
- package/dist/inbound-mail-foundation/watch-supervisor.js +24 -13
- package/dist/inbound-provider-imap/imap-client.js +4 -3
- package/dist/jobs/db/queries/stale-run-sweep.d.ts +1 -1
- package/dist/jobs/db/queries/stale-run-sweep.js +12 -5
- package/dist/jobs/feature.js +5 -3
- package/dist/jobs/handlers/list.query.js +3 -1
- package/dist/jobs/handlers/retry.write.d.ts +1 -1
- package/dist/jobs/handlers/stale-run-sweep.job.d.ts +1 -1
- package/dist/jobs/handlers/stale-run-sweep.job.js +2 -2
- package/dist/jobs/handlers/trigger.write.d.ts +1 -1
- package/dist/jobs/job-run-logger.js +32 -10
- package/dist/jobs/tenant-job-run-table.js +3 -2
- package/dist/ledger/entity.js +3 -1
- package/dist/ledger/recurring.js +1 -3
- package/dist/managed-pages/branding.d.ts +2 -0
- package/dist/managed-pages/branding.js +24 -2
- package/dist/managed-pages/feature.js +1 -0
- package/dist/managed-pages/handlers/set.write.d.ts +1 -1
- package/dist/managed-pages/i18n.js +1 -0
- package/dist/managed-pages/screens/branding-screen.js +8 -1
- package/dist/page-render/branding.d.ts +1 -0
- package/dist/page-render/branding.js +1 -0
- package/dist/page-render/markdown.js +1 -1
- package/dist/personal-access-tokens/feature.js +1 -1
- package/dist/personal-access-tokens/handlers/create.write.d.ts +1 -1
- package/dist/personal-access-tokens/handlers/list.query.js +2 -1
- package/dist/personal-access-tokens/handlers/revoke.write.d.ts +1 -1
- package/dist/personal-access-tokens/revoke-for-user.js +2 -1
- package/dist/renderer-foundation/api.d.ts +13 -13
- package/dist/renderer-foundation/api.js +1 -1
- package/dist/renderer-foundation/constants.js +2 -4
- package/dist/renderer-foundation/feature.js +7 -7
- package/dist/renderer-foundation/index.d.ts +2 -1
- package/dist/renderer-foundation/index.js +1 -0
- package/dist/renderer-foundation/types.d.ts +94 -32
- package/dist/renderer-pdf/feature.js +7 -1
- package/dist/renderer-pdf/render-table-pdf.d.ts +27 -33
- package/dist/renderer-pdf/render-table-pdf.js +95 -34
- package/dist/secrets/feature.js +4 -3
- package/dist/secrets/handlers/delete.write.d.ts +1 -1
- package/dist/secrets/handlers/set.write.d.ts +1 -1
- package/dist/secrets/secrets-context.js +15 -3
- package/dist/sessions/db/queries/cleanup.js +2 -2
- package/dist/sessions/feature.js +1 -1
- package/dist/sessions/handlers/list.query.js +2 -1
- package/dist/sessions/handlers/revoke-all-for-user.write.d.ts +1 -1
- package/dist/sessions/handlers/revoke-all-others.write.d.ts +1 -1
- package/dist/sessions/handlers/revoke.write.d.ts +1 -1
- package/dist/shared/attachment-file-name.d.ts +3 -0
- package/dist/shared/attachment-file-name.js +35 -0
- package/dist/shared/chunked-entity-migration.d.ts +1 -0
- package/dist/shared/chunked-entity-migration.js +1 -1
- package/dist/shared/index.d.ts +3 -0
- package/dist/shared/index.js +3 -0
- package/dist/shared/is-tenant-db.js +1 -1
- package/dist/shared/mail-template-body.d.ts +7 -0
- package/dist/shared/mail-template-body.js +27 -0
- package/dist/shared/sharp-input-limits.d.ts +6 -0
- package/dist/shared/sharp-input-limits.js +22 -0
- package/dist/shared/signup-handover.js +2 -2
- package/dist/subscription-stripe/plugin-methods.js +18 -8
- package/dist/subscription-stripe/verify-webhook.d.ts +1 -1
- package/dist/tags/handlers/assign-tag.write.js +3 -1
- package/dist/tags/web/tag-chip.js +2 -0
- package/dist/template-resolver/api.d.ts +30 -2
- package/dist/template-resolver/api.js +42 -6
- package/dist/template-resolver/compile-document-body.d.ts +12 -0
- package/dist/template-resolver/compile-document-body.js +205 -0
- package/dist/template-resolver/constants.d.ts +13 -4
- package/dist/template-resolver/constants.js +32 -10
- package/dist/template-resolver/document-sanitizer.d.ts +32 -0
- package/dist/template-resolver/document-sanitizer.js +201 -0
- package/dist/template-resolver/document-schema.d.ts +21 -0
- package/dist/template-resolver/document-schema.js +316 -0
- package/dist/template-resolver/feature.d.ts +20 -0
- package/dist/template-resolver/feature.js +53 -6
- package/dist/template-resolver/handlers/bundle-import.d.ts +20 -0
- package/dist/template-resolver/handlers/bundle-import.js +147 -0
- package/dist/template-resolver/handlers/collection-set.write.d.ts +1 -1
- package/dist/template-resolver/handlers/collection-set.write.js +25 -2
- package/dist/template-resolver/handlers/copy-locale.write.d.ts +19 -0
- package/dist/template-resolver/handlers/copy-locale.write.js +134 -0
- package/dist/template-resolver/handlers/diff-to-system.query.d.ts +22 -0
- package/dist/template-resolver/handlers/diff-to-system.query.js +30 -0
- package/dist/template-resolver/handlers/discard-draft.write.d.ts +13 -0
- package/dist/template-resolver/handlers/discard-draft.write.js +37 -0
- package/dist/template-resolver/handlers/export-bundle.query.d.ts +35 -0
- package/dist/template-resolver/handlers/export-bundle.query.js +118 -0
- package/dist/template-resolver/handlers/find-by-id.query.d.ts +2 -0
- package/dist/template-resolver/handlers/find-by-id.query.js +9 -6
- package/dist/template-resolver/handlers/import-bundle-system.write.d.ts +39 -0
- package/dist/template-resolver/handlers/import-bundle-system.write.js +36 -0
- package/dist/template-resolver/handlers/import-bundle.write.d.ts +40 -0
- package/dist/template-resolver/handlers/import-bundle.write.js +19 -0
- package/dist/template-resolver/handlers/layout-source.query.d.ts +31 -0
- package/dist/template-resolver/handlers/layout-source.query.js +87 -0
- package/dist/template-resolver/handlers/list.query.d.ts +4 -1
- package/dist/template-resolver/handlers/list.query.js +9 -8
- package/dist/template-resolver/handlers/override-diff.d.ts +20 -0
- package/dist/template-resolver/handlers/override-diff.js +41 -0
- package/dist/template-resolver/handlers/overview.query.d.ts +65 -0
- package/dist/template-resolver/handlers/overview.query.js +205 -0
- package/dist/template-resolver/handlers/preview.query.d.ts +12 -0
- package/dist/template-resolver/handlers/preview.query.js +24 -0
- package/dist/template-resolver/handlers/reset-to-system.write.d.ts +8 -0
- package/dist/template-resolver/handlers/reset-to-system.write.js +50 -0
- package/dist/template-resolver/handlers/resource-link-pdf.write.d.ts +8 -0
- package/dist/template-resolver/handlers/resource-link-pdf.write.js +32 -0
- package/dist/template-resolver/handlers/resource-remove.write.d.ts +9 -0
- package/dist/template-resolver/handlers/resource-remove.write.js +44 -0
- package/dist/template-resolver/handlers/resource-shared.d.ts +9 -0
- package/dist/template-resolver/handlers/resource-shared.js +14 -0
- package/dist/template-resolver/handlers/resource-upload.write.d.ts +13 -0
- package/dist/template-resolver/handlers/resource-upload.write.js +98 -0
- package/dist/template-resolver/handlers/send-test.write.d.ts +13 -0
- package/dist/template-resolver/handlers/send-test.write.js +108 -0
- package/dist/template-resolver/handlers/set.write.d.ts +5 -2
- package/dist/template-resolver/handlers/set.write.js +25 -3
- package/dist/template-resolver/handlers/shared.d.ts +9 -5
- package/dist/template-resolver/handlers/shared.js +6 -2
- package/dist/template-resolver/handlers/template-gates.d.ts +36 -0
- package/dist/template-resolver/handlers/template-gates.js +142 -0
- package/dist/template-resolver/handlers/template-write.d.ts +45 -0
- package/dist/template-resolver/handlers/template-write.js +141 -0
- package/dist/template-resolver/handlers/tenant-overrides.query.d.ts +49 -0
- package/dist/template-resolver/handlers/tenant-overrides.query.js +112 -0
- package/dist/template-resolver/handlers/toggle-status.write.d.ts +27 -2
- package/dist/template-resolver/handlers/toggle-status.write.js +47 -15
- package/dist/template-resolver/handlers/upsert-system.write.d.ts +5 -8
- package/dist/template-resolver/handlers/upsert-system.write.js +14 -59
- package/dist/template-resolver/handlers/upsert-tenant.write.d.ts +5 -8
- package/dist/template-resolver/handlers/upsert-tenant.write.js +11 -62
- package/dist/template-resolver/handlers/usage.query.d.ts +17 -0
- package/dist/template-resolver/handlers/usage.query.js +24 -0
- package/dist/template-resolver/handlers/validate.query.d.ts +74 -0
- package/dist/template-resolver/handlers/validate.query.js +80 -0
- package/dist/template-resolver/index.d.ts +19 -1
- package/dist/template-resolver/index.js +17 -1
- package/dist/template-resolver/layout-appearance.d.ts +10 -0
- package/dist/template-resolver/layout-appearance.js +26 -0
- package/dist/template-resolver/layout-regions.d.ts +23 -0
- package/dist/template-resolver/layout-regions.js +125 -0
- package/dist/template-resolver/linked-resources.d.ts +17 -0
- package/dist/template-resolver/linked-resources.js +75 -0
- package/dist/template-resolver/pdf-constants.d.ts +7 -0
- package/dist/template-resolver/pdf-constants.js +8 -0
- package/dist/template-resolver/preview-display-html.d.ts +7 -0
- package/dist/template-resolver/preview-display-html.js +27 -0
- package/dist/template-resolver/print-document.d.ts +1 -0
- package/dist/template-resolver/print-document.js +55 -0
- package/dist/template-resolver/qualified-names.d.ts +34 -0
- package/dist/template-resolver/qualified-names.js +34 -0
- package/dist/template-resolver/record-mapping.d.ts +2 -0
- package/dist/template-resolver/record-mapping.js +21 -0
- package/dist/template-resolver/resource-image.d.ts +15 -0
- package/dist/template-resolver/resource-image.js +34 -0
- package/dist/template-resolver/seeding.d.ts +14 -1
- package/dist/template-resolver/seeding.js +125 -18
- package/dist/template-resolver/store-template-resource.d.ts +64 -0
- package/dist/template-resolver/store-template-resource.js +264 -0
- package/dist/template-resolver/table.d.ts +150 -8
- package/dist/template-resolver/table.js +20 -2
- package/dist/template-resolver/template-bundle.d.ts +36 -0
- package/dist/template-resolver/template-bundle.js +60 -0
- package/dist/template-resolver/template-diff.d.ts +7 -0
- package/dist/template-resolver/template-diff.js +68 -0
- package/dist/template-resolver/template-draft.d.ts +51 -0
- package/dist/template-resolver/template-draft.js +72 -0
- package/dist/template-resolver/template-meta.d.ts +85 -0
- package/dist/template-resolver/template-meta.js +145 -0
- package/dist/template-resolver/template-preview.d.ts +103 -0
- package/dist/template-resolver/template-preview.js +418 -0
- package/dist/template-resolver/template-resources.d.ts +19 -0
- package/dist/template-resolver/template-resources.js +73 -0
- package/dist/template-resolver/template-usage.d.ts +16 -0
- package/dist/template-resolver/template-usage.js +35 -0
- package/dist/template-resolver/template-validation.d.ts +22 -0
- package/dist/template-resolver/template-validation.js +173 -0
- package/dist/template-resolver/testing.js +5 -5
- package/dist/template-resolver/user-content-table.d.ts +14 -8
- package/dist/template-resolver/web/client-plugin.d.ts +1 -0
- package/dist/template-resolver/web/client-plugin.js +12 -7
- package/dist/template-resolver/web/document-editor-extensions.d.ts +18 -0
- package/dist/template-resolver/web/document-editor-extensions.js +145 -0
- package/dist/template-resolver/web/document-editor.d.ts +4 -0
- package/dist/template-resolver/web/document-editor.js +17 -0
- package/dist/template-resolver/web/document-tiptap-editor.d.ts +3 -0
- package/dist/template-resolver/web/document-tiptap-editor.js +492 -0
- package/dist/template-resolver/web/document-toolbar-blocks.d.ts +41 -0
- package/dist/template-resolver/web/document-toolbar-blocks.js +19 -0
- package/dist/template-resolver/web/editor-handle.d.ts +3 -0
- package/dist/template-resolver/web/editor-handle.js +24 -0
- package/dist/template-resolver/web/i18n.js +91 -0
- package/dist/template-resolver/web/index.d.ts +5 -0
- package/dist/template-resolver/web/index.js +5 -0
- package/dist/template-resolver/web/placeholder-line-editor-lazy.d.ts +4 -0
- package/dist/template-resolver/web/placeholder-line-editor-lazy.js +16 -0
- package/dist/template-resolver/web/placeholder-line-editor.d.ts +17 -0
- package/dist/template-resolver/web/placeholder-line-editor.js +94 -0
- package/dist/template-resolver/web/placeholder-text.d.ts +4 -0
- package/dist/template-resolver/web/placeholder-text.js +34 -0
- package/dist/template-resolver/web/use-stable-by-key.d.ts +1 -0
- package/dist/template-resolver/web/use-stable-by-key.js +13 -0
- package/dist/tenant/handlers/add-member.write.d.ts +1 -1
- package/dist/tenant/handlers/cancel-invitation.write.d.ts +1 -1
- package/dist/tenant/handlers/create.write.d.ts +1 -1
- package/dist/tenant/handlers/remove-member.write.d.ts +1 -1
- package/dist/tenant/handlers/team-list.query.js +3 -2
- package/dist/tenant/handlers/toggle-enabled.write.d.ts +2 -2
- package/dist/tenant/handlers/update-member-roles.write.d.ts +1 -1
- package/dist/tenant/handlers/update.write.d.ts +1 -1
- package/dist/tenant/seeding.js +31 -6
- package/dist/tenant-handover/handlers/claim.write.d.ts +1 -1
- package/dist/tenant-lifecycle/handlers/cancel-destruction.write.d.ts +2 -2
- package/dist/tenant-lifecycle/handlers/cancel-destruction.write.js +2 -1
- package/dist/tenant-lifecycle/handlers/request-destruction.write.d.ts +1 -1
- package/dist/tier-engine/feature.js +9 -3
- package/dist/tier-engine/handlers/set-tenant-tier.write.d.ts +1 -1
- package/dist/user/db/queries/stream-tenant-backfill.js +4 -4
- package/dist/user/handlers/create.write.d.ts +1 -1
- package/dist/user/handlers/detail.query.js +2 -1
- package/dist/user/handlers/update.write.d.ts +1 -1
- package/dist/user/resolve-display-names.js +5 -9
- package/dist/user/seeding.js +20 -1
- package/dist/user-data-rights/db/queries/forget-cleanup.d.ts +2 -2
- package/dist/user-data-rights/email-templates.d.ts +1 -29
- package/dist/user-data-rights/email-templates.js +5 -106
- package/dist/user-data-rights/feature.js +53 -35
- package/dist/user-data-rights/handlers/cancel-deletion.write.d.ts +2 -2
- package/dist/user-data-rights/handlers/cancel-deletion.write.js +2 -1
- package/dist/user-data-rights/handlers/confirm-deletion-by-token.write.d.ts +1 -1
- package/dist/user-data-rights/handlers/lift-restriction.write.d.ts +1 -1
- package/dist/user-data-rights/handlers/request-deletion-by-email.write.d.ts +1 -1
- package/dist/user-data-rights/handlers/request-deletion.write.d.ts +1 -1
- package/dist/user-data-rights/handlers/request-deletion.write.js +5 -7
- package/dist/user-data-rights/handlers/request-export.write.d.ts +1 -1
- package/dist/user-data-rights/handlers/restrict-account.write.d.ts +1 -1
- package/dist/user-data-rights/handlers/run-forget-cleanup.write.d.ts +1 -1
- package/dist/user-data-rights/lib/default-mailers.d.ts +15 -7
- package/dist/user-data-rights/lib/default-mailers.js +100 -48
- package/dist/user-data-rights/lib/update-user-lifecycle.js +1 -1
- package/dist/user-data-rights/mail-notifications.d.ts +13 -0
- package/dist/user-data-rights/mail-notifications.js +118 -0
- package/dist/user-data-rights/web/privacy-center-screen.js +2 -1
- package/dist/user-profile/handlers/change-email.write.d.ts +1 -1
- package/dist/user-profile/web/profile-screen.js +2 -1
- package/dist/waitlist/feature.js +10 -0
- package/dist/waitlist/handlers/invite.write.d.ts +1 -1
- package/dist/waitlist/handlers/reject.write.d.ts +1 -1
- package/dist/waitlist/handlers/submit.write.d.ts +1 -1
- package/dist/waitlist/handlers/submit.write.js +3 -4
- package/dist/waitlist/mail.d.ts +19 -6
- package/dist/waitlist/mail.js +50 -14
- package/dist/waitlist/options.js +1 -1
- package/dist/workflow-runner/event-trigger.js +2 -2
- package/dist/workflow-runner/handlers/resume-run.write.js +7 -2
- package/package.json +37 -17
- package/src/admin-shell/changes.json +6 -0
- package/src/audit/changes.json +6 -0
- package/src/auth-email-password/changes.json +51 -0
- package/src/billing-foundation/changes.json +43 -0
- package/src/cap-counter/changes.json +6 -0
- package/src/channel-email/changes.json +19 -0
- package/src/config/changes.json +6 -0
- package/src/crypto-shredding/changes.json +6 -0
- package/src/custom-fields/changes.json +6 -0
- package/src/data-retention/changes.json +6 -0
- package/src/delivery/changes.json +66 -0
- package/src/file-foundation/changes.json +6 -0
- package/src/files-tenant-data/changes.json +6 -0
- package/src/inbound-mail-foundation/changes.json +6 -0
- package/src/jobs/changes.json +19 -0
- package/src/ledger/changes.json +7 -0
- package/src/managed-pages/changes.json +6 -0
- package/src/renderer-foundation/changes.json +39 -0
- package/src/renderer-pdf/changes.json +12 -0
- package/src/secrets/changes.json +6 -0
- package/src/shared/changes.json +6 -0
- package/src/subscription-stripe/changes.json +6 -0
- package/src/template-resolver/changes.json +186 -0
- package/src/tier-engine/changes.json +6 -0
- package/src/user/changes.json +6 -0
- package/src/user-data-rights/changes.json +13 -0
- package/src/user-profile/changes.json +1 -1
- package/src/waitlist/changes.json +7 -0
- package/src/workflow-runner/changes.json +6 -0
- package/dist/renderer-simple/feature.d.ts +0 -10
- package/dist/renderer-simple/feature.js +0 -42
- package/dist/renderer-simple/index.d.ts +0 -3
- package/dist/renderer-simple/index.js +0 -2
- package/dist/renderer-simple/mail-blocks.d.ts +0 -21
- package/dist/renderer-simple/mail-blocks.js +0 -107
- package/dist/renderer-simple/resolve-variables.d.ts +0 -7
- package/dist/renderer-simple/resolve-variables.js +0 -47
- package/dist/renderer-simple/simple-renderer.d.ts +0 -24
- package/dist/renderer-simple/simple-renderer.js +0 -200
- package/src/renderer-simple/changes.json +0 -27
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { createSystemDbView, createTenantDb } from "@cosmicdrift/kumiko-framework/db";
|
|
2
|
-
import { createSystemUser } from "@cosmicdrift/kumiko-framework/engine";
|
|
2
|
+
import { assertNotificationVariables, createSystemUser, } from "@cosmicdrift/kumiko-framework/engine";
|
|
3
3
|
import { createFallbackLogger } from "@cosmicdrift/kumiko-framework/logging";
|
|
4
|
-
import { createEscapeHatchReporter } from "@cosmicdrift/kumiko-framework/pipeline";
|
|
4
|
+
import { assertNotifyTargetsOwnTenant, createEscapeHatchReporter, } from "@cosmicdrift/kumiko-framework/pipeline";
|
|
5
5
|
import { bridgeStub } from "@cosmicdrift/kumiko-framework/testing/handler-context";
|
|
6
6
|
import { generateId } from "@cosmicdrift/kumiko-framework/utils";
|
|
7
|
+
import { fileContextFromResolver } from "../file-foundation/index.js";
|
|
7
8
|
import { redactErrorText } from "../shared/index.js";
|
|
8
9
|
import { hashUnsubscribeAddress } from "./address-opt-out.js";
|
|
9
10
|
import { appendAttemptEvent, logAttempt } from "./attempt-log.js";
|
|
@@ -25,12 +26,8 @@ export function collectChannels(registry) {
|
|
|
25
26
|
return { name: usage.entityName, ...usage.options };
|
|
26
27
|
});
|
|
27
28
|
}
|
|
28
|
-
// `collectRenderers` entfernt 2026-05-19: notificationRenderer-Extension-Point
|
|
29
|
-
// wurde nie konsumiert (channel-email nimmt renderer als Konstruktor-Option,
|
|
30
|
-
// nicht aus Extension-Usages). Multi-Kind-Plugin-Pool lebt jetzt im
|
|
31
|
-
// `renderer-foundation`-Bundle via `collectRendererPlugins`.
|
|
32
29
|
export function createDeliveryService(options) {
|
|
33
|
-
const { db, registry, sseBroker, channels, tenantUserIdsQuery, rateLimit, isChannelKilled, idempotencyRedis, jobRunner, secrets, escapeHatchAuditSink, log, } = options;
|
|
30
|
+
const { db, registry, sseBroker, channels, tenantUserIdsQuery, rateLimit, isChannelKilled, idempotencyRedis, jobRunner, secrets, filesForTenant, tenantConfigFor, escapeHatchAuditSink, log, } = options;
|
|
34
31
|
const idemRedis = idempotencyRedis ?? rateLimit?.redis;
|
|
35
32
|
const logError = createFallbackLogger("delivery", log);
|
|
36
33
|
// Rate limit check: atomic INCR + TTL + over-limit rollback via server-side
|
|
@@ -96,9 +93,9 @@ export function createDeliveryService(options) {
|
|
|
96
93
|
const routedChannelCount = channels.filter((channel) => route[channel.name]).length;
|
|
97
94
|
return routedChannelCount === 1 ? idempotencyKey : undefined;
|
|
98
95
|
}
|
|
99
|
-
async function resolveUserIdsForTenant(tenantId) {
|
|
96
|
+
async function resolveUserIdsForTenant(run, tenantId) {
|
|
100
97
|
if (!tenantUserIdsQuery) {
|
|
101
|
-
throw new Error("Tenant broadcast requires tenantUserIdsQuery in DeliveryServiceOptions");
|
|
98
|
+
throw new Error("Tenant broadcast (to: { tenant }) requires the tenant feature (default wiring) or an explicit tenantUserIdsQuery in DeliveryServiceOptions");
|
|
102
99
|
}
|
|
103
100
|
const handler = registry.getQueryHandler(tenantUserIdsQuery);
|
|
104
101
|
if (!handler) {
|
|
@@ -117,7 +114,7 @@ export function createDeliveryService(options) {
|
|
|
117
114
|
// path a cross-tenant read the dispatcher never allows. Cross-tenant reach only
|
|
118
115
|
// exists through the grant-carrying systemDb view below.
|
|
119
116
|
const isSystem = registry.isHandlerSystemScoped(tenantUserIdsQuery);
|
|
120
|
-
const tenantDb = createTenantDb(db, tenantId, "tenant", undefined, undefined, undefined, {
|
|
117
|
+
const tenantDb = createTenantDb(run.db, tenantId, "tenant", undefined, undefined, undefined, {
|
|
121
118
|
report,
|
|
122
119
|
});
|
|
123
120
|
// The dispatcher grants a systemScope handler's ctx.systemDb.unsafeRaw() ungated —
|
|
@@ -125,7 +122,7 @@ export function createDeliveryService(options) {
|
|
|
125
122
|
// caller. Only the systemDb view's own source TenantDb carries the grant, so it
|
|
126
123
|
// can't leak onto ctx.db.
|
|
127
124
|
const systemDbSourceTenantDb = isSystem
|
|
128
|
-
? createTenantDb(db, tenantId, "system", undefined, undefined, undefined, {
|
|
125
|
+
? createTenantDb(run.db, tenantId, "system", undefined, undefined, undefined, {
|
|
129
126
|
report,
|
|
130
127
|
unsafeRaw: { reason: `r.systemScope() handler "${tenantUserIdsQuery}"` },
|
|
131
128
|
})
|
|
@@ -157,7 +154,7 @@ export function createDeliveryService(options) {
|
|
|
157
154
|
// attempts instead append a queued event up front and a terminal event from
|
|
158
155
|
// the send job — see deliverViaChannel + jobs.ts.
|
|
159
156
|
async function logDelivery(run, entry) {
|
|
160
|
-
const attemptId = await logAttempt(db, registry, entry);
|
|
157
|
+
const attemptId = await logAttempt(run.db, registry, entry);
|
|
161
158
|
recordDelivery(run, entry, attemptId);
|
|
162
159
|
}
|
|
163
160
|
// The stored/returned error is only the code; the redacted message goes to the log.
|
|
@@ -199,7 +196,7 @@ export function createDeliveryService(options) {
|
|
|
199
196
|
error: null,
|
|
200
197
|
priority,
|
|
201
198
|
};
|
|
202
|
-
await appendAttemptEvent(db, registry, deliveryAttemptId, queuedEntry);
|
|
199
|
+
await appendAttemptEvent(run.db, registry, deliveryAttemptId, queuedEntry);
|
|
203
200
|
await run.jobDispatcher.dispatch(channel.render ? DeliveryJobs.render : DeliveryJobs.send, {
|
|
204
201
|
channelName: channel.name,
|
|
205
202
|
address,
|
|
@@ -276,7 +273,7 @@ export function createDeliveryService(options) {
|
|
|
276
273
|
// route address with no user account) — same suppression rule either way:
|
|
277
274
|
// no blind-index key configured means no hash, so nothing to look up. One
|
|
278
275
|
// query per notify() call, not per recipient and channel.
|
|
279
|
-
async function loadSuppressedAddresses(tenantId, notificationType, priority, addresses) {
|
|
276
|
+
async function loadSuppressedAddresses(run, tenantId, notificationType, priority, addresses) {
|
|
280
277
|
// Critical priority skips address opt-outs, same rule as user preferences.
|
|
281
278
|
if (priority === "critical")
|
|
282
279
|
return new Set();
|
|
@@ -286,32 +283,32 @@ export function createDeliveryService(options) {
|
|
|
286
283
|
if (addressHash !== undefined)
|
|
287
284
|
addressHashes.add(addressHash);
|
|
288
285
|
}
|
|
289
|
-
return selectOptedOutAddresses(db, tenantId, notificationType, [...addressHashes]);
|
|
286
|
+
return selectOptedOutAddresses(run.db, tenantId, notificationType, [...addressHashes]);
|
|
290
287
|
}
|
|
291
288
|
function isAddressSuppressed(suppressed, address, channelName) {
|
|
292
289
|
const addressHash = hashUnsubscribeAddress(address);
|
|
293
290
|
return addressHash !== undefined && suppressed.has(addressOptOutKey(addressHash, channelName));
|
|
294
291
|
}
|
|
295
292
|
const recipientChannelKey = (userId, channelName) => `${userId}|${channelName}`;
|
|
296
|
-
async function isChannelDeliverable(userId, tenantId, notificationType, channelName) {
|
|
293
|
+
async function isChannelDeliverable(run, userId, tenantId, notificationType, channelName) {
|
|
297
294
|
if (isChannelKilled && (await isChannelKilled(tenantId, channelName)))
|
|
298
295
|
return false;
|
|
299
|
-
return isChannelEnabled(userId, tenantId, notificationType, channelName);
|
|
296
|
+
return isChannelEnabled(run, userId, tenantId, notificationType, channelName);
|
|
300
297
|
}
|
|
301
298
|
// Resolves every user's channel addresses up front so the opt-out lookup can
|
|
302
299
|
// cover them all at once. A resolve that throws stays out of the map: the
|
|
303
300
|
// delivery loop resolves it again and reports channel_error as before.
|
|
304
301
|
// Channels the kill switch or the user's preferences disable are never resolved
|
|
305
302
|
// here: resolve may have side effects and deliverToUser skips those channels.
|
|
306
|
-
async function prefetchUserAddresses(userIds, tenantId, notificationType) {
|
|
303
|
+
async function prefetchUserAddresses(run, userIds, tenantId, notificationType) {
|
|
307
304
|
const addresses = new Map();
|
|
308
|
-
const channelCtx = buildChannelContext(db, registry, sseBroker, tenantId, secrets);
|
|
305
|
+
const channelCtx = buildChannelContext(run.db, registry, sseBroker, tenantId, secrets, tenantConfigFor?.(tenantId), run.filesForTenant);
|
|
309
306
|
for (const userId of userIds) {
|
|
310
307
|
for (const channel of channels) {
|
|
311
308
|
if (!channel.resolve)
|
|
312
309
|
continue;
|
|
313
310
|
try {
|
|
314
|
-
if (!(await isChannelDeliverable(userId, tenantId, notificationType, channel.name))) {
|
|
311
|
+
if (!(await isChannelDeliverable(run, userId, tenantId, notificationType, channel.name))) {
|
|
315
312
|
continue;
|
|
316
313
|
}
|
|
317
314
|
addresses.set(recipientChannelKey(userId, channel.name), await channel.resolve(userId, channelCtx));
|
|
@@ -332,8 +329,8 @@ export function createDeliveryService(options) {
|
|
|
332
329
|
// { type: "*", channel: "inApp", enabled: false } disables inApp globally
|
|
333
330
|
// { type: "orderAssigned", channel: "*", enabled: true } enables orderAssigned everywhere
|
|
334
331
|
// → orderAssigned on inApp: disabled (conservative) unless an exact entry overrides.
|
|
335
|
-
async function isChannelEnabled(userId, tenantId, notificationType, channelName) {
|
|
336
|
-
const prefs = await selectNotificationPreferences(db, tenantId, userId, notificationType, channelName);
|
|
332
|
+
async function isChannelEnabled(run, userId, tenantId, notificationType, channelName) {
|
|
333
|
+
const prefs = await selectNotificationPreferences(run.db, tenantId, userId, notificationType, channelName);
|
|
337
334
|
if (prefs.length === 0)
|
|
338
335
|
return true;
|
|
339
336
|
// Exact match (both specific) wins over any wildcard
|
|
@@ -345,7 +342,7 @@ export function createDeliveryService(options) {
|
|
|
345
342
|
return !prefs.some((p) => p.enabled === false);
|
|
346
343
|
}
|
|
347
344
|
async function deliverToUser(userId, notificationType, data, tenantId, priority, run, prefetchedAddresses, suppressed) {
|
|
348
|
-
const channelCtx = buildChannelContext(db, registry, sseBroker, tenantId, secrets);
|
|
345
|
+
const channelCtx = buildChannelContext(run.db, registry, sseBroker, tenantId, secrets, tenantConfigFor?.(tenantId), run.filesForTenant);
|
|
349
346
|
for (const channel of channels) {
|
|
350
347
|
// Route-only channel (no per-user address): not a user-notification target.
|
|
351
348
|
if (!channel.resolve)
|
|
@@ -370,7 +367,7 @@ export function createDeliveryService(options) {
|
|
|
370
367
|
}
|
|
371
368
|
// Check preferences (critical priority skips preference check)
|
|
372
369
|
if (priority !== "critical") {
|
|
373
|
-
const enabled = await isChannelEnabled(userId, tenantId, notificationType, channel.name);
|
|
370
|
+
const enabled = await isChannelEnabled(run, userId, tenantId, notificationType, channel.name);
|
|
374
371
|
if (!enabled) {
|
|
375
372
|
await logDelivery(run, {
|
|
376
373
|
tenantId,
|
|
@@ -464,8 +461,8 @@ export function createDeliveryService(options) {
|
|
|
464
461
|
}
|
|
465
462
|
}
|
|
466
463
|
async function deliverDirect(route, notificationType, data, tenantId, priority, recipientId, run) {
|
|
467
|
-
const channelCtx = buildChannelContext(db, registry, sseBroker, tenantId, secrets);
|
|
468
|
-
const suppressed = await loadSuppressedAddresses(tenantId, notificationType, priority, Object.values(route));
|
|
464
|
+
const channelCtx = buildChannelContext(run.db, registry, sseBroker, tenantId, secrets, tenantConfigFor?.(tenantId), run.filesForTenant);
|
|
465
|
+
const suppressed = await loadSuppressedAddresses(run, tenantId, notificationType, priority, Object.values(route));
|
|
469
466
|
// Direct routing skips preferences (no user account) but NOT rate limit
|
|
470
467
|
// — direct sends can still be abused (webhook replays, test harnesses).
|
|
471
468
|
for (const channel of channels) {
|
|
@@ -549,29 +546,38 @@ export function createDeliveryService(options) {
|
|
|
549
546
|
userIds = [to];
|
|
550
547
|
}
|
|
551
548
|
else if ("tenant" in to) {
|
|
552
|
-
userIds = await resolveUserIdsForTenant(to.tenant);
|
|
549
|
+
userIds = await resolveUserIdsForTenant(run, to.tenant);
|
|
553
550
|
}
|
|
554
551
|
else {
|
|
555
552
|
userIds = to;
|
|
556
553
|
}
|
|
557
554
|
const prefetchedAddresses = priority === "critical"
|
|
558
555
|
? new Map()
|
|
559
|
-
: await prefetchUserAddresses(userIds, tenantId, notificationType);
|
|
560
|
-
const suppressed = await loadSuppressedAddresses(tenantId, notificationType, priority, [...prefetchedAddresses.values()].filter((address) => !!address));
|
|
556
|
+
: await prefetchUserAddresses(run, userIds, tenantId, notificationType);
|
|
557
|
+
const suppressed = await loadSuppressedAddresses(run, tenantId, notificationType, priority, [...prefetchedAddresses.values()].filter((address) => !!address));
|
|
561
558
|
for (const userId of userIds) {
|
|
562
559
|
await deliverToUser(userId, notificationType, data, tenantId, priority, run, prefetchedAddresses, suppressed);
|
|
563
560
|
}
|
|
564
561
|
}
|
|
565
562
|
}
|
|
563
|
+
function dispatched(run) {
|
|
564
|
+
return { kind: "dispatched", deliveries: run.deliveries };
|
|
565
|
+
}
|
|
566
566
|
return {
|
|
567
|
-
async notify(notificationType, options, _user, tenantId, jobDispatcher) {
|
|
567
|
+
async notify(notificationType, options, _user, tenantId, jobDispatcher, deliveryDb, fileProviderResolver) {
|
|
568
|
+
assertNotifyTargetsOwnTenant(options, tenantId);
|
|
569
|
+
assertNotificationVariables(registry, notificationType, options.data);
|
|
568
570
|
const { route, idempotencyKey } = options;
|
|
569
571
|
const priority = options.priority ?? "normal";
|
|
570
572
|
const run = {
|
|
573
|
+
db: deliveryDb ?? db,
|
|
571
574
|
deliveries: [],
|
|
572
575
|
jobDispatcher: options.immediate ? undefined : (jobDispatcher ?? jobRunner),
|
|
573
576
|
locale: options.locale,
|
|
574
577
|
jobIdempotencyKey: jobIdempotencyKeyFor(idempotencyKey, route),
|
|
578
|
+
filesForTenant: fileProviderResolver === undefined
|
|
579
|
+
? filesForTenant
|
|
580
|
+
: (fileTenantId) => fileContextFromResolver(fileProviderResolver, fileTenantId),
|
|
575
581
|
};
|
|
576
582
|
if (idempotencyKey) {
|
|
577
583
|
const first = await claimIdempotency(tenantId, idempotencyKey);
|
|
@@ -586,7 +592,7 @@ export function createDeliveryService(options) {
|
|
|
586
592
|
error: "duplicate_idempotency_key",
|
|
587
593
|
priority,
|
|
588
594
|
});
|
|
589
|
-
return
|
|
595
|
+
return dispatched(run);
|
|
590
596
|
}
|
|
591
597
|
}
|
|
592
598
|
try {
|
|
@@ -602,7 +608,7 @@ export function createDeliveryService(options) {
|
|
|
602
608
|
throw err;
|
|
603
609
|
}
|
|
604
610
|
await releaseIdempotencyIfNothingDelivered(tenantId, idempotencyKey, run);
|
|
605
|
-
return
|
|
611
|
+
return dispatched(run);
|
|
606
612
|
},
|
|
607
613
|
};
|
|
608
614
|
}
|
package/dist/delivery/feature.js
CHANGED
|
@@ -19,7 +19,7 @@ export function createDeliveryFeature(options) {
|
|
|
19
19
|
const resolvedAccess = options?.access ?? access.admin;
|
|
20
20
|
const attemptLogRetentionDays = resolveAttemptLogRetentionDays(options?.attemptLogRetentionDays);
|
|
21
21
|
return defineFeature("delivery", (r) => {
|
|
22
|
-
r.describe(
|
|
22
|
+
r.describe('The notification dispatch core: call `ctx.notify(notificationType, { to, route, data, priority, idempotencyKey })` from any handler to fan out a notification across all registered channels (email, in-app, push). Inside a write handler the delivery runs after the write has committed (the call returns `{ kind: "deferred" }`, a rolled-back write sends nothing); `immediate: true` delivers synchronously and returns `{ kind: "dispatched", deliveries }`. It stores per-user channel preferences in the `notification-preference` entity, opt-outs for no-account recipient addresses in `notification-address-opt-out` (keyed by a blind-index hash, never the plaintext address), logs every attempt to `store_delivery_attempts`, and enforces idempotency and rate-limiting \u2014 add `channel-email`, `channel-in-app`, or `channel-push` on top to actually send anything. Unsubscribe/resubscribe links are served by `createUnsubscribeRoutes({ secret })` mounted via the app\'s `extraRoutes` at `/api/delivery/unsubscribe` and `/api/delivery/resubscribe`: `GET /unsubscribe?token=` renders a confirmation page (no write), `POST /unsubscribe` performs the opt-out (RFC 8058 one-click, token from the form body or query), `POST /resubscribe` undoes it (JSON `{token}` or form body only, never the query — reachable by a mail-client link prefetcher) — sign links with `signUnsubscribeToken` / `signAddressUnsubscribeToken` using the same secret. `channel-email` sets `List-Unsubscribe` / `List-Unsubscribe-Post` automatically when a message\'s `data.unsubscribeUrl` points at the unsubscribe route.');
|
|
23
23
|
r.uiHints({
|
|
24
24
|
displayLabel: "Notifications \u00b7 Dispatch Core",
|
|
25
25
|
category: "notifications",
|
|
@@ -6,4 +6,4 @@ export declare const resubscribeAddressWrite: import("@cosmicdrift/kumiko-types/
|
|
|
6
6
|
}, z.core.$strip>, {
|
|
7
7
|
notificationType: string;
|
|
8
8
|
channel: string;
|
|
9
|
-
}, import("@cosmicdrift/kumiko-types/event-type-map").KumikoEventTypeMap>;
|
|
9
|
+
}, import("@cosmicdrift/kumiko-types/event-type-map").KumikoEventTypeMap, undefined>;
|
|
@@ -6,4 +6,4 @@ export declare const resubscribeUserWrite: import("@cosmicdrift/kumiko-types/def
|
|
|
6
6
|
}, z.core.$strip>, {
|
|
7
7
|
notificationType: string;
|
|
8
8
|
channel: string;
|
|
9
|
-
}, import("@cosmicdrift/kumiko-types/event-type-map").KumikoEventTypeMap>;
|
|
9
|
+
}, import("@cosmicdrift/kumiko-types/event-type-map").KumikoEventTypeMap, undefined>;
|
|
@@ -7,4 +7,4 @@ export declare const setPreferenceWrite: import("@cosmicdrift/kumiko-types/defin
|
|
|
7
7
|
notificationType: string;
|
|
8
8
|
channel: string;
|
|
9
9
|
enabled: boolean;
|
|
10
|
-
}, import("@cosmicdrift/kumiko-types/event-type-map").KumikoEventTypeMap>;
|
|
10
|
+
}, import("@cosmicdrift/kumiko-types/event-type-map").KumikoEventTypeMap, undefined>;
|
|
@@ -6,4 +6,4 @@ export declare const unsubscribeAddressWrite: import("@cosmicdrift/kumiko-types/
|
|
|
6
6
|
}, z.core.$strip>, {
|
|
7
7
|
notificationType: string;
|
|
8
8
|
channel: string;
|
|
9
|
-
}, import("@cosmicdrift/kumiko-types/event-type-map").KumikoEventTypeMap>;
|
|
9
|
+
}, import("@cosmicdrift/kumiko-types/event-type-map").KumikoEventTypeMap, undefined>;
|
|
@@ -6,4 +6,4 @@ export declare const unsubscribeUserWrite: import("@cosmicdrift/kumiko-types/def
|
|
|
6
6
|
}, z.core.$strip>, {
|
|
7
7
|
notificationType: string;
|
|
8
8
|
channel: string;
|
|
9
|
-
}, import("@cosmicdrift/kumiko-types/event-type-map").KumikoEventTypeMap>;
|
|
9
|
+
}, import("@cosmicdrift/kumiko-types/event-type-map").KumikoEventTypeMap, undefined>;
|
package/dist/delivery/index.d.ts
CHANGED
|
@@ -1,13 +1,16 @@
|
|
|
1
|
+
export { sanitizePdfAttachmentFileName } from "../shared/index.js";
|
|
1
2
|
export { hashUnsubscribeAddress } from "./address-opt-out.js";
|
|
3
|
+
export { DELIVERY_ENVELOPE_DATA_KEYS, type EmailAttachmentRef, emailAttachmentRefSchema, type FileAttachmentRef, fileAttachmentRefSchema, type InlineAttachmentRef, inlineAttachmentRefSchema, MAX_EMAIL_ATTACHMENTS, MAX_RENDERED_PDF_VARIABLES_BYTES, type RenderedPdfAttachmentRef, renderedPdfAttachmentRefSchema, toAttachmentRef, withoutDeliveryEnvelopeKeys, } from "./attachment-ref.js";
|
|
2
4
|
export { type AttemptLogRetentionOptions, DEFAULT_ATTEMPT_LOG_RETENTION_DAYS, runAttemptLogRetention, } from "./attempt-log-retention.js";
|
|
5
|
+
export { createEmailAttachmentReader, createTemplateMailRenderer } from "./channel-context.js";
|
|
3
6
|
export { type ChatWebhookChannelOptions, chatMessageText, createChatWebhookChannel, resolveChatWebhookTarget, toChannelResult, } from "./chat-webhook-channel.js";
|
|
4
7
|
export { type ChatSendFailureCode, type ChatSendResult, type ChatWebhookRequest, type ChatWebhookResponse, type ChatWebhookTarget, chatConnectionNameSchema, chatWebhookUrlSchema, checkChatWebhookTarget, DEFAULT_CHAT_TIMEOUT_MS, postChatWebhook, truncateChars, } from "./chat-webhook-sender.js";
|
|
5
8
|
export type { DeliveryStatusValue } from "./constants.js";
|
|
6
|
-
export { DELIVERY_ATTEMPT_ADDRESS_ERASED_EVENT, DELIVERY_CHANNEL_EXTENSION, DELIVERY_FEATURE, DELIVERY_LOG_SCREEN_ID, DELIVERY_UNSUBSCRIBE_PATH, DeliveryErrors, DeliveryHandlers, DeliveryJobs, DeliveryQueries, DeliveryStatus, } from "./constants.js";
|
|
9
|
+
export { DELIVERY_ATTEMPT_ADDRESS_ERASED_EVENT, DELIVERY_CHANNEL_EXTENSION, DELIVERY_FEATURE, DELIVERY_LOG_SCREEN_ID, DELIVERY_UNSUBSCRIBE_PATH, DeliveryErrors, DeliveryHandlers, DeliveryJobs, DeliveryQueries, DeliveryStatus, EMAIL_CHANNEL_NAME, } from "./constants.js";
|
|
7
10
|
export { collectChannels, createDeliveryService, type DeliveryServiceOptions, type KillSwitchResolver, type RateLimitConfig, } from "./delivery-service.js";
|
|
8
11
|
export { createDeliveryFeature, type DeliveryFeatureOptions } from "./feature.js";
|
|
9
12
|
export { createDeliveryNotifyFactory } from "./notify-factory.js";
|
|
10
13
|
export { deliveryAttemptsTable, notificationAddressOptOutEntity, notificationAddressOptOutsTable, notificationPreferenceEntity, notificationPreferencesTable, } from "./tables.js";
|
|
11
14
|
export { type CreateDeliveryTestContextOptions, createDeliveryTestContext } from "./testing.js";
|
|
12
|
-
export { type ChannelContext, type ChannelMessage, type ChannelResult, type DeliveryChannel, type DeliveryChannelMode, type DeliveryChannelPlugin, type DeliveryLogEntry, type DeliveryService, isDeliveryChannelPlugin, type
|
|
15
|
+
export { type AttachmentOrigin, type ChannelContext, type ChannelMessage, type ChannelResult, type DeliveryChannel, type DeliveryChannelMode, type DeliveryChannelPlugin, type DeliveryLogEntry, type DeliveryService, isDeliveryChannelPlugin, type RenderedMessage, } from "./types.js";
|
|
13
16
|
export { type AddressUnsubscribeTokenPayload, createUnsubscribeRoutes, DELIVERY_UNSUBSCRIBE_ONE_CLICK_HEADER_VALUE, signAddressUnsubscribeToken, signUnsubscribeToken, type UnsubscribeRouteOptions, type UnsubscribeTokenPayload, } from "./unsubscribe.js";
|
package/dist/delivery/index.js
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
|
+
export { sanitizePdfAttachmentFileName } from "../shared/index.js";
|
|
1
2
|
export { hashUnsubscribeAddress } from "./address-opt-out.js";
|
|
3
|
+
export { DELIVERY_ENVELOPE_DATA_KEYS, emailAttachmentRefSchema, fileAttachmentRefSchema, inlineAttachmentRefSchema, MAX_EMAIL_ATTACHMENTS, MAX_RENDERED_PDF_VARIABLES_BYTES, renderedPdfAttachmentRefSchema, toAttachmentRef, withoutDeliveryEnvelopeKeys, } from "./attachment-ref.js";
|
|
2
4
|
export { DEFAULT_ATTEMPT_LOG_RETENTION_DAYS, runAttemptLogRetention, } from "./attempt-log-retention.js";
|
|
5
|
+
export { createEmailAttachmentReader, createTemplateMailRenderer } from "./channel-context.js";
|
|
3
6
|
export { chatMessageText, createChatWebhookChannel, resolveChatWebhookTarget, toChannelResult, } from "./chat-webhook-channel.js";
|
|
4
7
|
export { chatConnectionNameSchema, chatWebhookUrlSchema, checkChatWebhookTarget, DEFAULT_CHAT_TIMEOUT_MS, postChatWebhook, truncateChars, } from "./chat-webhook-sender.js";
|
|
5
|
-
export { DELIVERY_ATTEMPT_ADDRESS_ERASED_EVENT, DELIVERY_CHANNEL_EXTENSION, DELIVERY_FEATURE, DELIVERY_LOG_SCREEN_ID, DELIVERY_UNSUBSCRIBE_PATH, DeliveryErrors, DeliveryHandlers, DeliveryJobs, DeliveryQueries, DeliveryStatus, } from "./constants.js";
|
|
8
|
+
export { DELIVERY_ATTEMPT_ADDRESS_ERASED_EVENT, DELIVERY_CHANNEL_EXTENSION, DELIVERY_FEATURE, DELIVERY_LOG_SCREEN_ID, DELIVERY_UNSUBSCRIBE_PATH, DeliveryErrors, DeliveryHandlers, DeliveryJobs, DeliveryQueries, DeliveryStatus, EMAIL_CHANNEL_NAME, } from "./constants.js";
|
|
6
9
|
export { collectChannels, createDeliveryService, } from "./delivery-service.js";
|
|
7
10
|
export { createDeliveryFeature } from "./feature.js";
|
|
8
11
|
export { createDeliveryNotifyFactory } from "./notify-factory.js";
|
package/dist/delivery/jobs.js
CHANGED
|
@@ -6,6 +6,8 @@
|
|
|
6
6
|
// these handlers append the terminal sent/failed event on the same stream.
|
|
7
7
|
import { InternalError } from "@cosmicdrift/kumiko-framework/errors";
|
|
8
8
|
import * as z from "zod";
|
|
9
|
+
import { fileContextForTenant } from "../file-foundation/index.js";
|
|
10
|
+
import { emailAttachmentRefSchema, MAX_EMAIL_ATTACHMENTS, toAttachmentRef, } from "./attachment-ref.js";
|
|
9
11
|
import { appendAttemptEvent } from "./attempt-log.js";
|
|
10
12
|
import { buildChannelContext } from "./channel-context.js";
|
|
11
13
|
import { DeliveryJobs, deliveryIdempotencyRedisKey, deliveryPriorityRank } from "./constants.js";
|
|
@@ -30,7 +32,14 @@ const renderJobPayloadSchema = z.object({
|
|
|
30
32
|
});
|
|
31
33
|
const sendJobPayloadSchema = renderJobPayloadSchema.extend({
|
|
32
34
|
rendered: z
|
|
33
|
-
.object({
|
|
35
|
+
.object({
|
|
36
|
+
html: z.string(),
|
|
37
|
+
subject: z.string(),
|
|
38
|
+
text: z.string().optional(),
|
|
39
|
+
fromName: z.string().optional(),
|
|
40
|
+
replyTo: z.string().optional(),
|
|
41
|
+
attachments: z.array(emailAttachmentRefSchema).max(MAX_EMAIL_ATTACHMENTS).optional(),
|
|
42
|
+
})
|
|
34
43
|
.optional(),
|
|
35
44
|
});
|
|
36
45
|
function requireTenantScopedDeps(ctx, tenantId) {
|
|
@@ -39,15 +48,30 @@ function requireTenantScopedDeps(ctx, tenantId) {
|
|
|
39
48
|
throw new Error("delivery job: missing registry in job context");
|
|
40
49
|
// delivery is r.systemScope()'d, so JobContext.systemDb is guaranteed; unsafeRaw
|
|
41
50
|
// hands the underlying DbConnection to both — safe since they filter by tenantId themselves.
|
|
51
|
+
// Attachment lookups read only this tenant's FileRefs, plus the system tenant's for inline images.
|
|
42
52
|
if (!ctx.systemDb) {
|
|
43
53
|
throw new InternalError({
|
|
44
54
|
message: "delivery job: ctx.systemDb missing on a system-scoped job",
|
|
45
55
|
});
|
|
46
56
|
}
|
|
57
|
+
// Also makes ctx.config (built for the job's tenant) the accessor of the payload tenant;
|
|
58
|
+
// job contexts carry no configFor.
|
|
47
59
|
ctx.systemDb.assertTenantMatch(tenantId);
|
|
48
|
-
const db = ctx.systemDb.unsafeRaw("
|
|
60
|
+
const db = ctx.systemDb.unsafeRaw("attempt append and channel context filter by this job's tenantId; attachment lookup reads only the job tenant's FileRefs plus, for inline images, the system tenant's"); // @cast-boundary db-operator — DbRunner narrows to DbConnection, jobs never run inside a DbTx
|
|
49
61
|
return { db, registry };
|
|
50
62
|
}
|
|
63
|
+
function toSendRendered(rendered) {
|
|
64
|
+
return {
|
|
65
|
+
html: rendered.html,
|
|
66
|
+
subject: rendered.subject,
|
|
67
|
+
...(rendered.text !== undefined && { text: rendered.text }),
|
|
68
|
+
...(rendered.fromName !== undefined && { fromName: rendered.fromName }),
|
|
69
|
+
...(rendered.replyTo !== undefined && { replyTo: rendered.replyTo }),
|
|
70
|
+
...(rendered.attachments !== undefined && {
|
|
71
|
+
attachments: rendered.attachments.map(toAttachmentRef),
|
|
72
|
+
}),
|
|
73
|
+
};
|
|
74
|
+
}
|
|
51
75
|
function resolveChannel(registry, name) {
|
|
52
76
|
const channel = collectChannels(registry).find((c) => c.name === name);
|
|
53
77
|
if (!channel)
|
|
@@ -76,12 +100,11 @@ function entryFor(p, status, error, address, confirmed) {
|
|
|
76
100
|
...(confirmed === false && { confirmed }),
|
|
77
101
|
};
|
|
78
102
|
}
|
|
79
|
-
//
|
|
80
|
-
//
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
if (!ctx.finalAttempt || !p.idempotencyKey)
|
|
103
|
+
// Assumes ctx.redis is the handle the service claimed on; runProdApp wires the same
|
|
104
|
+
// instance into both.
|
|
105
|
+
async function releaseIdempotencyKey(ctx, p) {
|
|
106
|
+
// skip: no idempotency key was claimed for this notification
|
|
107
|
+
if (!p.idempotencyKey)
|
|
85
108
|
return;
|
|
86
109
|
if (!ctx.redis) {
|
|
87
110
|
ctx.log.warn("delivery: idempotency key not released, job context has no redis", {
|
|
@@ -98,6 +121,26 @@ async function releaseIdempotencyOnTerminalFailure(ctx, p) {
|
|
|
98
121
|
});
|
|
99
122
|
}
|
|
100
123
|
}
|
|
124
|
+
// Only the final retry frees the key: earlier failures are retried by the runner and
|
|
125
|
+
// must still dedup.
|
|
126
|
+
async function releaseIdempotencyOnTerminalFailure(ctx, p) {
|
|
127
|
+
// skip: not the last attempt, the runner retries and the key must keep deduplicating
|
|
128
|
+
if (!ctx.finalAttempt)
|
|
129
|
+
return;
|
|
130
|
+
await releaseIdempotencyKey(ctx, p);
|
|
131
|
+
}
|
|
132
|
+
// Covers every throw after the payload parsed, not just the attempt-logged ones: a
|
|
133
|
+
// missing registry or unknown channel on the last attempt would otherwise hold the key
|
|
134
|
+
// until its TTL.
|
|
135
|
+
async function releasingKeyOnTerminalFailure(ctx, p, run) {
|
|
136
|
+
try {
|
|
137
|
+
return await run();
|
|
138
|
+
}
|
|
139
|
+
catch (err) {
|
|
140
|
+
await releaseIdempotencyOnTerminalFailure(ctx, p);
|
|
141
|
+
throw err;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
101
144
|
// The job runner stores err.message in the run row, and BullMQ keeps failedReason and
|
|
102
145
|
// the stack trace in Redis in plain text, so the rethrown error carries only the code.
|
|
103
146
|
// The redacted message goes to the log.
|
|
@@ -108,7 +151,6 @@ async function failAttempt(ctx, db, registry, p, code, err) {
|
|
|
108
151
|
channel: p.channelName,
|
|
109
152
|
});
|
|
110
153
|
await appendAttemptEvent(db, registry, p.deliveryAttemptId, entryFor(p, "failed", code, p.address));
|
|
111
|
-
await releaseIdempotencyOnTerminalFailure(ctx, p);
|
|
112
154
|
throw new Error(code);
|
|
113
155
|
}
|
|
114
156
|
// Render the message and hand off to delivery.send. On failure: record the
|
|
@@ -116,10 +158,13 @@ async function failAttempt(ctx, db, registry, p, code, err) {
|
|
|
116
158
|
// the send step is never reached, so a stuck render can't half-send.
|
|
117
159
|
export const deliveryRenderJob = async (payload, ctx) => {
|
|
118
160
|
const p = renderJobPayloadSchema.parse(payload);
|
|
161
|
+
return releasingKeyOnTerminalFailure(ctx, p, () => renderStep(p, ctx));
|
|
162
|
+
};
|
|
163
|
+
async function renderStep(p, ctx) {
|
|
119
164
|
const tenantId = p.tenantId; // @cast-boundary engine-payload — stream tenant
|
|
120
165
|
const { db, registry } = requireTenantScopedDeps(ctx, tenantId);
|
|
121
166
|
const channel = resolveChannel(registry, p.channelName);
|
|
122
|
-
const channelCtx = buildChannelContext(db, registry, undefined, tenantId, ctx.secrets);
|
|
167
|
+
const channelCtx = buildChannelContext(db, registry, undefined, tenantId, ctx.secrets, ctx.config, (fileTenantId) => fileContextForTenant(ctx, ctx.systemUser.tenantId, fileTenantId));
|
|
123
168
|
let rendered;
|
|
124
169
|
try {
|
|
125
170
|
if (!channel.render) {
|
|
@@ -132,26 +177,33 @@ export const deliveryRenderJob = async (payload, ctx) => {
|
|
|
132
177
|
}
|
|
133
178
|
try {
|
|
134
179
|
const jobRunner = ctx["jobRunner"]; // @cast-boundary dynamic-key — dispatch lives on the concrete runner
|
|
135
|
-
await jobRunner.dispatch(DeliveryJobs.send, { ...p, rendered }, { priority: deliveryPriorityRank[p.priority] });
|
|
180
|
+
await jobRunner.dispatch(DeliveryJobs.send, { ...p, rendered: toSendRendered(rendered) }, { priority: deliveryPriorityRank[p.priority] });
|
|
136
181
|
}
|
|
137
182
|
catch (err) {
|
|
138
183
|
return failAttempt(ctx, db, registry, p, "channel_error", err);
|
|
139
184
|
}
|
|
140
|
-
}
|
|
185
|
+
}
|
|
141
186
|
// Deliver via the channel using the rendered payload (if any). On failure:
|
|
142
187
|
// record the terminal failed event and rethrow so BullMQ retries the send with
|
|
143
188
|
// the same already-rendered HTML — no re-render needed.
|
|
144
189
|
export const deliverySendJob = async (payload, ctx) => {
|
|
145
190
|
const p = sendJobPayloadSchema.parse(payload);
|
|
191
|
+
return releasingKeyOnTerminalFailure(ctx, p, () => sendStep(p, ctx));
|
|
192
|
+
};
|
|
193
|
+
async function sendStep(p, ctx) {
|
|
146
194
|
const tenantId = p.tenantId; // @cast-boundary engine-payload — stream tenant
|
|
147
195
|
const { db, registry } = requireTenantScopedDeps(ctx, tenantId);
|
|
148
196
|
const channel = resolveChannel(registry, p.channelName);
|
|
149
|
-
const channelCtx = buildChannelContext(db, registry, undefined, tenantId, ctx.secrets);
|
|
197
|
+
const channelCtx = buildChannelContext(db, registry, undefined, tenantId, ctx.secrets, ctx.config, (fileTenantId) => fileContextForTenant(ctx, ctx.systemUser.tenantId, fileTenantId));
|
|
150
198
|
try {
|
|
151
199
|
const result = await channel.send(p.address, toMessage(p), channelCtx, p.rendered);
|
|
152
200
|
await appendAttemptEvent(db, registry, p.deliveryAttemptId, entryFor(p, result.status, result.error ?? null, result.address ?? p.address, result.confirmed));
|
|
201
|
+
// A soft failure ends the job successfully, so no retry follows and the caller's
|
|
202
|
+
// retry must not be skipped as a duplicate.
|
|
203
|
+
if (result.status === "failed")
|
|
204
|
+
await releaseIdempotencyKey(ctx, p);
|
|
153
205
|
}
|
|
154
206
|
catch (err) {
|
|
155
207
|
return failAttempt(ctx, db, registry, p, "send_failed", err);
|
|
156
208
|
}
|
|
157
|
-
}
|
|
209
|
+
}
|
|
@@ -2,5 +2,8 @@
|
|
|
2
2
|
// job dispatcher is handed to notify() per call, so queued channels go through
|
|
3
3
|
// the delivery jobs. deliverQueuedInline drops the dispatcher and sends inline.
|
|
4
4
|
export function createDeliveryNotifyFactory(deliveryService, options = {}) {
|
|
5
|
-
return (user, tenantId, jobDispatcher
|
|
5
|
+
return (user, tenantId, jobDispatcher, deliveryDb, fileProviderResolver) => {
|
|
6
|
+
const notify = (notificationType, notifyOptions) => deliveryService.notify(notificationType, notifyOptions, user, tenantId, options.deliverQueuedInline === true ? undefined : jobDispatcher, deliveryDb, fileProviderResolver);
|
|
7
|
+
return notify;
|
|
8
|
+
};
|
|
6
9
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { SseBroker } from "@cosmicdrift/kumiko-framework/api";
|
|
2
2
|
import type { DbConnection } from "@cosmicdrift/kumiko-framework/db";
|
|
3
|
-
import type { EscapeHatchAuditSink, Registry } from "@cosmicdrift/kumiko-framework/engine";
|
|
3
|
+
import type { ConfigAccessor, EscapeHatchAuditSink, Registry, TenantId } from "@cosmicdrift/kumiko-framework/engine";
|
|
4
|
+
import type { FileContext } from "@cosmicdrift/kumiko-framework/files";
|
|
4
5
|
import type { JobRunner } from "@cosmicdrift/kumiko-framework/jobs";
|
|
5
6
|
import type { Logger } from "@cosmicdrift/kumiko-framework/logging";
|
|
6
7
|
import type { SecretsContext } from "@cosmicdrift/kumiko-framework/secrets";
|
|
@@ -16,6 +17,8 @@ export type CreateDeliveryTestContextOptions = {
|
|
|
16
17
|
readonly jobRunner?: JobRunner;
|
|
17
18
|
readonly secrets?: SecretsContext;
|
|
18
19
|
readonly idempotencyRedis?: Redis;
|
|
20
|
+
readonly filesForTenant?: (tenantId: string) => FileContext | undefined;
|
|
21
|
+
readonly tenantConfigFor?: (tenantId: TenantId) => ConfigAccessor;
|
|
19
22
|
};
|
|
20
23
|
/**
|
|
21
24
|
* Helper for setupTestStack: creates a DeliveryService + _notifyFactory for the test context.
|
package/dist/delivery/types.d.ts
CHANGED
|
@@ -1,15 +1,35 @@
|
|
|
1
1
|
import type { SseBroker } from "@cosmicdrift/kumiko-framework/api";
|
|
2
|
-
import type { TenantDb } from "@cosmicdrift/kumiko-framework/db";
|
|
3
|
-
import type { DeliveryErrorCode, NotifyDeliveryStatus, NotifyJobDispatcher, NotifyOptions, NotifyPriority,
|
|
2
|
+
import type { DbConnection, TenantDb } from "@cosmicdrift/kumiko-framework/db";
|
|
3
|
+
import type { DeliveryErrorCode, NotifyDeliveryStatus, NotifyDispatched, NotifyJobDispatcher, NotifyOptions, NotifyPriority, Registry, SessionUser, TenantId } from "@cosmicdrift/kumiko-framework/engine";
|
|
4
|
+
import type { FileProviderResolver } from "@cosmicdrift/kumiko-framework/files";
|
|
4
5
|
import type { SecretsContext } from "@cosmicdrift/kumiko-framework/secrets";
|
|
6
|
+
import type { MailPayload, RenderedTemplateRef } from "../renderer-foundation/index.js";
|
|
7
|
+
import type { EmailAttachmentRef } from "./attachment-ref.js";
|
|
5
8
|
import { DELIVERY_CHANNEL_EXTENSION } from "./constants.js";
|
|
9
|
+
export type RenderedMail = {
|
|
10
|
+
readonly subject: string;
|
|
11
|
+
readonly html: string;
|
|
12
|
+
readonly text: string;
|
|
13
|
+
readonly previewText?: string;
|
|
14
|
+
readonly fromName?: string;
|
|
15
|
+
readonly replyTo?: string;
|
|
16
|
+
readonly attachments?: readonly EmailAttachmentRef[];
|
|
17
|
+
/** The layout version the html was rendered through; the editor preview names it. */
|
|
18
|
+
readonly layout?: RenderedTemplateRef;
|
|
19
|
+
};
|
|
6
20
|
export type ChannelContext = {
|
|
7
21
|
readonly db: TenantDb;
|
|
8
22
|
readonly registry: Registry;
|
|
9
23
|
readonly sseBroker: SseBroker | undefined;
|
|
10
24
|
readonly tenantId: TenantId;
|
|
11
25
|
readonly secrets?: SecretsContext | undefined;
|
|
26
|
+
readonly renderMail?: (payload: MailPayload) => Promise<RenderedMail>;
|
|
27
|
+
readonly readAttachment?: (ref: EmailAttachmentRef, origin?: AttachmentOrigin) => Promise<{
|
|
28
|
+
readonly content: Uint8Array;
|
|
29
|
+
readonly contentType: string;
|
|
30
|
+
} | null>;
|
|
12
31
|
};
|
|
32
|
+
export type AttachmentOrigin = "template" | "message";
|
|
13
33
|
export type ChannelMessage = {
|
|
14
34
|
readonly notificationType: string;
|
|
15
35
|
readonly title: string;
|
|
@@ -27,6 +47,9 @@ export type RenderedMessage = {
|
|
|
27
47
|
readonly html: string;
|
|
28
48
|
readonly subject: string;
|
|
29
49
|
readonly text?: string;
|
|
50
|
+
readonly fromName?: string;
|
|
51
|
+
readonly replyTo?: string;
|
|
52
|
+
readonly attachments?: readonly EmailAttachmentRef[];
|
|
30
53
|
};
|
|
31
54
|
export declare const DELIVERY_CHANNEL_MODES: readonly ["inline", "queued"];
|
|
32
55
|
export type DeliveryChannelMode = (typeof DELIVERY_CHANNEL_MODES)[number];
|
|
@@ -38,17 +61,6 @@ export type DeliveryChannel = {
|
|
|
38
61
|
render?(message: ChannelMessage, ctx: ChannelContext): Promise<RenderedMessage>;
|
|
39
62
|
send(address: string, message: ChannelMessage, ctx: ChannelContext, rendered?: RenderedMessage): Promise<ChannelResult>;
|
|
40
63
|
};
|
|
41
|
-
export type RendererInput = {
|
|
42
|
-
readonly template: string;
|
|
43
|
-
readonly variables: Readonly<Record<string, unknown>>;
|
|
44
|
-
readonly locale?: string | undefined;
|
|
45
|
-
};
|
|
46
|
-
export type NotificationRenderer = {
|
|
47
|
-
readonly name: string;
|
|
48
|
-
render(input: RendererInput): Promise<string>;
|
|
49
|
-
/** Plain-text version of the same mail; the email channel sends it as the text part of a multipart/alternative mail. */
|
|
50
|
-
renderText?(input: RendererInput): Promise<string>;
|
|
51
|
-
};
|
|
52
64
|
export type DeliveryLogEntry = {
|
|
53
65
|
readonly tenantId: TenantId;
|
|
54
66
|
readonly notificationType: string;
|
|
@@ -61,7 +73,7 @@ export type DeliveryLogEntry = {
|
|
|
61
73
|
readonly confirmed?: false;
|
|
62
74
|
};
|
|
63
75
|
export type DeliveryService = {
|
|
64
|
-
notify(notificationType: string, options: NotifyOptions, user: SessionUser, tenantId: TenantId, jobDispatcher?: NotifyJobDispatcher): Promise<
|
|
76
|
+
notify(notificationType: string, options: NotifyOptions, user: SessionUser, tenantId: TenantId, jobDispatcher?: NotifyJobDispatcher, deliveryDb?: DbConnection, fileProviderResolver?: FileProviderResolver): Promise<NotifyDispatched>;
|
|
65
77
|
};
|
|
66
78
|
export type DeliveryChannelPlugin = Omit<DeliveryChannel, "name">;
|
|
67
79
|
export declare function isDeliveryChannelPlugin(o: unknown): o is DeliveryChannelPlugin;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { ExtraRouteRejection, signatureRoute, } from "@cosmicdrift/kumiko-framework/api";
|
|
2
2
|
import { escapeHtmlAttr } from "@cosmicdrift/kumiko-headless";
|
|
3
|
+
import { isPlainObject } from "@cosmicdrift/kumiko-types/type-guards";
|
|
3
4
|
import * as jose from "jose";
|
|
4
5
|
import * as z from "zod";
|
|
5
6
|
import { hashUnsubscribeAddress } from "./address-opt-out.js";
|
|
@@ -280,7 +281,9 @@ function tokenFromRequestBody(request) {
|
|
|
280
281
|
if (contentType?.includes("application/json")) {
|
|
281
282
|
try {
|
|
282
283
|
const parsed = JSON.parse(request.rawBody);
|
|
283
|
-
return typeof parsed
|
|
284
|
+
return isPlainObject(parsed) && typeof parsed["token"] === "string"
|
|
285
|
+
? parsed["token"]
|
|
286
|
+
: undefined;
|
|
284
287
|
}
|
|
285
288
|
catch {
|
|
286
289
|
return undefined;
|
|
@@ -3,12 +3,9 @@
|
|
|
3
3
|
// region blur (see BlurRegion). Server-only: sharp is a native binding.
|
|
4
4
|
import QRCode from "qrcode";
|
|
5
5
|
import sharp from "sharp";
|
|
6
|
+
import { assertNotSvg, SHARP_INPUT_OPTIONS } from "../shared/index.js";
|
|
6
7
|
const MIN_SHARP_SIGMA = 0.3;
|
|
7
8
|
const MAX_SHARP_SIGMA = 1000;
|
|
8
|
-
// Above this, a decoded pixel buffer runs into hundreds of MB before any
|
|
9
|
-
// resize even happens — sharp's own default (~268MP) is still large enough
|
|
10
|
-
// to be an OOM primitive for a single request.
|
|
11
|
-
const MAX_INPUT_PIXELS = 100_000_000;
|
|
12
9
|
// Longest output edge a caller may request via `size`/`maxEdge`. Without a
|
|
13
10
|
// cap, `{ maxEdge: 50000 }` allocates a multi-gigabyte buffer per encoder
|
|
14
11
|
// step before any client sees a byte back.
|
|
@@ -27,26 +24,12 @@ const MAX_QR_DATA_LENGTH = 1024;
|
|
|
27
24
|
// camera capture — the AC claims phone-scannable, so an under-sized layer
|
|
28
25
|
// throws instead of caching a silently unusable image.
|
|
29
26
|
const MIN_QR_PIXEL_WIDTH = 160;
|
|
30
|
-
// sequentialRead trades random pixel access for lower peak memory on
|
|
31
|
-
// streamed formats — applied at every decode entry point below since none
|
|
32
|
-
// of them need non-sequential access to the *original* bytes (extract runs
|
|
33
|
-
// against a single already-buffered region per call).
|
|
34
|
-
const SHARP_INPUT_OPTIONS = { limitInputPixels: MAX_INPUT_PIXELS, sequentialRead: true };
|
|
35
27
|
function normalizeMimeType(mimeType) {
|
|
36
28
|
return mimeType.toLowerCase().split(";")[0]?.trim() ?? "";
|
|
37
29
|
}
|
|
38
30
|
function clampSigma(sigma) {
|
|
39
31
|
return Math.min(MAX_SHARP_SIGMA, Math.max(MIN_SHARP_SIGMA, sigma));
|
|
40
32
|
}
|
|
41
|
-
// Shared trust boundary for every image byte source this renderer decodes —
|
|
42
|
-
// the original upload (renderImage) and an overlay layer's bytes (applyOverlays)
|
|
43
|
-
// alike: a base64 string in a field declaration isn't more trustworthy than
|
|
44
|
-
// an upload just because it came from app code.
|
|
45
|
-
function assertNotSvg(format) {
|
|
46
|
-
if (format === "svg") {
|
|
47
|
-
throw new Error("derivatives-sharp: SVG is not supported — rendering untrusted SVG through libvips is a trust boundary this renderer doesn't need to cross.");
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
33
|
function assertValidRegion(region) {
|
|
51
34
|
const values = [region.x, region.y, region.width, region.height];
|
|
52
35
|
if (values.some((v) => !Number.isFinite(v)) || region.width <= 0 || region.height <= 0) {
|