@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
|
@@ -83,21 +83,22 @@ export function requireSecretsContext(
|
|
|
83
83
|
"createSecretsContext({db, masterKeyProvider, registry}) via extraContext.secrets at boot.",
|
|
84
84
|
});
|
|
85
85
|
}
|
|
86
|
-
const raw = ctx.secrets;
|
|
87
86
|
const userId = ctx._userId;
|
|
88
87
|
if (!userId) {
|
|
89
88
|
throw new InternalError({
|
|
90
89
|
message: `[${handlerName}] ctx._userId missing — cannot audit secret reads without a caller identity.`,
|
|
91
90
|
});
|
|
92
91
|
}
|
|
93
|
-
|
|
92
|
+
const withAudit = (raw: SecretsContext): SecretsContext => ({
|
|
93
|
+
withRunner: (runner) => withAudit(raw.withRunner(runner)),
|
|
94
94
|
get: (tenantId, key, overrideAudit) =>
|
|
95
95
|
raw.get(tenantId, key, overrideAudit ?? { userId, handlerName }),
|
|
96
96
|
// No audit injection: has() is metadata-only and never logs a read.
|
|
97
97
|
has: raw.has.bind(raw),
|
|
98
98
|
set: raw.set.bind(raw),
|
|
99
99
|
delete: raw.delete.bind(raw),
|
|
100
|
-
};
|
|
100
|
+
});
|
|
101
|
+
return withAudit(ctx.secrets);
|
|
101
102
|
}
|
|
102
103
|
|
|
103
104
|
export type SecretsFeatureOptions = {
|
|
@@ -17,6 +17,7 @@ import {
|
|
|
17
17
|
createEventStoreExecutor,
|
|
18
18
|
createTenantDb,
|
|
19
19
|
type DbConnection,
|
|
20
|
+
type DbRunner,
|
|
20
21
|
} from "@cosmicdrift/kumiko-framework/db";
|
|
21
22
|
import type { SessionUser } from "@cosmicdrift/kumiko-framework/engine";
|
|
22
23
|
import { InternalError, type WriteErrorInfo } from "@cosmicdrift/kumiko-framework/errors";
|
|
@@ -91,8 +92,20 @@ function resolveKey(keyOrHandle: string | { readonly name: string }): string {
|
|
|
91
92
|
}
|
|
92
93
|
|
|
93
94
|
export function createSecretsContext(opts: SecretsContextOptions): SecretsContext {
|
|
94
|
-
const {
|
|
95
|
+
const { masterKeyProvider } = opts;
|
|
95
96
|
const provider = withDekCache(masterKeyProvider, opts.dekCache ?? createDekCache());
|
|
97
|
+
return buildSecretsContext(opts, provider, opts.db, false);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// `runnerIsTransaction`: the runner is a caller-owned transaction, so audited reads join it
|
|
101
|
+
// instead of opening a second connection (which would deadlock a small pool).
|
|
102
|
+
function buildSecretsContext(
|
|
103
|
+
opts: SecretsContextOptions,
|
|
104
|
+
provider: MasterKeyProvider,
|
|
105
|
+
db: DbRunner,
|
|
106
|
+
runnerIsTransaction: boolean,
|
|
107
|
+
): SecretsContext {
|
|
108
|
+
const { masterKeyProvider } = opts;
|
|
96
109
|
|
|
97
110
|
type SecretLookupRow = {
|
|
98
111
|
readonly id: string;
|
|
@@ -105,6 +118,10 @@ export function createSecretsContext(opts: SecretsContextOptions): SecretsContex
|
|
|
105
118
|
}
|
|
106
119
|
|
|
107
120
|
return {
|
|
121
|
+
withRunner(runner) {
|
|
122
|
+
return buildSecretsContext(opts, provider, runner, true);
|
|
123
|
+
},
|
|
124
|
+
|
|
108
125
|
async get(tenantId, keyOrHandle, auditCtx) {
|
|
109
126
|
const key = resolveKey(keyOrHandle);
|
|
110
127
|
// Atomic audit + read: a decrypt that "escaped" the audit trail
|
|
@@ -120,7 +137,7 @@ export function createSecretsContext(opts: SecretsContextOptions): SecretsContex
|
|
|
120
137
|
return createSecret(plaintext);
|
|
121
138
|
}
|
|
122
139
|
|
|
123
|
-
const
|
|
140
|
+
const readAndAudit = async (tx: DbRunner): Promise<string | undefined> => {
|
|
124
141
|
// Inline select inside the TX via raw client — fetchOne's connection
|
|
125
142
|
// type doesn't widen to the transaction object cleanly.
|
|
126
143
|
const envelope = await selectTenantSecretEnvelope(tx, tenantId, key);
|
|
@@ -154,7 +171,10 @@ export function createSecretsContext(opts: SecretsContextOptions): SecretsContex
|
|
|
154
171
|
metadata,
|
|
155
172
|
});
|
|
156
173
|
return pt;
|
|
157
|
-
}
|
|
174
|
+
};
|
|
175
|
+
const plaintext = runnerIsTransaction
|
|
176
|
+
? await readAndAudit(db)
|
|
177
|
+
: await transaction(db, readAndAudit);
|
|
158
178
|
|
|
159
179
|
if (plaintext === undefined) return undefined;
|
|
160
180
|
// Brand the plaintext only after audit committed. The response
|
|
@@ -6,7 +6,7 @@ export async function deleteStaleSessionsBatch(
|
|
|
6
6
|
olderThanDays: number,
|
|
7
7
|
batchSize: number,
|
|
8
8
|
): Promise<number> {
|
|
9
|
-
const rows =
|
|
9
|
+
const rows = await asRawClient(db).unsafe<{ id: string }>(
|
|
10
10
|
`DELETE FROM "store_user_sessions"
|
|
11
11
|
WHERE "id" IN (
|
|
12
12
|
SELECT "id" FROM "store_user_sessions"
|
|
@@ -16,6 +16,6 @@ export async function deleteStaleSessionsBatch(
|
|
|
16
16
|
)
|
|
17
17
|
RETURNING "id"`,
|
|
18
18
|
[olderThanDays, batchSize],
|
|
19
|
-
)
|
|
19
|
+
);
|
|
20
20
|
return rows.length;
|
|
21
21
|
}
|
|
@@ -71,11 +71,9 @@ export function bindAutoRevokeFromFeature(
|
|
|
71
71
|
): BindAutoRevokeOnPasswordChange | undefined {
|
|
72
72
|
const exports = feature.exports;
|
|
73
73
|
if (exports && typeof exports === "object" && "bindAutoRevokeOnPasswordChange" in exports) {
|
|
74
|
-
const { bindAutoRevokeOnPasswordChange } = exports
|
|
75
|
-
bindAutoRevokeOnPasswordChange: unknown;
|
|
76
|
-
};
|
|
74
|
+
const { bindAutoRevokeOnPasswordChange } = exports;
|
|
77
75
|
if (typeof bindAutoRevokeOnPasswordChange === "function") {
|
|
78
|
-
// @cast-boundary exports
|
|
76
|
+
// @cast-boundary feature-exports — feature.exports is untyped by design
|
|
79
77
|
return bindAutoRevokeOnPasswordChange as BindAutoRevokeOnPasswordChange;
|
|
80
78
|
}
|
|
81
79
|
}
|
|
@@ -4,6 +4,7 @@ import {
|
|
|
4
4
|
definePagedQueryHandler,
|
|
5
5
|
MAX_LIST_LIMIT,
|
|
6
6
|
} from "@cosmicdrift/kumiko-framework/engine";
|
|
7
|
+
import { isOneOf } from "@cosmicdrift/kumiko-types/type-guards";
|
|
7
8
|
import * as z from "zod";
|
|
8
9
|
import { decryptStoredPii } from "../../shared/index.js";
|
|
9
10
|
import { userSessionTable } from "../schema/user-session.js";
|
|
@@ -17,7 +18,7 @@ const SORTABLE_COLUMNS = ["id", "userId", "createdAt", "expiresAt", "revokedAt"]
|
|
|
17
18
|
type SortableColumn = (typeof SORTABLE_COLUMNS)[number];
|
|
18
19
|
|
|
19
20
|
function isSortableColumn(value: string): value is SortableColumn {
|
|
20
|
-
return (SORTABLE_COLUMNS
|
|
21
|
+
return isOneOf(SORTABLE_COLUMNS, value);
|
|
21
22
|
}
|
|
22
23
|
|
|
23
24
|
// Admin view of every session in the active tenant. ctx.db (TenantDb)
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import * as z from "zod";
|
|
2
|
+
|
|
3
|
+
const MAX_FILE_NAME_LENGTH = 255;
|
|
4
|
+
|
|
5
|
+
function hasControlCharacter(value: string): boolean {
|
|
6
|
+
return value.split("").some((char) => {
|
|
7
|
+
const code = char.charCodeAt(0);
|
|
8
|
+
return code <= 0x1f || code === 0x7f;
|
|
9
|
+
});
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export const attachmentFileNameSchema = z
|
|
13
|
+
.string()
|
|
14
|
+
.min(1)
|
|
15
|
+
.max(MAX_FILE_NAME_LENGTH)
|
|
16
|
+
.refine((name) => !name.includes("/") && !name.includes("\\") && !hasControlCharacter(name), {
|
|
17
|
+
message: "file name must not contain path separators or control characters",
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
const PDF_FILE_NAME_MAX_LENGTH = 200;
|
|
21
|
+
const PDF_EXTENSION = ".pdf";
|
|
22
|
+
|
|
23
|
+
// Shared by everything that names a rendered PDF, so a mail attachment and a download of the same
|
|
24
|
+
// template get the same name. The cut leaves room for the extension: the attachment schema caps
|
|
25
|
+
// rendered-pdf names, and a name that only fits without ".pdf" would fail it.
|
|
26
|
+
export function sanitizePdfAttachmentFileName(raw: string, templateSlug: string): string {
|
|
27
|
+
const cleaned = raw
|
|
28
|
+
.split("")
|
|
29
|
+
.filter((char) => !hasControlCharacter(char))
|
|
30
|
+
.join("")
|
|
31
|
+
.replace(/[/\\";]/g, "-")
|
|
32
|
+
.replaceAll("..", "-")
|
|
33
|
+
.trim()
|
|
34
|
+
.slice(0, PDF_FILE_NAME_MAX_LENGTH - PDF_EXTENSION.length)
|
|
35
|
+
.trim();
|
|
36
|
+
const lastSegment = templateSlug.split(":").pop() ?? templateSlug;
|
|
37
|
+
if (cleaned === "") return `${lastSegment}${PDF_EXTENSION}`;
|
|
38
|
+
return cleaned.toLowerCase().endsWith(PDF_EXTENSION) ? cleaned : `${cleaned}${PDF_EXTENSION}`;
|
|
39
|
+
}
|
|
@@ -1,4 +1,10 @@
|
|
|
1
1
|
[
|
|
2
|
+
{
|
|
3
|
+
"version": "0.364.0",
|
|
4
|
+
"type": "improvement",
|
|
5
|
+
"title": "Mail template body helpers exported as `./shared/mail-template-body`",
|
|
6
|
+
"detail": "Apps that register their own notification templates can build the body with the same helpers the framework uses: `mailParagraph`, `mailHeading`, `mailRepeatedRows`, `mailTextWithSlotsHtml`, plus the new `mailButton`, `mailParagraphWithLink` and `mailSubject`. The subpath only depends on kumiko-headless and is safe on the server. Apps that copied these helpers can import them instead."
|
|
7
|
+
},
|
|
2
8
|
{
|
|
3
9
|
"version": "0.335.0",
|
|
4
10
|
"type": "fix",
|
|
@@ -33,6 +33,9 @@ export type ChunkedMigrationOptions<Row> = {
|
|
|
33
33
|
readonly maxFailures: number;
|
|
34
34
|
// Epoch ms; Number.POSITIVE_INFINITY for no time bound.
|
|
35
35
|
readonly deadlineAt: number;
|
|
36
|
+
// Batches processed even when the deadline already passed. Callers whose setup before the
|
|
37
|
+
// loop can eat the whole budget set 1, so every run makes progress instead of timing out empty.
|
|
38
|
+
readonly minBatches?: number;
|
|
36
39
|
readonly signal?: AbortSignal | undefined;
|
|
37
40
|
readonly onRowError?: (row: Row, err: unknown) => void;
|
|
38
41
|
};
|
|
@@ -51,7 +54,7 @@ export async function runChunkedMigration<Row>(
|
|
|
51
54
|
stoppedReason = "signal";
|
|
52
55
|
break;
|
|
53
56
|
}
|
|
54
|
-
if (Date.now() >= opts.deadlineAt) {
|
|
57
|
+
if (batchesProcessed >= (opts.minBatches ?? 0) && Date.now() >= opts.deadlineAt) {
|
|
55
58
|
stoppedReason = "timeout";
|
|
56
59
|
break;
|
|
57
60
|
}
|
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
export { collectErasureFailure, throwIfErasureFailed } from "./assert-erased.js";
|
|
2
2
|
export { assertNoWhereRuleInOwnershipWrite } from "./assert-no-where-rule-in-ownership-write.js";
|
|
3
|
+
export {
|
|
4
|
+
attachmentFileNameSchema,
|
|
5
|
+
sanitizePdfAttachmentFileName,
|
|
6
|
+
} from "./attachment-file-name.js";
|
|
3
7
|
export {
|
|
4
8
|
type ChunkedMigrationOptions,
|
|
5
9
|
type ChunkedMigrationResult,
|
|
@@ -18,6 +22,7 @@ export { isWithinGracePeriod } from "./grace-period.js";
|
|
|
18
22
|
export { isIdentityV3Hash, verifyIdentityV3Hash } from "./identity-v3-hash.js";
|
|
19
23
|
export { isTenantDb } from "./is-tenant-db.js";
|
|
20
24
|
export { createLockoutCounter, type LockoutCounterState } from "./lockout-counter.js";
|
|
25
|
+
export { mailHeading, mailParagraph, mailRepeatedRows } from "./mail-template-body.js";
|
|
21
26
|
export { mapWithConcurrency } from "./map-with-concurrency.js";
|
|
22
27
|
export {
|
|
23
28
|
denyUnlessJoinRowParentVisible,
|
|
@@ -43,6 +48,7 @@ export {
|
|
|
43
48
|
export { runInSubTransaction } from "./run-in-sub-transaction.js";
|
|
44
49
|
export { registrationLocaleFromRequest, sessionLocaleField } from "./session-locale-field.js";
|
|
45
50
|
export { sessionTimezoneField } from "./session-timezone-field.js";
|
|
51
|
+
export { assertNotSvg, MAX_INPUT_PIXELS, SHARP_INPUT_OPTIONS } from "./sharp-input-limits.js";
|
|
46
52
|
export {
|
|
47
53
|
peekTokenSubject,
|
|
48
54
|
signToken,
|
|
@@ -5,5 +5,5 @@ import type { DbRunner, TenantDb } from "@cosmicdrift/kumiko-framework/db";
|
|
|
5
5
|
// Shared so retention/compliance-profile resolvers that accept either (bulk cron
|
|
6
6
|
// vs. per-hook TenantDb) branch through one predicate instead of duplicating it.
|
|
7
7
|
export function isTenantDb(db: DbRunner | TenantDb): db is TenantDb {
|
|
8
|
-
return
|
|
8
|
+
return "unsafeRaw" in db && typeof db.unsafeRaw === "function";
|
|
9
9
|
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { escapeHtml, escapeHtmlAttr } from "@cosmicdrift/kumiko-headless";
|
|
2
|
+
|
|
3
|
+
const SLOT_PATTERN = /\{([A-Za-z_][A-Za-z0-9_]*)\}/g;
|
|
4
|
+
|
|
5
|
+
// Author text uses `{name}` slots. The text is escaped first and the slots are
|
|
6
|
+
// swapped for variable nodes afterwards, so a slot can never smuggle markup.
|
|
7
|
+
export function mailTextWithSlotsHtml(text: string): string {
|
|
8
|
+
return escapeHtml(text).replace(
|
|
9
|
+
SLOT_PATTERN,
|
|
10
|
+
(_, name: string) => `<span data-variable="${escapeHtmlAttr(name)}"></span>`,
|
|
11
|
+
);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function mailParagraph(text: string): string {
|
|
15
|
+
return `<p>${mailTextWithSlotsHtml(text)}</p>`;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function mailHeading(text: string): string {
|
|
19
|
+
return `<h2>${mailTextWithSlotsHtml(text)}</h2>`;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function mailButton(label: string, linkVariable: string): string {
|
|
23
|
+
return `<p><a data-button href="{{${escapeHtmlAttr(linkVariable)}}}">${escapeHtml(label)}</a></p>`;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function mailParagraphWithLink(
|
|
27
|
+
text: string,
|
|
28
|
+
linkLabel: string,
|
|
29
|
+
linkVariable: string,
|
|
30
|
+
): string {
|
|
31
|
+
return `<p>${mailTextWithSlotsHtml(text)} <a href="{{${escapeHtmlAttr(linkVariable)}}}">${escapeHtml(linkLabel)}</a></p>`;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// One table row per list entry; the cell shows the entry's `field`.
|
|
35
|
+
export function mailRepeatedRows(listName: string, field: string): string {
|
|
36
|
+
return `<table><tbody><tr data-each="${escapeHtmlAttr(listName)}"><td><p><span data-variable="${escapeHtmlAttr(field)}"></span></p></td></tr></tbody></table>`;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Subjects are Handlebars text, so the same `{name}` slots become `{{name}}`.
|
|
40
|
+
export function mailSubject(text: string): string {
|
|
41
|
+
return text.replace(SLOT_PATTERN, "{{$1}}");
|
|
42
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
// Sharp-free so callers can import the limits without loading the native binding.
|
|
2
|
+
|
|
3
|
+
// Above this, a decoded pixel buffer runs into hundreds of MB before any
|
|
4
|
+
// resize even happens — sharp's own default (~268MP) is still large enough
|
|
5
|
+
// to be an OOM primitive for a single request.
|
|
6
|
+
export const MAX_INPUT_PIXELS = 100_000_000;
|
|
7
|
+
// sequentialRead trades random pixel access for lower peak memory on
|
|
8
|
+
// streamed formats — applied at every decode entry point below since none
|
|
9
|
+
// of them need non-sequential access to the *original* bytes (extract runs
|
|
10
|
+
// against a single already-buffered region per call).
|
|
11
|
+
export const SHARP_INPUT_OPTIONS = {
|
|
12
|
+
limitInputPixels: MAX_INPUT_PIXELS,
|
|
13
|
+
sequentialRead: true,
|
|
14
|
+
} as const;
|
|
15
|
+
|
|
16
|
+
// Shared trust boundary for every image byte source this renderer decodes —
|
|
17
|
+
// the original upload (renderImage) and an overlay layer's bytes (applyOverlays)
|
|
18
|
+
// alike: a base64 string in a field declaration isn't more trustworthy than
|
|
19
|
+
// an upload just because it came from app code.
|
|
20
|
+
export function assertNotSvg(format: string | undefined): void {
|
|
21
|
+
if (format === "svg") {
|
|
22
|
+
throw new Error(
|
|
23
|
+
"derivatives-sharp: SVG is not supported — rendering untrusted SVG through libvips is a trust boundary this renderer doesn't need to cross.",
|
|
24
|
+
);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
|
|
14
14
|
import type { DbRunner } from "@cosmicdrift/kumiko-framework/db";
|
|
15
15
|
import type { Registry } from "@cosmicdrift/kumiko-framework/engine";
|
|
16
|
+
import { isPlainObject } from "@cosmicdrift/kumiko-types/type-guards";
|
|
16
17
|
|
|
17
18
|
export const EXT_SIGNUP_HANDOVER = "signupHandover" as const;
|
|
18
19
|
|
|
@@ -46,10 +47,9 @@ export type SignupHandoverProvider = {
|
|
|
46
47
|
|
|
47
48
|
export function isSignupHandoverProvider(options: unknown): options is SignupHandoverProvider {
|
|
48
49
|
return (
|
|
49
|
-
|
|
50
|
-
options
|
|
51
|
-
typeof
|
|
52
|
-
typeof (options as Record<string, unknown>)["mintClaim"] === "function"
|
|
50
|
+
isPlainObject(options) &&
|
|
51
|
+
typeof options["verifyGrant"] === "function" &&
|
|
52
|
+
typeof options["mintClaim"] === "function"
|
|
53
53
|
);
|
|
54
54
|
}
|
|
55
55
|
|
|
@@ -1,4 +1,10 @@
|
|
|
1
1
|
[
|
|
2
|
+
{
|
|
3
|
+
"version": "0.364.0",
|
|
4
|
+
"type": "fix",
|
|
5
|
+
"title": "A stale Stripe customer id no longer turns the customer portal into a 500",
|
|
6
|
+
"detail": "createPortalSession now maps Stripe's \"No such customer\" rejection (account switch, customer deleted in the dashboard) to an UnprocessableError with the i18n key billing-foundation.errors.providerCustomerUnknown, translated in German and Spanish, instead of letting the raw Stripe error surface as a server error."
|
|
7
|
+
},
|
|
2
8
|
{
|
|
3
9
|
"version": "0.338.0",
|
|
4
10
|
"type": "improvement",
|
|
@@ -109,12 +109,10 @@ export function mapAppLocaleToStripeLocale(
|
|
|
109
109
|
return knownLocales.find((known) => known === language) ?? "auto";
|
|
110
110
|
}
|
|
111
111
|
|
|
112
|
-
function isUnknownCustomerStripeError(
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
error.param === "customer"
|
|
117
|
-
);
|
|
112
|
+
function isUnknownCustomerStripeError(
|
|
113
|
+
error: unknown,
|
|
114
|
+
): error is Stripe.errors.StripeInvalidRequestError {
|
|
115
|
+
return isResourceMissingStripeError(error) && error.param === "customer";
|
|
118
116
|
}
|
|
119
117
|
|
|
120
118
|
export function createStripeCheckoutSession(
|
|
@@ -203,11 +201,21 @@ export type StripePortalOptions = Parameters<
|
|
|
203
201
|
export function createStripePortalSession(runtime: StripeCtxRuntime) {
|
|
204
202
|
return async (ctx: HandlerContext, options: StripePortalOptions): Promise<{ url: string }> => {
|
|
205
203
|
const stripe = await runtime.clientForCtx(ctx);
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
204
|
+
try {
|
|
205
|
+
const session = await stripe.billingPortal.sessions.create({
|
|
206
|
+
customer: options.providerCustomerId,
|
|
207
|
+
return_url: options.returnUrl,
|
|
208
|
+
});
|
|
209
|
+
return { url: session.url };
|
|
210
|
+
} catch (error) {
|
|
211
|
+
// A portal needs an existing customer, so unlike checkout there is no retry without one.
|
|
212
|
+
if (!isUnknownCustomerStripeError(error)) throw error;
|
|
213
|
+
throw new UnprocessableError("provider_customer_unknown", {
|
|
214
|
+
i18nKey: "billing-foundation.errors.providerCustomerUnknown",
|
|
215
|
+
message: `subscription-stripe: Stripe does not know customer "${options.providerCustomerId}" (account switch or customer deleted).`,
|
|
216
|
+
cause: error,
|
|
217
|
+
});
|
|
218
|
+
}
|
|
211
219
|
};
|
|
212
220
|
}
|
|
213
221
|
|
|
@@ -54,7 +54,9 @@ import type { StripeWebhookRuntime } from "./runtime.js";
|
|
|
54
54
|
|
|
55
55
|
const log = createFallbackLogger("subscription-stripe");
|
|
56
56
|
|
|
57
|
-
export function isResourceMissingStripeError(
|
|
57
|
+
export function isResourceMissingStripeError(
|
|
58
|
+
error: unknown,
|
|
59
|
+
): error is Stripe.errors.StripeInvalidRequestError {
|
|
58
60
|
return (
|
|
59
61
|
error instanceof Stripe.errors.StripeInvalidRequestError && error.code === "resource_missing"
|
|
60
62
|
);
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { AccessRule, WriteHandlerDef } from "@cosmicdrift/kumiko-framework/engine";
|
|
2
2
|
import { NotFoundError, writeFailure } from "@cosmicdrift/kumiko-framework/errors";
|
|
3
|
+
import { isPlainObject } from "@cosmicdrift/kumiko-types/type-guards";
|
|
3
4
|
import { denyUnlessJoinRowParentVisible } from "../../shared/index.js";
|
|
4
5
|
import { tagAssignmentAggregateId } from "../aggregate-id.js";
|
|
5
6
|
import { DEFAULT_TAG_ACCESS } from "../constants.js";
|
|
@@ -67,7 +68,10 @@ export function createAssignTagHandler(
|
|
|
67
68
|
// above and this restore() call, so restore() correctly refuses with
|
|
68
69
|
// "not_deleted" — but the desired end state is already true. Converge
|
|
69
70
|
// like the create() race below instead of surfacing a spurious error.
|
|
70
|
-
if (
|
|
71
|
+
if (
|
|
72
|
+
isPlainObject(restored.error.details) &&
|
|
73
|
+
restored.error.details["reason"] === "not_deleted"
|
|
74
|
+
) {
|
|
71
75
|
return { isSuccess: true as const, data: { id } };
|
|
72
76
|
}
|
|
73
77
|
if (restored.error.code !== "not_found") return restored;
|
|
@@ -21,7 +21,8 @@ export function contrastText(color: string): "#000000" | "#ffffff" | null {
|
|
|
21
21
|
function hexToRgb(color: string): { r: number; g: number; b: number } | null {
|
|
22
22
|
const match = /^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/.exec(color.trim());
|
|
23
23
|
if (match === null) return null;
|
|
24
|
-
const short = match[1]
|
|
24
|
+
const short = match[1];
|
|
25
|
+
if (short === undefined) return null;
|
|
25
26
|
const hex =
|
|
26
27
|
short.length === 3
|
|
27
28
|
? `${short[0]}${short[0]}${short[1]}${short[1]}${short[2]}${short[2]}`
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# template-resolver
|
|
2
2
|
|
|
3
|
-
Strukturierter Content-Storage mit Tenant-Override-Hierarchie, Locale-Fallback und Resource-Linking via `file-foundation`. Eine Entity (`template-resource`) für alle Content-Arten — Render-Templates (`mail
|
|
3
|
+
Strukturierter Content-Storage mit Tenant-Override-Hierarchie, Locale-Fallback und Resource-Linking via `file-foundation`. Eine Entity (`template-resource`) für alle Content-Arten — Render-Templates (`mail`, `document-pdf`, `notification`, `image-snapshot`), Rahmen (`mail-layout`, `pdf-layout`), Markdown-Textblöcke (`text-block`, ex `text-content`-Feature) und AI-Prompts (`ai-prompt`).
|
|
4
4
|
|
|
5
5
|
**Plan-Doc:** [`kumiko-platform/docs/plans/features/template-resolver.md`](../../../../../../kumiko-platform/docs/plans/features/template-resolver.md)
|
|
6
6
|
|
|
@@ -38,14 +38,51 @@ async function someHandler(ctx) {
|
|
|
38
38
|
const template = await templateResolver.resolveTemplate({
|
|
39
39
|
tenantId: ctx.user.tenantId,
|
|
40
40
|
slug: "nka-versand",
|
|
41
|
-
kind: "mail
|
|
41
|
+
kind: "mail",
|
|
42
42
|
locale: "de",
|
|
43
43
|
});
|
|
44
|
-
// template.content + template.variableSchema + template.linkedResources verwenden
|
|
44
|
+
// template.content + template.meta + template.variableSchema + template.linkedResources verwenden
|
|
45
45
|
// ...
|
|
46
46
|
}
|
|
47
47
|
```
|
|
48
48
|
|
|
49
|
+
## Kinds, Format und Meta
|
|
50
|
+
|
|
51
|
+
| kind | contentFormat | meta |
|
|
52
|
+
|---|---|---|
|
|
53
|
+
| `mail` | `html` (Dokument-Schema, wird serverseitig neu aufgebaut) | Pflicht: `subject`; optional `previewText`, `layoutSlug`, `category` (`transactional`/`marketing`), `senderName`, `replyTo` |
|
|
54
|
+
| `mail-layout` | `mjml` | leer (`{}`) oder fehlend |
|
|
55
|
+
| `pdf-layout` | `html` | leer (`{}`) oder fehlend |
|
|
56
|
+
| `document-pdf` | `html` | fehlt |
|
|
57
|
+
| übrige | frei | fehlt |
|
|
58
|
+
|
|
59
|
+
`meta` liegt als JSON in der Spalte `meta` und wird beim Schreiben pro kind gegen
|
|
60
|
+
`template-meta.ts` geprüft (Fehler als Field-Issues auf `meta.*`). Subject und
|
|
61
|
+
Preview-Text erlauben nur einfache Platzhalter (`{{name}}`, `{{name.feld}}`), keine
|
|
62
|
+
Zeilenumbrüche.
|
|
63
|
+
|
|
64
|
+
Slugs sind kebab-Segmente, mit `:` verbunden (`templateSlugSchema`); Notification-Templates
|
|
65
|
+
liegen unter ihrem qualifizierten Namen `feature:notify:name`.
|
|
66
|
+
|
|
67
|
+
## Platzhalter-Validierung (kind `mail`)
|
|
68
|
+
|
|
69
|
+
Die Variablen kommen aus der Notification mit gleichem qualifiziertem Namen
|
|
70
|
+
(`r.notification({ variables })`), sonst aus `variableSchema` der Zeile. Systemplatzhalter
|
|
71
|
+
(`tenantName`, `signature`, `imprint`, `unsubscribeUrl`) und der Resource-Key `logo` sind immer erlaubt.
|
|
72
|
+
|
|
73
|
+
- `upsertSystem` und `upsertTenant` lehnen unbekannte Platzhalter ab (Field-Issue auf `content`).
|
|
74
|
+
- `upsertSystem` und `upsertTenant` mit `status: "active"` verlangen zusätzlich, dass jede `required`-Variable benutzt wird. Ein Tenant-Draft darf unvollständig sein und liefert `validation` zurück.
|
|
75
|
+
- `publish` prüft die gespeicherte Zeile erneut.
|
|
76
|
+
- `seedSystemTemplate` wirft bei beiden Problemen.
|
|
77
|
+
|
|
78
|
+
## Notification-Templates seeden
|
|
79
|
+
|
|
80
|
+
`r.notification({ variables, template, meta })` bringt Text und Meta mit dem Code mit.
|
|
81
|
+
`seedNotificationTemplates(db, registry)` (aus `.../template-resolver/seeding`) schreibt sie
|
|
82
|
+
pro Locale als System-Template; `applyBootSeeds` ruft das beim Boot auf. Der Code ist
|
|
83
|
+
maßgeblich: geänderte Texte aktualisieren die Zeile, unveränderte erzeugen kein Event.
|
|
84
|
+
Tenant-Overrides bleiben unberührt.
|
|
85
|
+
|
|
49
86
|
## Resolver-Reihenfolge (4-Stufen-Fallback)
|
|
50
87
|
|
|
51
88
|
1. `tenantId` + requested locale
|
|
@@ -63,10 +100,10 @@ import { seedSystemTemplate } from "@cosmicdrift/kumiko-bundled-features/templat
|
|
|
63
100
|
// runProdApp seeds — idempotent, default skip if slug exists
|
|
64
101
|
await seedSystemTemplate(db, {
|
|
65
102
|
slug: "welcome-email",
|
|
66
|
-
kind: "
|
|
103
|
+
kind: "mail",
|
|
67
104
|
locale: "de",
|
|
68
|
-
content:
|
|
69
|
-
contentFormat: "
|
|
105
|
+
content: '<p>Willkommen, <span data-variable="name"></span></p>',
|
|
106
|
+
contentFormat: "html",
|
|
70
107
|
});
|
|
71
108
|
```
|
|
72
109
|
|
|
@@ -80,8 +117,16 @@ await seedSystemTemplate(db, {
|
|
|
80
117
|
| `TemplateResolverHandlers.upsertTenant` | `template-resolver:write:upsert-tenant` | TenantAdmin (eigener Tenant) + SystemAdmin via `tenantIdOverride` | Erstellt/Updated Tenant-Overrides (scope='tenant'), default-status='draft' |
|
|
81
118
|
| `TemplateResolverHandlers.publish` | `template-resolver:write:publish` | TenantAdmin (eigener Tenant) | Setzt status='active' |
|
|
82
119
|
| `TemplateResolverHandlers.archive` | `template-resolver:write:archive` | TenantAdmin (eigener Tenant) | Setzt status='archived' (Resolver ignoriert es danach) |
|
|
120
|
+
| `TemplateResolverHandlers.publishSystem` | `template-resolver:write:publish-system` | SystemAdmin | Setzt status='active' auf einem System-Default (gleiche Mail-Prüfung wie `publish`) |
|
|
121
|
+
| `TemplateResolverHandlers.archiveSystem` | `template-resolver:write:archive-system` | SystemAdmin | Setzt status='archived' auf einem System-Default; Tenants ohne Override fallen durch |
|
|
122
|
+
| `TemplateResolverHandlers.resetToSystem` | `template-resolver:write:reset-to-system` | TenantAdmin (eigener Tenant) | Archiviert den eigenen Override, sodass wieder die aktive System-Vorlage greift; ohne aktive System-Vorlage `no_system_template` |
|
|
83
123
|
| `TemplateResolverQueries.findById` | `template-resolver:query:find-by-id` | TenantAdmin + User (eigener Tenant + system-templates sichtbar) | Raw-Lookup für Edit-UI |
|
|
84
124
|
| `TemplateResolverQueries.list` | `template-resolver:query:list` | gleich | Filter nach kind/locale/status, optional includeSystem |
|
|
125
|
+
| `TemplateResolverQueries.usage` | `template-resolver:query:usage` | TenantAdmin | Welche Notifications eine Mail-Vorlage bzw. ein Mail-Layout verwenden (aus der Registry) |
|
|
126
|
+
| `TemplateResolverQueries.overview` | `template-resolver:query:overview` | TenantAdmin + SystemAdmin | Eine Zeile je Slug und Art (Mail, PDF, Layouts) mit Herkunft, Sprachen, Status, Version, Layout und Verwendung; durchsuch-, filter-, sortier- und seitenweise lesbar |
|
|
127
|
+
| `TemplateResolverQueries.layoutSource` | `template-resolver:query:layout-source` | TenantAdmin + SystemAdmin | Eigene und System-Fassung eines Layouts, Kopf/Signatur/Fuß-Bereiche der wirksamen html-Fassung, Speicher-Locale; MJML-Quelle nur für SystemAdmin |
|
|
128
|
+
| `TemplateResolverQueries.tenantOverrides` | `template-resolver:query:tenant-overrides` | SystemAdmin | Anzahl aktiver/Entwurf-Overrides einer System-Vorlage und aktive Tenants gesamt, nur Zahlen |
|
|
129
|
+
| `TemplateResolverQueries.diffToSystem` | `template-resolver:query:diff-to-system` | TenantAdmin (eigener Tenant) | Block- und Meta-Vergleich eines Tenant-Overrides mit der System-Vorlage |
|
|
85
130
|
|
|
86
131
|
**SystemAdmin-Cross-Tenant für publish/archive/findById:** deferred (Task 8) — `upsertTenant` hat `tenantIdOverride` bereits; publish/archive brauchen das erst wenn ein Sysadmin-Panel fremde Tenant-Templates kuratiert. `ctx.db` ist tenant-scoped, fremde IDs → NotFound ohne Override.
|
|
87
132
|
|
|
@@ -92,10 +137,12 @@ upsertSystem ──┐
|
|
|
92
137
|
├──► status: "active" (System-Default sofort aktiv)
|
|
93
138
|
upsertTenant ──┴──► status: "draft" (Default) | "active" (explizit)
|
|
94
139
|
|
|
95
|
-
publish ───────► status: "active"
|
|
140
|
+
publish ───────► status: "active" (übernimmt einen offenen Entwurf)
|
|
96
141
|
archive ───────► status: "archived"
|
|
142
|
+
discardDraft ──► verwirft den Entwurf neben der aktiven Fassung
|
|
97
143
|
```
|
|
98
144
|
|
|
145
|
+
Ein Entwurf über einer aktiven Zeile (`upsertTenant` ohne `status`, `copy-locale`, `import-bundle`) liegt als JSON in `draft` neben der Zeile; die Hauptfelder bleiben die ausgelieferte Fassung. `find-by-id` und `layout-source` liefern die Arbeitsfassung (Entwurf, sonst gespeichert) mit `active { version, since }`.
|
|
99
146
|
Resolver returnt **nur** Templates mit `status: "active"`. draft/archived werden ignoriert.
|
|
100
147
|
|
|
101
148
|
## Consumer Conformance
|
|
@@ -162,7 +209,7 @@ createTemplateResolverFeature({
|
|
|
162
209
|
collections: [
|
|
163
210
|
{
|
|
164
211
|
id: "reply-snippets",
|
|
165
|
-
kind: "mail
|
|
212
|
+
kind: "mail",
|
|
166
213
|
access: { roles: ["Agent", "TenantAdmin"] },
|
|
167
214
|
nav: { label: "mail:nav.snippets", parent: "mail:nav:root" },
|
|
168
215
|
},
|
|
@@ -245,6 +292,17 @@ nebeneinander und führt sie in der UI zusammen.
|
|
|
245
292
|
Unter `tenantIdOverride` (SystemAdmin) bleibt die Zeile die **eigene** des
|
|
246
293
|
Aufrufers im fremden Tenant — ein Admin editiert nie die Signatur eines anderen.
|
|
247
294
|
|
|
295
|
+
## Validierung, Layouts, Bundles
|
|
296
|
+
|
|
297
|
+
- `validate` (Query) trocken-läuft die Schreib-Pipeline: `canSave`, `canPublish`, verwendete/unbekannte/fehlende Platzhalter, verfügbare Variablen. `mail`, `document-pdf`, `pdf-layout` und `mail-layout` laufen durch dasselbe Platzhalter-Gate; `page`/`pages` gibt es nur in `document-pdf` und `pdf-layout`.
|
|
298
|
+
- `preview` (Query, TenantAdmin/SystemAdmin) rendert einen Entwurf oder, ohne `content`, die gespeicherte Vorlage mit Beispielwerten aus dem `example` jeder deklarierten Variable (Listen: zwei Zeilen, ohne `example` feste Ersatzwerte). Der Entwurf läuft durch dieselben Gates wie ein Speichern, eine Mail durch den echten Mail-Renderer. Antwort: Betreff, HTML, `displayHtml` (nur Mail: `cid:`-Bilder als Data-URI für den Editor, 5 MiB Budget), Text, Anhänge (`kind: "inline"` mit `cid` oder `kind: "file"`, jeweils mit Größe) und `checks` (`resources_resolved`, `text_present`, `size_under_cap` mit 10 MiB, `required_placeholders`). `document-pdf` liefert den bereinigten Body und die Layout-Regionen, kein PDF.
|
|
299
|
+
- `send-test` (Write, TenantAdmin/SystemAdmin) schickt die gerenderte Mail nur an die eigene Adresse des Aufrufers, Betreff mit `[TEST] `, höchstens 10 pro Nutzer und Stunde. Es gibt keine Empfänger-Eingabe. Braucht `delivery`, `channel-email` und `renderer-mail-html`.
|
|
300
|
+
- Layouts: Tenant-`mail-layout` und `pdf-layout` sind strukturiertes HTML mit `data-content`-Slot und Regionen (Header, Signatur, Footer). Rohes MJML mit `{{{content}}}` genau einmal darf nur das System schreiben.
|
|
301
|
+
- `export-bundle`, `import-bundle`, `import-bundle-system`: ein Template mit allen Locales und Ressourcen-Dateien (base64) als eigenständiges Bundle; bestehende Ziele nur mit `overwrite`. Systemimport mit Ressourcen braucht einen Caller im System-Tenant.
|
|
302
|
+
- `copy-locale`: Startpunkt für Übersetzungen, kopiert als Tenant-Draft. Mit `scope: "system"` (nur SystemAdmin) wird eine Systemvorlage als System-Draft kopiert; andere Aufrufer bekommen 403.
|
|
303
|
+
- Mail-Anhänge: `resource-upload` mit `purpose: "attachment"` und `fileName` legt eine PDF-, PNG- oder JPEG-Datei (geprüft am Inhalt, Originalbytes, höchstens 10 pro Mail-Vorlage) neben den Inline-Bildern ab. `resource-remove`, `copy-locale` und die Bundles tragen sie mit (Bundle-Eintrag `purpose: "attachment"`). Der Mail-Renderer hängt sie nach den Inline-Bildern an.
|
|
304
|
+
- `r.notification(..., { record: { entity, map } })` mappt eine Entity-Zeile auf die deklarierten Variablen (`mapRecordToVariables`); Boot prüft Entity, Variablen und Feldpfade.
|
|
305
|
+
|
|
248
306
|
## Out-of-Scope
|
|
249
307
|
|
|
250
308
|
- Rendering (Markdown/MJML → HTML/PDF) — siehe `renderer-foundation`
|