@cosmicdrift/kumiko-samples 0.389.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/audit/feature.ts +1 -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 +6 -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/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 +12 -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 +2 -2
- package/packages/bundled-features/src/auth-email-password/handlers/verify-email.write.ts +3 -2
- package/packages/bundled-features/src/auth-email-password/seeding.ts +14 -9
- package/packages/bundled-features/src/auth-foundation/feature.ts +13 -4
- 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 +9 -3
- 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 +1 -1
- package/packages/bundled-features/src/cap-counter/constants.ts +1 -1
- package/packages/bundled-features/src/cap-counter/feature.ts +2 -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/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/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 +4 -4
- package/packages/bundled-features/src/config/handlers/values.query.ts +1 -1
- package/packages/bundled-features/src/crypto-shredding/handlers/forget-subject.write.ts +8 -8
- 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/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/delivery-service.ts +6 -5
- package/packages/bundled-features/src/delivery/feature.ts +2 -2
- package/packages/bundled-features/src/delivery/jobs.ts +11 -11
- package/packages/bundled-features/src/delivery/testing.ts +1 -1
- package/packages/bundled-features/src/document-ingest-foundation/events.ts +1 -1
- 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/inbound-mail-foundation/events.ts +1 -1
- package/packages/bundled-features/src/inbound-mail-foundation/feature.ts +1 -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/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/legal-pages/README.md +1 -1
- package/packages/bundled-features/src/legal-pages/lib/boot-check.ts +12 -8
- package/packages/bundled-features/src/mail-foundation/feature.ts +2 -2
- package/packages/bundled-features/src/mail-transport-smtp/changes.json +6 -0
- package/packages/bundled-features/src/mail-transport-smtp/feature.ts +4 -1
- package/packages/bundled-features/src/managed-pages/seeding.ts +13 -8
- package/packages/bundled-features/src/notes-history/handlers/add-note.write.ts +1 -1
- package/packages/bundled-features/src/personal-access-tokens/feature.ts +1 -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/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 +1 -1
- package/packages/bundled-features/src/seo/feature.ts +1 -1
- package/packages/bundled-features/src/sessions/changes.json +12 -0
- package/packages/bundled-features/src/sessions/feature.ts +1 -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/screens.ts +8 -4
- 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 +12 -2
- package/packages/bundled-features/src/shared/require-for-tenant.ts +7 -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/feature.ts +1 -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/template-resolver/api.ts +3 -3
- package/packages/bundled-features/src/template-resolver/changes.json +18 -0
- 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/overview.query.ts +2 -2
- package/packages/bundled-features/src/template-resolver/handlers/send-test-shared.ts +1 -1
- 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 +12 -2
- package/packages/bundled-features/src/template-resolver/store-template-resource.ts +18 -13
- 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/placeholder-line-editor.tsx +19 -6
- 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/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/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/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 +7 -3
- 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/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 +24 -0
- package/packages/bundled-features/src/user-data-rights/feature.ts +29 -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 +2 -0
- 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 +10 -0
- package/packages/bundled-features/src/user-data-rights/web/index.ts +6 -1
- package/packages/bundled-features/src/user-profile/handlers/change-email.write.ts +8 -6
- 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 +9 -4
- package/packages/bundled-features/src/waitlist/legacy-migrate.ts +6 -1
- package/packages/bundled-features/src/waitlist/link-on-signup.ts +7 -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/server.ts +2 -2
- package/samples/apps/use-all-bundled/feature-manifest.json +1 -1
- package/samples/apps/use-all-bundled/src/app/seed.ts +12 -2
- 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/lane-routing/package.json +1 -1
- package/samples/recipes/legal-pages/README.md +2 -2
- package/samples/recipes/lifecycle-hooks/README.md +2 -2
- package/samples/recipes/lifecycle-hooks/src/feature.ts +1 -1
- package/samples/recipes/managed-config/README.md +3 -3
- 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 +1 -1
- package/samples/recipes/state-machine/src/reducer.ts +1 -1
- package/samples/recipes/workflow-engine/src/resume-loop.ts +4 -4
- package/samples/recipes/workflow-engine/src/workflow-runner.ts +2 -2
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
// One-time migration tooling for event streams that live on the wrong tenant.
|
|
2
|
+
//
|
|
3
|
+
// An aggregate type that became tenant-independent (systemStream) or whose
|
|
4
|
+
// stream must follow the tenant in its payload has older streams on whichever
|
|
5
|
+
// tenant happened to write them. Newer writes then version-conflict against
|
|
6
|
+
// the empty target stream, and a raw tenant_id UPDATE trips
|
|
7
|
+
// events_aggregate_version_uq as soon as an aggregate has BOTH a legacy
|
|
8
|
+
// stream and newer events on the target (split stream).
|
|
9
|
+
//
|
|
10
|
+
// This tool merges per aggregate: all events of the aggregate, ordered by
|
|
11
|
+
// global event id, are renumbered 1..n and retenanted to the target - two-phase
|
|
12
|
+
// version writes (negative interim values) dodge the unique index while the
|
|
13
|
+
// per-aggregate transaction is in flight. Snapshots of migrated aggregates
|
|
14
|
+
// are dropped (version numbering changed; the next snapshotting load
|
|
15
|
+
// recreates them); archived-stream markers move with the stream.
|
|
16
|
+
//
|
|
17
|
+
// Idempotent: candidates are only aggregates that still have events outside
|
|
18
|
+
// their target tenant. One failing aggregate does not abort the run: the rest
|
|
19
|
+
// of the estate migrates, failures are reported.
|
|
20
|
+
//
|
|
21
|
+
// After a run, rebuild the affected projections so read-table tenant_id
|
|
22
|
+
// reflects the stream move.
|
|
23
|
+
|
|
24
|
+
import type { DbConnection } from "@cosmicdrift/kumiko-framework/db";
|
|
25
|
+
import {
|
|
26
|
+
asRawClient,
|
|
27
|
+
toPostgresArrayLiteral,
|
|
28
|
+
transaction,
|
|
29
|
+
unsafeReadRetrying,
|
|
30
|
+
} from "@cosmicdrift/kumiko-framework/db";
|
|
31
|
+
import { SYSTEM_TENANT_ID } from "@cosmicdrift/kumiko-framework/engine";
|
|
32
|
+
|
|
33
|
+
export type StreamTenantTarget =
|
|
34
|
+
| { readonly kind: "system" }
|
|
35
|
+
// The tenantId carried by the aggregate's earliest event payload.
|
|
36
|
+
| { readonly kind: "payload-tenant" };
|
|
37
|
+
|
|
38
|
+
export type StreamBackfillCandidate = {
|
|
39
|
+
readonly aggregateId: string;
|
|
40
|
+
readonly targetTenantId: string;
|
|
41
|
+
readonly sourceTenantIds: readonly string[];
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
export type StreamBackfillFailure = { readonly aggregateId: string; readonly error: string };
|
|
45
|
+
|
|
46
|
+
export type StreamBackfillResult = {
|
|
47
|
+
readonly aggregatesMigrated: number;
|
|
48
|
+
readonly eventsMigrated: number;
|
|
49
|
+
readonly failed: readonly StreamBackfillFailure[];
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
export type StreamBackfillOptions = {
|
|
53
|
+
readonly aggregateType: string;
|
|
54
|
+
readonly target: StreamTenantTarget;
|
|
55
|
+
// Runs per candidate before its events move; a throw skips the aggregate
|
|
56
|
+
// and lands in `failed` (search cleanup must not be lost by a move).
|
|
57
|
+
readonly beforeMove?: (candidate: StreamBackfillCandidate) => Promise<void>;
|
|
58
|
+
// Restricts the candidate scan to events on these tenants, which lets Postgres use
|
|
59
|
+
// events_tenant_type_idx instead of grouping every event of the type. Streams on a tenant
|
|
60
|
+
// outside the list are not found; omit it for the full scan.
|
|
61
|
+
readonly scanTenantIds?: readonly string[];
|
|
62
|
+
// Called once when the scan found candidates, before the first move. "skip" ends the run
|
|
63
|
+
// with nothing moved (this process cannot do the work); a throw propagates to the caller.
|
|
64
|
+
readonly onFirstCandidate?: () => Promise<"proceed" | "skip">;
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
type CandidateRow = {
|
|
68
|
+
aggregate_id: string;
|
|
69
|
+
target_tenant_id: string | null;
|
|
70
|
+
source_tenant_ids: string[];
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
export async function backfillAggregateStreamTenants(
|
|
74
|
+
db: DbConnection,
|
|
75
|
+
options: StreamBackfillOptions,
|
|
76
|
+
): Promise<StreamBackfillResult> {
|
|
77
|
+
const rows = await readCandidateRows(db, options);
|
|
78
|
+
if (rows.length > 0 && options.onFirstCandidate) {
|
|
79
|
+
if ((await options.onFirstCandidate()) === "skip") {
|
|
80
|
+
return { aggregatesMigrated: 0, eventsMigrated: 0, failed: [] };
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
let aggregatesMigrated = 0;
|
|
85
|
+
let eventsMigrated = 0;
|
|
86
|
+
const failed: StreamBackfillFailure[] = [];
|
|
87
|
+
|
|
88
|
+
for (const row of rows) {
|
|
89
|
+
try {
|
|
90
|
+
if (!row.target_tenant_id) {
|
|
91
|
+
throw new Error(`earliest event of the aggregate carries no payload.tenantId`);
|
|
92
|
+
}
|
|
93
|
+
const candidate: StreamBackfillCandidate = {
|
|
94
|
+
aggregateId: row.aggregate_id,
|
|
95
|
+
targetTenantId: row.target_tenant_id,
|
|
96
|
+
sourceTenantIds: row.source_tenant_ids,
|
|
97
|
+
};
|
|
98
|
+
await options.beforeMove?.(candidate);
|
|
99
|
+
eventsMigrated += await migrateAggregate(db, options.aggregateType, candidate);
|
|
100
|
+
aggregatesMigrated++;
|
|
101
|
+
} catch (e) {
|
|
102
|
+
failed.push({
|
|
103
|
+
aggregateId: row.aggregate_id,
|
|
104
|
+
error: e instanceof Error ? e.message : String(e),
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
return { aggregatesMigrated, eventsMigrated, failed };
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
async function readCandidateRows(
|
|
112
|
+
db: DbConnection,
|
|
113
|
+
options: StreamBackfillOptions,
|
|
114
|
+
): Promise<readonly CandidateRow[]> {
|
|
115
|
+
const targetSql =
|
|
116
|
+
options.target.kind === "system"
|
|
117
|
+
? `$2::text`
|
|
118
|
+
: `(SELECT e2."payload"->>'tenantId' FROM "kumiko_events" e2
|
|
119
|
+
WHERE e2."aggregate_type" = $1 AND e2."aggregate_id" = e."aggregate_id"
|
|
120
|
+
ORDER BY e2."id" ASC LIMIT 1)`;
|
|
121
|
+
const params: unknown[] = [options.aggregateType];
|
|
122
|
+
if (options.target.kind === "system") params.push(SYSTEM_TENANT_ID);
|
|
123
|
+
let scanSql = "";
|
|
124
|
+
if (options.scanTenantIds) {
|
|
125
|
+
// Events already on the SYSTEM target are not legacy; leaving them out keeps the scan to
|
|
126
|
+
// the legacy events only. A payload target differs per aggregate, so nothing is left out.
|
|
127
|
+
const scanned =
|
|
128
|
+
options.target.kind === "system"
|
|
129
|
+
? options.scanTenantIds.filter((id) => id !== SYSTEM_TENANT_ID)
|
|
130
|
+
: options.scanTenantIds;
|
|
131
|
+
params.push(toPostgresArrayLiteral(scanned));
|
|
132
|
+
scanSql = ` AND e."tenant_id" = ANY($${params.length}::uuid[])`;
|
|
133
|
+
}
|
|
134
|
+
// An aggregate whose earliest event has no payload tenant yields a NULL
|
|
135
|
+
// target and is reported as failed instead of being silently skipped.
|
|
136
|
+
return unsafeReadRetrying<CandidateRow>(
|
|
137
|
+
db,
|
|
138
|
+
`SELECT e."aggregate_id" AS aggregate_id,
|
|
139
|
+
${targetSql} AS target_tenant_id,
|
|
140
|
+
array_agg(DISTINCT e."tenant_id"::text) AS source_tenant_ids
|
|
141
|
+
FROM "kumiko_events" e
|
|
142
|
+
WHERE e."aggregate_type" = $1${scanSql}
|
|
143
|
+
GROUP BY e."aggregate_id"
|
|
144
|
+
HAVING bool_or(e."tenant_id"::text IS DISTINCT FROM ${targetSql})`,
|
|
145
|
+
params,
|
|
146
|
+
);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
async function migrateAggregate(
|
|
150
|
+
db: DbConnection,
|
|
151
|
+
aggregateType: string,
|
|
152
|
+
candidate: StreamBackfillCandidate,
|
|
153
|
+
): Promise<number> {
|
|
154
|
+
const { aggregateId, targetTenantId } = candidate;
|
|
155
|
+
return transaction(db, async (tx) => {
|
|
156
|
+
const raw = asRawClient(tx);
|
|
157
|
+
// Global id order = commit-adjacent replay order; merges a split stream
|
|
158
|
+
// into one consistent version sequence.
|
|
159
|
+
const events = await raw.unsafe<{ id: bigint | string }>(
|
|
160
|
+
`SELECT "id" FROM "kumiko_events"
|
|
161
|
+
WHERE "aggregate_type" = $1 AND "aggregate_id" = $2::uuid
|
|
162
|
+
ORDER BY "id" ASC
|
|
163
|
+
FOR UPDATE`,
|
|
164
|
+
[aggregateType, aggregateId],
|
|
165
|
+
);
|
|
166
|
+
|
|
167
|
+
// Negative interim versions: real versions are always >= 1, so nothing
|
|
168
|
+
// can collide with events_aggregate_version_uq mid-migration.
|
|
169
|
+
for (const [i, ev] of events.entries()) {
|
|
170
|
+
await raw.unsafe(
|
|
171
|
+
`UPDATE "kumiko_events" SET "tenant_id" = $1::uuid, "version" = $2 WHERE "id" = $3`,
|
|
172
|
+
[targetTenantId, -(i + 1), ev.id],
|
|
173
|
+
);
|
|
174
|
+
}
|
|
175
|
+
for (const [i, ev] of events.entries()) {
|
|
176
|
+
await raw.unsafe(`UPDATE "kumiko_events" SET "version" = $1 WHERE "id" = $2`, [i + 1, ev.id]);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// Version numbering changed, so any snapshot of this aggregate is stale.
|
|
180
|
+
await raw.unsafe(`DELETE FROM "kumiko_snapshots" WHERE "aggregate_id" = $1::uuid`, [
|
|
181
|
+
aggregateId,
|
|
182
|
+
]);
|
|
183
|
+
|
|
184
|
+
// Archived markers key on (tenant_id, aggregate_id): move them with the
|
|
185
|
+
// stream, keeping an existing target marker if both exist.
|
|
186
|
+
await raw.unsafe(
|
|
187
|
+
`INSERT INTO "kumiko_archived_streams" ("tenant_id", "aggregate_id", "aggregate_type", "archived_at", "archived_by", "reason")
|
|
188
|
+
SELECT $1::uuid, "aggregate_id", "aggregate_type", "archived_at", "archived_by", "reason"
|
|
189
|
+
FROM "kumiko_archived_streams"
|
|
190
|
+
WHERE "aggregate_id" = $2::uuid AND "tenant_id" <> $1::uuid
|
|
191
|
+
ON CONFLICT DO NOTHING`,
|
|
192
|
+
[targetTenantId, aggregateId],
|
|
193
|
+
);
|
|
194
|
+
await raw.unsafe(
|
|
195
|
+
`DELETE FROM "kumiko_archived_streams" WHERE "aggregate_id" = $1::uuid AND "tenant_id" <> $2::uuid`,
|
|
196
|
+
[aggregateId, targetTenantId],
|
|
197
|
+
);
|
|
198
|
+
|
|
199
|
+
return events.length;
|
|
200
|
+
});
|
|
201
|
+
}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
// Derived state of a stream-tenant backfill: search docs in the buckets the streams
|
|
2
|
+
// left, projection rows and the index on SYSTEM_TENANT_ID. The stream move itself lives
|
|
3
|
+
// in stream-tenant-backfill.ts; this part is repeatable and safe to run without moves.
|
|
4
|
+
|
|
5
|
+
import type { DbConnection } from "@cosmicdrift/kumiko-framework/db";
|
|
6
|
+
import { type Registry, SYSTEM_TENANT_ID } from "@cosmicdrift/kumiko-framework/engine";
|
|
7
|
+
import { rebuildProjection } from "@cosmicdrift/kumiko-framework/pipeline";
|
|
8
|
+
import { reindexEntity, type SearchAdapter } from "@cosmicdrift/kumiko-framework/search";
|
|
9
|
+
import type { StreamBackfillCandidate, StreamBackfillFailure } from "./stream-tenant-backfill.ts";
|
|
10
|
+
|
|
11
|
+
export type StreamBackfillStepFailure = StreamBackfillFailure & { readonly step: string };
|
|
12
|
+
|
|
13
|
+
// "if-moved" skips rebuild and reindex when no stream moved; "always" repairs
|
|
14
|
+
// derived state after a run that moved streams and then crashed.
|
|
15
|
+
export type DerivedStateMode = "if-moved" | "always";
|
|
16
|
+
|
|
17
|
+
export function createSearchBucketCleanup(
|
|
18
|
+
searchAdapter: SearchAdapter | undefined,
|
|
19
|
+
entityType: string,
|
|
20
|
+
): {
|
|
21
|
+
readonly beforeMove: (candidate: StreamBackfillCandidate) => Promise<void>;
|
|
22
|
+
readonly cleanedAggregates: () => number;
|
|
23
|
+
} {
|
|
24
|
+
let cleaned = 0;
|
|
25
|
+
return {
|
|
26
|
+
cleanedAggregates: () => cleaned,
|
|
27
|
+
beforeMove: async (candidate) => {
|
|
28
|
+
// skip: no adapter means no search docs to clean
|
|
29
|
+
if (!searchAdapter) return;
|
|
30
|
+
const oldBuckets = candidate.sourceTenantIds.filter((id) => id !== candidate.targetTenantId);
|
|
31
|
+
for (const bucket of oldBuckets) {
|
|
32
|
+
if (searchAdapter.removeBatch) {
|
|
33
|
+
await searchAdapter.removeBatch(bucket, [
|
|
34
|
+
{ entityType, entityId: candidate.aggregateId },
|
|
35
|
+
]);
|
|
36
|
+
} else {
|
|
37
|
+
await searchAdapter.remove(bucket, entityType, candidate.aggregateId);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
if (oldBuckets.length > 0) cleaned++;
|
|
41
|
+
},
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export type DerivedStateResult = {
|
|
46
|
+
readonly projectionsRebuilt: readonly string[];
|
|
47
|
+
readonly reindexedRows: number;
|
|
48
|
+
readonly failures: readonly StreamBackfillStepFailure[];
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
export async function rebuildAndReindex(deps: {
|
|
52
|
+
readonly db: DbConnection;
|
|
53
|
+
readonly registry: Registry;
|
|
54
|
+
readonly searchAdapter?: SearchAdapter;
|
|
55
|
+
readonly projections: readonly string[];
|
|
56
|
+
readonly searchEntity: string;
|
|
57
|
+
}): Promise<DerivedStateResult> {
|
|
58
|
+
const { db, registry, searchAdapter, projections, searchEntity } = deps;
|
|
59
|
+
const failures: StreamBackfillStepFailure[] = [];
|
|
60
|
+
const projectionsRebuilt: string[] = [];
|
|
61
|
+
for (const projection of projections) {
|
|
62
|
+
try {
|
|
63
|
+
await rebuildProjection(projection, { db, registry });
|
|
64
|
+
projectionsRebuilt.push(projection);
|
|
65
|
+
} catch (e) {
|
|
66
|
+
failures.push({ aggregateId: projection, step: "rebuild", error: messageOf(e) });
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
let reindexedRows = 0;
|
|
71
|
+
if (searchAdapter && isEntitySearchable(registry, searchEntity)) {
|
|
72
|
+
try {
|
|
73
|
+
const reindex = await reindexEntity(
|
|
74
|
+
db,
|
|
75
|
+
registry,
|
|
76
|
+
searchAdapter,
|
|
77
|
+
searchEntity,
|
|
78
|
+
SYSTEM_TENANT_ID,
|
|
79
|
+
);
|
|
80
|
+
reindexedRows = reindex.indexedRows;
|
|
81
|
+
for (const f of reindex.failures) {
|
|
82
|
+
failures.push({ aggregateId: f.entityId, step: "reindex", error: f.reason });
|
|
83
|
+
}
|
|
84
|
+
} catch (e) {
|
|
85
|
+
failures.push({ aggregateId: searchEntity, step: "reindex", error: messageOf(e) });
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
return { projectionsRebuilt, reindexedRows, failures };
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function isEntitySearchable(registry: Registry, entityName: string): boolean {
|
|
92
|
+
return (
|
|
93
|
+
registry.getSearchableFields(entityName).length > 0 ||
|
|
94
|
+
registry.getSearchPayloadExtensions(entityName).length > 0
|
|
95
|
+
);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export function stepFailures(
|
|
99
|
+
step: string,
|
|
100
|
+
failed: readonly StreamBackfillFailure[],
|
|
101
|
+
): StreamBackfillStepFailure[] {
|
|
102
|
+
return failed.map((f) => ({ ...f, step }));
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function messageOf(e: unknown): string {
|
|
106
|
+
return e instanceof Error ? e.message : String(e);
|
|
107
|
+
}
|
|
@@ -94,7 +94,7 @@ export function createStepDispatcherFeature(): FeatureDefinition {
|
|
|
94
94
|
tenantId,
|
|
95
95
|
userId,
|
|
96
96
|
callerFeature: STEP_DISPATCHER_FEATURE,
|
|
97
|
-
...(ctx.secrets && { secrets: ctx.secrets }),
|
|
97
|
+
...(ctx.services.secrets && { secrets: ctx.services.secrets }),
|
|
98
98
|
}),
|
|
99
99
|
});
|
|
100
100
|
},
|
|
@@ -225,7 +225,7 @@ async function fillPriceCache(
|
|
|
225
225
|
} else {
|
|
226
226
|
const message =
|
|
227
227
|
outcome.reason instanceof Error ? outcome.reason.message : String(outcome.reason);
|
|
228
|
-
ctx.log?.warn("subscription-dodo: product lookup failed", { message });
|
|
228
|
+
ctx.obs.log?.warn("subscription-dodo: product lookup failed", { message });
|
|
229
229
|
}
|
|
230
230
|
}
|
|
231
231
|
}
|
|
@@ -64,11 +64,11 @@ export function createDodoRuntimes(deps: DodoRuntimeDeps): DodoRuntimes {
|
|
|
64
64
|
|
|
65
65
|
async function ctxApiKey(ctx: HandlerContext): Promise<string> {
|
|
66
66
|
let key: string | undefined;
|
|
67
|
-
if (ctx.secrets) {
|
|
68
|
-
const got = await requireSecretsContext(
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
);
|
|
67
|
+
if (ctx.services.secrets) {
|
|
68
|
+
const got = await requireSecretsContext(
|
|
69
|
+
{ secrets: ctx.services.secrets, user: ctx.user },
|
|
70
|
+
SUBSCRIPTION_DODO_FEATURE,
|
|
71
|
+
).get(SYSTEM_TENANT_ID, deps.apiKeyHandle.name);
|
|
72
72
|
key = parseStoredSecret(got?.reveal());
|
|
73
73
|
}
|
|
74
74
|
if (!key && deps.fallback.apiKey && deps.fallback.apiKey.length > 0) {
|
|
@@ -102,8 +102,8 @@ export function createDodoRuntimes(deps: DodoRuntimeDeps): DodoRuntimes {
|
|
|
102
102
|
if (live !== true) return false;
|
|
103
103
|
if (deps.fallback.apiKey !== undefined && deps.fallback.apiKey.length > 0) return true;
|
|
104
104
|
// Existence-only probe on every billing-plans render: must stay un-audited.
|
|
105
|
-
return ctx.secrets
|
|
106
|
-
? await ctx.secrets.has(SYSTEM_TENANT_ID, deps.apiKeyHandle.name)
|
|
105
|
+
return ctx.services.secrets
|
|
106
|
+
? await ctx.services.secrets.has(SYSTEM_TENANT_ID, deps.apiKeyHandle.name)
|
|
107
107
|
: false;
|
|
108
108
|
},
|
|
109
109
|
},
|
|
@@ -169,7 +169,9 @@ export function createStripeCheckoutSession(
|
|
|
169
169
|
if (!options.providerCustomerId || !isUnknownCustomerStripeError(error)) throw error;
|
|
170
170
|
// Stripe account switch (test -> live) or a customer deleted in the dashboard leaves a
|
|
171
171
|
// stored customer id Stripe no longer knows; checkout creates a fresh customer instead.
|
|
172
|
-
ctx.log?.warn(
|
|
172
|
+
ctx.obs.log?.warn(
|
|
173
|
+
"subscription-stripe: stored customer unknown to Stripe, retrying without it",
|
|
174
|
+
);
|
|
173
175
|
session = await stripe.checkout.sessions.create(sessionParams(undefined));
|
|
174
176
|
}
|
|
175
177
|
|
|
@@ -383,7 +385,7 @@ async function resolvePricesViaCache(
|
|
|
383
385
|
} else {
|
|
384
386
|
const message =
|
|
385
387
|
outcome.reason instanceof Error ? outcome.reason.message : String(outcome.reason);
|
|
386
|
-
ctx.log?.warn("subscription-stripe: prices.retrieve failed", { message });
|
|
388
|
+
ctx.obs.log?.warn("subscription-stripe: prices.retrieve failed", { message });
|
|
387
389
|
}
|
|
388
390
|
}
|
|
389
391
|
}
|
|
@@ -402,7 +404,7 @@ export function createStripeRetrievePrices(runtime: StripeCtxRuntime, cache: Str
|
|
|
402
404
|
const rawInterval = price.recurring?.interval;
|
|
403
405
|
if (rawInterval && mapped.interval === null && !warnedUnknownIntervalPriceIds.has(priceId)) {
|
|
404
406
|
warnedUnknownIntervalPriceIds.add(priceId);
|
|
405
|
-
ctx.log?.warn("subscription-stripe: price has an unknown recurring interval", {
|
|
407
|
+
ctx.obs.log?.warn("subscription-stripe: price has an unknown recurring interval", {
|
|
406
408
|
priceId,
|
|
407
409
|
interval: rawInterval,
|
|
408
410
|
});
|
|
@@ -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
|
},
|
|
@@ -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,22 @@
|
|
|
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
|
+
},
|
|
2
20
|
{
|
|
3
21
|
"version": "0.389.0",
|
|
4
22
|
"type": "fix",
|
|
@@ -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);
|
|
@@ -412,7 +412,7 @@ export function makeOverviewQuery(policy: TemplateEditPolicy) {
|
|
|
412
412
|
const truncated = fetchedRows.length > OVERVIEW_ROW_LIMIT;
|
|
413
413
|
const rows = truncated ? fetchedRows.slice(0, OVERVIEW_ROW_LIMIT) : fetchedRows;
|
|
414
414
|
if (truncated) {
|
|
415
|
-
ctx.log?.warn("template overview: row limit reached, result is incomplete", {
|
|
415
|
+
ctx.obs.log?.warn("template overview: row limit reached, result is incomplete", {
|
|
416
416
|
limit: OVERVIEW_ROW_LIMIT,
|
|
417
417
|
});
|
|
418
418
|
}
|
|
@@ -434,7 +434,7 @@ export function makeOverviewQuery(policy: TemplateEditPolicy) {
|
|
|
434
434
|
? withOverriddenBy(
|
|
435
435
|
rowsWithUsage,
|
|
436
436
|
tenantOverrideCountsResultSchema.parse(
|
|
437
|
-
await ctx.query(TemplateResolverQueries.tenantOverrideCounts, {}),
|
|
437
|
+
await ctx.dispatch.query(TemplateResolverQueries.tenantOverrideCounts, {}),
|
|
438
438
|
),
|
|
439
439
|
)
|
|
440
440
|
: rowsWithUsage;
|
|
@@ -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,
|
|
@@ -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
|
}
|
|
@@ -107,7 +107,12 @@ export async function seedSystemTemplate(
|
|
|
107
107
|
): Promise<{ id: string }> {
|
|
108
108
|
const tenantId = SYSTEM_TENANT_ID;
|
|
109
109
|
const by = opts.by ?? createSystemUser(tenantId);
|
|
110
|
-
const tdb = createTenantDb({
|
|
110
|
+
const tdb = createTenantDb({
|
|
111
|
+
db,
|
|
112
|
+
tenantId,
|
|
113
|
+
mode: "system",
|
|
114
|
+
reason: "system templates live in the system tenant",
|
|
115
|
+
});
|
|
111
116
|
|
|
112
117
|
const label = `seedSystemTemplate ${opts.slug}/${opts.kind}/${opts.locale}`;
|
|
113
118
|
const slug = templateSlugSchema.safeParse(opts.slug);
|
|
@@ -408,7 +413,12 @@ export async function seedTextBlock(
|
|
|
408
413
|
opts: SeedTextBlockOptions,
|
|
409
414
|
): Promise<{ id: string }> {
|
|
410
415
|
const by = opts.by ?? createSystemUser(opts.tenantId);
|
|
411
|
-
const tdb = createTenantDb({
|
|
416
|
+
const tdb = createTenantDb({
|
|
417
|
+
db,
|
|
418
|
+
tenantId: opts.tenantId,
|
|
419
|
+
mode: "system",
|
|
420
|
+
reason: "seeding writes text blocks into an explicitly named tenant",
|
|
421
|
+
});
|
|
412
422
|
|
|
413
423
|
const existingRow = await fetchOne<TemplateResourceRow>(db, templateResourcesTable, {
|
|
414
424
|
tenantId: opts.tenantId,
|