@cosmicdrift/kumiko-samples 0.366.0 → 0.368.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 +16 -2
- package/packages/bundled-features/src/audit/handlers/details.query.ts +1 -1
- package/packages/bundled-features/src/audit/handlers/list.query.ts +1 -1
- package/packages/bundled-features/src/audit/run-escape-hatch-retention.ts +5 -5
- package/packages/bundled-features/src/auth-email-password/bootstrap.ts +1 -1
- package/packages/bundled-features/src/auth-email-password/handlers/invite-accept-with-login.write.ts +10 -3
- package/packages/bundled-features/src/auth-email-password/handlers/invite-accept.write.ts +10 -3
- package/packages/bundled-features/src/auth-email-password/handlers/invite-info.query.ts +1 -1
- package/packages/bundled-features/src/auth-email-password/handlers/invite-signup-complete.write.ts +6 -2
- package/packages/bundled-features/src/auth-email-password/handlers/signup-confirm.write.ts +1 -1
- package/packages/bundled-features/src/auth-email-password/seeding.ts +2 -2
- package/packages/bundled-features/src/auth-foundation/feature.ts +43 -34
- package/packages/bundled-features/src/auth-foundation/index.ts +1 -0
- package/packages/bundled-features/src/auth-mfa/db/queries.ts +5 -2
- package/packages/bundled-features/src/auth-mfa/handlers/enable-confirm-preauth.write.ts +1 -1
- package/packages/bundled-features/src/auth-mfa/handlers/enable-start-preauth.write.ts +1 -1
- package/packages/bundled-features/src/auth-mfa/handlers/reencrypt.job.ts +2 -2
- package/packages/bundled-features/src/auth-mfa/handlers/status.query.ts +1 -1
- package/packages/bundled-features/src/auth-mfa/handlers/verify.write.ts +1 -1
- package/packages/bundled-features/src/auth-mfa/mfa-code-verifier.ts +1 -1
- package/packages/bundled-features/src/auth-mfa/mfa-status-checker.ts +1 -1
- package/packages/bundled-features/src/auth-mfa-user-data/index.ts +19 -10
- package/packages/bundled-features/src/billing-foundation/billing-info-query.ts +1 -1
- package/packages/bundled-features/src/billing-foundation/feature.ts +138 -135
- package/packages/bundled-features/src/billing-foundation/handlers/create-portal-session.write.ts +1 -1
- package/packages/bundled-features/src/billing-foundation/projection.ts +3 -2
- package/packages/bundled-features/src/cap-counter/calendar-cap-usage-reservation.ts +52 -1
- package/packages/bundled-features/src/cap-counter/cap-reservation.ts +203 -15
- package/packages/bundled-features/src/cap-counter/changes.json +13 -0
- package/packages/bundled-features/src/cap-counter/feature.ts +84 -73
- package/packages/bundled-features/src/cap-counter/handlers/increment-rolling.write.ts +3 -4
- package/packages/bundled-features/src/cap-counter/index.ts +9 -1
- package/packages/bundled-features/src/cap-counter/tables.ts +10 -3
- package/packages/bundled-features/src/cap-overview/handlers/caps-usage.query.ts +1 -2
- package/packages/bundled-features/src/cap-overview/handlers/tenant-caps-list.query.ts +6 -2
- package/packages/bundled-features/src/cap-overview/handlers/tenant-options.query.ts +1 -1
- package/packages/bundled-features/src/channel-email/changes.json +6 -0
- package/packages/bundled-features/src/channel-email/email-channel.ts +12 -2
- package/packages/bundled-features/src/channel-email/feature.ts +1 -1
- package/packages/bundled-features/src/channel-in-app/handlers/inbox.query.ts +1 -1
- package/packages/bundled-features/src/channel-in-app/handlers/mark-all-read.write.ts +1 -1
- package/packages/bundled-features/src/channel-in-app/handlers/mark-read.write.ts +1 -1
- package/packages/bundled-features/src/channel-in-app/handlers/unread-count.query.ts +1 -1
- package/packages/bundled-features/src/channel-in-app/tables.ts +1 -1
- package/packages/bundled-features/src/compliance-profiles/handlers/for-tenant.query.ts +1 -1
- package/packages/bundled-features/src/compliance-profiles/handlers/needs-profile.query.ts +1 -1
- package/packages/bundled-features/src/compliance-profiles/handlers/set-profile.write.ts +1 -2
- package/packages/bundled-features/src/compliance-profiles/resolve-for-tenant.ts +1 -1
- package/packages/bundled-features/src/compliance-profiles/seeding.ts +8 -4
- package/packages/bundled-features/src/compliance-profiles-ops/index.ts +26 -17
- package/packages/bundled-features/src/config/db/queries/resolver.ts +1 -1
- package/packages/bundled-features/src/config/handlers/reencrypt.job.ts +2 -2
- package/packages/bundled-features/src/config/resolver.ts +1 -1
- package/packages/bundled-features/src/config/write-helpers.ts +1 -1
- package/packages/bundled-features/src/crypto-shredding/changes.json +6 -0
- package/packages/bundled-features/src/crypto-shredding/handlers/forget-subject.write.ts +9 -7
- package/packages/bundled-features/src/custom-fields/db/queries/field-access.ts +1 -1
- package/packages/bundled-features/src/custom-fields/db/queries/projection.ts +1 -1
- package/packages/bundled-features/src/custom-fields/db/queries/quota.ts +1 -1
- package/packages/bundled-features/src/custom-fields/db/queries/retention.ts +6 -2
- package/packages/bundled-features/src/custom-fields/db/queries/user-data-rights.ts +1 -1
- package/packages/bundled-features/src/custom-fields/feature.ts +11 -14
- package/packages/bundled-features/src/custom-fields/handlers/clear-custom-field.write.ts +2 -2
- package/packages/bundled-features/src/custom-fields/handlers/delete-system-field.write.ts +2 -2
- package/packages/bundled-features/src/custom-fields/handlers/delete-tenant-field.write.ts +2 -2
- package/packages/bundled-features/src/custom-fields/handlers/set-custom-field.write.ts +2 -2
- package/packages/bundled-features/src/custom-fields/wire-for-entity.ts +4 -4
- package/packages/bundled-features/src/data-retention/resolve-for-tenant.ts +1 -1
- package/packages/bundled-features/src/data-retention/run-retention-cleanup.ts +16 -10
- package/packages/bundled-features/src/delivery/address-opt-out.ts +5 -2
- package/packages/bundled-features/src/delivery/attempt-log-retention.ts +1 -1
- package/packages/bundled-features/src/delivery/changes.json +12 -0
- package/packages/bundled-features/src/delivery/channel-context.ts +2 -1
- package/packages/bundled-features/src/delivery/db/queries/address-opt-outs.ts +1 -1
- package/packages/bundled-features/src/delivery/db/queries/preferences.ts +1 -1
- package/packages/bundled-features/src/delivery/delivery-service.ts +70 -8
- package/packages/bundled-features/src/delivery/feature.ts +2 -2
- package/packages/bundled-features/src/delivery/handlers/log.query.ts +3 -2
- package/packages/bundled-features/src/delivery/handlers/preferences.query.ts +1 -1
- package/packages/bundled-features/src/delivery/handlers/unsubscribe-address.write.ts +10 -5
- package/packages/bundled-features/src/delivery/index.ts +6 -0
- package/packages/bundled-features/src/delivery/testing.ts +2 -0
- package/packages/bundled-features/src/delivery/types.ts +4 -0
- package/packages/bundled-features/src/delivery/unsubscribe-link.ts +73 -0
- package/packages/bundled-features/src/delivery/unsubscribe-page-strings.ts +17 -0
- package/packages/bundled-features/src/delivery/unsubscribe.ts +94 -31
- package/packages/bundled-features/src/delivery/upsert-preference.ts +5 -2
- package/packages/bundled-features/src/derivatives-sharp/feature.ts +21 -12
- package/packages/bundled-features/src/derivatives-sharp/index.ts +1 -1
- package/packages/bundled-features/src/document-ingest-foundation/feature.ts +112 -103
- package/packages/bundled-features/src/document-ingest-foundation/forget-extract-with-file-ref.ts +3 -3
- package/packages/bundled-features/src/document-ingest-foundation/index.ts +4 -1
- package/packages/bundled-features/src/feature-toggles/global-feature-state-table.ts +1 -1
- package/packages/bundled-features/src/feature-toggles/handlers/set.write.ts +3 -5
- package/packages/bundled-features/src/feature-toggles/toggle-runtime.ts +1 -1
- package/packages/bundled-features/src/file-derivatives/feature.ts +134 -129
- package/packages/bundled-features/src/file-derivatives/handlers/public-variant-by-file-ref.query.ts +1 -1
- package/packages/bundled-features/src/file-derivatives/handlers/public-variant.query.ts +1 -1
- package/packages/bundled-features/src/file-foundation/feature.ts +38 -29
- package/packages/bundled-features/src/file-foundation/index.ts +1 -0
- package/packages/bundled-features/src/file-foundation/read-file-ref-content.ts +1 -1
- package/packages/bundled-features/src/file-provider-inmemory/feature.ts +32 -21
- package/packages/bundled-features/src/file-provider-inmemory/index.ts +6 -1
- package/packages/bundled-features/src/file-provider-s3/changes.json +10 -1
- package/packages/bundled-features/src/file-provider-s3/env-feature.ts +62 -0
- package/packages/bundled-features/src/file-provider-s3/feature.ts +107 -73
- package/packages/bundled-features/src/file-provider-s3/index.ts +6 -1
- package/packages/bundled-features/src/file-provider-s3/lib/index.ts +3 -0
- package/packages/bundled-features/src/{files-provider-s3 → file-provider-s3/lib}/s3-provider.ts +19 -19
- package/packages/bundled-features/src/file-provider-s3-env/index.ts +7 -2
- package/packages/bundled-features/src/files-provider-s3/index.ts +11 -3
- package/packages/bundled-features/src/files-tenant-data/handlers/sweep-orphaned-derivatives.job.ts +1 -1
- package/packages/bundled-features/src/files-tenant-data/hooks.ts +1 -2
- package/packages/bundled-features/src/folders/feature.ts +8 -3
- package/packages/bundled-features/src/folders-user-data/index.ts +27 -18
- package/packages/bundled-features/src/form-draft/db/queries/cleanup.ts +1 -1
- package/packages/bundled-features/src/form-draft/db/queries/draft-count.ts +1 -1
- package/packages/bundled-features/src/form-draft/db/queries/owned-file-refs.ts +3 -2
- package/packages/bundled-features/src/form-draft/feature.ts +6 -1
- package/packages/bundled-features/src/form-draft/handlers/cleanup.job.ts +2 -2
- package/packages/bundled-features/src/form-draft/index.ts +1 -1
- package/packages/bundled-features/src/form-draft/lookup.ts +1 -1
- package/packages/bundled-features/src/form-draft-user-data/index.ts +19 -10
- package/packages/bundled-features/src/inbound-mail-foundation/feature.ts +165 -154
- package/packages/bundled-features/src/inbound-mail-foundation/handlers/ingest-message.write.ts +1 -2
- package/packages/bundled-features/src/inbound-mail-foundation/index.ts +1 -1
- package/packages/bundled-features/src/inbound-mail-foundation/oauth-access-token.ts +5 -2
- package/packages/bundled-features/src/inbound-mail-foundation/projection.ts +3 -2
- package/packages/bundled-features/src/inbound-mail-foundation/watch-supervisor.ts +1 -1
- package/packages/bundled-features/src/inbound-provider-imap/feature.ts +22 -13
- package/packages/bundled-features/src/inbound-provider-imap/index.ts +6 -1
- package/packages/bundled-features/src/inbound-provider-inmemory/feature.ts +20 -12
- package/packages/bundled-features/src/inbound-provider-inmemory/index.ts +1 -0
- package/packages/bundled-features/src/jobs/db/queries/retention.ts +1 -1
- package/packages/bundled-features/src/jobs/db/queries/stale-run-sweep.ts +1 -1
- package/packages/bundled-features/src/jobs/handlers/detail.query.ts +1 -1
- package/packages/bundled-features/src/jobs/handlers/list.query.ts +7 -2
- package/packages/bundled-features/src/jobs/handlers/retry.write.ts +1 -1
- package/packages/bundled-features/src/jobs/handlers/tenant-failures.query.ts +1 -1
- package/packages/bundled-features/src/jobs/handlers/tenant-runs.query.ts +1 -1
- package/packages/bundled-features/src/jobs/job-run-logger.ts +11 -10
- package/packages/bundled-features/src/jobs/tenant-job-failure-table.ts +1 -1
- package/packages/bundled-features/src/jobs/tenant-job-run-table.ts +1 -1
- package/packages/bundled-features/src/ledger/feature.ts +6 -3
- package/packages/bundled-features/src/mail-foundation/feature.ts +107 -55
- package/packages/bundled-features/src/mail-foundation/index.ts +4 -0
- package/packages/bundled-features/src/mail-transport-inmemory/feature.ts +25 -17
- package/packages/bundled-features/src/mail-transport-inmemory/index.ts +6 -1
- package/packages/bundled-features/src/mail-transport-smtp/feature.ts +91 -79
- package/packages/bundled-features/src/mail-transport-smtp/index.ts +1 -0
- package/packages/bundled-features/src/managed-pages/handlers/by-slug.query.ts +1 -1
- package/packages/bundled-features/src/managed-pages/handlers/by-tenant-published.query.ts +1 -2
- package/packages/bundled-features/src/managed-pages/handlers/set.write.ts +1 -2
- package/packages/bundled-features/src/managed-pages/seeding.ts +8 -4
- package/packages/bundled-features/src/metrics/compute.ts +1 -1
- package/packages/bundled-features/src/metrics/filters.ts +1 -1
- package/packages/bundled-features/src/metrics/validate.ts +1 -1
- package/packages/bundled-features/src/notes-history/feature.ts +6 -3
- package/packages/bundled-features/src/notes-history/handlers/add-note.write.ts +1 -1
- package/packages/bundled-features/src/notes-history-user-data/hooks.ts +1 -1
- package/packages/bundled-features/src/notes-history-user-data/index.ts +27 -18
- package/packages/bundled-features/src/personal-access-tokens/changes.json +6 -0
- package/packages/bundled-features/src/personal-access-tokens/feature.ts +17 -2
- package/packages/bundled-features/src/personal-access-tokens/handlers/create.write.ts +1 -1
- package/packages/bundled-features/src/personal-access-tokens/handlers/list.query.ts +2 -2
- package/packages/bundled-features/src/personal-access-tokens/handlers/revoke.write.ts +1 -1
- package/packages/bundled-features/src/personal-access-tokens/resolver.ts +1 -1
- package/packages/bundled-features/src/personal-access-tokens/revoke-for-user.ts +2 -2
- package/packages/bundled-features/src/personal-access-tokens/roles.ts +8 -6
- package/packages/bundled-features/src/personal-access-tokens/screens.ts +2 -0
- package/packages/bundled-features/src/readiness/feature.ts +25 -16
- package/packages/bundled-features/src/readiness/index.ts +1 -1
- package/packages/bundled-features/src/secrets/db/queries/read.ts +1 -1
- package/packages/bundled-features/src/secrets/feature.ts +8 -3
- package/packages/bundled-features/src/secrets/handlers/rotate.job.ts +2 -2
- package/packages/bundled-features/src/secrets/secrets-context.ts +4 -3
- package/packages/bundled-features/src/sessions/db/queries/cleanup.ts +1 -1
- package/packages/bundled-features/src/sessions/handlers/detail.query.ts +1 -1
- package/packages/bundled-features/src/sessions/handlers/list.query.ts +2 -2
- package/packages/bundled-features/src/sessions/handlers/mine.query.ts +1 -1
- package/packages/bundled-features/src/sessions/handlers/revoke-all-for-user.write.ts +1 -1
- 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/session-callbacks.ts +14 -7
- package/packages/bundled-features/src/sessions/testing.ts +1 -1
- package/packages/bundled-features/src/tags/feature.ts +6 -3
- package/packages/bundled-features/src/template-resolver/api.ts +1 -1
- package/packages/bundled-features/src/template-resolver/changes.json +25 -0
- package/packages/bundled-features/src/template-resolver/compile-document-body.ts +61 -9
- package/packages/bundled-features/src/template-resolver/document-sanitizer.ts +19 -0
- package/packages/bundled-features/src/template-resolver/document-schema.ts +33 -0
- package/packages/bundled-features/src/template-resolver/handlers/bundle-import.ts +10 -2
- package/packages/bundled-features/src/template-resolver/handlers/by-slug.query.ts +1 -1
- package/packages/bundled-features/src/template-resolver/handlers/by-tenant.query.ts +1 -2
- package/packages/bundled-features/src/template-resolver/handlers/collection-item.query.ts +1 -1
- package/packages/bundled-features/src/template-resolver/handlers/collection-list.query.ts +1 -2
- package/packages/bundled-features/src/template-resolver/handlers/collection-set.write.ts +1 -1
- package/packages/bundled-features/src/template-resolver/handlers/copy-locale.write.ts +9 -3
- package/packages/bundled-features/src/template-resolver/handlers/diff-to-system.query.ts +1 -1
- package/packages/bundled-features/src/template-resolver/handlers/export-bundle.query.ts +1 -1
- package/packages/bundled-features/src/template-resolver/handlers/find-by-id.query.ts +9 -2
- package/packages/bundled-features/src/template-resolver/handlers/layout-source.query.ts +1 -1
- package/packages/bundled-features/src/template-resolver/handlers/list.query.ts +7 -2
- package/packages/bundled-features/src/template-resolver/handlers/overview.query.ts +8 -3
- package/packages/bundled-features/src/template-resolver/handlers/reset-to-system.write.ts +1 -1
- package/packages/bundled-features/src/template-resolver/handlers/resource-remove.write.ts +1 -1
- package/packages/bundled-features/src/template-resolver/handlers/set.write.ts +1 -1
- package/packages/bundled-features/src/template-resolver/handlers/template-write.ts +22 -2
- package/packages/bundled-features/src/template-resolver/handlers/tenant-overrides.query.ts +1 -1
- package/packages/bundled-features/src/template-resolver/handlers/toggle-status.write.ts +1 -1
- package/packages/bundled-features/src/template-resolver/handlers/usage.query.ts +1 -1
- package/packages/bundled-features/src/template-resolver/handlers/validate.query.ts +1 -1
- package/packages/bundled-features/src/template-resolver/index.ts +4 -0
- package/packages/bundled-features/src/template-resolver/qualified-names.ts +1 -0
- package/packages/bundled-features/src/template-resolver/seeding.ts +57 -10
- package/packages/bundled-features/src/template-resolver/store-template-resource.ts +1 -1
- package/packages/bundled-features/src/template-resolver/template-preview.ts +3 -1
- package/packages/bundled-features/src/template-resolver/template-resources.ts +1 -1
- package/packages/bundled-features/src/template-resolver/template-system-only.ts +21 -0
- package/packages/bundled-features/src/template-resolver/template-validation.ts +48 -24
- package/packages/bundled-features/src/template-resolver/testing.ts +1 -1
- package/packages/bundled-features/src/template-resolver/web/i18n.ts +2 -0
- package/packages/bundled-features/src/template-resolver-user-data/index.ts +23 -14
- package/packages/bundled-features/src/tenant/handlers/active-tenant-ids.query.ts +1 -1
- package/packages/bundled-features/src/tenant/handlers/add-member.write.ts +1 -2
- package/packages/bundled-features/src/tenant/handlers/cancel-invitation.write.ts +1 -1
- package/packages/bundled-features/src/tenant/handlers/invitations.query.ts +1 -1
- package/packages/bundled-features/src/tenant/handlers/me.query.ts +1 -1
- package/packages/bundled-features/src/tenant/handlers/member-directory.query.ts +1 -1
- package/packages/bundled-features/src/tenant/handlers/members.query.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 -1
- package/packages/bundled-features/src/tenant/handlers/resolve-user-ids.query.ts +1 -1
- package/packages/bundled-features/src/tenant/handlers/team-list.query.ts +1 -1
- package/packages/bundled-features/src/tenant/handlers/tenant-directory.query.ts +1 -1
- package/packages/bundled-features/src/tenant/handlers/update-member-roles.write.ts +1 -2
- package/packages/bundled-features/src/tenant/invited-membership.ts +2 -2
- package/packages/bundled-features/src/tenant/last-tenant-admin.ts +1 -2
- package/packages/bundled-features/src/tenant/seeding.ts +12 -12
- package/packages/bundled-features/src/tenant-lifecycle/handlers/cancel-destruction.write.ts +1 -2
- package/packages/bundled-features/src/tenant-lifecycle/handlers/request-destruction.write.ts +1 -2
- package/packages/bundled-features/src/tenant-lifecycle/lib/revoke-tenant-sessions.ts +1 -1
- package/packages/bundled-features/src/tenant-lifecycle/lifecycle-gate.ts +1 -1
- package/packages/bundled-features/src/tenant-lifecycle/run-tenant-destroy.ts +3 -3
- package/packages/bundled-features/src/tenant-lifecycle/stages.ts +10 -5
- package/packages/bundled-features/src/tenant-lifecycle/testing.ts +3 -3
- package/packages/bundled-features/src/tenant-settings/feature.ts +34 -29
- package/packages/bundled-features/src/tier-engine/feature.ts +426 -428
- package/packages/bundled-features/src/tier-engine/handlers/get-tenant-tier.query.ts +2 -3
- package/packages/bundled-features/src/tier-engine/handlers/set-tenant-tier.write.ts +5 -2
- package/packages/bundled-features/src/user/changes.json +6 -0
- package/packages/bundled-features/src/user/db/queries/stream-tenant-backfill.ts +1 -1
- package/packages/bundled-features/src/user/handlers/create.write.ts +1 -2
- package/packages/bundled-features/src/user/handlers/find-for-auth.query.ts +1 -1
- package/packages/bundled-features/src/user/handlers/list.query.ts +1 -1
- package/packages/bundled-features/src/user/handlers/update-roles.ts +2 -1
- package/packages/bundled-features/src/user/index.ts +5 -1
- package/packages/bundled-features/src/user/invited-global-roles.ts +2 -2
- package/packages/bundled-features/src/user/principal-status.ts +13 -3
- package/packages/bundled-features/src/user/resolve-display-names.ts +1 -1
- package/packages/bundled-features/src/user/seeding.ts +5 -5
- package/packages/bundled-features/src/user-data-rights/audit-download.ts +2 -2
- package/packages/bundled-features/src/user-data-rights/changes.json +6 -0
- package/packages/bundled-features/src/user-data-rights/db/queries/export-jobs.ts +1 -1
- package/packages/bundled-features/src/user-data-rights/db/queries/forget-cleanup.ts +1 -1
- package/packages/bundled-features/src/user-data-rights/handlers/download-by-job.query.ts +1 -1
- package/packages/bundled-features/src/user-data-rights/handlers/download-by-token.query.ts +1 -1
- package/packages/bundled-features/src/user-data-rights/handlers/export-status.query.ts +1 -1
- package/packages/bundled-features/src/user-data-rights/handlers/list-download-attempts.query.ts +1 -1
- package/packages/bundled-features/src/user-data-rights/handlers/my-audit-log.query.ts +1 -1
- package/packages/bundled-features/src/user-data-rights/handlers/request-export.write.ts +1 -2
- package/packages/bundled-features/src/user-data-rights/handlers/run-forget-cleanup.write.ts +3 -3
- package/packages/bundled-features/src/user-data-rights/lib/deny-if-target-outside-admin-tenant.ts +1 -1
- package/packages/bundled-features/src/user-data-rights/lib/update-user-lifecycle.ts +7 -2
- package/packages/bundled-features/src/user-data-rights/run-export-jobs.ts +7 -3
- package/packages/bundled-features/src/user-data-rights/run-forget-cleanup.ts +11 -5
- package/packages/bundled-features/src/user-data-rights/run-user-export.ts +10 -5
- package/packages/bundled-features/src/waitlist/handlers/invite.write.ts +1 -2
- package/packages/bundled-features/src/waitlist/handlers/reject.write.ts +1 -1
- package/packages/bundled-features/src/waitlist/lib.ts +5 -2
- package/packages/bundled-features/src/workflow-runner/event-subscriber.ts +1 -1
- package/packages/bundled-features/src/workflow-runner/feature.ts +58 -47
- package/packages/bundled-features/src/workflow-runner/handlers/resume-run.write.ts +1 -1
- package/packages/bundled-features/src/workflow-runner/index.ts +1 -1
- package/packages/bundled-features/src/workflow-runner/pending-projection.ts +1 -1
- package/samples/apps/admin-console/src/run-config.ts +2 -2
- package/samples/apps/cap-billing-demo/package.json +3 -0
- package/samples/apps/cap-billing-demo/src/feature.ts +2 -2
- package/samples/apps/cap-billing-demo/src/run-config.ts +8 -8
- package/samples/apps/marketing-demo/src/app/seed.ts +1 -1
- package/samples/apps/marketing-demo/src/db/queries/seed-counts.ts +1 -1
- package/samples/apps/showcase/package.json +3 -0
- package/samples/apps/showcase/src/app/seed-items.ts +1 -1
- package/samples/apps/styleguide/src/app/seed.ts +2 -2
- package/samples/apps/styleguide/src/features/ai-settings/seed.ts +2 -2
- package/samples/apps/ui-walkthrough/scripts/emit-e2e-data.ts +1 -1
- package/samples/apps/ui-walkthrough/src/app/auth-constants.ts +1 -1
- package/samples/apps/ui-walkthrough/src/app/seed-rental.ts +2 -2
- package/samples/apps/ui-walkthrough/src/app/seed-templates.ts +2 -0
- package/samples/apps/ui-walkthrough/src/app/seed-vehicles.ts +1 -1
- package/samples/apps/ui-walkthrough/src/app/seed.ts +1 -1
- package/samples/apps/ui-walkthrough/src/app/template-seed-data.ts +12 -0
- package/samples/apps/ui-walkthrough/src/db/queries/row-counts.ts +1 -1
- package/samples/apps/ui-walkthrough/src/db/queries/task-counts.ts +1 -1
- package/samples/apps/ui-walkthrough/src/features/billing/feature.ts +7 -0
- package/samples/apps/ui-walkthrough/src/features/billing/i18n.ts +2 -0
- package/samples/apps/use-all-bundled/feature-manifest.json +4 -4
- package/samples/apps/use-all-bundled/package.json +4 -0
- package/samples/apps/use-all-bundled/scripts/check-coverage.ts +2 -2
- package/samples/apps/use-all-bundled/src/app/auth-constants.ts +1 -1
- package/samples/apps/use-all-bundled/src/app/cap-overview-caps.ts +2 -2
- package/samples/apps/use-all-bundled/src/app/dev-app-options.ts +1 -1
- package/samples/apps/use-all-bundled/src/app/seed-documents.ts +1 -1
- package/samples/apps/use-all-bundled/src/app/seed.ts +11 -5
- package/samples/apps/use-all-bundled/src/run-config.ts +55 -56
- package/samples/apps/user-data-rights-demo/README.md +2 -2
- package/samples/apps/user-data-rights-demo/package.json +3 -0
- package/samples/apps/user-data-rights-demo/src/run-config.ts +7 -7
- package/samples/apps/workspaces/package.json +3 -0
- package/samples/recipes/access-control/package.json +3 -0
- package/samples/recipes/anonymous-access/package.json +3 -0
- package/samples/recipes/anonymous-access-multitenant/package.json +3 -0
- package/samples/recipes/apex-landing/package.json +1 -0
- package/samples/recipes/apex-surface-auth/package.json +3 -0
- package/samples/recipes/apex-surface-auth/src/feature.ts +2 -2
- package/samples/recipes/api-worker-split/package.json +3 -0
- package/samples/recipes/api-worker-split/src/feature.ts +1 -2
- package/samples/recipes/auth-claims/package.json +3 -0
- package/samples/recipes/auth-foundation-providers/src/feature.ts +1 -1
- package/samples/recipes/auth-mfa/package.json +3 -0
- package/samples/recipes/basic-entity/package.json +3 -0
- package/samples/recipes/chat-channels/package.json +3 -0
- package/samples/recipes/compliance-profiles-demo/package.json +3 -0
- package/samples/recipes/cross-feature-events/package.json +3 -1
- package/samples/recipes/cross-feature-events/src/feature.ts +1 -1
- package/samples/recipes/crypto-shredding-hr/package.json +3 -0
- package/samples/recipes/currencies-global/package.json +3 -0
- package/samples/recipes/currencies-per-tenant/package.json +3 -0
- package/samples/recipes/custom-fields-basic/package.json +3 -0
- package/samples/recipes/custom-handlers/package.json +3 -0
- package/samples/recipes/dashboard-stats/package.json +3 -0
- package/samples/recipes/delivery-notifications/package.json +3 -0
- package/samples/recipes/e2e-generator/package.json +3 -0
- package/samples/recipes/embedded/package.json +3 -0
- package/samples/recipes/embedded-entity-form/package.json +1 -0
- package/samples/recipes/encrypted-tenant-config/README.md +1 -1
- package/samples/recipes/encrypted-tenant-config/package.json +3 -0
- package/samples/recipes/error-contract/package.json +3 -0
- package/samples/recipes/event-sourcing/package.json +3 -1
- package/samples/recipes/event-sourcing/src/feature.ts +16 -20
- package/samples/recipes/event-sourcing/tsconfig.json +2 -6
- package/samples/recipes/feature-toggles/package.json +3 -0
- package/samples/recipes/field-access/package.json +3 -0
- package/samples/recipes/files-post-processing/package.json +1 -0
- package/samples/recipes/golden-path/README.md +30 -0
- package/samples/recipes/golden-path/package.json +11 -0
- package/samples/recipes/golden-path/src/feature.ts +70 -0
- package/samples/recipes/idempotency/package.json +3 -0
- package/samples/recipes/inbound-mail-triage/package.json +3 -0
- package/samples/recipes/lane-routing/package.json +3 -0
- package/samples/recipes/ledger-banking/package.json +3 -0
- package/samples/recipes/ledger-invoicing/package.json +3 -0
- package/samples/recipes/legal-pages/package.json +3 -0
- package/samples/recipes/lifecycle-hooks/package.json +3 -0
- package/samples/recipes/managed-config/package.json +3 -0
- package/samples/recipes/managed-pages/package.json +3 -0
- package/samples/recipes/notes-history-basic/package.json +3 -0
- package/samples/recipes/ownership/package.json +3 -0
- package/samples/recipes/pdf-audit-export/package.json +1 -0
- package/samples/recipes/pipeline-basics/package.json +3 -0
- package/samples/recipes/public-share-token/package.json +3 -0
- package/samples/recipes/public-share-token/src/feature.ts +1 -1
- package/samples/recipes/rate-limiting/package.json +3 -0
- package/samples/recipes/realtime-sse/package.json +3 -0
- package/samples/recipes/record-detail-layout/package.json +1 -0
- package/samples/recipes/reference-data/package.json +3 -0
- package/samples/recipes/relations/package.json +3 -0
- package/samples/recipes/screens-nav/package.json +3 -0
- package/samples/recipes/search/package.json +3 -0
- package/samples/recipes/secrets-demo/package.json +3 -0
- package/samples/recipes/security-baseline/src/feature.ts +2 -2
- package/samples/recipes/session-revocation/README.md +1 -1
- package/samples/recipes/session-revocation/package.json +3 -0
- package/samples/recipes/session-revocation/src/feature.ts +1 -1
- package/samples/recipes/state-machine/package.json +3 -1
- package/samples/recipes/state-machine/src/entities/invoice.ts +2 -2
- package/samples/recipes/state-machine/src/events.ts +19 -0
- package/samples/recipes/state-machine/src/feature.ts +25 -30
- package/samples/recipes/state-machine/src/handlers/_helpers.ts +13 -14
- package/samples/recipes/state-machine/src/handlers/create.ts +1 -1
- package/samples/recipes/state-machine/src/handlers/transitions.ts +129 -122
- package/samples/recipes/state-machine/src/reducer.ts +1 -1
- package/samples/recipes/state-machine/tsconfig.json +2 -6
- package/samples/recipes/tags-basic/package.json +3 -0
- package/samples/recipes/tenant-isolation/package.json +3 -0
- package/samples/recipes/tenant-settings/package.json +3 -0
- package/samples/recipes/tier-admin/package.json +3 -0
- package/samples/recipes/timezones/package.json +3 -0
- package/samples/recipes/tsconfig.base.json +1 -1
- package/samples/recipes/user-data-rights/package.json +3 -0
- package/samples/recipes/user-data-rights/src/feature.ts +6 -2
- package/samples/recipes/user-profile/package.json +3 -0
- package/samples/recipes/user-profile/src/feature.ts +2 -2
- package/samples/recipes/webhook-step/package.json +3 -0
- package/samples/recipes/wizard-form/package.json +1 -0
- package/samples/recipes/workflow-engine/package.json +3 -0
- package/samples/recipes/workflow-engine/src/db/queries/suspended-runs.ts +5 -2
- package/samples/recipes/writeform-section/package.json +1 -0
- package/packages/bundled-features/src/file-provider-s3-env/feature.ts +0 -58
- /package/packages/bundled-features/src/{files-provider-s3 → file-provider-s3/lib}/env-helper.ts +0 -0
|
@@ -5,7 +5,7 @@ import {
|
|
|
5
5
|
type TenantDb,
|
|
6
6
|
} from "@cosmicdrift/kumiko-framework/db";
|
|
7
7
|
import type { SessionUser, WriteResult } from "@cosmicdrift/kumiko-framework/engine";
|
|
8
|
-
import { reraiseAsKumikoError } from "@cosmicdrift/kumiko-framework/errors";
|
|
8
|
+
import { reraiseAsKumikoError, UnprocessableError } from "@cosmicdrift/kumiko-framework/errors";
|
|
9
9
|
import {
|
|
10
10
|
appendEventInTenantDb,
|
|
11
11
|
getStreamVersionInTenantDb,
|
|
@@ -45,6 +45,9 @@ type CapReservationRow = {
|
|
|
45
45
|
readonly expiresAt: Temporal.Instant;
|
|
46
46
|
readonly createdAt: Temporal.Instant;
|
|
47
47
|
readonly settledAt: Temporal.Instant | null;
|
|
48
|
+
// Units consume already turned into booked ones; the counter holds them on top of amount.
|
|
49
|
+
readonly consumedAmount: number;
|
|
50
|
+
readonly releasedAt: Temporal.Instant | null;
|
|
48
51
|
};
|
|
49
52
|
|
|
50
53
|
const NOTHING_TO_RELEASE: WriteResult = { isSuccess: true, data: {} };
|
|
@@ -69,6 +72,8 @@ async function insertReservationRow(
|
|
|
69
72
|
expiresAt: now.add({ minutes: CAP_RESERVATION_TTL_MINUTES }),
|
|
70
73
|
createdAt: now,
|
|
71
74
|
settledAt: null,
|
|
75
|
+
consumedAmount: 0,
|
|
76
|
+
releasedAt: null,
|
|
72
77
|
});
|
|
73
78
|
}
|
|
74
79
|
|
|
@@ -197,6 +202,7 @@ async function confirmCapReservation(
|
|
|
197
202
|
id,
|
|
198
203
|
tenantId: confirm.tenantId,
|
|
199
204
|
settledAt: null,
|
|
205
|
+
releasedAt: null,
|
|
200
206
|
});
|
|
201
207
|
}
|
|
202
208
|
|
|
@@ -208,6 +214,8 @@ async function giveBackCalendar(
|
|
|
208
214
|
if (row.periodStartIso === null) {
|
|
209
215
|
throw new Error(`cap-counter: calendar reservation ${row.id} has no period to release against`);
|
|
210
216
|
}
|
|
217
|
+
// Fully consumed by consumeCalendarCapReservation: nothing left to give back.
|
|
218
|
+
if (row.amount === 0) return NOTHING_TO_RELEASE;
|
|
211
219
|
return applyCapDeltaOn(
|
|
212
220
|
txDb,
|
|
213
221
|
user,
|
|
@@ -247,29 +255,45 @@ async function giveBackReservation(
|
|
|
247
255
|
});
|
|
248
256
|
if (!row) return NOTHING_TO_RELEASE;
|
|
249
257
|
const claimed = await claimReservationRow(txDb, user, row);
|
|
250
|
-
if (!claimed) return NOTHING_TO_RELEASE;
|
|
258
|
+
if (!claimed || claimed.releasedAt !== null) return NOTHING_TO_RELEASE;
|
|
251
259
|
const given =
|
|
252
|
-
|
|
253
|
-
? await giveBackRolling(txDb, user,
|
|
254
|
-
: await giveBackCalendar(txDb, user,
|
|
260
|
+
claimed.kind === "rolling"
|
|
261
|
+
? await giveBackRolling(txDb, user, claimed)
|
|
262
|
+
: await giveBackCalendar(txDb, user, claimed);
|
|
255
263
|
if (!given.isSuccess) return given;
|
|
256
|
-
|
|
264
|
+
// Only consumed units need remembering: a later settle or consume must not count them twice.
|
|
265
|
+
if (claimed.kind === "rolling" || claimed.consumedAmount === 0) {
|
|
266
|
+
await txDb.deleteMany(capReservationsTable, ownRow);
|
|
267
|
+
return given;
|
|
268
|
+
}
|
|
269
|
+
const now = Temporal.Now.instant();
|
|
270
|
+
await txDb.updateMany(
|
|
271
|
+
capReservationsTable,
|
|
272
|
+
{
|
|
273
|
+
amount: 0,
|
|
274
|
+
releasedAt: now,
|
|
275
|
+
expiresAt: now.add({ minutes: CAP_SETTLED_TOMBSTONE_TTL_MINUTES }),
|
|
276
|
+
},
|
|
277
|
+
ownRow,
|
|
278
|
+
);
|
|
257
279
|
return given;
|
|
258
280
|
}
|
|
259
281
|
|
|
260
|
-
// Waits for a confirm still holding the row;
|
|
261
|
-
//
|
|
282
|
+
// Waits for a confirm still holding the row; undefined means that confirm or a settle won (Postgres
|
|
283
|
+
// re-checks settled_at after the lock wait). A released row is claimed too: callers branch on
|
|
284
|
+
// releasedAt. The returned row is read after the lock wait, so it already includes a concurrent
|
|
285
|
+
// consume.
|
|
262
286
|
async function claimReservationRow(
|
|
263
287
|
txDb: TenantDb,
|
|
264
288
|
user: SessionUser,
|
|
265
289
|
row: CapReservationRow,
|
|
266
|
-
): Promise<
|
|
267
|
-
const claimed = await txDb.updateMany(
|
|
290
|
+
): Promise<CapReservationRow | undefined> {
|
|
291
|
+
const [claimed] = await txDb.updateMany<CapReservationRow>(
|
|
268
292
|
capReservationsTable,
|
|
269
293
|
{ expiresAt: row.expiresAt },
|
|
270
294
|
{ id: row.id, tenantId: user.tenantId, settledAt: null },
|
|
271
295
|
);
|
|
272
|
-
return claimed
|
|
296
|
+
return claimed;
|
|
273
297
|
}
|
|
274
298
|
|
|
275
299
|
export async function releaseCapReservation(
|
|
@@ -289,9 +313,28 @@ export async function releaseCapReservation(
|
|
|
289
313
|
export type SettleCalendarCapOptions = {
|
|
290
314
|
readonly capName: string;
|
|
291
315
|
readonly periodStartIso: string;
|
|
316
|
+
// Total used, including units already consumed through a handle.
|
|
292
317
|
readonly actualAmount: number;
|
|
293
318
|
};
|
|
294
319
|
|
|
320
|
+
export class CapSettleBelowConsumedError extends UnprocessableError {
|
|
321
|
+
constructor(capName: string, consumed: number, actualAmount: number) {
|
|
322
|
+
super("cap_settle_below_consumed", {
|
|
323
|
+
message: `Cap "${capName}": cannot settle ${actualAmount}, ${consumed} units were already consumed.`,
|
|
324
|
+
details: { capName, consumed, actualAmount },
|
|
325
|
+
});
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
export class CapReservationSettledError extends UnprocessableError {
|
|
330
|
+
constructor(capName: string) {
|
|
331
|
+
super("cap_reservation_settled", {
|
|
332
|
+
message: `Cap "${capName}": the reservation is already settled, consume is no longer possible.`,
|
|
333
|
+
details: { capName },
|
|
334
|
+
});
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
|
|
295
338
|
function bookCalendarDelta(
|
|
296
339
|
txDb: TenantDb,
|
|
297
340
|
user: SessionUser,
|
|
@@ -318,8 +361,22 @@ async function settleInTransaction(
|
|
|
318
361
|
...ownRow,
|
|
319
362
|
settledAt: null,
|
|
320
363
|
});
|
|
321
|
-
|
|
322
|
-
|
|
364
|
+
const claimed = open && (await claimReservationRow(txDb, user, open));
|
|
365
|
+
if (claimed) {
|
|
366
|
+
if (options.actualAmount < claimed.consumedAmount) {
|
|
367
|
+
throw new CapSettleBelowConsumedError(
|
|
368
|
+
options.capName,
|
|
369
|
+
claimed.consumedAmount,
|
|
370
|
+
options.actualAmount,
|
|
371
|
+
);
|
|
372
|
+
}
|
|
373
|
+
const alreadyBooked = claimed.amount + claimed.consumedAmount;
|
|
374
|
+
const booked = await bookCalendarDelta(
|
|
375
|
+
txDb,
|
|
376
|
+
user,
|
|
377
|
+
options,
|
|
378
|
+
options.actualAmount - alreadyBooked,
|
|
379
|
+
);
|
|
323
380
|
if (!booked.isSuccess) return booked;
|
|
324
381
|
const now = Temporal.Now.instant();
|
|
325
382
|
await txDb.updateMany(
|
|
@@ -335,6 +392,10 @@ async function settleInTransaction(
|
|
|
335
392
|
}
|
|
336
393
|
// Row swept or released: its amount was given back, so the full actual amount is booked and a
|
|
337
394
|
// tombstone makes a repeated settle a no-op. A parallel settle inserting first fails this insert.
|
|
395
|
+
// Booking first: a lost counter race returns a result instead of throwing, so the transaction
|
|
396
|
+
// commits and a tombstone written before it would turn the retry into a no-op.
|
|
397
|
+
const booked = await bookCalendarDelta(txDb, user, options, options.actualAmount);
|
|
398
|
+
if (!booked.isSuccess) return booked;
|
|
338
399
|
const now = Temporal.Now.instant();
|
|
339
400
|
await txDb.insertOne(capReservationsTable, {
|
|
340
401
|
...ownRow,
|
|
@@ -345,8 +406,10 @@ async function settleInTransaction(
|
|
|
345
406
|
expiresAt: now.add({ minutes: CAP_SETTLED_TOMBSTONE_TTL_MINUTES }),
|
|
346
407
|
createdAt: now,
|
|
347
408
|
settledAt: now,
|
|
409
|
+
consumedAmount: 0,
|
|
410
|
+
releasedAt: null,
|
|
348
411
|
});
|
|
349
|
-
return
|
|
412
|
+
return booked;
|
|
350
413
|
}
|
|
351
414
|
|
|
352
415
|
// The failed tombstone insert aborts the whole attempt, so nothing was booked; the repeat sees the
|
|
@@ -382,8 +445,128 @@ export async function settleCalendarCapReservation(
|
|
|
382
445
|
if (!settled.isSuccess) throw reraiseAsKumikoError(settled.error);
|
|
383
446
|
}
|
|
384
447
|
|
|
448
|
+
export type ConsumeCalendarCapOptions = {
|
|
449
|
+
readonly capName: string;
|
|
450
|
+
readonly periodStartIso: string;
|
|
451
|
+
readonly units: number;
|
|
452
|
+
};
|
|
453
|
+
|
|
454
|
+
export class CapReservationOverConsumedError extends UnprocessableError {
|
|
455
|
+
constructor(capName: string, remaining: number, requested: number) {
|
|
456
|
+
super("cap_reservation_over_consumed", {
|
|
457
|
+
message: `Cap "${capName}": cannot consume ${requested} units, only ${remaining} still reserved.`,
|
|
458
|
+
details: { capName, remaining, requested },
|
|
459
|
+
});
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
async function consumeClaimedRow(
|
|
464
|
+
txDb: TenantDb,
|
|
465
|
+
user: SessionUser,
|
|
466
|
+
claimed: CapReservationRow,
|
|
467
|
+
options: ConsumeCalendarCapOptions,
|
|
468
|
+
): Promise<WriteResult> {
|
|
469
|
+
const ownRow = { id: claimed.id, tenantId: user.tenantId };
|
|
470
|
+
if (claimed.releasedAt !== null) {
|
|
471
|
+
// The reserved rest was given back, so each unit is booked on top of what is still counted.
|
|
472
|
+
const booked = await bookCalendarDelta(txDb, user, options, options.units);
|
|
473
|
+
if (!booked.isSuccess) return booked;
|
|
474
|
+
await txDb.updateMany(
|
|
475
|
+
capReservationsTable,
|
|
476
|
+
{ consumedAmount: claimed.consumedAmount + options.units },
|
|
477
|
+
ownRow,
|
|
478
|
+
);
|
|
479
|
+
return booked;
|
|
480
|
+
}
|
|
481
|
+
if (options.units > claimed.amount) {
|
|
482
|
+
throw new CapReservationOverConsumedError(options.capName, claimed.amount, options.units);
|
|
483
|
+
}
|
|
484
|
+
await txDb.updateMany(
|
|
485
|
+
capReservationsTable,
|
|
486
|
+
{
|
|
487
|
+
amount: claimed.amount - options.units,
|
|
488
|
+
consumedAmount: claimed.consumedAmount + options.units,
|
|
489
|
+
},
|
|
490
|
+
ownRow,
|
|
491
|
+
);
|
|
492
|
+
return NOTHING_TO_RELEASE;
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
// Row already expired away: the units are booked directly and a released tombstone remembers them
|
|
496
|
+
// so a later settle subtracts them. A parallel consume inserting first fails this insert.
|
|
497
|
+
async function consumeWithoutRow(
|
|
498
|
+
txDb: TenantDb,
|
|
499
|
+
user: SessionUser,
|
|
500
|
+
id: string,
|
|
501
|
+
options: ConsumeCalendarCapOptions,
|
|
502
|
+
): Promise<WriteResult> {
|
|
503
|
+
// Booking first, for the same reason as settle's missing-row path.
|
|
504
|
+
const booked = await bookCalendarDelta(txDb, user, options, options.units);
|
|
505
|
+
if (!booked.isSuccess) return booked;
|
|
506
|
+
const now = Temporal.Now.instant();
|
|
507
|
+
await txDb.insertOne(capReservationsTable, {
|
|
508
|
+
id,
|
|
509
|
+
tenantId: user.tenantId,
|
|
510
|
+
capName: options.capName,
|
|
511
|
+
kind: "calendar",
|
|
512
|
+
periodStartIso: options.periodStartIso,
|
|
513
|
+
amount: 0,
|
|
514
|
+
expiresAt: now.add({ minutes: CAP_SETTLED_TOMBSTONE_TTL_MINUTES }),
|
|
515
|
+
createdAt: now,
|
|
516
|
+
settledAt: null,
|
|
517
|
+
consumedAmount: options.units,
|
|
518
|
+
releasedAt: now,
|
|
519
|
+
});
|
|
520
|
+
return booked;
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
async function consumeInTransaction(
|
|
524
|
+
txDb: TenantDb,
|
|
525
|
+
user: SessionUser,
|
|
526
|
+
id: string,
|
|
527
|
+
options: ConsumeCalendarCapOptions,
|
|
528
|
+
): Promise<WriteResult> {
|
|
529
|
+
const ownRow = { id, tenantId: user.tenantId };
|
|
530
|
+
const open = await txDb.fetchOne<CapReservationRow>(capReservationsTable, {
|
|
531
|
+
...ownRow,
|
|
532
|
+
settledAt: null,
|
|
533
|
+
});
|
|
534
|
+
if (open && open.capName !== options.capName) {
|
|
535
|
+
throw new Error(
|
|
536
|
+
`cap-counter: reservation ${id} belongs to cap "${open.capName}", not "${options.capName}"`,
|
|
537
|
+
);
|
|
538
|
+
}
|
|
539
|
+
const claimed = open && (await claimReservationRow(txDb, user, open));
|
|
540
|
+
if (claimed) return consumeClaimedRow(txDb, user, claimed, options);
|
|
541
|
+
if (await txDb.fetchOne<CapReservationRow>(capReservationsTable, ownRow)) {
|
|
542
|
+
throw new CapReservationSettledError(options.capName);
|
|
543
|
+
}
|
|
544
|
+
return consumeWithoutRow(txDb, user, id, options);
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
// Turns reserved units into booked ones without touching the counter; the row keeps the unconsumed
|
|
548
|
+
// rest so a release gives back exactly that. After a release or sweep the units are booked directly.
|
|
549
|
+
export async function consumeCalendarCapReservation(
|
|
550
|
+
ctx: CapCounterContext,
|
|
551
|
+
id: string,
|
|
552
|
+
options: ConsumeCalendarCapOptions,
|
|
553
|
+
outsideTransaction = true,
|
|
554
|
+
): Promise<void> {
|
|
555
|
+
if (!Number.isInteger(options.units) || options.units < 1) {
|
|
556
|
+
throw new Error(`cap-counter: consume units must be a positive integer, got ${options.units}`);
|
|
557
|
+
}
|
|
558
|
+
const { user, writeDb } = resolveCapCounterScope(ctx, outsideTransaction);
|
|
559
|
+
const consumed = await retryCounterWriteOnVersionConflict(() =>
|
|
560
|
+
retryOnceOnTombstoneRace(() =>
|
|
561
|
+
runInOwnTransaction(writeDb, (txDb) => consumeInTransaction(txDb, user, id, options)),
|
|
562
|
+
),
|
|
563
|
+
);
|
|
564
|
+
if (!consumed.isSuccess) throw reraiseAsKumikoError(consumed.error);
|
|
565
|
+
}
|
|
566
|
+
|
|
385
567
|
// A failed release leaves its row behind; giving it back once the TTL passed bounds how long that
|
|
386
|
-
// capacity stays counted. Expired settled tombstones are only deleted:
|
|
568
|
+
// capacity stays counted. Expired settled and released tombstones are only deleted: what they
|
|
569
|
+
// stand for stays booked.
|
|
387
570
|
export async function releaseExpiredCapReservations(
|
|
388
571
|
ctx: CapCounterContext,
|
|
389
572
|
capName: string,
|
|
@@ -399,9 +582,14 @@ export async function releaseExpiredCapReservations(
|
|
|
399
582
|
...expiredWhere,
|
|
400
583
|
settledAt: { ne: null },
|
|
401
584
|
});
|
|
585
|
+
await writeDb.deleteMany(capReservationsTable, {
|
|
586
|
+
...expiredWhere,
|
|
587
|
+
releasedAt: { ne: null },
|
|
588
|
+
});
|
|
402
589
|
const expired = await writeDb.selectMany<Pick<CapReservationRow, "id">>(capReservationsTable, {
|
|
403
590
|
...expiredWhere,
|
|
404
591
|
settledAt: null,
|
|
592
|
+
releasedAt: null,
|
|
405
593
|
});
|
|
406
594
|
for (const { id } of expired) {
|
|
407
595
|
try {
|
|
@@ -1,4 +1,17 @@
|
|
|
1
1
|
[
|
|
2
|
+
{
|
|
3
|
+
"version": "0.368.0",
|
|
4
|
+
"type": "improvement",
|
|
5
|
+
"title": "calendarCapReservationHandle consumes a reservation per unit",
|
|
6
|
+
"detail": "`reserveCalendarCapUsage` now returns `reservationId`. `calendarCapReservationHandle(ctx, reservationId, { capName, periodStartIso })` returns a handle with `consume(units)` (turns reserved units into booked ones, counter unchanged) and `release()` (gives back the unconsumed rest). A handler reserves the worst case before the job is dispatched, the job consumes per written unit, so parallel dispatches can no longer overshoot the hard cap and a retry only counts what was written.\n\n`consume` beyond the still-reserved rest throws `CapReservationOverConsumedError` (422, `cap_reservation_over_consumed`). `consume` against a settle tombstone throws `CapReservationSettledError` (422, `cap_reservation_settled`); `release` against it is a no-op. When the reservation outlived its TTL and was swept, `consume` books the units directly, the way settle does for a missing row, without a hard-cap check."
|
|
7
|
+
},
|
|
8
|
+
{
|
|
9
|
+
"version": "0.368.0",
|
|
10
|
+
"type": "fix",
|
|
11
|
+
"title": "Swept or released calendar reservations remember the units consume booked",
|
|
12
|
+
"detail": "The reservation row now stores `consumed_amount`. A release or the TTL sweep gives back only the unconsumed rest. If units were consumed, it keeps the row as a released tombstone (`released_at` set, `amount` 0) until the tombstone TTL passes, instead of deleting it. A later `settle` subtracts the consumed units from the booking, and a `consume` after the sweep books its units directly and is remembered the same way, so the counter ends at exactly the settled total. `settle` below the consumed units throws `CapSettleBelowConsumedError` (422, `cap_settle_below_consumed`) in every state. Expired released tombstones are deleted by the sweep.",
|
|
13
|
+
"migration": "Apps with cap-counter generate a migration for the new columns store_cap_reservations.consumed_amount (integer NOT NULL DEFAULT 0) and store_cap_reservations.released_at (kumiko migrate generate)."
|
|
14
|
+
},
|
|
2
15
|
{
|
|
3
16
|
"version": "0.366.0",
|
|
4
17
|
"type": "fix",
|
|
@@ -68,81 +68,92 @@ import { markSoftWarnedHandler } from "./handlers/mark-soft-warned.write.js";
|
|
|
68
68
|
import { CAP_COUNTER_I18N } from "./i18n.js";
|
|
69
69
|
import { capReservationsTableMeta } from "./tables.js";
|
|
70
70
|
|
|
71
|
-
export
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
71
|
+
export function createCapCounterFeature() {
|
|
72
|
+
return defineFeature(
|
|
73
|
+
CAP_COUNTER_FEATURE,
|
|
74
|
+
(r) => {
|
|
75
|
+
r.describe(
|
|
76
|
+
"Tracks per-tenant usage against configurable limits using two complementary storage models: calendar-period counters (one projection row per tenant/capName/period, reset implicitly by period rollover) and rolling-window counters (append-only event stream, no projection). Use `enforceCap` / `enforceRollingCap` (or the `withCapEnforcement` / `withRollingCapEnforcement` handler wrappers) in your write-handlers to check limits with soft-warn and hard-block tolerances; call `enforceCapAndMaybeNotify` when you also want to trigger a delivery notification on soft-threshold hits.",
|
|
77
|
+
);
|
|
78
|
+
r.uiHints({
|
|
79
|
+
displayLabel: "Cap Counter · Usage Limits",
|
|
80
|
+
category: "operations",
|
|
81
|
+
recommended: false,
|
|
82
|
+
});
|
|
83
|
+
r.entity({ name: "cap-counter", ...capCounterEntity });
|
|
84
|
+
r.storeTable(capReservationsTableMeta, {
|
|
85
|
+
reason: "coordination.cap_reservation_ledger",
|
|
86
|
+
});
|
|
84
87
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
88
|
+
// Custom domain event for the rolling counter. r.defineEvent registers the
|
|
89
|
+
// schema with the registry; ctx.unsafeAppendEvent in the handler reuses the
|
|
90
|
+
// same schema for append-time validation. QN after prefixing:
|
|
91
|
+
// "cap-counter:event:rolling-incremented" (see
|
|
92
|
+
// ROLLING_INCREMENTED_EVENT_QN).
|
|
93
|
+
r.defineEvent(ROLLING_INCREMENTED_EVENT_SHORT, rollingIncrementedSchema, {
|
|
94
|
+
piiFields: "none",
|
|
95
|
+
});
|
|
96
|
+
r.defineEvent(ROLLING_RELEASED_EVENT_SHORT, rollingReleasedSchema, { piiFields: "none" });
|
|
92
97
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
98
|
+
// Custom write-handlers.
|
|
99
|
+
// - increment: calendar period (CRUD via projection row).
|
|
100
|
+
// - increment-rolling: rolling window (custom event, no projection).
|
|
101
|
+
// - mark-soft-warned: anti-notification-storm flag (calendar only).
|
|
102
|
+
r.writeHandler(incrementCapHandler);
|
|
103
|
+
r.writeHandler(incrementRollingCapHandler);
|
|
104
|
+
r.writeHandler(markSoftWarnedHandler);
|
|
100
105
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
106
|
+
// Custom + standard reads. Sysadmin cross-tenant via list, per-tenant
|
|
107
|
+
// single row via get-counter. Detail-by-id handler left out on purpose
|
|
108
|
+
// (no use case; the natural lookup is capName + period, not the
|
|
109
|
+
// aggregate id).
|
|
110
|
+
r.queryHandler(
|
|
111
|
+
defineEntityListHandler("cap-counter", capCounterEntity, {
|
|
112
|
+
access: { roles: access.systemAdmin },
|
|
113
|
+
escapeHatch: {
|
|
114
|
+
reason:
|
|
115
|
+
"SystemAdmin quota operator view lists usage counters of every tenant to see who consumes which cap",
|
|
116
|
+
},
|
|
117
|
+
description:
|
|
118
|
+
"Lists usage-counter rows across all tenants with cap name, current value, period start and soft-warn timestamp; use it as the operator view of who is consuming which quota.",
|
|
119
|
+
}),
|
|
120
|
+
);
|
|
121
|
+
r.queryHandler(getCounterQuery);
|
|
117
122
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
123
|
+
r.screen({
|
|
124
|
+
id: CAP_COUNTER_LIST_SCREEN_ID,
|
|
125
|
+
type: "entityList",
|
|
126
|
+
entity: "cap-counter",
|
|
127
|
+
columns: [
|
|
128
|
+
"tenantId",
|
|
129
|
+
"capName",
|
|
130
|
+
"value",
|
|
131
|
+
"periodStart",
|
|
132
|
+
{ field: "lastSoftWarnedAt", hideOnNarrow: true },
|
|
133
|
+
],
|
|
134
|
+
defaultSort: { field: "capName", dir: "asc" },
|
|
135
|
+
// Search resolves against the caller's own tenant stream, so on this
|
|
136
|
+
// cross-tenant operator list it would only ever hit the SystemAdmin's tenant.
|
|
137
|
+
searchable: false,
|
|
138
|
+
access: { roles: ["SystemAdmin"] },
|
|
139
|
+
});
|
|
140
|
+
r.nav({
|
|
141
|
+
id: "cap-list",
|
|
142
|
+
label: "cap-counter:nav.cap-list",
|
|
143
|
+
screen: "cap-counter:screen:cap-list",
|
|
144
|
+
icon: "gauge",
|
|
145
|
+
order: 60,
|
|
146
|
+
});
|
|
147
|
+
r.translations({
|
|
148
|
+
keys: {
|
|
149
|
+
...CAP_COUNTER_I18N,
|
|
150
|
+
"cap-counter:nav.cap-list": { de: "Limits", en: "Caps" },
|
|
151
|
+
},
|
|
152
|
+
});
|
|
146
153
|
},
|
|
147
|
-
|
|
148
|
-
|
|
154
|
+
{ dedupeOptions: {} },
|
|
155
|
+
);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/** @deprecated Use createCapCounterFeature(). Run `bun kumiko-upgrade --apply`. Removed in the first release after 2026-10-29. */
|
|
159
|
+
export const capCounterFeature = createCapCounterFeature();
|
|
@@ -68,10 +68,9 @@ export const incrementRollingCapHandler: WriteHandlerDef = {
|
|
|
68
68
|
const payload = event.payload as IncrementRollingPayload;
|
|
69
69
|
const aggregateId = rollingCapAggregateId(event.user.tenantId, payload.capName);
|
|
70
70
|
|
|
71
|
-
// unsafeAppendEvent
|
|
72
|
-
//
|
|
73
|
-
//
|
|
74
|
-
// registriert hat.
|
|
71
|
+
// unsafeAppendEvent: the strict appendEvent is bound to an app's own event map, which
|
|
72
|
+
// bundled-features cannot see. Schema validation still runs because r.defineEvent
|
|
73
|
+
// registered the schema.
|
|
75
74
|
await ctx.unsafeAppendEvent({
|
|
76
75
|
aggregateId,
|
|
77
76
|
aggregateType: CAP_COUNTER_ROLLING_AGGREGATE_TYPE,
|
|
@@ -10,10 +10,18 @@ export {
|
|
|
10
10
|
readRollingCapUsage,
|
|
11
11
|
} from "./book-cap-usage.js";
|
|
12
12
|
export {
|
|
13
|
+
type CalendarCapReservationHandle,
|
|
14
|
+
type CalendarCapReservationHandleSpec,
|
|
13
15
|
type CalendarCapUsageReservation,
|
|
14
16
|
type CalendarCapUsageReservationSpec,
|
|
17
|
+
calendarCapReservationHandle,
|
|
15
18
|
reserveCalendarCapUsage,
|
|
16
19
|
} from "./calendar-cap-usage-reservation.js";
|
|
20
|
+
export {
|
|
21
|
+
CapReservationOverConsumedError,
|
|
22
|
+
CapReservationSettledError,
|
|
23
|
+
CapSettleBelowConsumedError,
|
|
24
|
+
} from "./cap-reservation.js";
|
|
17
25
|
export {
|
|
18
26
|
CAP_COUNTER_FEATURE,
|
|
19
27
|
CAP_COUNTER_ROLLING_AGGREGATE_TYPE,
|
|
@@ -41,7 +49,7 @@ export {
|
|
|
41
49
|
type StockCapResult,
|
|
42
50
|
} from "./enforce-cap.js";
|
|
43
51
|
export { capCounterEntity } from "./entity.js";
|
|
44
|
-
export { capCounterFeature } from "./feature.js";
|
|
52
|
+
export { capCounterFeature, createCapCounterFeature } from "./feature.js";
|
|
45
53
|
export { readPayloadField } from "./payload-field.js";
|
|
46
54
|
export {
|
|
47
55
|
type CapGuardContext,
|
|
@@ -13,9 +13,12 @@ const CAP_RESERVATIONS_TENANT_CAP_EXPIRES_INDEX =
|
|
|
13
13
|
"store_cap_reservations_tenant_id_cap_name_expires_at_idx";
|
|
14
14
|
|
|
15
15
|
// One row per capacity that was booked before a handler transaction and is not settled yet. The
|
|
16
|
-
// handler transaction deletes it on its own commit (confirm)
|
|
17
|
-
//
|
|
18
|
-
//
|
|
16
|
+
// handler transaction deletes it on its own commit (confirm). While a calendar row is open the
|
|
17
|
+
// counter holds amount + consumed_amount for it. A release or the TTL sweep gives back amount and
|
|
18
|
+
// keeps the row as a released tombstone (released_at set, amount 0, consumed_amount still booked)
|
|
19
|
+
// until its shortened TTL passes, so a later consume or settle accounts for the consumed units.
|
|
20
|
+
// A settle keeps the row as a settled tombstone (settled_at set) the same way, so a repeated settle
|
|
21
|
+
// or a late release finds it and does nothing.
|
|
19
22
|
// Unmanaged: the rows are a coordination aid, not state with an event history.
|
|
20
23
|
export const capReservationsTable = pgTable(
|
|
21
24
|
"store_cap_reservations",
|
|
@@ -30,6 +33,8 @@ export const capReservationsTable = pgTable(
|
|
|
30
33
|
expiresAt: instant("expires_at").notNull(),
|
|
31
34
|
createdAt: instant("created_at").notNull(),
|
|
32
35
|
settledAt: instant("settled_at"),
|
|
36
|
+
consumedAmount: integer("consumed_amount").notNull().default(0),
|
|
37
|
+
releasedAt: instant("released_at"),
|
|
33
38
|
},
|
|
34
39
|
(t) => [index(CAP_RESERVATIONS_TENANT_CAP_EXPIRES_INDEX).on(t.tenantId, t.capName, t.expiresAt)],
|
|
35
40
|
);
|
|
@@ -52,5 +57,7 @@ export const capReservationsTableMeta: EntityTableMeta = defineUnmanagedTable({
|
|
|
52
57
|
{ name: "expires_at", pgType: "timestamptz", notNull: true },
|
|
53
58
|
{ name: "created_at", pgType: "timestamptz", notNull: true },
|
|
54
59
|
{ name: "settled_at", pgType: "timestamptz", notNull: false },
|
|
60
|
+
{ name: "consumed_amount", pgType: "integer", notNull: true, defaultSql: "0" },
|
|
61
|
+
{ name: "released_at", pgType: "timestamptz", notNull: false },
|
|
55
62
|
],
|
|
56
63
|
});
|
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import { selectMany } from "@cosmicdrift/kumiko-framework/
|
|
2
|
-
import { buildEntityTable } from "@cosmicdrift/kumiko-framework/db";
|
|
1
|
+
import { buildEntityTable, selectMany } from "@cosmicdrift/kumiko-framework/db";
|
|
3
2
|
import {
|
|
4
3
|
defineQueryHandler,
|
|
5
4
|
mayOverrideTenant,
|
|
@@ -1,5 +1,9 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
1
|
+
import {
|
|
2
|
+
buildEntityTable,
|
|
3
|
+
decodeCursor,
|
|
4
|
+
encodeCursor,
|
|
5
|
+
selectMany,
|
|
6
|
+
} from "@cosmicdrift/kumiko-framework/db";
|
|
3
7
|
import {
|
|
4
8
|
type ConfigAccessor,
|
|
5
9
|
definePagedQueryHandler,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { selectMany } from "@cosmicdrift/kumiko-framework/
|
|
1
|
+
import { selectMany } from "@cosmicdrift/kumiko-framework/db";
|
|
2
2
|
import { defineQueryHandler } from "@cosmicdrift/kumiko-framework/engine";
|
|
3
3
|
import { InternalError } from "@cosmicdrift/kumiko-framework/errors";
|
|
4
4
|
import * as z from "zod";
|
|
@@ -1,4 +1,10 @@
|
|
|
1
1
|
[
|
|
2
|
+
{
|
|
3
|
+
"version": "0.367.0",
|
|
4
|
+
"type": "improvement",
|
|
5
|
+
"title": "Email channel can send through the tenant's own mail provider",
|
|
6
|
+
"detail": "EmailChannelOptions gets tenantTransport, a resolver called per send with the delivery channel context. mail-foundation exports tenantMailTransport for it: a tenant with a provider configured sends through its own transport, a broken provider fails the send, a tenant without a provider uses the app transport. The transport is built per send and never cached, and the tenant comes from the server-side delivery context only. ChannelContext now carries the tenant's config accessor as config. Without tenantTransport nothing changes."
|
|
7
|
+
},
|
|
2
8
|
{
|
|
3
9
|
"version": "0.365.0",
|
|
4
10
|
"type": "fix",
|