@cosmicdrift/kumiko-bundled-features 0.367.0 → 0.369.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/audit/handlers/details.query.js +1 -1
- package/dist/audit/handlers/list.query.js +1 -1
- package/dist/audit/run-escape-hatch-retention.js +1 -1
- package/dist/auth-email-password/bootstrap.js +1 -1
- package/dist/auth-email-password/handlers/invite-accept-with-login.write.js +6 -3
- package/dist/auth-email-password/handlers/invite-accept.write.js +6 -3
- package/dist/auth-email-password/handlers/invite-info.query.js +1 -1
- package/dist/auth-email-password/handlers/invite-signup-complete.write.js +6 -3
- package/dist/auth-email-password/handlers/signup-confirm.write.js +1 -1
- package/dist/auth-email-password/seeding.js +2 -2
- package/dist/auth-foundation/feature.d.ts +4 -0
- package/dist/auth-foundation/feature.js +37 -33
- package/dist/auth-foundation/index.d.ts +1 -1
- package/dist/auth-foundation/index.js +1 -1
- package/dist/auth-mfa/db/queries.js +1 -2
- package/dist/auth-mfa/handlers/enable-confirm-preauth.write.js +1 -1
- package/dist/auth-mfa/handlers/enable-start-preauth.write.js +1 -1
- package/dist/auth-mfa/handlers/reencrypt.job.js +2 -3
- package/dist/auth-mfa/handlers/status.query.js +1 -1
- package/dist/auth-mfa/handlers/verify.write.js +1 -1
- package/dist/auth-mfa/mfa-code-verifier.js +1 -1
- package/dist/auth-mfa/mfa-status-checker.js +1 -1
- package/dist/auth-mfa-user-data/index.d.ts +4 -0
- package/dist/auth-mfa-user-data/index.js +13 -9
- package/dist/billing-foundation/billing-info-query.js +1 -1
- package/dist/billing-foundation/feature.d.ts +1 -0
- package/dist/billing-foundation/feature.js +5 -6
- package/dist/billing-foundation/handlers/create-portal-session.write.js +1 -1
- package/dist/billing-foundation/projection.js +1 -2
- package/dist/cap-counter/calendar-cap-usage-reservation.d.ts +7 -0
- package/dist/cap-counter/calendar-cap-usage-reservation.js +28 -2
- package/dist/cap-counter/cap-reservation.d.ts +16 -0
- package/dist/cap-counter/cap-reservation.js +144 -14
- package/dist/cap-counter/feature.d.ts +4 -0
- package/dist/cap-counter/feature.js +74 -68
- package/dist/cap-counter/handlers/increment-rolling.write.js +3 -4
- package/dist/cap-counter/index.d.ts +3 -2
- package/dist/cap-counter/index.js +3 -2
- package/dist/cap-counter/tables.js +10 -3
- package/dist/cap-overview/handlers/caps-usage.query.js +1 -2
- package/dist/cap-overview/handlers/tenant-caps-list.query.js +1 -2
- package/dist/cap-overview/handlers/tenant-options.query.js +1 -1
- package/dist/channel-email/email-channel.d.ts +13 -5
- package/dist/channel-email/email-channel.js +17 -3
- package/dist/channel-email/feature.js +1 -1
- package/dist/channel-email/index.d.ts +1 -1
- package/dist/channel-email/index.js +1 -1
- package/dist/channel-in-app/handlers/inbox.query.js +1 -1
- package/dist/channel-in-app/handlers/mark-all-read.write.js +1 -1
- package/dist/channel-in-app/handlers/mark-read.write.js +1 -1
- package/dist/channel-in-app/handlers/unread-count.query.js +1 -1
- package/dist/channel-in-app/tables.js +1 -2
- package/dist/compliance-profiles/handlers/for-tenant.query.js +1 -1
- package/dist/compliance-profiles/handlers/needs-profile.query.js +1 -1
- package/dist/compliance-profiles/handlers/set-profile.write.js +1 -2
- package/dist/compliance-profiles/resolve-for-tenant.js +1 -1
- package/dist/compliance-profiles/seeding.js +3 -5
- package/dist/compliance-profiles-ops/index.d.ts +8 -0
- package/dist/compliance-profiles-ops/index.js +19 -15
- package/dist/config/db/queries/resolver.js +1 -1
- package/dist/config/handlers/reencrypt.job.js +2 -2
- package/dist/config/resolver.js +1 -1
- package/dist/config/web/i18n.js +1 -2
- package/dist/config/write-helpers.js +1 -1
- package/dist/crypto-shredding/handlers/forget-subject.write.js +6 -3
- package/dist/custom-fields/db/queries/field-access.js +1 -1
- package/dist/custom-fields/db/queries/projection.js +1 -1
- package/dist/custom-fields/db/queries/quota.js +1 -1
- package/dist/custom-fields/db/queries/retention.js +1 -2
- package/dist/custom-fields/db/queries/user-data-rights.js +1 -1
- package/dist/custom-fields/feature.d.ts +20 -2
- package/dist/custom-fields/feature.js +9 -13
- package/dist/custom-fields/handlers/clear-custom-field.write.js +2 -2
- package/dist/custom-fields/handlers/delete-system-field.write.js +2 -2
- package/dist/custom-fields/handlers/delete-tenant-field.write.js +2 -2
- package/dist/custom-fields/handlers/set-custom-field.write.js +2 -2
- package/dist/custom-fields/wire-for-entity.js +4 -4
- package/dist/data-retention/resolve-for-tenant.js +1 -1
- package/dist/data-retention/run-retention-cleanup.js +17 -13
- package/dist/delivery/address-opt-out.js +1 -2
- package/dist/delivery/attempt-log-retention.js +1 -1
- package/dist/delivery/channel-context.js +1 -1
- package/dist/delivery/db/queries/address-opt-outs.js +1 -1
- package/dist/delivery/db/queries/preferences.js +1 -1
- package/dist/delivery/delivery-service.js +16 -7
- package/dist/delivery/feature.js +2 -2
- package/dist/delivery/handlers/log.query.js +2 -3
- package/dist/delivery/handlers/preferences.query.js +1 -1
- package/dist/delivery/index.d.ts +3 -2
- package/dist/delivery/index.js +3 -2
- package/dist/delivery/unsubscribe.d.ts +13 -0
- package/dist/delivery/unsubscribe.js +32 -27
- package/dist/delivery/upsert-preference.js +1 -2
- package/dist/derivatives-sharp/feature.d.ts +4 -0
- package/dist/derivatives-sharp/feature.js +15 -11
- package/dist/derivatives-sharp/index.d.ts +1 -1
- package/dist/derivatives-sharp/index.js +1 -1
- package/dist/document-ingest-foundation/feature.d.ts +7 -0
- package/dist/document-ingest-foundation/feature.js +94 -90
- package/dist/document-ingest-foundation/forget-extract-with-file-ref.js +3 -3
- package/dist/document-ingest-foundation/index.d.ts +1 -1
- package/dist/document-ingest-foundation/index.js +1 -1
- package/dist/feature-toggles/global-feature-state-table.js +1 -2
- package/dist/feature-toggles/handlers/set.write.js +3 -5
- package/dist/feature-toggles/toggle-runtime.js +1 -1
- package/dist/file-derivatives/feature.d.ts +1 -0
- package/dist/file-derivatives/feature.js +2 -1
- package/dist/file-derivatives/handlers/public-variant-by-file-ref.query.js +1 -1
- package/dist/file-derivatives/handlers/public-variant.query.js +1 -1
- package/dist/file-foundation/feature.d.ts +6 -0
- package/dist/file-foundation/feature.js +29 -25
- package/dist/file-foundation/index.d.ts +1 -1
- package/dist/file-foundation/index.js +1 -1
- package/dist/file-foundation/read-file-ref-content.js +1 -1
- package/dist/file-provider-inmemory/feature.d.ts +4 -0
- package/dist/file-provider-inmemory/feature.js +24 -20
- package/dist/file-provider-inmemory/index.d.ts +1 -1
- package/dist/file-provider-inmemory/index.js +1 -1
- package/dist/file-provider-s3/env-feature.d.ts +3 -0
- package/dist/file-provider-s3/env-feature.js +46 -0
- package/dist/file-provider-s3/feature.d.ts +26 -0
- package/dist/file-provider-s3/feature.js +84 -72
- package/dist/file-provider-s3/index.d.ts +1 -1
- package/dist/file-provider-s3/index.js +1 -1
- package/dist/file-provider-s3/lib/index.d.ts +3 -0
- package/dist/file-provider-s3/lib/index.js +2 -0
- package/dist/{files-provider-s3 → file-provider-s3/lib}/s3-provider.js +19 -19
- package/dist/file-provider-s3-env/index.d.ts +8 -1
- package/dist/file-provider-s3-env/index.js +7 -2
- package/dist/files-provider-s3/index.d.ts +5 -3
- package/dist/files-provider-s3/index.js +5 -2
- package/dist/files-tenant-data/handlers/sweep-orphaned-derivatives.job.js +1 -1
- package/dist/files-tenant-data/hooks.js +1 -2
- package/dist/folders/feature.d.ts +6 -2
- package/dist/folders/feature.js +4 -2
- package/dist/folders-user-data/index.d.ts +4 -0
- package/dist/folders-user-data/index.js +21 -17
- package/dist/form-draft/db/queries/cleanup.js +1 -1
- package/dist/form-draft/db/queries/draft-count.js +1 -1
- package/dist/form-draft/db/queries/owned-file-refs.js +1 -2
- package/dist/form-draft/feature.d.ts +4 -0
- package/dist/form-draft/feature.js +5 -1
- package/dist/form-draft/handlers/cleanup.job.js +2 -2
- package/dist/form-draft/index.d.ts +1 -1
- package/dist/form-draft/index.js +1 -1
- package/dist/form-draft/lookup.js +1 -1
- package/dist/form-draft-user-data/index.d.ts +4 -0
- package/dist/form-draft-user-data/index.js +13 -9
- package/dist/inbound-mail-foundation/feature.d.ts +4 -0
- package/dist/inbound-mail-foundation/feature.js +151 -147
- package/dist/inbound-mail-foundation/handlers/ingest-message.write.js +1 -2
- package/dist/inbound-mail-foundation/index.d.ts +1 -1
- package/dist/inbound-mail-foundation/index.js +1 -1
- package/dist/inbound-mail-foundation/oauth-access-token.js +1 -2
- package/dist/inbound-mail-foundation/projection.js +1 -2
- package/dist/inbound-mail-foundation/watch-supervisor.js +1 -1
- package/dist/inbound-provider-imap/feature.d.ts +4 -0
- package/dist/inbound-provider-imap/feature.js +16 -12
- package/dist/inbound-provider-imap/index.d.ts +1 -1
- package/dist/inbound-provider-imap/index.js +1 -1
- package/dist/inbound-provider-inmemory/feature.d.ts +4 -0
- package/dist/inbound-provider-inmemory/feature.js +14 -10
- package/dist/inbound-provider-inmemory/index.d.ts +1 -1
- package/dist/inbound-provider-inmemory/index.js +1 -1
- package/dist/jobs/db/queries/retention.js +1 -1
- package/dist/jobs/db/queries/stale-run-sweep.js +1 -1
- package/dist/jobs/handlers/detail.query.js +1 -1
- package/dist/jobs/handlers/list.query.js +1 -2
- package/dist/jobs/handlers/retry.write.js +1 -1
- package/dist/jobs/handlers/tenant-failures.query.js +1 -1
- package/dist/jobs/handlers/tenant-runs.query.js +1 -1
- package/dist/jobs/job-run-logger.js +1 -2
- package/dist/jobs/tenant-job-failure-table.js +1 -2
- package/dist/jobs/tenant-job-run-table.js +1 -2
- package/dist/ledger/feature.d.ts +6 -2
- package/dist/ledger/feature.js +4 -2
- package/dist/mail-foundation/feature.d.ts +11 -0
- package/dist/mail-foundation/feature.js +52 -42
- package/dist/mail-foundation/i18n.d.ts +14 -0
- package/dist/mail-foundation/i18n.js +8 -0
- package/dist/mail-foundation/index.d.ts +1 -1
- package/dist/mail-foundation/index.js +1 -1
- package/dist/mail-transport-inmemory/feature.d.ts +4 -0
- package/dist/mail-transport-inmemory/feature.js +26 -16
- package/dist/mail-transport-inmemory/index.d.ts +1 -1
- package/dist/mail-transport-inmemory/index.js +1 -1
- package/dist/mail-transport-smtp/config-keys.d.ts +7 -0
- package/dist/mail-transport-smtp/config-keys.js +65 -0
- package/dist/mail-transport-smtp/feature.d.ts +17 -1
- package/dist/mail-transport-smtp/feature.js +58 -80
- package/dist/mail-transport-smtp/i18n.d.ts +41 -0
- package/dist/mail-transport-smtp/i18n.js +28 -0
- package/dist/mail-transport-smtp/index.d.ts +1 -1
- package/dist/mail-transport-smtp/index.js +1 -1
- package/dist/managed-pages/handlers/by-slug.query.js +1 -1
- package/dist/managed-pages/handlers/by-tenant-published.query.js +1 -2
- package/dist/managed-pages/handlers/set.write.js +1 -2
- package/dist/managed-pages/seeding.js +3 -5
- package/dist/metrics/compute.d.ts +1 -1
- package/dist/metrics/filters.d.ts +1 -1
- package/dist/metrics/filters.js +1 -1
- package/dist/metrics/validate.js +1 -1
- package/dist/notes-history/feature.d.ts +7 -2
- package/dist/notes-history/feature.js +4 -2
- package/dist/notes-history/handlers/add-note.write.js +1 -1
- package/dist/notes-history-user-data/hooks.js +1 -1
- package/dist/notes-history-user-data/index.d.ts +4 -0
- package/dist/notes-history-user-data/index.js +21 -17
- package/dist/personal-access-tokens/feature.d.ts +4 -1
- package/dist/personal-access-tokens/feature.js +10 -2
- package/dist/personal-access-tokens/handlers/create.write.js +1 -1
- package/dist/personal-access-tokens/handlers/list.query.js +2 -2
- package/dist/personal-access-tokens/handlers/revoke.write.js +1 -1
- package/dist/personal-access-tokens/resolver.js +1 -1
- package/dist/personal-access-tokens/revoke-for-user.js +1 -1
- package/dist/personal-access-tokens/roles.js +3 -5
- package/dist/personal-access-tokens/screens.js +2 -0
- package/dist/readiness/feature.d.ts +8 -0
- package/dist/readiness/feature.js +18 -14
- package/dist/readiness/index.d.ts +1 -1
- package/dist/readiness/index.js +1 -1
- package/dist/secrets/db/queries/read.js +1 -1
- package/dist/secrets/feature.d.ts +4 -0
- package/dist/secrets/feature.js +19 -6
- package/dist/secrets/handlers/delete.write.js +3 -2
- package/dist/secrets/handlers/rotate.job.js +2 -3
- package/dist/secrets/handlers/set.write.js +3 -2
- package/dist/secrets/secrets-context.js +3 -4
- package/dist/sessions/db/queries/cleanup.js +1 -1
- package/dist/sessions/handlers/detail.query.js +1 -1
- package/dist/sessions/handlers/list.query.js +2 -2
- package/dist/sessions/handlers/mine.query.js +1 -1
- package/dist/sessions/handlers/revoke-all-for-user.write.js +1 -1
- package/dist/sessions/handlers/revoke-all-others.write.js +1 -1
- package/dist/sessions/handlers/revoke.write.js +1 -1
- package/dist/sessions/session-callbacks.js +4 -6
- package/dist/sessions/testing.d.ts +1 -1
- package/dist/tags/feature.d.ts +6 -2
- package/dist/tags/feature.js +4 -2
- package/dist/template-resolver/api.js +1 -1
- package/dist/template-resolver/constants.d.ts +0 -1
- package/dist/template-resolver/constants.js +0 -1
- package/dist/template-resolver/handlers/bundle-import.js +1 -1
- package/dist/template-resolver/handlers/by-slug.query.js +1 -1
- package/dist/template-resolver/handlers/by-tenant.query.js +1 -2
- package/dist/template-resolver/handlers/collection-item.query.js +1 -1
- package/dist/template-resolver/handlers/collection-list.query.js +1 -2
- package/dist/template-resolver/handlers/collection-set.write.js +1 -1
- package/dist/template-resolver/handlers/copy-locale.write.js +1 -1
- package/dist/template-resolver/handlers/diff-to-system.query.js +1 -1
- package/dist/template-resolver/handlers/export-bundle.query.js +1 -1
- package/dist/template-resolver/handlers/find-by-id.query.js +1 -1
- package/dist/template-resolver/handlers/layout-source.query.js +1 -1
- package/dist/template-resolver/handlers/list.query.js +1 -1
- package/dist/template-resolver/handlers/overview.query.js +1 -1
- package/dist/template-resolver/handlers/reset-to-system.write.js +1 -1
- package/dist/template-resolver/handlers/resource-remove.write.js +1 -1
- package/dist/template-resolver/handlers/send-test.write.js +4 -2
- package/dist/template-resolver/handlers/set.write.js +1 -1
- package/dist/template-resolver/handlers/template-write.js +1 -1
- package/dist/template-resolver/handlers/tenant-overrides.query.js +1 -1
- package/dist/template-resolver/handlers/toggle-status.write.js +1 -1
- package/dist/template-resolver/handlers/usage.query.js +1 -1
- package/dist/template-resolver/handlers/validate.query.js +1 -1
- package/dist/template-resolver/seeding.js +3 -4
- package/dist/template-resolver/store-template-resource.js +1 -1
- package/dist/template-resolver/template-preview.js +1 -1
- package/dist/template-resolver/template-resources.js +1 -1
- package/dist/template-resolver/testing.js +1 -1
- package/dist/template-resolver-user-data/index.d.ts +4 -0
- package/dist/template-resolver-user-data/index.js +17 -13
- package/dist/tenant/handlers/active-tenant-ids.query.js +1 -1
- package/dist/tenant/handlers/add-member.write.js +1 -2
- package/dist/tenant/handlers/cancel-invitation.write.js +1 -1
- package/dist/tenant/handlers/invitations.query.js +1 -1
- package/dist/tenant/handlers/me.query.js +1 -1
- package/dist/tenant/handlers/member-directory.query.js +1 -1
- package/dist/tenant/handlers/members.query.js +1 -1
- package/dist/tenant/handlers/memberships.query.js +1 -1
- package/dist/tenant/handlers/remove-member.write.js +1 -2
- package/dist/tenant/handlers/resolve-user-ids.query.js +1 -1
- package/dist/tenant/handlers/team-list.query.js +1 -1
- package/dist/tenant/handlers/tenant-directory.query.js +1 -1
- package/dist/tenant/handlers/update-member-roles.write.js +1 -2
- package/dist/tenant/invited-membership.js +2 -3
- package/dist/tenant/last-tenant-admin.js +1 -2
- package/dist/tenant/seeding.js +12 -6
- package/dist/tenant-lifecycle/handlers/cancel-destruction.write.js +1 -2
- package/dist/tenant-lifecycle/handlers/request-destruction.write.js +1 -2
- package/dist/tenant-lifecycle/lib/revoke-tenant-sessions.js +1 -1
- package/dist/tenant-lifecycle/lifecycle-gate.js +1 -1
- package/dist/tenant-lifecycle/run-tenant-destroy.js +3 -5
- package/dist/tenant-lifecycle/stages.js +10 -6
- package/dist/tenant-lifecycle/testing.d.ts +1 -1
- package/dist/tenant-lifecycle/testing.js +2 -2
- package/dist/tenant-settings/feature.d.ts +1 -0
- package/dist/tenant-settings/feature.js +2 -1
- package/dist/tier-engine/feature.d.ts +1 -8
- package/dist/tier-engine/feature.js +71 -78
- package/dist/tier-engine/handlers/get-tenant-tier.query.js +2 -3
- package/dist/tier-engine/handlers/set-tenant-tier.write.js +1 -2
- package/dist/user/db/queries/stream-tenant-backfill.js +1 -1
- package/dist/user/handlers/create.write.js +1 -2
- package/dist/user/handlers/find-for-auth.query.js +1 -1
- package/dist/user/handlers/list.query.js +1 -1
- package/dist/user/handlers/update-roles.js +1 -2
- package/dist/user/index.d.ts +1 -1
- package/dist/user/index.js +1 -1
- package/dist/user/invited-global-roles.js +2 -3
- package/dist/user/principal-status.d.ts +4 -0
- package/dist/user/principal-status.js +9 -3
- package/dist/user/resolve-display-names.js +1 -1
- package/dist/user/seeding.js +5 -6
- package/dist/user-data-rights/audit-download.js +2 -2
- package/dist/user-data-rights/db/queries/export-jobs.js +1 -1
- package/dist/user-data-rights/db/queries/forget-cleanup.js +1 -1
- package/dist/user-data-rights/feature.js +7 -4
- package/dist/user-data-rights/handlers/download-by-job.query.js +1 -1
- package/dist/user-data-rights/handlers/download-by-token.query.js +1 -1
- package/dist/user-data-rights/handlers/export-status.query.js +1 -1
- package/dist/user-data-rights/handlers/list-download-attempts.query.js +1 -1
- package/dist/user-data-rights/handlers/my-audit-log.query.js +1 -1
- package/dist/user-data-rights/handlers/request-export.write.js +1 -2
- package/dist/user-data-rights/handlers/run-forget-cleanup.write.js +3 -3
- package/dist/user-data-rights/lib/default-mailers.d.ts +3 -0
- package/dist/user-data-rights/lib/default-mailers.js +25 -3
- package/dist/user-data-rights/lib/deny-if-target-outside-admin-tenant.d.ts +1 -1
- package/dist/user-data-rights/lib/deny-if-target-outside-admin-tenant.js +1 -1
- package/dist/user-data-rights/lib/update-user-lifecycle.d.ts +1 -0
- package/dist/user-data-rights/lib/update-user-lifecycle.js +3 -3
- package/dist/user-data-rights/run-export-jobs.js +2 -3
- package/dist/user-data-rights/run-forget-cleanup.js +9 -5
- package/dist/user-data-rights/run-user-export.js +9 -5
- package/dist/waitlist/handlers/invite.write.js +1 -2
- package/dist/waitlist/handlers/reject.write.js +1 -1
- package/dist/waitlist/lib.js +1 -2
- package/dist/workflow-runner/event-subscriber.js +1 -1
- package/dist/workflow-runner/feature.d.ts +4 -0
- package/dist/workflow-runner/feature.js +44 -40
- package/dist/workflow-runner/handlers/resume-run.write.js +1 -1
- package/dist/workflow-runner/index.d.ts +1 -1
- package/dist/workflow-runner/index.js +1 -1
- package/dist/workflow-runner/pending-projection.js +1 -1
- package/package.json +28 -11
- package/src/cap-counter/changes.json +13 -0
- package/src/channel-email/changes.json +7 -0
- package/src/crypto-shredding/changes.json +6 -0
- package/src/delivery/changes.json +6 -0
- package/src/file-provider-s3/changes.json +10 -1
- package/src/mail-transport-smtp/changes.json +7 -0
- package/src/personal-access-tokens/changes.json +6 -0
- package/src/secrets/changes.json +6 -0
- package/src/user/changes.json +6 -0
- package/src/user-data-rights/changes.json +6 -0
- package/dist/file-provider-s3-env/feature.d.ts +0 -3
- package/dist/file-provider-s3-env/feature.js +0 -46
- /package/dist/{files-provider-s3 → file-provider-s3/lib}/env-helper.d.ts +0 -0
- /package/dist/{files-provider-s3 → file-provider-s3/lib}/env-helper.js +0 -0
- /package/dist/{files-provider-s3 → file-provider-s3/lib}/s3-provider.d.ts +0 -0
package/dist/delivery/feature.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { updateMany, upsertByPk } from "@cosmicdrift/kumiko-framework/
|
|
1
|
+
import { updateMany, upsertByPk } from "@cosmicdrift/kumiko-framework/db";
|
|
2
2
|
import { access, defineFeature, i18nKey, } from "@cosmicdrift/kumiko-framework/engine";
|
|
3
3
|
import { ConsumerLagError } from "@cosmicdrift/kumiko-framework/pipeline";
|
|
4
4
|
import { resolveAttemptLogRetentionDays, runAttemptLogRetention } from "./attempt-log-retention.js";
|
|
@@ -19,7 +19,7 @@ export function createDeliveryFeature(options) {
|
|
|
19
19
|
const resolvedAccess = options?.access ?? access.admin;
|
|
20
20
|
const attemptLogRetentionDays = resolveAttemptLogRetentionDays(options?.attemptLogRetentionDays);
|
|
21
21
|
return defineFeature("delivery", (r) => {
|
|
22
|
-
r.describe("The notification dispatch core: call `ctx.notify(notificationType, { to, route, data, priority, idempotencyKey })` from any handler to fan out a notification across all registered channels (email, in-app, push). Inside a write handler the delivery runs after the write has committed (the call returns `{ kind: \"deferred\" }`, a rolled-back write sends nothing); `immediate: true` delivers synchronously and returns `{ kind: \"dispatched\", deliveries }`. It stores per-user channel preferences in the `notification-preference` entity, opt-outs for no-account recipient addresses in `notification-address-opt-out` (keyed by a blind-index hash, never the plaintext address), logs every attempt to `store_delivery_attempts`, and enforces idempotency and rate-limiting \u2014 add `channel-email`, `channel-in-app`, or `channel-push` on top to actually send anything. Unsubscribe/resubscribe links are served by `createUnsubscribeRoutes({ secret })` mounted via the app's `extraRoutes` at `/api/delivery/unsubscribe` and `/api/delivery/resubscribe`: `GET /unsubscribe?token=` renders a confirmation page (no write), `POST /unsubscribe` performs the opt-out (RFC 8058 one-click, token from the form body or query), `POST /resubscribe` undoes it (JSON `{token}` or form body only, never the query — reachable by a mail-client link prefetcher) — sign links with `signUnsubscribeToken` / `signAddressUnsubscribeToken` using the same secret. `channel-email` sets `List-Unsubscribe` / `List-Unsubscribe-Post` automatically when a message's `data.unsubscribeUrl` points at the unsubscribe route. A notification declared with `unsubscribe: true` gets that link from delivery itself when the service has `unsubscribeLinks` (`runProdApp` option `unsubscribe: { secret }` mounts the routes and wires it; base URL from `auth.publicOrigin`, `auth.mail.baseUrl` or `APP_BASE_URL`): user recipients get a user token, `route` recipients an address token (needs a blind-index key, otherwise no link); a `data.unsubscribeUrl` from the caller wins. `unsubscribe.tenantOrigin: (ctx) => origin | null` points the link at the tenant's own host (subdomain or custom domain, must serve this app's `/api`); null, a throw or a non-https value fall back to the app origin. The token carries the mail's locale, so the confirmation and result pages render in the mail's language. To react to a no-account opt-out (e.g. remove the app's own subscriber row), declare `r.job(name, { trigger: { on: DeliveryHandlers.unsubscribeAddress } }, handler)` on a lane the app consumes: it runs after each committed opt-out, also an idempotent repeat, with `unsubscribeAddressPayloadSchema` (`addressHash`, `notificationType`, `channel`) as payload and the tenant in `ctx.triggeredBy.tenantId`; match the subscriber via `hashUnsubscribeAddress(email)`. Declare `unsubscribeUrl` as an optional template variable if the mail body shows it.");
|
|
22
|
+
r.describe("The notification dispatch core: call `ctx.notify(notificationType, { to, route, data, priority, idempotencyKey })` from any handler to fan out a notification across all registered channels (email, in-app, push). Inside a write handler the delivery runs after the write has committed (the call returns `{ kind: \"deferred\" }`, a rolled-back write sends nothing); `immediate: true` delivers synchronously and returns `{ kind: \"dispatched\", deliveries }`. It stores per-user channel preferences in the `notification-preference` entity, opt-outs for no-account recipient addresses in `notification-address-opt-out` (keyed by a blind-index hash, never the plaintext address), logs every attempt to `store_delivery_attempts`, and enforces idempotency and rate-limiting \u2014 add `channel-email`, `channel-in-app`, or `channel-push` on top to actually send anything. Unsubscribe/resubscribe links are served by `createUnsubscribeRoutes({ secret })` mounted via the app's `extraRoutes` at `/api/delivery/unsubscribe` and `/api/delivery/resubscribe`: `GET /unsubscribe?token=` renders a confirmation page (no write), `POST /unsubscribe` performs the opt-out (RFC 8058 one-click, token from the form body or query), `POST /resubscribe` undoes it (JSON `{token}` or form body only, never the query — reachable by a mail-client link prefetcher) — sign links with `signUnsubscribeToken` / `signAddressUnsubscribeToken` using the same secret. `channel-email` sets `List-Unsubscribe` / `List-Unsubscribe-Post` automatically when a message's `data.unsubscribeUrl` points at the unsubscribe route. A notification declared with `unsubscribe: true` gets that link from delivery itself when the service has `unsubscribeLinks` (the `runProdApp` and `runDevApp` option `unsubscribe: { secret }` mounts the routes and wires it; base URL from `auth.publicOrigin`, `auth.mail.baseUrl` or `APP_BASE_URL`): user recipients get a user token, `route` recipients an address token (needs a blind-index key, otherwise no link); a `data.unsubscribeUrl` from the caller wins. `unsubscribe.tenantOrigin: (ctx) => origin | null` points the link at the tenant's own host (subdomain or custom domain, must serve this app's `/api`); null, a throw or a non-https value fall back to the app origin. The token carries the mail's locale, so the confirmation and result pages render in the mail's language. An app serving its own (e.g. legacy) unsubscribe links renders the same result page with `renderUnsubscribeResultPage({ kind, locale })`. To react to a no-account opt-out (e.g. remove the app's own subscriber row), declare `r.job(name, { trigger: { on: DeliveryHandlers.unsubscribeAddress } }, handler)` on a lane the app consumes: it runs after each committed opt-out, also an idempotent repeat, with `unsubscribeAddressPayloadSchema` (`addressHash`, `notificationType`, `channel`) as payload and the tenant in `ctx.triggeredBy.tenantId`; match the subscriber via `hashUnsubscribeAddress(email)`. Declare `unsubscribeUrl` as an optional template variable if the mail body shows it.");
|
|
23
23
|
r.uiHints({
|
|
24
24
|
displayLabel: "Notifications \u00b7 Dispatch Core",
|
|
25
25
|
category: "notifications",
|
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import { selectMany } from "@cosmicdrift/kumiko-framework/
|
|
2
|
-
import { decodeKeysetCursor, encodeKeysetCursor, } from "@cosmicdrift/kumiko-framework/db";
|
|
1
|
+
import { decodeKeysetCursor, encodeKeysetCursor, selectMany, } from "@cosmicdrift/kumiko-framework/db";
|
|
3
2
|
import { access, definePagedQueryHandler, isDeliveryErrorCode, isUuid, MAX_LIST_LIMIT, SYSTEM_USER_ID, } from "@cosmicdrift/kumiko-framework/engine";
|
|
4
3
|
import { InternalError } from "@cosmicdrift/kumiko-framework/errors";
|
|
5
4
|
import { Temporal } from "@cosmicdrift/kumiko-types/temporal";
|
|
@@ -30,7 +29,7 @@ function isDeliveryLogSortField(value) {
|
|
|
30
29
|
return value in DELIVERY_LOG_SORT_COLUMNS;
|
|
31
30
|
}
|
|
32
31
|
// createdAt round-trips through Temporal.Instant (matches how the WHERE
|
|
33
|
-
// builder coerces timestamptz values — see
|
|
32
|
+
// builder coerces timestamptz values — see db/query.ts prepareValue);
|
|
34
33
|
// every other whitelisted column is a plain text column. Keyed by the
|
|
35
34
|
// PHYSICAL column, not the display alias — row only has the DB field names
|
|
36
35
|
// (e.g. notificationType), never the alias (e.g. "type").
|
|
@@ -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";
|
package/dist/delivery/index.d.ts
CHANGED
|
@@ -2,7 +2,7 @@ export { sanitizePdfAttachmentFileName } from "../shared/index.js";
|
|
|
2
2
|
export { hashUnsubscribeAddress } from "./address-opt-out.js";
|
|
3
3
|
export { DELIVERY_ENVELOPE_DATA_KEYS, type EmailAttachmentRef, emailAttachmentRefSchema, type FileAttachmentRef, fileAttachmentRefSchema, type InlineAttachmentRef, inlineAttachmentRefSchema, MAX_EMAIL_ATTACHMENTS, MAX_RENDERED_PDF_VARIABLES_BYTES, type RenderedPdfAttachmentRef, renderedPdfAttachmentRefSchema, toAttachmentRef, withoutDeliveryEnvelopeKeys, } from "./attachment-ref.js";
|
|
4
4
|
export { type AttemptLogRetentionOptions, DEFAULT_ATTEMPT_LOG_RETENTION_DAYS, runAttemptLogRetention, } from "./attempt-log-retention.js";
|
|
5
|
-
export { createEmailAttachmentReader, createTemplateMailRenderer } from "./channel-context.js";
|
|
5
|
+
export { buildChannelContext, createEmailAttachmentReader, createTemplateMailRenderer, } from "./channel-context.js";
|
|
6
6
|
export { type ChatWebhookChannelOptions, chatMessageText, createChatWebhookChannel, resolveChatWebhookTarget, toChannelResult, } from "./chat-webhook-channel.js";
|
|
7
7
|
export { type ChatSendFailureCode, type ChatSendResult, type ChatWebhookRequest, type ChatWebhookResponse, type ChatWebhookTarget, chatConnectionNameSchema, chatWebhookUrlSchema, checkChatWebhookTarget, DEFAULT_CHAT_TIMEOUT_MS, postChatWebhook, truncateChars, } from "./chat-webhook-sender.js";
|
|
8
8
|
export type { DeliveryStatusValue } from "./constants.js";
|
|
@@ -10,10 +10,11 @@ export { DELIVERY_ATTEMPT_ADDRESS_ERASED_EVENT, DELIVERY_CHANNEL_EXTENSION, DELI
|
|
|
10
10
|
export { collectChannels, createDeliveryService, type DeliveryServiceOptions, type KillSwitchResolver, type RateLimitConfig, } from "./delivery-service.js";
|
|
11
11
|
export { createDeliveryFeature, type DeliveryFeatureOptions } from "./feature.js";
|
|
12
12
|
export { type UnsubscribeAddressPayload, unsubscribeAddressPayloadSchema, } from "./handlers/unsubscribe-address.write.js";
|
|
13
|
+
export { currentNotificationType } from "./notification-type-aliases.js";
|
|
13
14
|
export { createDeliveryNotifyFactory } from "./notify-factory.js";
|
|
14
15
|
export { deliveryAttemptsTable, notificationAddressOptOutEntity, notificationAddressOptOutsTable, notificationPreferenceEntity, notificationPreferencesTable, } from "./tables.js";
|
|
15
16
|
export { type CreateDeliveryTestContextOptions, createDeliveryTestContext } from "./testing.js";
|
|
16
17
|
export { type AttachmentOrigin, type ChannelContext, type ChannelMessage, type ChannelResult, type DeliveryChannel, type DeliveryChannelMode, type DeliveryChannelPlugin, type DeliveryLogEntry, type DeliveryService, isDeliveryChannelPlugin, type RenderedMessage, } from "./types.js";
|
|
17
|
-
export { type AddressUnsubscribeTokenPayload, createUnsubscribeRoutes, DELIVERY_UNSUBSCRIBE_ONE_CLICK_HEADER_VALUE, signAddressUnsubscribeToken, signUnsubscribeToken, type UnsubscribeRouteOptions, type UnsubscribeTokenPayload, } from "./unsubscribe.js";
|
|
18
|
+
export { type AddressUnsubscribeTokenPayload, createUnsubscribeRoutes, DELIVERY_UNSUBSCRIBE_ONE_CLICK_HEADER_VALUE, renderUnsubscribeResultPage, signAddressUnsubscribeToken, signUnsubscribeToken, type UnsubscribeResultPage, type UnsubscribeRouteOptions, type UnsubscribeTokenPayload, } from "./unsubscribe.js";
|
|
18
19
|
export type { UnsubscribeLinkConfig } from "./unsubscribe-link.js";
|
|
19
20
|
export { UNSUBSCRIBE_PAGE_EN } from "./unsubscribe-page-strings.js";
|
package/dist/delivery/index.js
CHANGED
|
@@ -2,16 +2,17 @@ export { sanitizePdfAttachmentFileName } from "../shared/index.js";
|
|
|
2
2
|
export { hashUnsubscribeAddress } from "./address-opt-out.js";
|
|
3
3
|
export { DELIVERY_ENVELOPE_DATA_KEYS, emailAttachmentRefSchema, fileAttachmentRefSchema, inlineAttachmentRefSchema, MAX_EMAIL_ATTACHMENTS, MAX_RENDERED_PDF_VARIABLES_BYTES, renderedPdfAttachmentRefSchema, toAttachmentRef, withoutDeliveryEnvelopeKeys, } from "./attachment-ref.js";
|
|
4
4
|
export { DEFAULT_ATTEMPT_LOG_RETENTION_DAYS, runAttemptLogRetention, } from "./attempt-log-retention.js";
|
|
5
|
-
export { createEmailAttachmentReader, createTemplateMailRenderer } from "./channel-context.js";
|
|
5
|
+
export { buildChannelContext, createEmailAttachmentReader, createTemplateMailRenderer, } from "./channel-context.js";
|
|
6
6
|
export { chatMessageText, createChatWebhookChannel, resolveChatWebhookTarget, toChannelResult, } from "./chat-webhook-channel.js";
|
|
7
7
|
export { chatConnectionNameSchema, chatWebhookUrlSchema, checkChatWebhookTarget, DEFAULT_CHAT_TIMEOUT_MS, postChatWebhook, truncateChars, } from "./chat-webhook-sender.js";
|
|
8
8
|
export { DELIVERY_ATTEMPT_ADDRESS_ERASED_EVENT, DELIVERY_CHANNEL_EXTENSION, DELIVERY_FEATURE, DELIVERY_LOG_SCREEN_ID, DELIVERY_UNSUBSCRIBE_PATH, DeliveryErrors, DeliveryHandlers, DeliveryJobs, DeliveryQueries, DeliveryStatus, EMAIL_CHANNEL_NAME, } from "./constants.js";
|
|
9
9
|
export { collectChannels, createDeliveryService, } from "./delivery-service.js";
|
|
10
10
|
export { createDeliveryFeature } from "./feature.js";
|
|
11
11
|
export { unsubscribeAddressPayloadSchema, } from "./handlers/unsubscribe-address.write.js";
|
|
12
|
+
export { currentNotificationType } from "./notification-type-aliases.js";
|
|
12
13
|
export { createDeliveryNotifyFactory } from "./notify-factory.js";
|
|
13
14
|
export { deliveryAttemptsTable, notificationAddressOptOutEntity, notificationAddressOptOutsTable, notificationPreferenceEntity, notificationPreferencesTable, } from "./tables.js";
|
|
14
15
|
export { createDeliveryTestContext } from "./testing.js";
|
|
15
16
|
export { isDeliveryChannelPlugin, } from "./types.js";
|
|
16
|
-
export { createUnsubscribeRoutes, DELIVERY_UNSUBSCRIBE_ONE_CLICK_HEADER_VALUE, signAddressUnsubscribeToken, signUnsubscribeToken, } from "./unsubscribe.js";
|
|
17
|
+
export { createUnsubscribeRoutes, DELIVERY_UNSUBSCRIBE_ONE_CLICK_HEADER_VALUE, renderUnsubscribeResultPage, signAddressUnsubscribeToken, signUnsubscribeToken, } from "./unsubscribe.js";
|
|
17
18
|
export { UNSUBSCRIBE_PAGE_EN } from "./unsubscribe-page-strings.js";
|
|
@@ -34,4 +34,17 @@ export declare function signUnsubscribeToken(payload: UnsubscribeTokenPayload, s
|
|
|
34
34
|
*/
|
|
35
35
|
export declare function signAddressUnsubscribeToken(payload: AddressUnsubscribeTokenPayload, secret: string): Promise<string>;
|
|
36
36
|
export declare const DELIVERY_UNSUBSCRIBE_ONE_CLICK_HEADER_VALUE: "List-Unsubscribe=One-Click";
|
|
37
|
+
export type UnsubscribeResultPage = {
|
|
38
|
+
readonly kind: "unsubscribed";
|
|
39
|
+
readonly resubscribeToken?: string;
|
|
40
|
+
} | {
|
|
41
|
+
readonly kind: "resubscribed";
|
|
42
|
+
} | {
|
|
43
|
+
readonly kind: "failed";
|
|
44
|
+
};
|
|
45
|
+
/** The plain result page the unsubscribe routes answer with, for apps serving their own
|
|
46
|
+
* (e.g. legacy) unsubscribe links. Texts come from the mail translations of `locale`. */
|
|
47
|
+
export declare function renderUnsubscribeResultPage(page: UnsubscribeResultPage & {
|
|
48
|
+
readonly locale?: string | undefined;
|
|
49
|
+
}): string;
|
|
37
50
|
export declare function createUnsubscribeRoutes(options: UnsubscribeRouteOptions): readonly ExtraRouteDefinition[];
|
|
@@ -272,23 +272,30 @@ function confirmationPage(token, locale) {
|
|
|
272
272
|
<button type="submit">${escapeHtmlAttr(mailT(lang, "mail.unsubscribe.confirmButton"))}</button>
|
|
273
273
|
</form>`);
|
|
274
274
|
}
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
275
|
+
/** The plain result page the unsubscribe routes answer with, for apps serving their own
|
|
276
|
+
* (e.g. legacy) unsubscribe links. Texts come from the mail translations of `locale`. */
|
|
277
|
+
export function renderUnsubscribeResultPage(page) {
|
|
278
|
+
const lang = pageLocale(page.locale);
|
|
279
|
+
switch (page.kind) {
|
|
280
|
+
case "unsubscribed": {
|
|
281
|
+
const undoForm = page.resubscribeToken === undefined
|
|
282
|
+
? ""
|
|
283
|
+
: `
|
|
278
284
|
<form method="post" action="${DELIVERY_RESUBSCRIBE_PATH}">
|
|
279
|
-
<input type="hidden" name="token" value="${escapeHtmlAttr(resubscribeToken)}">
|
|
285
|
+
<input type="hidden" name="token" value="${escapeHtmlAttr(page.resubscribeToken)}">
|
|
280
286
|
<button type="submit">${escapeHtmlAttr(mailT(lang, "mail.unsubscribe.undoButton"))}</button>
|
|
281
|
-
</form
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
287
|
+
</form>`;
|
|
288
|
+
return htmlPage(lang, mailT(lang, "mail.unsubscribe.doneTitle"), `<p>${escapeHtmlAttr(mailT(lang, "mail.unsubscribe.doneMessage"))}</p>${undoForm}`);
|
|
289
|
+
}
|
|
290
|
+
case "resubscribed":
|
|
291
|
+
return htmlPage(lang, mailT(lang, "mail.unsubscribe.resubscribedTitle"), `<p>${escapeHtmlAttr(mailT(lang, "mail.unsubscribe.resubscribedMessage"))}</p>`);
|
|
292
|
+
case "failed": {
|
|
293
|
+
const message = mailT(lang, "mail.unsubscribe.failed");
|
|
294
|
+
return htmlPage(lang, message, `<p>${escapeHtmlAttr(message)}</p>`);
|
|
295
|
+
}
|
|
296
|
+
}
|
|
291
297
|
}
|
|
298
|
+
const NO_STORE_HEADERS = { "Cache-Control": "no-store" };
|
|
292
299
|
function isFormRequest(contentType) {
|
|
293
300
|
return contentType?.includes("application/x-www-form-urlencoded") === true;
|
|
294
301
|
}
|
|
@@ -350,9 +357,13 @@ export function createUnsubscribeRoutes(options) {
|
|
|
350
357
|
// (e.g. events-table missing, DB down) behind a misleading 400.
|
|
351
358
|
const succeeded = await dispatchUnsubscribeWrite(verified, deps);
|
|
352
359
|
if (!succeeded) {
|
|
353
|
-
return c.html(
|
|
360
|
+
return c.html(renderUnsubscribeResultPage({ kind: "failed", locale: verified.locale }), 500, NO_STORE_HEADERS);
|
|
354
361
|
}
|
|
355
|
-
return c.html(
|
|
362
|
+
return c.html(renderUnsubscribeResultPage({
|
|
363
|
+
kind: "unsubscribed",
|
|
364
|
+
resubscribeToken: await signResubscribeToken(verified, encodedSecret),
|
|
365
|
+
locale: verified.locale,
|
|
366
|
+
}), 200, NO_STORE_HEADERS);
|
|
356
367
|
},
|
|
357
368
|
});
|
|
358
369
|
// Undo direction: token only from the JSON/form body, never the query
|
|
@@ -368,22 +379,16 @@ export function createUnsubscribeRoutes(options) {
|
|
|
368
379
|
// The undo button on the unsubscribed page posts a form; a browser should land on a page.
|
|
369
380
|
if (isFormRequest(c.req.header("content-type"))) {
|
|
370
381
|
return outcome === "success"
|
|
371
|
-
? c.html(
|
|
372
|
-
: c.html(
|
|
373
|
-
"Cache-Control": "no-store",
|
|
374
|
-
});
|
|
382
|
+
? c.html(renderUnsubscribeResultPage({ kind: "resubscribed", locale: verified.locale }), 200, NO_STORE_HEADERS)
|
|
383
|
+
: c.html(renderUnsubscribeResultPage({ kind: "failed", locale: verified.locale }), outcome === "limit_reached" ? 409 : 500, NO_STORE_HEADERS);
|
|
375
384
|
}
|
|
376
385
|
if (outcome === "limit_reached") {
|
|
377
|
-
return c.json({ error: { code: DeliveryErrors.resubscribeLimitReached } }, 409,
|
|
378
|
-
"Cache-Control": "no-store",
|
|
379
|
-
});
|
|
386
|
+
return c.json({ error: { code: DeliveryErrors.resubscribeLimitReached } }, 409, NO_STORE_HEADERS);
|
|
380
387
|
}
|
|
381
388
|
if (outcome === "failed") {
|
|
382
|
-
return c.json({ error: { code: "resubscribe_failed" } }, 500,
|
|
383
|
-
"Cache-Control": "no-store",
|
|
384
|
-
});
|
|
389
|
+
return c.json({ error: { code: "resubscribe_failed" } }, 500, NO_STORE_HEADERS);
|
|
385
390
|
}
|
|
386
|
-
return c.json({ isSuccess: true }, 200,
|
|
391
|
+
return c.json({ isSuccess: true }, 200, NO_STORE_HEADERS);
|
|
387
392
|
},
|
|
388
393
|
});
|
|
389
394
|
return [confirmRoute, writeRoute, resubscribeRoute];
|
|
@@ -12,8 +12,7 @@
|
|
|
12
12
|
// loser's create() comes back as a `version_conflict` failure; re-lookup and
|
|
13
13
|
// fall through to update, or no-op if the winner already set the target
|
|
14
14
|
// `enabled` state.
|
|
15
|
-
import { fetchOne } from "@cosmicdrift/kumiko-framework/
|
|
16
|
-
import { createEventStoreExecutor } from "@cosmicdrift/kumiko-framework/db";
|
|
15
|
+
import { createEventStoreExecutor, fetchOne, } from "@cosmicdrift/kumiko-framework/db";
|
|
17
16
|
import { generateDeterministicId } from "@cosmicdrift/kumiko-framework/utils";
|
|
18
17
|
import { notificationPreferenceEntity, notificationPreferencesTable } from "./tables.js";
|
|
19
18
|
const executor = createEventStoreExecutor(notificationPreferencesTable, notificationPreferenceEntity, { entityName: "notification-preference" });
|
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
export declare function createDerivativesSharpFeature(): import("@cosmicdrift/kumiko-types/feature").FeatureDefinition & {
|
|
2
|
+
readonly exports: void;
|
|
3
|
+
};
|
|
4
|
+
/** @deprecated Use createDerivativesSharpFeature(). Run `bun kumiko-upgrade --apply`. Removed in the first release after 2026-10-29. */
|
|
1
5
|
export declare const derivativesSharpFeature: import("@cosmicdrift/kumiko-types/feature").FeatureDefinition & {
|
|
2
6
|
readonly exports: void;
|
|
3
7
|
};
|
|
@@ -2,14 +2,18 @@
|
|
|
2
2
|
import { defineFeature, EXT_DERIVATIVE_RENDERER } from "@cosmicdrift/kumiko-framework/engine";
|
|
3
3
|
import { renderImage } from "./render.js";
|
|
4
4
|
const FEATURE_NAME = "derivatives-sharp";
|
|
5
|
-
export
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
5
|
+
export function createDerivativesSharpFeature() {
|
|
6
|
+
return defineFeature(FEATURE_NAME, (r) => {
|
|
7
|
+
r.describe("Registers an `image/*` renderer against the `derivativeRenderer` extension point declared by `file-derivatives`, backed by sharp. Supports resize/fit (cover, inside, contain), format conversion with quality (webp, avif, jpeg), whole-image blur, and blurring individual regions burned in before resize (see BlurRegion). EXIF orientation is applied and then normalized away, and all other EXIF (including GPS) is dropped. Server-only: sharp is a native binding — never import this from client code.");
|
|
8
|
+
r.uiHints({
|
|
9
|
+
displayLabel: "File Derivatives · Sharp",
|
|
10
|
+
category: "storage",
|
|
11
|
+
recommended: false,
|
|
12
|
+
});
|
|
13
|
+
r.requires({ features: ["file-derivatives"] });
|
|
14
|
+
const plugin = { render: renderImage };
|
|
15
|
+
r.useExtension(EXT_DERIVATIVE_RENDERER, "image/*", plugin);
|
|
16
|
+
}, { dedupeOptions: {} });
|
|
17
|
+
}
|
|
18
|
+
/** @deprecated Use createDerivativesSharpFeature(). Run `bun kumiko-upgrade --apply`. Removed in the first release after 2026-10-29. */
|
|
19
|
+
export const derivativesSharpFeature = createDerivativesSharpFeature();
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export { derivativesSharpFeature } from "./feature.js";
|
|
1
|
+
export { createDerivativesSharpFeature, derivativesSharpFeature } from "./feature.js";
|
|
2
2
|
export { imageMetadata } from "./render.js";
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export { derivativesSharpFeature } from "./feature.js";
|
|
1
|
+
export { createDerivativesSharpFeature, derivativesSharpFeature } from "./feature.js";
|
|
2
2
|
export { imageMetadata } from "./render.js";
|
|
@@ -1,3 +1,10 @@
|
|
|
1
|
+
export declare function createDocumentIngestFoundationFeature(): import("@cosmicdrift/kumiko-types/feature").FeatureDefinition & {
|
|
2
|
+
readonly exports: {
|
|
3
|
+
ocrLanguageConfigKey: import("@cosmicdrift/kumiko-types/config").ConfigKeyHandle<"text">;
|
|
4
|
+
maxPagesPerFileConfigKey: import("@cosmicdrift/kumiko-types/config").ConfigKeyHandle<"number">;
|
|
5
|
+
};
|
|
6
|
+
};
|
|
7
|
+
/** @deprecated Use createDocumentIngestFoundationFeature(). Run `bun kumiko-upgrade --apply`. Removed in the first release after 2026-10-29. */
|
|
1
8
|
export declare const documentIngestFoundationFeature: import("@cosmicdrift/kumiko-types/feature").FeatureDefinition & {
|
|
2
9
|
readonly exports: {
|
|
3
10
|
ocrLanguageConfigKey: import("@cosmicdrift/kumiko-types/config").ConfigKeyHandle<"text">;
|
|
@@ -81,95 +81,99 @@ async function requestIngestForFileRef(event, fileRef, ctx) {
|
|
|
81
81
|
},
|
|
82
82
|
});
|
|
83
83
|
}
|
|
84
|
-
export
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
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
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
84
|
+
export function createDocumentIngestFoundationFeature() {
|
|
85
|
+
return defineFeature(FEATURE_NAME, (r) => {
|
|
86
|
+
r.describe("Shared PDF/Scan/Image → normalized-text ingest primitive. Owns the `documentExtract` entity (fileRefId, storageKey, per-page text + metadata) as an implicit entity-projection, the per-tenant `ocrLanguage`/`maxPagesPerFile` config keys, the `documentIngestProvider` extension-point providers register accepted mimeTypes/size caps under, and a fileRef.created/fileRef.restored trigger that resolves the provider for a file's mimeType and requests ingest via `documentIngest.requested` tagged with the winning provider — a delete→restore round-trip re-requests ingest the same way a fresh upload does. An orphan-extract guard forgets any `documentExtract` whose fileRef is no longer live at processing time, regardless of how a provider's own write races the fileRef's delete/forget — providers should write through `writeDocumentExtractForLiveFileRef`.");
|
|
87
|
+
r.uiHints({
|
|
88
|
+
displayLabel: "Document Ingest Foundation",
|
|
89
|
+
category: "storage",
|
|
90
|
+
recommended: false,
|
|
91
|
+
});
|
|
92
|
+
// tenant-lifecycle hosts EXT_TENANT_DATA — documentExtract.pages is
|
|
93
|
+
// tenant-subject ciphertext and needs the destroy hook below.
|
|
94
|
+
r.requires({ features: ["config", "tenant-lifecycle"] });
|
|
95
|
+
r.entity({ name: "documentExtract", ...documentExtractEntity });
|
|
96
|
+
r.useExtension(EXT_TENANT_DATA, "documentExtract", {
|
|
97
|
+
destroy: documentExtractTenantDestroyHook,
|
|
98
|
+
});
|
|
99
|
+
// No onRegister: resolution happens at boot and per-apply, not on register.
|
|
100
|
+
r.extendsRegistrar(EXT_DOCUMENT_INGEST_PROVIDER, {});
|
|
101
|
+
r.bootCheck(({ features }) => validateDocumentIngestProviderWiring(features));
|
|
102
|
+
const ocrLanguageConfigKey = r.config("ocrLanguage", createTenantConfig("text", {
|
|
103
|
+
default: "deu+eng",
|
|
104
|
+
// Tesseract -l argument syntax: one or more 3-letter language codes
|
|
105
|
+
// joined by "+" (e.g. "deu+eng"). Unconstrained free text here would
|
|
106
|
+
// reach the OCR provider call unvalidated (#1501).
|
|
107
|
+
pattern: { regex: "^[a-z]{3}(\\+[a-z]{3})*$" },
|
|
108
|
+
write: access.roles("TenantAdmin", "SystemAdmin"),
|
|
109
|
+
read: access.roles("TenantAdmin", "SystemAdmin", "User"),
|
|
110
|
+
}));
|
|
111
|
+
const maxPagesPerFileConfigKey = r.config("maxPagesPerFile", createTenantConfig("number", {
|
|
112
|
+
default: 50,
|
|
113
|
+
// Without a bound a tenant could set 0 (silently kills ingest) or an
|
|
114
|
+
// unbounded value (OCR worker runs per-file for as long as the PDF
|
|
115
|
+
// has pages — job-queue starvation) (#1501).
|
|
116
|
+
bounds: { min: 1, max: 500 },
|
|
117
|
+
write: access.roles("TenantAdmin", "SystemAdmin"),
|
|
118
|
+
read: access.roles("TenantAdmin", "SystemAdmin", "User"),
|
|
119
|
+
}));
|
|
120
|
+
// "fileName" can carry a real person's name, but it reaches both events already as the
|
|
121
|
+
// fileRef's own self-subject ciphertext (copied from fileRef.created/restored), which
|
|
122
|
+
// append() passes through; shredding the fileRef's key shreds it here too.
|
|
123
|
+
r.defineEvent(DOCUMENT_INGEST_REQUESTED_EVENT_SHORT, documentIngestRequestedPayloadSchema, {
|
|
124
|
+
piiFields: "none",
|
|
125
|
+
version: 2,
|
|
126
|
+
migrations: [
|
|
127
|
+
{
|
|
128
|
+
fromVersion: 1,
|
|
129
|
+
toVersion: 2,
|
|
130
|
+
// The sentinel can never match a real where.provider filter (providers
|
|
131
|
+
// may not register under it) — an upcast v1 row reads as unclaimed
|
|
132
|
+
// instead of silently misrouting to a currently mounted provider.
|
|
133
|
+
transform: { default: { provider: UNCLAIMED_PROVIDER_SENTINEL } },
|
|
134
|
+
},
|
|
135
|
+
],
|
|
136
|
+
});
|
|
137
|
+
r.defineEvent(DOCUMENT_INGEST_SKIPPED_EVENT_SHORT, documentIngestSkippedPayloadSchema, {
|
|
138
|
+
piiFields: "none",
|
|
139
|
+
});
|
|
140
|
+
r.multiStreamProjection({
|
|
141
|
+
name: "request-ingest",
|
|
142
|
+
apply: {
|
|
143
|
+
[FILE_REF_CREATED]: async (event, _tx, ctx) => {
|
|
144
|
+
// entity-event payloads are generic Record<string, unknown> — parse
|
|
145
|
+
// at the MSP boundary (same pattern as storage-tracking's readNumber).
|
|
146
|
+
const parsed = fileRefIngestFieldsSchema.safeParse(event.payload);
|
|
147
|
+
// skip: malformed / incomplete payload — don't poison the consumer
|
|
148
|
+
if (!parsed.success)
|
|
149
|
+
return;
|
|
150
|
+
await requestIngestForFileRef(event, parsed.data, ctx);
|
|
151
|
+
},
|
|
152
|
+
[FILE_REF_RESTORED]: async (event, _tx, ctx) => {
|
|
153
|
+
// fileRef.restored carries { previous }, the pre-restore soft-deleted
|
|
154
|
+
// row (event-store-executor-write.ts restore()) — same field shape
|
|
155
|
+
// as fileRef.created's payload.
|
|
156
|
+
const parsed = fileRefIngestFieldsSchema.safeParse(event.payload["previous"]);
|
|
157
|
+
// skip: malformed / incomplete previous snapshot — don't poison the consumer
|
|
158
|
+
if (!parsed.success)
|
|
159
|
+
return;
|
|
160
|
+
await requestIngestForFileRef(event, parsed.data, ctx);
|
|
161
|
+
},
|
|
150
162
|
},
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
163
|
+
});
|
|
164
|
+
// fileRef delete/forget cleanup: documentExtract rows are derived data —
|
|
165
|
+
// once the source file is gone (soft-deleted or Art.17-forgotten), the
|
|
166
|
+
// extracted text has no reason to survive it either.
|
|
167
|
+
r.multiStreamProjection({
|
|
168
|
+
name: "forget-extract-with-file-ref",
|
|
169
|
+
apply: {
|
|
170
|
+
[FILE_REF_DELETED]: forgetExtractOnFileRefDeletedHook,
|
|
171
|
+
[FILE_REF_FORGOTTEN]: forgetExtractOnFileRefForgottenHook,
|
|
172
|
+
[DOCUMENT_EXTRACT_CREATED]: forgetOrphanedDocumentExtractHook,
|
|
160
173
|
},
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
name: "forget-extract-with-file-ref",
|
|
168
|
-
apply: {
|
|
169
|
-
[FILE_REF_DELETED]: forgetExtractOnFileRefDeletedHook,
|
|
170
|
-
[FILE_REF_FORGOTTEN]: forgetExtractOnFileRefForgottenHook,
|
|
171
|
-
[DOCUMENT_EXTRACT_CREATED]: forgetOrphanedDocumentExtractHook,
|
|
172
|
-
},
|
|
173
|
-
});
|
|
174
|
-
return { ocrLanguageConfigKey, maxPagesPerFileConfigKey };
|
|
175
|
-
});
|
|
174
|
+
});
|
|
175
|
+
return { ocrLanguageConfigKey, maxPagesPerFileConfigKey };
|
|
176
|
+
}, { dedupeOptions: {} });
|
|
177
|
+
}
|
|
178
|
+
/** @deprecated Use createDocumentIngestFoundationFeature(). Run `bun kumiko-upgrade --apply`. Removed in the first release after 2026-10-29. */
|
|
179
|
+
export const documentIngestFoundationFeature = createDocumentIngestFoundationFeature();
|
|
@@ -48,7 +48,7 @@ export const forgetOrphanedDocumentExtractHook = async (event, tx) => {
|
|
|
48
48
|
// skip: malformed payload — don't poison the consumer
|
|
49
49
|
if (!parsed.success)
|
|
50
50
|
return;
|
|
51
|
-
const tenantDb = createTenantDb(tx, event.tenantId);
|
|
51
|
+
const tenantDb = createTenantDb({ db: tx, tenantId: event.tenantId });
|
|
52
52
|
// skip: fileRef still live — the extract is legitimate
|
|
53
53
|
if (await isFileRefLive(tenantDb, parsed.data.fileRefId))
|
|
54
54
|
return;
|
|
@@ -60,7 +60,7 @@ export const forgetOrphanedDocumentExtractHook = async (event, tx) => {
|
|
|
60
60
|
throw new Error(`document-ingest-foundation: failed to forget orphaned documentExtract ${event.aggregateId}: ${result.error.message}`);
|
|
61
61
|
};
|
|
62
62
|
export const forgetExtractOnFileRefDeletedHook = async (event, tx) => {
|
|
63
|
-
const tenantDb = createTenantDb(tx, event.tenantId);
|
|
63
|
+
const tenantDb = createTenantDb({ db: tx, tenantId: event.tenantId });
|
|
64
64
|
// skip: fileRef is live again — request-ingest and this consumer poll via
|
|
65
65
|
// separate cursors, so a delete→restore round-trip can finish before this
|
|
66
66
|
// consumer sees fileRef.deleted; forgetting would drop the live file's extract.
|
|
@@ -69,6 +69,6 @@ export const forgetExtractOnFileRefDeletedHook = async (event, tx) => {
|
|
|
69
69
|
await forgetExtractsForFileRef(event, tenantDb);
|
|
70
70
|
};
|
|
71
71
|
export const forgetExtractOnFileRefForgottenHook = async (event, tx) => {
|
|
72
|
-
const tenantDb = createTenantDb(tx, event.tenantId);
|
|
72
|
+
const tenantDb = createTenantDb({ db: tx, tenantId: event.tenantId });
|
|
73
73
|
await forgetExtractsForFileRef(event, tenantDb);
|
|
74
74
|
};
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export { documentIngestAggregateId } from "./aggregate-id.js";
|
|
2
2
|
export { type DocumentExtractMeta, documentExtractEntity, documentExtractsTable, type IngestPage, } from "./entity.js";
|
|
3
3
|
export { DOCUMENT_INGEST_AGGREGATE_TYPE, DOCUMENT_INGEST_REQUESTED_EVENT_QN, DOCUMENT_INGEST_REQUESTED_EVENT_SHORT, DOCUMENT_INGEST_SKIPPED_EVENT_QN, DOCUMENT_INGEST_SKIPPED_EVENT_SHORT, type DocumentIngestRequestedPayload, type DocumentIngestSkippedPayload, documentIngestRequestedPayloadSchema, documentIngestSkippedPayloadSchema, } from "./events.js";
|
|
4
|
-
export { documentIngestFoundationFeature } from "./feature.js";
|
|
4
|
+
export { createDocumentIngestFoundationFeature, documentIngestFoundationFeature, } from "./feature.js";
|
|
5
5
|
export { readIngestPages, writeIngestPages } from "./pages.js";
|
|
6
6
|
export { type DocumentIngestProviderOptions, documentIngestProviderOptionsSchema, documentIngestProviderTrigger, EXT_DOCUMENT_INGEST_PROVIDER, listIngestibleMimeTypes, type ResolvedDocumentIngestProvider, resolveDocumentIngestProviders, } from "./providers.js";
|
|
7
7
|
export { type DocumentExtractWriteResult, writeDocumentExtractForLiveFileRef, } from "./write-document-extract.js";
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
export { documentIngestAggregateId } from "./aggregate-id.js";
|
|
3
3
|
export { documentExtractEntity, documentExtractsTable, } from "./entity.js";
|
|
4
4
|
export { DOCUMENT_INGEST_AGGREGATE_TYPE, DOCUMENT_INGEST_REQUESTED_EVENT_QN, DOCUMENT_INGEST_REQUESTED_EVENT_SHORT, DOCUMENT_INGEST_SKIPPED_EVENT_QN, DOCUMENT_INGEST_SKIPPED_EVENT_SHORT, documentIngestRequestedPayloadSchema, documentIngestSkippedPayloadSchema, } from "./events.js";
|
|
5
|
-
export { documentIngestFoundationFeature } from "./feature.js";
|
|
5
|
+
export { createDocumentIngestFoundationFeature, documentIngestFoundationFeature, } from "./feature.js";
|
|
6
6
|
export { readIngestPages, writeIngestPages } from "./pages.js";
|
|
7
7
|
export { documentIngestProviderOptionsSchema, documentIngestProviderTrigger, EXT_DOCUMENT_INGEST_PROVIDER, listIngestibleMimeTypes, resolveDocumentIngestProviders, } from "./providers.js";
|
|
8
8
|
export { writeDocumentExtractForLiveFileRef, } from "./write-document-extract.js";
|
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import { requireEntityTableMeta } from "@cosmicdrift/kumiko-framework/
|
|
2
|
-
import { boolean, declareGlobalTenancy, instant, integer, table as pgTable, sql, text, } from "@cosmicdrift/kumiko-framework/db";
|
|
1
|
+
import { boolean, declareGlobalTenancy, instant, integer, table as pgTable, requireEntityTableMeta, sql, text, } from "@cosmicdrift/kumiko-framework/db";
|
|
3
2
|
// Global feature-toggle override state. One row per feature that has ever
|
|
4
3
|
// been explicitly flipped by an operator. Missing row = "no override,
|
|
5
4
|
// fall back to the feature's r.toggleable({ default }) value".
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { insertOne, selectMany } from "@cosmicdrift/kumiko-framework/
|
|
1
|
+
import { insertOne, selectMany } from "@cosmicdrift/kumiko-framework/db";
|
|
2
2
|
import { defineWriteHandler, SYSTEM_TENANT_ID } from "@cosmicdrift/kumiko-framework/engine";
|
|
3
3
|
import { UnprocessableError, VersionConflictError, writeFailure, } from "@cosmicdrift/kumiko-framework/errors";
|
|
4
4
|
import { Temporal } from "@cosmicdrift/kumiko-types/temporal";
|
|
@@ -109,10 +109,8 @@ export function createSetWriteHandler(getRuntime) {
|
|
|
109
109
|
// and filtering by payload.featureName is trivial at query time.
|
|
110
110
|
// This mirrors how `config` handles the same constraint for
|
|
111
111
|
// its config-changed events.
|
|
112
|
-
// unsafeAppendEvent
|
|
113
|
-
//
|
|
114
|
-
// path; bundled-features bleibt bei der unsafe-Variante. Schema-
|
|
115
|
-
// Validation läuft trotzdem via r.defineEvent("toggle-set", ...).
|
|
112
|
+
// unsafeAppendEvent: the strict appendEvent is bound to an app's own event map, which
|
|
113
|
+
// bundled-features cannot see. Schema validation still runs via r.defineEvent("toggle-set", ...).
|
|
116
114
|
await ctx.unsafeAppendEvent({
|
|
117
115
|
aggregateId: SYSTEM_TENANT_ID,
|
|
118
116
|
aggregateType: FEATURE_TOGGLE_AGGREGATE_TYPE,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { selectMany } from "@cosmicdrift/kumiko-framework/
|
|
1
|
+
import { selectMany } from "@cosmicdrift/kumiko-framework/db";
|
|
2
2
|
import { computeEffectiveFeatures, } from "@cosmicdrift/kumiko-framework/engine";
|
|
3
3
|
import { createFallbackLogger } from "@cosmicdrift/kumiko-framework/logging";
|
|
4
4
|
import { createDefaultCacheSyncBus, } from "@cosmicdrift/kumiko-framework/redis";
|
|
@@ -25,4 +25,5 @@ export type FileDerivativesOptions = {
|
|
|
25
25
|
readonly publicTenantResolution?: PublicVariantTenantResolution;
|
|
26
26
|
};
|
|
27
27
|
export declare function createFileDerivativesFeature(opts?: FileDerivativesOptions): FeatureDefinition;
|
|
28
|
+
/** @deprecated Use createFileDerivativesFeature(). Run `bun kumiko-upgrade --apply`. Removed in the first release after 2026-10-29. */
|
|
28
29
|
export declare const fileDerivativesFeature: FeatureDefinition;
|
|
@@ -161,6 +161,7 @@ export function createFileDerivativesFeature(opts = {}) {
|
|
|
161
161
|
},
|
|
162
162
|
});
|
|
163
163
|
}
|
|
164
|
-
});
|
|
164
|
+
}, Object.keys(opts).length === 0 ? { dedupeOptions: {} } : undefined);
|
|
165
165
|
}
|
|
166
|
+
/** @deprecated Use createFileDerivativesFeature(). Run `bun kumiko-upgrade --apply`. Removed in the first release after 2026-10-29. */
|
|
166
167
|
export const fileDerivativesFeature = createFileDerivativesFeature();
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { fetchOne } from "@cosmicdrift/kumiko-framework/
|
|
1
|
+
import { fetchOne } from "@cosmicdrift/kumiko-framework/db";
|
|
2
2
|
import { createAnonymousUser, defineQueryHandler, isUuid, } from "@cosmicdrift/kumiko-framework/engine";
|
|
3
3
|
import { fileRefsTable } from "@cosmicdrift/kumiko-framework/files";
|
|
4
4
|
import { isTenantServingPublicContent, tenantTable, } from "../../tenant/index.js";
|