@cosmicdrift/kumiko-samples 0.388.0 → 0.390.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 +25 -13
- package/packages/bundled-features/src/agent-tools/changes.json +6 -0
- package/packages/bundled-features/src/agent-tools/index.ts +1 -1
- package/packages/bundled-features/src/agent-tools/tool-dispatch.ts +42 -26
- package/packages/bundled-features/src/audit/feature.ts +2 -1
- package/packages/bundled-features/src/auth-email-password/auth-user-row.ts +1 -1
- package/packages/bundled-features/src/auth-email-password/bootstrap.ts +13 -1
- package/packages/bundled-features/src/auth-email-password/changes.json +18 -0
- package/packages/bundled-features/src/auth-email-password/feature.ts +2 -2
- package/packages/bundled-features/src/auth-email-password/handlers/change-password.write.ts +7 -7
- package/packages/bundled-features/src/auth-email-password/handlers/confirm-account-unlock.write.ts +3 -3
- package/packages/bundled-features/src/auth-email-password/handlers/confirm-token-flow.ts +5 -5
- package/packages/bundled-features/src/auth-email-password/handlers/invite-accept-with-login.write.ts +9 -7
- package/packages/bundled-features/src/auth-email-password/handlers/invite-accept.write.ts +11 -7
- package/packages/bundled-features/src/auth-email-password/handlers/invite-create.write.ts +4 -4
- package/packages/bundled-features/src/auth-email-password/handlers/invite-info.query.ts +2 -2
- package/packages/bundled-features/src/auth-email-password/handlers/invite-signup-complete.write.ts +9 -7
- package/packages/bundled-features/src/auth-email-password/handlers/login.write.ts +23 -16
- package/packages/bundled-features/src/auth-email-password/handlers/request-account-unlock.write.ts +1 -0
- package/packages/bundled-features/src/auth-email-password/handlers/request-email-verification.write.ts +1 -0
- package/packages/bundled-features/src/auth-email-password/handlers/request-password-reset.write.ts +1 -0
- package/packages/bundled-features/src/auth-email-password/handlers/reset-password.write.ts +3 -2
- package/packages/bundled-features/src/auth-email-password/handlers/signup-confirm.write.ts +12 -12
- package/packages/bundled-features/src/auth-email-password/handlers/signup-request.write.ts +16 -8
- package/packages/bundled-features/src/auth-email-password/handlers/switch-tenant-mfa-gate.write.ts +4 -2
- package/packages/bundled-features/src/auth-email-password/handlers/token-request-handler.ts +10 -2
- package/packages/bundled-features/src/auth-email-password/handlers/verify-email.write.ts +3 -2
- package/packages/bundled-features/src/auth-email-password/magic-link-mail.ts +16 -3
- package/packages/bundled-features/src/auth-email-password/seeding.ts +14 -9
- package/packages/bundled-features/src/auth-email-password/signup-activation.ts +2 -0
- package/packages/bundled-features/src/auth-email-password/web/auth-client.ts +8 -2
- package/packages/bundled-features/src/auth-email-password/web/auth-redirect.ts +23 -21
- package/packages/bundled-features/src/auth-email-password/web/default-auth-pages-gate.tsx +6 -3
- package/packages/bundled-features/src/auth-email-password/web/forgot-password-screen.tsx +11 -1
- package/packages/bundled-features/src/auth-email-password/web/index.ts +5 -0
- package/packages/bundled-features/src/auth-email-password/web/login-screen.tsx +16 -5
- package/packages/bundled-features/src/auth-email-password/web/reset-password-screen.tsx +7 -1
- package/packages/bundled-features/src/auth-email-password/web/return-path.ts +35 -0
- package/packages/bundled-features/src/auth-email-password/web/session.tsx +6 -1
- package/packages/bundled-features/src/auth-email-password/web/signup-complete-screen.tsx +11 -3
- package/packages/bundled-features/src/auth-email-password/web/signup-screen.tsx +12 -2
- package/packages/bundled-features/src/auth-foundation/feature.ts +13 -4
- package/packages/bundled-features/src/auth-mfa/changes.json +6 -0
- package/packages/bundled-features/src/auth-mfa/config.ts +3 -7
- package/packages/bundled-features/src/auth-mfa/handlers/disable.write.ts +5 -5
- package/packages/bundled-features/src/auth-mfa/handlers/enable-confirm-preauth.write.ts +24 -14
- package/packages/bundled-features/src/auth-mfa/handlers/enable-confirm.write.ts +9 -3
- package/packages/bundled-features/src/auth-mfa/handlers/enable-start-preauth.write.ts +6 -1
- package/packages/bundled-features/src/auth-mfa/handlers/enable-start.write.ts +3 -1
- package/packages/bundled-features/src/auth-mfa/handlers/reencrypt.job.ts +14 -9
- package/packages/bundled-features/src/auth-mfa/handlers/regenerate-recovery.write.ts +5 -4
- package/packages/bundled-features/src/auth-mfa/handlers/verify.write.ts +25 -14
- package/packages/bundled-features/src/auth-mfa/mfa-code-verifier.ts +10 -4
- package/packages/bundled-features/src/auth-mfa/mfa-status-checker.ts +17 -8
- package/packages/bundled-features/src/auth-mfa/verify-factor.ts +1 -1
- package/packages/bundled-features/src/billing-foundation/billing-info-query.ts +2 -2
- package/packages/bundled-features/src/billing-foundation/changes.json +6 -0
- package/packages/bundled-features/src/billing-foundation/consumer-protection/consent-gate.ts +1 -1
- package/packages/bundled-features/src/billing-foundation/consumer-protection/issue-confirmation.ts +5 -5
- package/packages/bundled-features/src/billing-foundation/consumer-protection/register-consent.ts +10 -7
- package/packages/bundled-features/src/billing-foundation/consumer-protection/register-termination.ts +15 -12
- package/packages/bundled-features/src/billing-foundation/consumer-protection/termination-open-cancels.ts +22 -0
- package/packages/bundled-features/src/billing-foundation/consumer-protection/termination-provider-lock.ts +4 -0
- package/packages/bundled-features/src/billing-foundation/consumer-protection/termination-receipt-limit.ts +4 -4
- package/packages/bundled-features/src/billing-foundation/consumer-protection/termination-record.ts +17 -58
- package/packages/bundled-features/src/billing-foundation/consumer-protection/termination-request.ts +10 -8
- package/packages/bundled-features/src/billing-foundation/consumer-protection/termination-revoke.ts +18 -15
- package/packages/bundled-features/src/billing-foundation/events.ts +1 -1
- package/packages/bundled-features/src/billing-foundation/feature.ts +8 -3
- package/packages/bundled-features/src/billing-foundation/get-subscription-for-tenant.ts +1 -1
- package/packages/bundled-features/src/billing-foundation/handlers/process-event.write.ts +5 -5
- package/packages/bundled-features/src/billing-foundation/handlers/process-payment-event.write.ts +3 -3
- package/packages/bundled-features/src/billing-foundation/plan-catalog.ts +2 -2
- package/packages/bundled-features/src/billing-foundation/projection.ts +1 -1
- package/packages/bundled-features/src/billing-foundation/types.ts +1 -1
- package/packages/bundled-features/src/cap-counter/aggregate-id.ts +1 -1
- package/packages/bundled-features/src/cap-counter/book-cap-usage.ts +5 -4
- package/packages/bundled-features/src/cap-counter/cap-reservation.ts +21 -8
- package/packages/bundled-features/src/cap-counter/changes.json +12 -0
- package/packages/bundled-features/src/cap-counter/constants.ts +1 -1
- package/packages/bundled-features/src/cap-counter/feature.ts +3 -2
- package/packages/bundled-features/src/cap-counter/handlers/increment-rolling.write.ts +1 -1
- package/packages/bundled-features/src/cap-counter/reservation-chain.ts +1 -1
- package/packages/bundled-features/src/cap-counter/with-cap-enforcement.ts +4 -4
- package/packages/bundled-features/src/cap-overview/feature.ts +1 -0
- package/packages/bundled-features/src/cap-overview/handlers/caps-usage.query.ts +1 -1
- package/packages/bundled-features/src/cap-overview/handlers/tenant-caps-list.query.ts +1 -1
- package/packages/bundled-features/src/compliance-profiles/seeding.ts +12 -7
- package/packages/bundled-features/src/config/changes.json +6 -0
- package/packages/bundled-features/src/config/feature.ts +6 -6
- package/packages/bundled-features/src/config/handlers/cascade.query.ts +1 -1
- package/packages/bundled-features/src/config/handlers/readiness.query.ts +1 -1
- package/packages/bundled-features/src/config/handlers/reencrypt.job.ts +23 -13
- package/packages/bundled-features/src/config/handlers/reset.write.ts +3 -3
- package/packages/bundled-features/src/config/handlers/selected-extensions.query.ts +1 -1
- package/packages/bundled-features/src/config/handlers/set.write.ts +22 -5
- package/packages/bundled-features/src/config/handlers/values.query.ts +1 -1
- package/packages/bundled-features/src/crypto-shredding/changes.json +12 -0
- package/packages/bundled-features/src/crypto-shredding/handlers/forget-subject.write.ts +9 -9
- package/packages/bundled-features/src/crypto-shredding/resolve-file-bytes-deleter.ts +3 -3
- package/packages/bundled-features/src/custom-fields/changes.json +6 -0
- package/packages/bundled-features/src/custom-fields/handlers/clear-custom-field.write.ts +1 -1
- package/packages/bundled-features/src/custom-fields/handlers/delete-system-field.write.ts +1 -1
- package/packages/bundled-features/src/custom-fields/handlers/delete-tenant-field.write.ts +1 -1
- package/packages/bundled-features/src/custom-fields/handlers/set-custom-field.write.ts +1 -1
- package/packages/bundled-features/src/custom-fields/index.ts +4 -0
- package/packages/bundled-features/src/custom-fields/lib/parse-serialized-field.ts +1 -1
- package/packages/bundled-features/src/data-retention/feature.ts +1 -1
- package/packages/bundled-features/src/data-retention/run-retention-cleanup.ts +12 -2
- package/packages/bundled-features/src/delivery/attempt-log.ts +1 -1
- package/packages/bundled-features/src/delivery/changes.json +12 -0
- package/packages/bundled-features/src/delivery/delivery-service.ts +6 -5
- package/packages/bundled-features/src/delivery/feature.ts +3 -2
- package/packages/bundled-features/src/delivery/index.ts +5 -0
- package/packages/bundled-features/src/delivery/jobs.ts +11 -11
- package/packages/bundled-features/src/delivery/system-channel-context.ts +33 -0
- package/packages/bundled-features/src/delivery/testing.ts +1 -1
- package/packages/bundled-features/src/document-ingest-foundation/append-missing-extract-event.ts +84 -0
- package/packages/bundled-features/src/document-ingest-foundation/changes.json +6 -0
- package/packages/bundled-features/src/document-ingest-foundation/events.ts +16 -1
- package/packages/bundled-features/src/document-ingest-foundation/index.ts +11 -0
- package/packages/bundled-features/src/feature-toggles/feature.ts +1 -0
- package/packages/bundled-features/src/feature-toggles/handlers/registered.query.ts +1 -1
- package/packages/bundled-features/src/feature-toggles/handlers/set.write.ts +1 -1
- package/packages/bundled-features/src/feature-toggles/toggle-sync-signal.ts +1 -1
- package/packages/bundled-features/src/file-derivatives/feature.ts +2 -2
- package/packages/bundled-features/src/file-derivatives/handlers/public-variant-by-file-ref.query.ts +5 -1
- package/packages/bundled-features/src/file-derivatives/handlers/public-variant.query.ts +7 -5
- package/packages/bundled-features/src/file-foundation/feature.ts +1 -1
- package/packages/bundled-features/src/file-foundation/read-file-ref-content.ts +7 -5
- package/packages/bundled-features/src/file-provider-s3/feature.ts +4 -1
- package/packages/bundled-features/src/files-tenant-data/handlers/sweep-orphaned-derivatives.job.ts +8 -8
- package/packages/bundled-features/src/form-draft/feature.ts +1 -1
- package/packages/bundled-features/src/form-draft/handlers/cleanup.job.ts +22 -12
- package/packages/bundled-features/src/form-draft/handlers/discard.write.ts +3 -3
- package/packages/bundled-features/src/form-draft/handlers/get.query.ts +1 -1
- package/packages/bundled-features/src/foundation-shared/config-helpers.ts +1 -1
- package/packages/bundled-features/src/host-routing/changes.json +6 -0
- package/packages/bundled-features/src/host-routing/validate-options.ts +5 -0
- package/packages/bundled-features/src/inbound-mail-foundation/events.ts +1 -1
- package/packages/bundled-features/src/inbound-mail-foundation/feature.ts +2 -1
- package/packages/bundled-features/src/inbound-mail-foundation/handlers/account-state.ts +2 -2
- package/packages/bundled-features/src/inbound-mail-foundation/handlers/connect-account.write.ts +1 -1
- package/packages/bundled-features/src/inbound-mail-foundation/handlers/disconnect-account.write.ts +1 -1
- package/packages/bundled-features/src/inbound-mail-foundation/handlers/ingest-message.write.ts +10 -10
- package/packages/bundled-features/src/inbound-mail-foundation/handlers/update-account.write.ts +1 -1
- package/packages/bundled-features/src/inbound-mail-foundation/projection.ts +1 -1
- package/packages/bundled-features/src/inbound-mail-foundation/watch-supervisor.ts +1 -1
- package/packages/bundled-features/src/inbound-provider-imap/feature.ts +4 -1
- package/packages/bundled-features/src/jobs/changes.json +6 -0
- package/packages/bundled-features/src/jobs/events.ts +1 -1
- package/packages/bundled-features/src/jobs/feature.ts +1 -0
- package/packages/bundled-features/src/jobs/handlers/consumer.write.ts +7 -4
- package/packages/bundled-features/src/jobs/handlers/projection-rebuild.job.ts +1 -1
- package/packages/bundled-features/src/jobs/handlers/reindex-entity.job.ts +11 -5
- package/packages/bundled-features/src/jobs/handlers/retention-cleanup.job.ts +1 -1
- package/packages/bundled-features/src/jobs/handlers/retry.write.ts +1 -1
- package/packages/bundled-features/src/jobs/handlers/stale-run-sweep.job.ts +1 -1
- package/packages/bundled-features/src/jobs/handlers/trigger.write.ts +2 -2
- package/packages/bundled-features/src/jobs/job-run-logger.ts +139 -90
- package/packages/bundled-features/src/ledger/web/index.ts +1 -1
- package/packages/bundled-features/src/legal-pages/README.md +1 -1
- package/packages/bundled-features/src/legal-pages/lib/boot-check.ts +12 -8
- package/packages/bundled-features/src/legal-pages/seed.ts +5 -2
- package/packages/bundled-features/src/mail-foundation/feature.ts +2 -2
- package/packages/bundled-features/src/mail-transport-smtp/changes.json +12 -0
- package/packages/bundled-features/src/mail-transport-smtp/config-keys.ts +1 -1
- package/packages/bundled-features/src/mail-transport-smtp/feature.ts +13 -12
- package/packages/bundled-features/src/managed-pages/changes.json +6 -0
- package/packages/bundled-features/src/managed-pages/constants.ts +1 -0
- package/packages/bundled-features/src/managed-pages/feature.ts +3 -1
- package/packages/bundled-features/src/managed-pages/index.ts +1 -0
- package/packages/bundled-features/src/managed-pages/seeding.ts +13 -8
- package/packages/bundled-features/src/notes-history/handlers/add-note.write.ts +2 -2
- package/packages/bundled-features/src/personal-access-tokens/feature.ts +2 -1
- package/packages/bundled-features/src/personal-access-tokens/handlers/create.write.ts +2 -2
- package/packages/bundled-features/src/personal-access-tokens/handlers/revoke.write.ts +1 -1
- package/packages/bundled-features/src/rate-limiting/handlers/status.query.ts +2 -2
- package/packages/bundled-features/src/readiness/handlers/status.query.ts +4 -1
- package/packages/bundled-features/src/renderer-foundation/api.ts +3 -3
- package/packages/bundled-features/src/secrets/announce-secret-change.ts +4 -4
- package/packages/bundled-features/src/secrets/changes.json +6 -0
- package/packages/bundled-features/src/secrets/feature.ts +9 -9
- package/packages/bundled-features/src/secrets/handlers/delete.write.ts +4 -3
- package/packages/bundled-features/src/secrets/handlers/rotate.job.ts +13 -8
- package/packages/bundled-features/src/secrets/handlers/set.write.ts +4 -3
- package/packages/bundled-features/src/secrets/secrets-context.ts +16 -6
- package/packages/bundled-features/src/secrets/write-gate.ts +10 -3
- package/packages/bundled-features/src/seo/feature.ts +1 -1
- package/packages/bundled-features/src/sessions/changes.json +19 -0
- package/packages/bundled-features/src/sessions/feature.ts +2 -1
- package/packages/bundled-features/src/sessions/handlers/cleanup.job.ts +2 -2
- package/packages/bundled-features/src/sessions/handlers/detail.query.ts +5 -2
- package/packages/bundled-features/src/sessions/handlers/list.query.ts +5 -2
- package/packages/bundled-features/src/sessions/handlers/mine.query.ts +5 -2
- package/packages/bundled-features/src/sessions/handlers/revoke-all-for-user.write.ts +3 -3
- package/packages/bundled-features/src/sessions/handlers/revoke-all-others.write.ts +1 -1
- package/packages/bundled-features/src/sessions/handlers/revoke.write.ts +1 -1
- package/packages/bundled-features/src/sessions/index.ts +1 -1
- package/packages/bundled-features/src/sessions/screens.ts +8 -4
- package/packages/bundled-features/src/sessions/session-callbacks.ts +1 -9
- package/packages/bundled-features/src/sessions/session-revoked-event.ts +1 -1
- package/packages/bundled-features/src/sessions/web/session-device-cell.tsx +10 -12
- package/packages/bundled-features/src/sessions/web/user-agent.ts +12 -0
- package/packages/bundled-features/src/shared/index.ts +15 -0
- package/packages/bundled-features/src/shared/purge-files-reaching-subject.ts +36 -4
- package/packages/bundled-features/src/shared/require-for-tenant.ts +7 -5
- package/packages/bundled-features/src/shared/run-in-sub-transaction.ts +5 -5
- package/packages/bundled-features/src/shared/stream-tenant-backfill.ts +201 -0
- package/packages/bundled-features/src/shared/stream-tenant-derived-state.ts +107 -0
- package/packages/bundled-features/src/step-dispatcher/changes.json +6 -0
- package/packages/bundled-features/src/step-dispatcher/deliver-dispatch-request.ts +17 -3
- package/packages/bundled-features/src/step-dispatcher/feature.ts +1 -1
- package/packages/bundled-features/src/step-dispatcher/mail-runner.ts +2 -1
- package/packages/bundled-features/src/subscription-dodo/plugin-methods.ts +1 -1
- package/packages/bundled-features/src/subscription-dodo/runtime.ts +7 -7
- package/packages/bundled-features/src/subscription-stripe/plugin-methods.ts +5 -3
- package/packages/bundled-features/src/subscription-stripe/runtime.ts +7 -7
- package/packages/bundled-features/src/tags/feature.ts +1 -0
- package/packages/bundled-features/src/template-resolver/api.ts +3 -3
- package/packages/bundled-features/src/template-resolver/changes.json +54 -0
- package/packages/bundled-features/src/template-resolver/document-ops.ts +20 -1
- package/packages/bundled-features/src/template-resolver/feature.ts +2 -2
- package/packages/bundled-features/src/template-resolver/handlers/bundle-import.ts +5 -4
- package/packages/bundled-features/src/template-resolver/handlers/copy-locale.write.ts +2 -2
- package/packages/bundled-features/src/template-resolver/handlers/export-bundle.query.ts +1 -1
- package/packages/bundled-features/src/template-resolver/handlers/layout-source.query.ts +36 -11
- package/packages/bundled-features/src/template-resolver/handlers/overview.query.ts +32 -7
- package/packages/bundled-features/src/template-resolver/handlers/patch-draft.write.ts +1 -1
- package/packages/bundled-features/src/template-resolver/handlers/send-test-shared.ts +1 -1
- package/packages/bundled-features/src/template-resolver/handlers/send-test-status.query.ts +22 -19
- package/packages/bundled-features/src/template-resolver/handlers/send-test.write.ts +2 -2
- package/packages/bundled-features/src/template-resolver/handlers/tenant-override-selection.ts +6 -2
- package/packages/bundled-features/src/template-resolver/handlers/tenant-overrides.query.ts +4 -2
- package/packages/bundled-features/src/template-resolver/handlers/usage.query.ts +1 -1
- package/packages/bundled-features/src/template-resolver/seeding.ts +22 -5
- package/packages/bundled-features/src/template-resolver/store-template-resource.ts +68 -19
- package/packages/bundled-features/src/template-resolver/template-resources.ts +4 -4
- package/packages/bundled-features/src/template-resolver/testing.ts +2 -2
- package/packages/bundled-features/src/template-resolver/web/document-insert-fields.tsx +222 -0
- package/packages/bundled-features/src/template-resolver/web/document-insert-model.ts +164 -0
- package/packages/bundled-features/src/template-resolver/web/document-insert-panel.tsx +115 -0
- package/packages/bundled-features/src/template-resolver/web/document-tiptap-bubble.tsx +177 -0
- package/packages/bundled-features/src/template-resolver/web/document-tiptap-content.tsx +38 -0
- package/packages/bundled-features/src/template-resolver/web/document-tiptap-context.ts +119 -0
- package/packages/bundled-features/src/template-resolver/web/document-tiptap-editor.tsx +51 -1098
- package/packages/bundled-features/src/template-resolver/web/document-tiptap-helpers.ts +161 -0
- package/packages/bundled-features/src/template-resolver/web/document-tiptap-layout.tsx +83 -0
- package/packages/bundled-features/src/template-resolver/web/document-tiptap-toolbar-groups.tsx +153 -0
- package/packages/bundled-features/src/template-resolver/web/document-tiptap-toolbar.tsx +182 -0
- package/packages/bundled-features/src/template-resolver/web/editor-handle.ts +18 -0
- package/packages/bundled-features/src/template-resolver/web/i18n.ts +6 -0
- package/packages/bundled-features/src/template-resolver/web/placeholder-line-editor.tsx +19 -6
- package/packages/bundled-features/src/template-resolver/web/qualified-names.ts +1 -0
- package/packages/bundled-features/src/template-resolver/web/use-document-tiptap-model.ts +138 -0
- package/packages/bundled-features/src/tenant/changes.json +19 -0
- package/packages/bundled-features/src/tenant/feature.ts +1 -0
- package/packages/bundled-features/src/tenant/handlers/cancel-invitation.write.ts +1 -1
- package/packages/bundled-features/src/tenant/handlers/memberships.query.ts +1 -1
- package/packages/bundled-features/src/tenant/handlers/remove-member.write.ts +2 -2
- package/packages/bundled-features/src/tenant/handlers/team-list.query.ts +3 -3
- package/packages/bundled-features/src/tenant/handlers/update-member-roles.write.ts +1 -1
- package/packages/bundled-features/src/tenant/index.ts +5 -0
- package/packages/bundled-features/src/tenant/invited-membership.ts +6 -1
- package/packages/bundled-features/src/tenant/schema/tenant.ts +4 -1
- package/packages/bundled-features/src/tenant/seeding.ts +65 -62
- package/packages/bundled-features/src/tenant/stream-tenant-backfill.ts +115 -0
- package/packages/bundled-features/src/tenant/testing.ts +77 -0
- package/packages/bundled-features/src/tenant-handover/events.ts +1 -1
- package/packages/bundled-features/src/tenant-handover/handlers/claim.write.ts +1 -1
- package/packages/bundled-features/src/tenant-lifecycle/changes.json +6 -0
- package/packages/bundled-features/src/tenant-lifecycle/feature.ts +5 -5
- package/packages/bundled-features/src/tenant-lifecycle/handlers/cancel-destruction.write.ts +22 -10
- package/packages/bundled-features/src/tenant-lifecycle/handlers/request-destruction.write.ts +22 -11
- package/packages/bundled-features/src/tenant-lifecycle/run-tenant-destroy.ts +23 -12
- package/packages/bundled-features/src/tenant-lifecycle/stages.ts +42 -4
- package/packages/bundled-features/src/tenant-lifecycle/testing.ts +9 -8
- package/packages/bundled-features/src/tier-engine/entity.ts +1 -1
- package/packages/bundled-features/src/tier-engine/feature.ts +8 -3
- package/packages/bundled-features/src/tier-engine/handlers/active-tier.query.ts +3 -3
- package/packages/bundled-features/src/tier-engine/handlers/get-tenant-tier.query.ts +7 -1
- package/packages/bundled-features/src/tier-engine/handlers/set-tenant-tier.write.ts +3 -3
- package/packages/bundled-features/src/user/db/queries/stream-tenant-backfill.ts +71 -97
- package/packages/bundled-features/src/user/feature.ts +1 -0
- package/packages/bundled-features/src/user/handlers/find-for-auth.query.ts +2 -2
- package/packages/bundled-features/src/user/handlers/update-roles.ts +2 -2
- package/packages/bundled-features/src/user/index.ts +2 -0
- package/packages/bundled-features/src/user/invited-global-roles.ts +6 -1
- package/packages/bundled-features/src/user/principal-status.ts +1 -1
- package/packages/bundled-features/src/user/seeding.ts +24 -6
- package/packages/bundled-features/src/user/testing.ts +22 -0
- package/packages/bundled-features/src/user-data-rights/COMPLIANCE.md +1 -1
- package/packages/bundled-features/src/user-data-rights/README.md +1 -1
- package/packages/bundled-features/src/user-data-rights/audit-download.ts +12 -2
- package/packages/bundled-features/src/user-data-rights/changes.json +36 -0
- package/packages/bundled-features/src/user-data-rights/feature.ts +30 -24
- package/packages/bundled-features/src/user-data-rights/handlers/confirm-deletion-by-token.write.ts +1 -1
- package/packages/bundled-features/src/user-data-rights/handlers/deletion-grace-period.ts +1 -1
- package/packages/bundled-features/src/user-data-rights/handlers/download-by-job.query.ts +2 -2
- package/packages/bundled-features/src/user-data-rights/handlers/download-by-token.query.ts +2 -2
- package/packages/bundled-features/src/user-data-rights/handlers/request-deletion.write.ts +1 -1
- package/packages/bundled-features/src/user-data-rights/handlers/restrict-account.write.ts +4 -4
- package/packages/bundled-features/src/user-data-rights/handlers/run-forget-cleanup.write.ts +12 -6
- package/packages/bundled-features/src/user-data-rights/index.ts +8 -1
- package/packages/bundled-features/src/user-data-rights/lib/mail-transport-resolver.ts +1 -1
- package/packages/bundled-features/src/user-data-rights/lib/storage-provider-resolver.ts +1 -1
- package/packages/bundled-features/src/user-data-rights/lib/update-user-lifecycle.ts +6 -1
- package/packages/bundled-features/src/user-data-rights/run-export-jobs.ts +8 -2
- package/packages/bundled-features/src/user-data-rights/run-forget-cleanup.ts +14 -3
- package/packages/bundled-features/src/user-data-rights/run-user-export.ts +11 -1
- package/packages/bundled-features/src/user-data-rights/web/constants.ts +16 -0
- package/packages/bundled-features/src/user-data-rights/web/index.ts +6 -0
- package/packages/bundled-features/src/user-profile/handlers/change-email.write.ts +8 -6
- package/packages/bundled-features/src/waitlist/changes.json +18 -0
- package/packages/bundled-features/src/waitlist/feature.ts +1 -0
- package/packages/bundled-features/src/waitlist/handlers/export-purge.write.ts +1 -1
- package/packages/bundled-features/src/waitlist/handlers/invite.write.ts +2 -2
- package/packages/bundled-features/src/waitlist/handlers/reject.write.ts +1 -1
- package/packages/bundled-features/src/waitlist/handlers/submit.write.ts +34 -10
- package/packages/bundled-features/src/waitlist/import.ts +23 -3
- package/packages/bundled-features/src/waitlist/legacy-migrate.ts +8 -2
- package/packages/bundled-features/src/waitlist/link-on-signup.ts +7 -1
- package/packages/bundled-features/src/waitlist/options.ts +2 -1
- package/packages/bundled-features/src/workflow-runner/changes.json +6 -0
- package/packages/bundled-features/src/workflow-runner/db/queries/due-runs.ts +1 -1
- package/packages/bundled-features/src/workflow-runner/event-subscriber.ts +4 -3
- package/packages/bundled-features/src/workflow-runner/event-trigger.ts +2 -1
- package/packages/bundled-features/src/workflow-runner/feature.ts +2 -2
- package/packages/bundled-features/src/workflow-runner/handlers/resume-run.write.ts +8 -8
- package/packages/bundled-features/src/workflow-runner/runner.ts +6 -4
- package/samples/apps/cap-billing-demo/src/feature.ts +19 -5
- package/samples/apps/config-demo/src/app/server.ts +1 -1
- package/samples/apps/styleguide/src/app/client.tsx +2 -0
- package/samples/apps/styleguide/src/app/seed.ts +6 -4
- package/samples/apps/styleguide/src/app/server.ts +6 -3
- package/samples/apps/styleguide/src/features/widgets/feature.ts +15 -4
- package/samples/apps/styleguide/src/features/workboard/feature.ts +185 -0
- package/samples/apps/styleguide/src/features/workboard/i18n.ts +27 -0
- package/samples/apps/styleguide/src/features/workboard/seed.ts +121 -0
- package/samples/apps/styleguide/src/features/workboard/web.ts +13 -0
- package/samples/apps/ui-walkthrough/src/features/rental/i18n.ts +2 -0
- package/samples/apps/ui-walkthrough/src/features/rental/screens.ts +9 -0
- package/samples/apps/use-all-bundled/bin/main.ts +2 -0
- package/samples/apps/use-all-bundled/feature-manifest.json +17 -1
- package/samples/apps/use-all-bundled/kumiko/schema.ts +2 -0
- package/samples/apps/use-all-bundled/kumiko.config.ts +2 -0
- package/samples/apps/use-all-bundled/package.json +2 -1
- package/samples/apps/use-all-bundled/src/app/dev-app-options.ts +2 -0
- package/samples/apps/use-all-bundled/src/app/notes-feature.ts +2 -0
- package/samples/apps/use-all-bundled/src/app/seed.ts +12 -2
- package/samples/apps/use-all-bundled/src/bundled-lists.ts +15 -0
- package/samples/apps/user-data-rights-demo/README.md +1 -1
- package/samples/apps/user-data-rights-demo/src/feature.ts +3 -2
- package/samples/recipes/api-worker-split/README.md +2 -1
- package/samples/recipes/api-worker-split/src/feature.ts +2 -1
- package/samples/recipes/cross-feature-events/README.md +5 -5
- package/samples/recipes/cross-feature-events/package.json +1 -1
- package/samples/recipes/cross-feature-events/src/feature.ts +4 -4
- package/samples/recipes/event-sourcing/README.md +5 -5
- package/samples/recipes/event-sourcing/package.json +1 -1
- package/samples/recipes/event-sourcing/src/feature.ts +15 -15
- package/samples/recipes/files-post-processing/README.md +1 -1
- package/samples/recipes/files-post-processing/package.json +1 -1
- package/samples/recipes/files-post-processing/src/feature.ts +6 -4
- package/samples/recipes/golden-path/src/feature.ts +2 -1
- package/samples/recipes/lane-routing/package.json +1 -1
- package/samples/recipes/legal-pages/README.md +2 -2
- package/samples/recipes/lifecycle-hooks/README.md +4 -0
- package/samples/recipes/lifecycle-hooks/src/feature.ts +26 -1
- package/samples/recipes/list-table-options/README.md +50 -0
- package/samples/recipes/list-table-options/package.json +11 -0
- package/samples/recipes/list-table-options/src/feature.ts +221 -0
- package/samples/recipes/managed-config/README.md +3 -3
- package/samples/recipes/pdf-audit-export/src/feature.ts +2 -1
- package/samples/recipes/search/README.md +1 -1
- package/samples/recipes/search/src/feature.ts +3 -3
- package/samples/recipes/secrets-demo/README.md +1 -1
- package/samples/recipes/secrets-demo/src/feature.ts +5 -2
- package/samples/recipes/state-machine/src/events.ts +4 -3
- package/samples/recipes/state-machine/src/reducer.ts +1 -1
- package/samples/recipes/tenant-isolation/README.md +1 -0
- package/samples/recipes/tenant-isolation/src/feature.ts +36 -0
- package/samples/recipes/workflow-engine/src/resume-loop.ts +4 -4
- package/samples/recipes/workflow-engine/src/workflow-runner.ts +2 -2
|
@@ -114,11 +114,11 @@ export function createStripeRuntimes(deps: StripeRuntimeDeps): StripeRuntimes {
|
|
|
114
114
|
|
|
115
115
|
async function ctxApiKey(ctx: HandlerContext): Promise<string> {
|
|
116
116
|
let key: string | undefined;
|
|
117
|
-
if (ctx.secrets) {
|
|
118
|
-
const got = await requireSecretsContext(
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
);
|
|
117
|
+
if (ctx.services.secrets) {
|
|
118
|
+
const got = await requireSecretsContext(
|
|
119
|
+
{ secrets: ctx.services.secrets, user: ctx.user },
|
|
120
|
+
SUBSCRIPTION_STRIPE_FEATURE,
|
|
121
|
+
).get(SYSTEM_TENANT_ID, deps.apiKeyHandle.name);
|
|
122
122
|
key = parseStoredSecret(got?.reveal());
|
|
123
123
|
}
|
|
124
124
|
if (!key && deps.fallback.apiKey && deps.fallback.apiKey.length > 0) {
|
|
@@ -167,8 +167,8 @@ export function createStripeRuntimes(deps: StripeRuntimeDeps): StripeRuntimes {
|
|
|
167
167
|
if (hasFallbackKey) return true;
|
|
168
168
|
// Existence-only probe — called on every billing-plans page render,
|
|
169
169
|
// must stay un-audited (see SecretsContext.has's own doc comment).
|
|
170
|
-
return ctx.secrets
|
|
171
|
-
? await ctx.secrets.has(SYSTEM_TENANT_ID, deps.apiKeyHandle.name)
|
|
170
|
+
return ctx.services.secrets
|
|
171
|
+
? await ctx.services.secrets.has(SYSTEM_TENANT_ID, deps.apiKeyHandle.name)
|
|
172
172
|
: false;
|
|
173
173
|
},
|
|
174
174
|
},
|
|
@@ -54,6 +54,7 @@ function registerTags(
|
|
|
54
54
|
ownership: EntityDefinition["access"] | undefined,
|
|
55
55
|
parents: readonly string[] | undefined,
|
|
56
56
|
): void {
|
|
57
|
+
r.adoptsBundledLists();
|
|
57
58
|
r.describe(
|
|
58
59
|
"Generic, host-agnostic tagging for any entity. Owns two event-sourced entities — the per-tenant `tag` catalog (`read_tags`, with optional `color` and `scope`) and `tag-assignment` join rows keyed by (entityType, entityId) (`read_tag_assignments`) — so tagging adds NO column to the host entity and needs no relational pivot or JOIN. Catalog screens are declarative (`entityList` + `entityEdit`) and use convention QNs `tag:{create,update,delete}`; TagManager/TagPicker keep `create-tag`/`update-tag`/`delete-tag`. Also: `assign-tag` (idempotent), `remove-tag` (idempotent) and list queries for the catalog and the assignments. Read which tags an entity has, or which entities carry a tag, by listing `tag-assignment` filtered on `entityId` or `tagId` and composing in the read-layer. A tag with empty `scope` is global; a `scope` of an entityType restricts it to that type in the picker. Every path uses one access rule — adopt the host's model with createTagsFeature({ access: { openToAll: { reason } } }) or pin roles with createTagsFeature({ roles }). Pass { toggleable: { default: false } } to make the whole feature tier-gatable via the tier-engine (no host hook).",
|
|
59
60
|
);
|
|
@@ -332,12 +332,12 @@ export class TemplateNotFoundError extends Error {
|
|
|
332
332
|
// Single source of truth for "this handler requires template-resolver".
|
|
333
333
|
// Pattern symmetric with requireTextContent.
|
|
334
334
|
export function requireTemplateResolver(
|
|
335
|
-
ctx: { readonly
|
|
335
|
+
ctx: { readonly services: { readonly extensions: Readonly<Record<string, unknown>> } },
|
|
336
336
|
callerName: string,
|
|
337
337
|
): TemplateResolverApi {
|
|
338
338
|
// @cast-boundary engine-bridge — templateResolver kommt per extraContext
|
|
339
|
-
// from App-Bootstrap; the framework
|
|
340
|
-
const api =
|
|
339
|
+
// from App-Bootstrap and lands in services.extensions; the framework does not know its type.
|
|
340
|
+
const api = ctx.services.extensions["templateResolver"] as TemplateResolverApi | undefined;
|
|
341
341
|
if (!api) {
|
|
342
342
|
throw new InternalError({
|
|
343
343
|
message:
|
|
@@ -1,4 +1,58 @@
|
|
|
1
1
|
[
|
|
2
|
+
{
|
|
3
|
+
"version": "0.390.0",
|
|
4
|
+
"type": "fix",
|
|
5
|
+
"title": "Rail inserts no longer do nothing after the placeholder labels load",
|
|
6
|
+
"detail": "Inserting a placeholder into the subject, preheader, header chip or footer field could silently do nothing once the placeholder labels had loaded, because the replaced editor unregistered the handle of its remounted successor. The editors now report their handle from the React commit instead of tiptap's create and destroy callbacks."
|
|
7
|
+
},
|
|
8
|
+
{
|
|
9
|
+
"version": "0.390.0",
|
|
10
|
+
"type": "fix",
|
|
11
|
+
"title": "Placeholder inserts keep working after a line editor remounts",
|
|
12
|
+
"detail": "When a placeholder line editor (subject, preheader, header chip, footer note) remounted (labels arriving from the async validate query, or a new external value), TipTap could destroy the replaced editor after the new one was created. Its late null report then removed the live handle and placeholder inserts into that field did nothing. A null from a superseded editor is now ignored."
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
"version": "0.390.0",
|
|
16
|
+
"type": "improvement",
|
|
17
|
+
"title": "Split DocumentTiptapEditor into a model hook and render functions",
|
|
18
|
+
"detail": "DocumentTiptapEditor and its insert panel compose a model hook, pure helpers and render functions for toolbar, bubble menu, content and insert fields. Rendered markup and behavior are unchanged."
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
"version": "0.389.0",
|
|
22
|
+
"type": "fix",
|
|
23
|
+
"title": "copy-locale reads source files from the owning tenant's storage",
|
|
24
|
+
"detail": "The copy of linked files uses readFileRefContent with the provider of the fileRef's tenant. A source file whose fileRef row exists but whose bytes cannot be read fails the copy with template_resource_source_unreadable and releases the files already copied. A deleted fileRef is still left out of the copy."
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
"version": "0.389.0",
|
|
28
|
+
"type": "improvement",
|
|
29
|
+
"title": "replaceText explains finds across formatting or placeholders",
|
|
30
|
+
"detail": "replaceText matches inside single text nodes so formatting stays intact. A find that appears in the block line but crosses a formatting change now fails with text_spans_formatting, and a find with a {{placeholder}} fails with placeholder_not_replaceable, both with translated messages. Only a string that is not in the block at all still gives text_not_found."
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
"version": "0.389.0",
|
|
34
|
+
"type": "fix",
|
|
35
|
+
"title": "send-test-status follows the tenantEditable roles",
|
|
36
|
+
"detail": "send-test-status takes its access from templateEditAccess(policy.tenantEditable), like send-test. Roles that may send a test mail now get the status, and a TenantAdmin without an edit opt-in gets a 403 on both."
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
"version": "0.389.0",
|
|
40
|
+
"type": "fix",
|
|
41
|
+
"title": "overview follows the tenantEditable roles",
|
|
42
|
+
"detail": "The overview query takes its access from templateEditAccess(policy.tenantEditable). An opted-in role lists exactly the released templates, and a TenantAdmin without an opt-in is refused with a 403, matching the screen visibility and the write handlers."
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
"version": "0.389.0",
|
|
46
|
+
"type": "fix",
|
|
47
|
+
"title": "overview search and sort use the translated title",
|
|
48
|
+
"detail": "The overview query translates a seeded titleI18nKey with the request locale before it filters by search and sorts by title. A search for the visible title now matches, and the raw key is still searchable."
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
"version": "0.389.0",
|
|
52
|
+
"type": "fix",
|
|
53
|
+
"title": "legalContentSeed keeps and can set the content folder",
|
|
54
|
+
"detail": "seedTextBlock no longer resets the folder of an existing block to null when the seed passes no folder. legalContentSeed takes an optional folder, so apps do not need their own seed loop to keep legal blocks in the SystemAdmin content tree."
|
|
55
|
+
},
|
|
2
56
|
{
|
|
3
57
|
"version": "0.388.0",
|
|
4
58
|
"type": "fix",
|
|
@@ -389,6 +389,23 @@ function removeBlockOp(
|
|
|
389
389
|
return null;
|
|
390
390
|
}
|
|
391
391
|
|
|
392
|
+
export const REPLACE_TEXT_ISSUE_CODES = {
|
|
393
|
+
notFound: "text_not_found",
|
|
394
|
+
spansFormatting: "text_spans_formatting",
|
|
395
|
+
placeholderNotReplaceable: "placeholder_not_replaceable",
|
|
396
|
+
} as const;
|
|
397
|
+
|
|
398
|
+
// document-blocks shows the line across formatting runs with placeholders as {{name}},
|
|
399
|
+
// so a find that is visible there but lies in no single text node needs its own explanation.
|
|
400
|
+
function textNotMatchedCode(block: JSONContent, find: string): string {
|
|
401
|
+
if (!textLinesOf(block).some((line) => line.includes(find))) {
|
|
402
|
+
return REPLACE_TEXT_ISSUE_CODES.notFound;
|
|
403
|
+
}
|
|
404
|
+
return find.includes("{{")
|
|
405
|
+
? REPLACE_TEXT_ISSUE_CODES.placeholderNotReplaceable
|
|
406
|
+
: REPLACE_TEXT_ISSUE_CODES.spansFormatting;
|
|
407
|
+
}
|
|
408
|
+
|
|
392
409
|
function replaceTextOp(
|
|
393
410
|
state: OpState,
|
|
394
411
|
op: Extract<DocumentOp, { op: "replaceText" }>,
|
|
@@ -401,7 +418,9 @@ function replaceTextOp(
|
|
|
401
418
|
(sum, node) => sum + occurrencesOf(node.text ?? "", op.find),
|
|
402
419
|
0,
|
|
403
420
|
);
|
|
404
|
-
if (count === 0)
|
|
421
|
+
if (count === 0) {
|
|
422
|
+
return patchIssue(`${path}.find`, textNotMatchedCode(entry.node, op.find), { find: op.find });
|
|
423
|
+
}
|
|
405
424
|
if (count > 1) return patchIssue(`${path}.find`, "text_ambiguous", { find: op.find, count });
|
|
406
425
|
entry.node = replaceInContent([entry.node], op.find, op.replace)[0] ?? entry.node;
|
|
407
426
|
return null;
|
|
@@ -29,7 +29,7 @@ import { makeResourceLinkPdfWrite } from "./handlers/resource-link-pdf.write.ts"
|
|
|
29
29
|
import { makeResourceRemoveWrite } from "./handlers/resource-remove.write.ts";
|
|
30
30
|
import { makeResourceUploadWrite } from "./handlers/resource-upload.write.ts";
|
|
31
31
|
import { makeSendTestWrite } from "./handlers/send-test.write.ts";
|
|
32
|
-
import {
|
|
32
|
+
import { makeSendTestStatusQuery } from "./handlers/send-test-status.query.ts";
|
|
33
33
|
import { makeSetWrite } from "./handlers/set.write.ts";
|
|
34
34
|
import { tenantOverrideCountsQuery } from "./handlers/tenant-override-counts.query.ts";
|
|
35
35
|
import { tenantOverridesQuery } from "./handlers/tenant-overrides.query.ts";
|
|
@@ -191,7 +191,7 @@ export function createTemplateResolverFeature(opts: TemplateResolverOptions = {}
|
|
|
191
191
|
preview: r.queryHandler(previewQuery),
|
|
192
192
|
documentBlocks: r.queryHandler(documentBlocksQuery),
|
|
193
193
|
exportBundle: r.queryHandler(exportBundleQuery),
|
|
194
|
-
sendTestStatus: r.queryHandler(
|
|
194
|
+
sendTestStatus: r.queryHandler(makeSendTestStatusQuery(policy)),
|
|
195
195
|
};
|
|
196
196
|
|
|
197
197
|
for (const collection of collections) {
|
|
@@ -14,6 +14,7 @@ import {
|
|
|
14
14
|
import { requireForTenant } from "../../shared/index.ts";
|
|
15
15
|
import type { TemplateWriteScope } from "../layout-regions.ts";
|
|
16
16
|
import type { LinkedResourceEntry } from "../linked-resources.ts";
|
|
17
|
+
import type { PrepareContext } from "../store-template-resource.ts";
|
|
17
18
|
import {
|
|
18
19
|
attachmentLimitFailure,
|
|
19
20
|
attachmentNeedsMailTemplate,
|
|
@@ -48,8 +49,8 @@ export function targetExistsIssue(locale: string): FieldIssue {
|
|
|
48
49
|
};
|
|
49
50
|
}
|
|
50
51
|
|
|
51
|
-
type BundleImport = {
|
|
52
|
-
readonly ctx:
|
|
52
|
+
type BundleImport<TCtx extends PrepareContext = HandlerContext> = {
|
|
53
|
+
readonly ctx: TCtx;
|
|
53
54
|
readonly user: SessionUser;
|
|
54
55
|
readonly tenantId: TenantId;
|
|
55
56
|
readonly scope: TemplateWriteScope;
|
|
@@ -60,7 +61,7 @@ type BundleImport = {
|
|
|
60
61
|
type BundleImportStatus = "draft" | "active";
|
|
61
62
|
|
|
62
63
|
async function collectBundleIssues(
|
|
63
|
-
args: BundleImport
|
|
64
|
+
args: BundleImport<PrepareContext>,
|
|
64
65
|
status: BundleImportStatus,
|
|
65
66
|
): Promise<readonly FieldIssue[]> {
|
|
66
67
|
const { ctx, tenantId, scope, bundle, overwrite } = args;
|
|
@@ -113,7 +114,7 @@ function checkBundleAttachments(bundle: TemplateBundle): WriteResult<null> {
|
|
|
113
114
|
// beforeTransaction half: validates the bundle and uploads every resource file to the target
|
|
114
115
|
// tenant's storage. A failed or throwing upload releases the ones before it.
|
|
115
116
|
export async function prepareTemplateBundleBeforeTransaction(
|
|
116
|
-
args: BundleImport & { readonly status: BundleImportStatus },
|
|
117
|
+
args: BundleImport<PrepareContext> & { readonly status: BundleImportStatus },
|
|
117
118
|
): Promise<BeforeTransactionResult<PreparedBundleResources>> {
|
|
118
119
|
const refused =
|
|
119
120
|
refuseTenantScopeUnderSystemTenant(args.scope, args.tenantId) ??
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { fetchOne } from "@cosmicdrift/kumiko-framework/db";
|
|
2
2
|
import {
|
|
3
3
|
defineWriteHandler,
|
|
4
|
-
type HandlerContext,
|
|
5
4
|
type SessionUser,
|
|
6
5
|
SYSTEM_TENANT_ID,
|
|
7
6
|
type TenantId,
|
|
@@ -18,6 +17,7 @@ import { isOneOf } from "@cosmicdrift/kumiko-types/type-guards";
|
|
|
18
17
|
import * as z from "zod";
|
|
19
18
|
import { requireForTenant } from "../../shared/index.ts";
|
|
20
19
|
import { type LinkedResourceEntry, parseLinkedResourceEntries } from "../linked-resources.ts";
|
|
20
|
+
import type { PrepareContext } from "../store-template-resource.ts";
|
|
21
21
|
import {
|
|
22
22
|
createLinkedResourceCopies,
|
|
23
23
|
prepareLinkedResourceCopiesBeforeTransaction,
|
|
@@ -43,7 +43,7 @@ import {
|
|
|
43
43
|
} from "./template-write.ts";
|
|
44
44
|
|
|
45
45
|
type CopyScope = "tenant" | "system";
|
|
46
|
-
type WriteCtx =
|
|
46
|
+
type WriteCtx = PrepareContext;
|
|
47
47
|
|
|
48
48
|
function readDbFor(ctx: WriteCtx, scope: CopyScope) {
|
|
49
49
|
return scope === "system" ? requireForTenant(ctx, SYSTEM_TENANT_ID).db : ctx.db;
|
|
@@ -124,7 +124,7 @@ export const exportBundleQuery = defineQueryHandler({
|
|
|
124
124
|
}
|
|
125
125
|
const attachment = isFileAttachmentEntry(entry);
|
|
126
126
|
const file = await readTemplateResource(
|
|
127
|
-
{ db: ctx.db, files: ctx.files, user: query.user },
|
|
127
|
+
{ db: ctx.db, services: { files: ctx.services.files, extensions: {} }, user: query.user },
|
|
128
128
|
attachment ? entry.fileRefId : entry,
|
|
129
129
|
);
|
|
130
130
|
if (!file) throw new NotFoundError("template-resource-file", key);
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { selectMany } from "@cosmicdrift/kumiko-framework/db";
|
|
2
2
|
import { defineQueryHandler } from "@cosmicdrift/kumiko-framework/engine";
|
|
3
|
+
import { AccessDeniedError } from "@cosmicdrift/kumiko-framework/errors";
|
|
3
4
|
import * as z from "zod";
|
|
4
5
|
import { filterDeliveredTo } from "../api.ts";
|
|
5
6
|
import { type LayoutRegions, readLayoutRegions } from "../layout-regions.ts";
|
|
@@ -14,6 +15,7 @@ import {
|
|
|
14
15
|
} from "../template-draft.ts";
|
|
15
16
|
import { type MailLayoutMeta, readMailLayoutMeta } from "../template-meta.ts";
|
|
16
17
|
import { FALLBACK_LOCALE } from "../web/constants.ts";
|
|
18
|
+
import { TemplateResolverErrors } from "../web/qualified-names.ts";
|
|
17
19
|
import { localeSchema, templateSlugSchema } from "./shared.ts";
|
|
18
20
|
import { parseStoredJson } from "./template-gates.ts";
|
|
19
21
|
|
|
@@ -66,20 +68,29 @@ function layoutFormat(row: WorkingRow): "html" | "mjml" | null {
|
|
|
66
68
|
return row.contentFormat === "html" || row.contentFormat === "mjml" ? row.contentFormat : null;
|
|
67
69
|
}
|
|
68
70
|
|
|
71
|
+
type EditScope = "tenant" | "system";
|
|
72
|
+
|
|
69
73
|
function pickEditedRows(
|
|
70
74
|
rows: readonly TemplateResourceRow[],
|
|
71
|
-
|
|
75
|
+
caller: { readonly tenantId: string; readonly roles: readonly string[] },
|
|
76
|
+
scope: EditScope,
|
|
72
77
|
locale: string | undefined,
|
|
73
78
|
): {
|
|
74
79
|
readonly tenantRow: WorkingRow | null;
|
|
75
80
|
readonly systemRow: WorkingRow | null;
|
|
76
81
|
} {
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
82
|
+
// A SystemAdmin editing the tenant layout sees the system version like a tenant does: served only, no draft.
|
|
83
|
+
const user =
|
|
84
|
+
scope === "system"
|
|
85
|
+
? caller
|
|
86
|
+
: { ...caller, roles: caller.roles.filter((role) => role !== "SystemAdmin") };
|
|
87
|
+
const tenantRow =
|
|
88
|
+
scope === "system"
|
|
89
|
+
? null
|
|
90
|
+
: pickByLocale(
|
|
91
|
+
rows.filter((row) => row.scope === "tenant"),
|
|
92
|
+
locale,
|
|
93
|
+
);
|
|
83
94
|
const systemRow = pickByLocale(
|
|
84
95
|
rows.filter((row) => row.scope === "system" && !isHiddenSystemDraft(user, row)),
|
|
85
96
|
locale,
|
|
@@ -136,13 +147,21 @@ export const layoutSourceQuery = defineQueryHandler({
|
|
|
136
147
|
kind: z.enum(["mail-layout", "pdf-layout"]),
|
|
137
148
|
slug: templateSlugSchema.default("default"),
|
|
138
149
|
locale: localeSchema.optional(),
|
|
150
|
+
scope: z.enum(["tenant", "system"]).optional(),
|
|
139
151
|
}),
|
|
140
152
|
access: { roles: ["TenantAdmin", "SystemAdmin"] },
|
|
141
153
|
description:
|
|
142
|
-
"Reads the layout the caller edits: the tenant's own version and the system version of one layout slug (each the open draft if there is one, else the stored version, with `active` naming the served version), the header, signature and footer regions of the effective html version, and the locale a save has to use; a SystemAdmin edits the system version and also receives its mjml source. Use it to load the layout editor.",
|
|
154
|
+
"Reads the layout the caller edits: the tenant's own version and the system version of one layout slug (each the open draft if there is one, else the stored version, with `active` naming the served version), the header, signature and footer regions of the effective html version, and the locale a save has to use; a SystemAdmin edits the system version by default and also receives its mjml source; scope tenant loads the SystemAdmin's own tenant layout instead, and scope system is refused for everyone else. Use it to load the layout editor.",
|
|
143
155
|
handler: async (query, ctx): Promise<LayoutSourceResponse> => {
|
|
144
156
|
const { kind, slug, locale } = query.payload;
|
|
145
|
-
const
|
|
157
|
+
const callerIsSystemAdmin = query.user.roles.includes("SystemAdmin");
|
|
158
|
+
const scope = query.payload.scope ?? (callerIsSystemAdmin ? "system" : "tenant");
|
|
159
|
+
if (scope === "system" && !callerIsSystemAdmin) {
|
|
160
|
+
throw new AccessDeniedError({
|
|
161
|
+
i18nKey: "errors.access.denied",
|
|
162
|
+
details: { reason: TemplateResolverErrors.systemAdminRequired },
|
|
163
|
+
});
|
|
164
|
+
}
|
|
146
165
|
const policy = deliveryPolicyOf(ctx.registry);
|
|
147
166
|
const rows = await filterDeliveredTo(
|
|
148
167
|
ctx.db,
|
|
@@ -150,7 +169,13 @@ export const layoutSourceQuery = defineQueryHandler({
|
|
|
150
169
|
query.user,
|
|
151
170
|
await selectMany<TemplateResourceRow>(ctx.db, templateResourcesTable, { kind, slug }),
|
|
152
171
|
);
|
|
153
|
-
const { tenantRow, systemRow } = pickEditedRows(rows, query.user, locale);
|
|
154
|
-
return buildLayoutSource({
|
|
172
|
+
const { tenantRow, systemRow } = pickEditedRows(rows, query.user, scope, locale);
|
|
173
|
+
return buildLayoutSource({
|
|
174
|
+
kind,
|
|
175
|
+
locale,
|
|
176
|
+
isSystemAdmin: scope === "system",
|
|
177
|
+
tenantRow,
|
|
178
|
+
systemRow,
|
|
179
|
+
});
|
|
155
180
|
},
|
|
156
181
|
});
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { selectMany } from "@cosmicdrift/kumiko-framework/db";
|
|
2
2
|
import { defineQueryHandler } from "@cosmicdrift/kumiko-framework/engine";
|
|
3
|
+
import { createI18n } from "@cosmicdrift/kumiko-framework/i18n";
|
|
3
4
|
import * as z from "zod";
|
|
4
5
|
import { type TemplateResourceRow, templateResourcesTable } from "../table.ts";
|
|
5
6
|
import {
|
|
@@ -16,7 +17,11 @@ import {
|
|
|
16
17
|
parseTemplateDraft,
|
|
17
18
|
servedVersionOf,
|
|
18
19
|
} from "../template-draft.ts";
|
|
19
|
-
import {
|
|
20
|
+
import {
|
|
21
|
+
mayEditTemplate,
|
|
22
|
+
type TemplateEditPolicy,
|
|
23
|
+
templateEditAccess,
|
|
24
|
+
} from "../template-edit-policy.ts";
|
|
20
25
|
import { isSystemOnlyTemplateHiddenFrom } from "../template-system-only.ts";
|
|
21
26
|
import { collectTemplateUsage, type EffectiveMailLayoutSlugs } from "../template-usage.ts";
|
|
22
27
|
import {
|
|
@@ -204,7 +209,15 @@ function splitDocumentSlug(slug: string): { feature: string; notification: strin
|
|
|
204
209
|
: { feature: slug.slice(0, separator), notification: slug.slice(separator + 1) };
|
|
205
210
|
}
|
|
206
211
|
|
|
207
|
-
|
|
212
|
+
type ShownTitle = (row: OverviewRow) => string;
|
|
213
|
+
|
|
214
|
+
function compareBy(
|
|
215
|
+
field: SortField,
|
|
216
|
+
a: OverviewRow,
|
|
217
|
+
b: OverviewRow,
|
|
218
|
+
shownTitle: ShownTitle,
|
|
219
|
+
): number {
|
|
220
|
+
if (field === "title") return shownTitle(a).localeCompare(shownTitle(b));
|
|
208
221
|
return String(a[field]).localeCompare(String(b[field]));
|
|
209
222
|
}
|
|
210
223
|
|
|
@@ -351,11 +364,17 @@ function filterRows(
|
|
|
351
364
|
search: string | undefined,
|
|
352
365
|
filters: readonly OverviewFilter[],
|
|
353
366
|
layoutSlug: string | undefined,
|
|
367
|
+
shownTitle: ShownTitle,
|
|
354
368
|
): OverviewRow[] {
|
|
355
369
|
const needle = search?.trim().toLowerCase() ?? "";
|
|
356
370
|
return rows.filter((row) => {
|
|
357
371
|
if (layoutSlug !== undefined && row.layoutSlug !== layoutSlug) return false;
|
|
358
|
-
if (
|
|
372
|
+
if (
|
|
373
|
+
needle !== "" &&
|
|
374
|
+
!`${shownTitle(row)} ${row.title} ${row.slug}`.toLowerCase().includes(needle)
|
|
375
|
+
) {
|
|
376
|
+
return false;
|
|
377
|
+
}
|
|
359
378
|
return filters.every((filter) => filter.value.includes(String(row[filter.field])));
|
|
360
379
|
});
|
|
361
380
|
}
|
|
@@ -379,7 +398,7 @@ export function makeOverviewQuery(policy: TemplateEditPolicy) {
|
|
|
379
398
|
offset: z.number().int().min(0).optional(),
|
|
380
399
|
totalCount: z.boolean().optional(),
|
|
381
400
|
}),
|
|
382
|
-
access:
|
|
401
|
+
access: templateEditAccess(policy.tenantEditable),
|
|
383
402
|
description:
|
|
384
403
|
"Lists mail, PDF and layout templates as one row per slug and kind for the caller's tenant (optionally only the mails and PDFs using one layout slug): where the effective version comes from (own override or system), its locales, status and version (the served one; `draftVersion` is set when an unpublished draft waits beside it, `activeSince` says since when the served version applies), the system version it overrides, the layout it uses and which notifications or templates use it; templates of notifications registered with `systemOnly` are listed for SystemAdmins only, and a TenantAdmin sees only the templates the app lists in tenantEditable; a tenant override of a template that is not listed is not delivered, so the row shows the system version with `lockedOverride` true (SystemAdmin only); use it to browse and filter all templates at a glance.",
|
|
385
404
|
handler: async (query, ctx) => {
|
|
@@ -393,7 +412,7 @@ export function makeOverviewQuery(policy: TemplateEditPolicy) {
|
|
|
393
412
|
const truncated = fetchedRows.length > OVERVIEW_ROW_LIMIT;
|
|
394
413
|
const rows = truncated ? fetchedRows.slice(0, OVERVIEW_ROW_LIMIT) : fetchedRows;
|
|
395
414
|
if (truncated) {
|
|
396
|
-
ctx.log?.warn("template overview: row limit reached, result is incomplete", {
|
|
415
|
+
ctx.obs.log?.warn("template overview: row limit reached, result is incomplete", {
|
|
397
416
|
limit: OVERVIEW_ROW_LIMIT,
|
|
398
417
|
});
|
|
399
418
|
}
|
|
@@ -415,21 +434,27 @@ export function makeOverviewQuery(policy: TemplateEditPolicy) {
|
|
|
415
434
|
? withOverriddenBy(
|
|
416
435
|
rowsWithUsage,
|
|
417
436
|
tenantOverrideCountsResultSchema.parse(
|
|
418
|
-
await ctx.query(TemplateResolverQueries.tenantOverrideCounts, {}),
|
|
437
|
+
await ctx.dispatch.query(TemplateResolverQueries.tenantOverrideCounts, {}),
|
|
419
438
|
),
|
|
420
439
|
)
|
|
421
440
|
: rowsWithUsage;
|
|
441
|
+
// A seeded title can be an i18n key; search and sort must see what the admin sees.
|
|
442
|
+
const i18n = createI18n(ctx.registry, { defaultLocale: "en" });
|
|
443
|
+
const shownTitle: ShownTitle = (row) => i18n.t(row.title, ctx.locale);
|
|
422
444
|
const filtered = filterRows(
|
|
423
445
|
overviewRows,
|
|
424
446
|
payload.search,
|
|
425
447
|
payload.filters ?? [],
|
|
426
448
|
payload.layoutSlug,
|
|
449
|
+
shownTitle,
|
|
427
450
|
);
|
|
428
451
|
|
|
429
452
|
const sortField: SortField =
|
|
430
453
|
payload.sort !== undefined && isSortField(payload.sort) ? payload.sort : "modifiedAt";
|
|
431
454
|
const direction = (payload.sortDirection ?? "desc") === "desc" ? -1 : 1;
|
|
432
|
-
filtered.sort(
|
|
455
|
+
filtered.sort(
|
|
456
|
+
(a, b) => direction * compareBy(sortField, a, b, shownTitle) || a.id.localeCompare(b.id),
|
|
457
|
+
);
|
|
433
458
|
|
|
434
459
|
const offset = payload.offset ?? 0;
|
|
435
460
|
return {
|
|
@@ -40,7 +40,7 @@ export function makePatchDraftWrite(policy: TemplateEditPolicy) {
|
|
|
40
40
|
}),
|
|
41
41
|
access: templateEditAccess(policy.tenantEditable),
|
|
42
42
|
description:
|
|
43
|
-
"Changes a mail or PDF document template with structured operations instead of a full rewrite and saves the result as the caller's tenant draft (never published, an active version keeps being served until publish): insertBlock after a block id (null = at the start; paragraph, heading, image, button, divider, pageBreak), replaceText inside one block (the text must occur exactly once), removeBlock, setResource on an image block, setMeta (flat merge, null removes a key) and setVariable (adds, replaces or with null removes a variable definition; refused when a notification declares the variables). Block ids and base come from document-blocks and refer to the state that was read; if the template changed since, the call fails with version_conflict and nothing is written. A patch that removes a required placeholder is refused. A TenantAdmin may patch only templates the app lists in tenantEditable, otherwise SystemAdmin only. Returns the written row.",
|
|
43
|
+
"Changes a mail or PDF document template with structured operations instead of a full rewrite and saves the result as the caller's tenant draft (never published, an active version keeps being served until publish): insertBlock after a block id (null = at the start; paragraph, heading, image, button, divider, pageBreak), replaceText inside one block (the text must occur exactly once within one formatting run and cannot contain a {{placeholder}}: a find across a formatting change fails with text_spans_formatting, a find with a placeholder with placeholder_not_replaceable, and the formatting of the replaced text is kept), removeBlock, setResource on an image block, setMeta (flat merge, null removes a key) and setVariable (adds, replaces or with null removes a variable definition; refused when a notification declares the variables). Block ids and base come from document-blocks and refer to the state that was read; if the template changed since, the call fails with version_conflict and nothing is written. A patch that removes a required placeholder is refused. A TenantAdmin may patch only templates the app lists in tenantEditable, otherwise SystemAdmin only. Returns the written row.",
|
|
44
44
|
handler: async (event, ctx) => {
|
|
45
45
|
const { slug, kind, locale, base, ops } = event.payload;
|
|
46
46
|
const refused = refuseTemplateEdit(policy, event.user, { slug, kind });
|
|
@@ -17,7 +17,7 @@ export function testMailChannelContext(ctx: HandlerContext, user: SessionUser):
|
|
|
17
17
|
registry: ctx.registry,
|
|
18
18
|
sseBroker: undefined,
|
|
19
19
|
tenantId: user.tenantId,
|
|
20
|
-
secrets: ctx.secrets,
|
|
20
|
+
secrets: ctx.services.secrets,
|
|
21
21
|
config: ctx.config,
|
|
22
22
|
readAttachment: createEmailAttachmentReader(
|
|
23
23
|
ctx.db,
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { defineQueryHandler } from "@cosmicdrift/kumiko-framework/engine";
|
|
2
2
|
import * as z from "zod";
|
|
3
|
+
import { type TemplateEditPolicy, templateEditAccess } from "../template-edit-policy.ts";
|
|
3
4
|
import {
|
|
4
5
|
emailChannelOf,
|
|
5
6
|
resolveTestMailAddress,
|
|
@@ -10,22 +11,24 @@ export type SendTestStatus =
|
|
|
10
11
|
| { readonly canSend: true; readonly reason: null }
|
|
11
12
|
| { readonly canSend: false; readonly reason: "no_channel" | "no_address" };
|
|
12
13
|
|
|
13
|
-
export
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
}
|
|
14
|
+
export function makeSendTestStatusQuery(policy: TemplateEditPolicy) {
|
|
15
|
+
return defineQueryHandler({
|
|
16
|
+
name: "send-test-status",
|
|
17
|
+
schema: z.object({}),
|
|
18
|
+
access: templateEditAccess(policy.tenantEditable),
|
|
19
|
+
description:
|
|
20
|
+
"Tells whether send-test can reach the calling user: canSend is true when an email channel is mounted and resolves an address for the caller, otherwise reason is no_channel or no_address; the address itself is never returned.",
|
|
21
|
+
handler: async (query, ctx): Promise<SendTestStatus> => {
|
|
22
|
+
const channel = emailChannelOf(ctx.registry);
|
|
23
|
+
if (channel === undefined) return { canSend: false, reason: "no_channel" };
|
|
24
|
+
const address = await resolveTestMailAddress(
|
|
25
|
+
channel,
|
|
26
|
+
testMailChannelContext(ctx, query.user),
|
|
27
|
+
query.user,
|
|
28
|
+
);
|
|
29
|
+
return address === null
|
|
30
|
+
? { canSend: false, reason: "no_address" }
|
|
31
|
+
: { canSend: true, reason: null };
|
|
32
|
+
},
|
|
33
|
+
});
|
|
34
|
+
}
|
|
@@ -112,7 +112,7 @@ export function makeSendTestWrite(policy: TemplateEditPolicy) {
|
|
|
112
112
|
sentStatus = result.status;
|
|
113
113
|
} catch (error) {
|
|
114
114
|
// Class and code only: the message can carry the address or a provider response body.
|
|
115
|
-
ctx.log?.warn("template test mail: channel threw", {
|
|
115
|
+
ctx.obs.log?.warn("template test mail: channel threw", {
|
|
116
116
|
tenantId: event.user.tenantId,
|
|
117
117
|
errorClass: error instanceof Error ? error.name : typeof error,
|
|
118
118
|
errorCode: errorCodeOf(error),
|
|
@@ -120,7 +120,7 @@ export function makeSendTestWrite(policy: TemplateEditPolicy) {
|
|
|
120
120
|
return testMailFailed("channel_error");
|
|
121
121
|
}
|
|
122
122
|
if (sentStatus !== "sent") {
|
|
123
|
-
ctx.log?.warn("template test mail: channel did not send", {
|
|
123
|
+
ctx.obs.log?.warn("template test mail: channel did not send", {
|
|
124
124
|
tenantId: event.user.tenantId,
|
|
125
125
|
status: sentStatus,
|
|
126
126
|
});
|
package/packages/bundled-features/src/template-resolver/handlers/tenant-override-selection.ts
CHANGED
|
@@ -10,7 +10,9 @@ const tenantIdsSchema = z.array(z.string());
|
|
|
10
10
|
|
|
11
11
|
type TenantIdQueryContext = {
|
|
12
12
|
readonly registry: { getQueryHandler(name: string): unknown };
|
|
13
|
-
readonly
|
|
13
|
+
readonly dispatch: {
|
|
14
|
+
readonly query: (name: string, payload: Record<string, never>) => Promise<unknown>;
|
|
15
|
+
};
|
|
14
16
|
};
|
|
15
17
|
|
|
16
18
|
/** Null when the tenant feature is not installed: every tenant row then counts. */
|
|
@@ -18,7 +20,9 @@ export async function loadEnabledTenantIds(
|
|
|
18
20
|
ctx: TenantIdQueryContext,
|
|
19
21
|
): Promise<ReadonlySet<string> | null> {
|
|
20
22
|
if (ctx.registry.getQueryHandler(TenantQueries.activeTenantIds) === undefined) return null;
|
|
21
|
-
return new Set(
|
|
23
|
+
return new Set(
|
|
24
|
+
tenantIdsSchema.parse(await ctx.dispatch.query(TenantQueries.activeTenantIds, {})),
|
|
25
|
+
);
|
|
22
26
|
}
|
|
23
27
|
|
|
24
28
|
export type OverrideCandidate = Pick<TemplateResourceRow, "tenantId" | "status" | "version">;
|
|
@@ -31,7 +31,9 @@ function chunksOf<T>(items: readonly T[], size: number): T[][] {
|
|
|
31
31
|
}
|
|
32
32
|
|
|
33
33
|
type DirectoryQueryContext = {
|
|
34
|
-
readonly
|
|
34
|
+
readonly dispatch: {
|
|
35
|
+
readonly query: (name: string, payload: { ids: string[] }) => Promise<unknown>;
|
|
36
|
+
};
|
|
35
37
|
};
|
|
36
38
|
|
|
37
39
|
// Looks up only the ids the overrides need, so labels never depend on the directory's default cap.
|
|
@@ -42,7 +44,7 @@ async function labelsFor(
|
|
|
42
44
|
): Promise<ReadonlyMap<string, string>> {
|
|
43
45
|
const labels = new Map<string, string>();
|
|
44
46
|
for (const chunk of chunksOf([...ids], MAX_LIST_LIMIT)) {
|
|
45
|
-
const raw = await ctx.query(queryName, { ids: chunk });
|
|
47
|
+
const raw = await ctx.dispatch.query(queryName, { ids: chunk });
|
|
46
48
|
for (const row of directorySchema.parse(raw).rows) labels.set(row.id, row.label);
|
|
47
49
|
}
|
|
48
50
|
return labels;
|
|
@@ -28,7 +28,7 @@ export function makeUsageQuery(policy: TemplateEditPolicy) {
|
|
|
28
28
|
{ orderBy: { col: "id", direction: "asc" }, limit: MAIL_ROW_LIMIT + 1 },
|
|
29
29
|
);
|
|
30
30
|
if (fetchedRows.length > MAIL_ROW_LIMIT) {
|
|
31
|
-
ctx.log?.warn("template usage: row limit reached, result is incomplete", {
|
|
31
|
+
ctx.obs.log?.warn("template usage: row limit reached, result is incomplete", {
|
|
32
32
|
limit: MAIL_ROW_LIMIT,
|
|
33
33
|
});
|
|
34
34
|
}
|