@cosmicdrift/kumiko-samples 0.386.0 → 0.388.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/LICENSE +21 -57
- package/README.md +1 -1
- package/package.json +2 -2
- package/packages/bundled-features/package.json +9 -2
- package/packages/bundled-features/src/agent-tools/changes.json +6 -0
- package/packages/bundled-features/src/agent-tools/tool-catalog.ts +1 -1
- package/packages/bundled-features/src/agent-tools/tool-dispatch.ts +9 -9
- package/packages/bundled-features/src/audit/changes.json +12 -0
- package/packages/bundled-features/src/audit/handlers/audit-events.ts +1 -1
- package/packages/bundled-features/src/audit/screens.ts +1 -1
- package/packages/bundled-features/src/auth-email-password/auth-user-row.ts +10 -10
- package/packages/bundled-features/src/auth-email-password/feature.ts +6 -6
- package/packages/bundled-features/src/auth-email-password/handlers/change-password.write.ts +3 -3
- package/packages/bundled-features/src/auth-email-password/handlers/confirm-token-flow.ts +5 -5
- package/packages/bundled-features/src/auth-email-password/handlers/invite-accept-with-login.write.ts +1 -1
- package/packages/bundled-features/src/auth-email-password/handlers/invite-accept.write.ts +18 -18
- package/packages/bundled-features/src/auth-email-password/handlers/invite-create.write.ts +12 -12
- 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 +24 -26
- package/packages/bundled-features/src/auth-email-password/handlers/login.write.ts +1 -1
- package/packages/bundled-features/src/auth-email-password/handlers/self-registration-status.query.ts +1 -1
- package/packages/bundled-features/src/auth-email-password/handlers/signup-confirm.write.ts +34 -37
- package/packages/bundled-features/src/auth-email-password/handlers/signup-request.write.ts +6 -7
- package/packages/bundled-features/src/auth-email-password/handlers/switch-tenant-mfa-gate.write.ts +1 -5
- package/packages/bundled-features/src/auth-email-password/index.ts +5 -5
- package/packages/bundled-features/src/auth-email-password/lockout-store.ts +1 -1
- package/packages/bundled-features/src/auth-email-password/seeding.ts +63 -66
- package/packages/bundled-features/src/auth-email-password/signup-token-store.ts +8 -8
- package/packages/bundled-features/src/auth-email-password/testing.ts +3 -3
- package/packages/bundled-features/src/auth-email-password/web/auth-client.ts +57 -57
- package/packages/bundled-features/src/auth-email-password/web/auth-form-primitives.tsx +14 -14
- package/packages/bundled-features/src/auth-email-password/web/auth-gate.tsx +15 -16
- package/packages/bundled-features/src/auth-email-password/web/client-plugin.ts +11 -12
- package/packages/bundled-features/src/auth-email-password/web/confirm-account-unlock-screen.tsx +1 -1
- package/packages/bundled-features/src/auth-email-password/web/constants.ts +3 -3
- package/packages/bundled-features/src/auth-email-password/web/default-topbar-actions.tsx +14 -14
- package/packages/bundled-features/src/auth-email-password/web/forgot-password-screen.tsx +11 -11
- package/packages/bundled-features/src/auth-email-password/web/index.ts +5 -5
- package/packages/bundled-features/src/auth-email-password/web/invite-accept-screen.tsx +17 -17
- package/packages/bundled-features/src/auth-email-password/web/login-screen.tsx +3 -4
- package/packages/bundled-features/src/auth-email-password/web/reset-password-screen.tsx +16 -17
- package/packages/bundled-features/src/auth-email-password/web/session.tsx +34 -34
- package/packages/bundled-features/src/auth-email-password/web/signup-complete-screen.tsx +3 -3
- package/packages/bundled-features/src/auth-email-password/web/signup-screen.tsx +14 -14
- package/packages/bundled-features/src/auth-email-password/web/tenant-switcher.tsx +26 -27
- package/packages/bundled-features/src/auth-email-password/web/use-shell-user.ts +12 -13
- package/packages/bundled-features/src/auth-email-password/web/user-menu.tsx +17 -18
- package/packages/bundled-features/src/auth-email-password/web/verify-email-screen.tsx +11 -12
- package/packages/bundled-features/src/auth-foundation/boot-checks.ts +4 -4
- package/packages/bundled-features/src/auth-foundation/resolve-anonymous-access.ts +4 -4
- package/packages/bundled-features/src/auth-foundation/types.ts +2 -2
- package/packages/bundled-features/src/auth-mfa/changes.json +6 -0
- package/packages/bundled-features/src/auth-mfa/feature-exports.ts +2 -2
- package/packages/bundled-features/src/auth-mfa/feature.ts +6 -0
- package/packages/bundled-features/src/auth-mfa/handlers/disable.write.ts +3 -3
- package/packages/bundled-features/src/auth-mfa/handlers/enable-confirm-preauth.write.ts +4 -4
- package/packages/bundled-features/src/auth-mfa/handlers/enable-confirm.write.ts +2 -2
- 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 +1 -1
- package/packages/bundled-features/src/auth-mfa/handlers/regenerate-recovery.write.ts +5 -5
- package/packages/bundled-features/src/auth-mfa/handlers/verify.write.ts +1 -1
- package/packages/bundled-features/src/auth-mfa/token-secrets.ts +1 -1
- package/packages/bundled-features/src/auth-mfa/web/client-plugin.ts +2 -2
- package/packages/bundled-features/src/auth-mfa/web/i18n.ts +9 -4
- package/packages/bundled-features/src/auth-mfa/web/index.ts +4 -4
- package/packages/bundled-features/src/auth-mfa/web/mfa-client.ts +17 -16
- package/packages/bundled-features/src/auth-mfa/web/mfa-code-or-recovery-field.tsx +68 -0
- package/packages/bundled-features/src/auth-mfa/web/mfa-disable-dialog.tsx +23 -13
- package/packages/bundled-features/src/auth-mfa/web/mfa-regenerate-recovery-dialog.tsx +23 -13
- package/packages/bundled-features/src/auth-mfa/web/mfa-setup-preauth-screen.tsx +15 -6
- package/packages/bundled-features/src/auth-mfa/web/mfa-verify-screen.tsx +71 -24
- package/packages/bundled-features/src/billing-foundation/aggregate-id.ts +10 -11
- package/packages/bundled-features/src/billing-foundation/changes.json +55 -0
- package/packages/bundled-features/src/billing-foundation/checkout-core.ts +32 -42
- package/packages/bundled-features/src/billing-foundation/consumer-protection/confirmation-mail.ts +8 -2
- package/packages/bundled-features/src/billing-foundation/consumer-protection/consent-gate.ts +2 -0
- package/packages/bundled-features/src/billing-foundation/consumer-protection/issue-confirmation.ts +14 -3
- package/packages/bundled-features/src/billing-foundation/consumer-protection/mail-notifications.ts +3 -4
- package/packages/bundled-features/src/billing-foundation/consumer-protection/termination-receipt-limit.ts +12 -6
- package/packages/bundled-features/src/billing-foundation/consumer-protection/termination-record.ts +35 -19
- package/packages/bundled-features/src/billing-foundation/consumer-protection/termination-request.ts +12 -8
- package/packages/bundled-features/src/billing-foundation/consumer-protection/termination-revoke.ts +14 -8
- package/packages/bundled-features/src/billing-foundation/consumer-protection/termination-texts.ts +5 -0
- package/packages/bundled-features/src/billing-foundation/consumer-protection/withdrawal-period.ts +8 -10
- package/packages/bundled-features/src/billing-foundation/entities.ts +27 -27
- package/packages/bundled-features/src/billing-foundation/events.ts +23 -21
- package/packages/bundled-features/src/billing-foundation/feature.ts +45 -44
- package/packages/bundled-features/src/billing-foundation/get-subscription-for-tenant.ts +3 -5
- package/packages/bundled-features/src/billing-foundation/handlers/billing-plans.query.ts +71 -6
- package/packages/bundled-features/src/billing-foundation/handlers/create-checkout-session.write.ts +19 -17
- package/packages/bundled-features/src/billing-foundation/handlers/create-portal-session.write.ts +11 -12
- package/packages/bundled-features/src/billing-foundation/handlers/list-subscriptions.query.ts +4 -4
- package/packages/bundled-features/src/billing-foundation/handlers/plan-switch-preview.query.ts +65 -0
- package/packages/bundled-features/src/billing-foundation/handlers/process-event.write.ts +29 -22
- package/packages/bundled-features/src/billing-foundation/handlers/start-plan-checkout.write.ts +44 -8
- package/packages/bundled-features/src/billing-foundation/handlers/switch-plan.write.ts +72 -82
- package/packages/bundled-features/src/billing-foundation/handlers/sync-subscription.write.ts +14 -1
- package/packages/bundled-features/src/billing-foundation/i18n.ts +22 -0
- package/packages/bundled-features/src/billing-foundation/index.ts +2 -0
- package/packages/bundled-features/src/billing-foundation/plan-catalog.ts +62 -27
- package/packages/bundled-features/src/billing-foundation/plan-switch.ts +84 -0
- package/packages/bundled-features/src/billing-foundation/projection.ts +38 -37
- package/packages/bundled-features/src/billing-foundation/subscription-tier-sync.ts +3 -3
- package/packages/bundled-features/src/billing-foundation/tenant-destroy-hook.ts +7 -8
- package/packages/bundled-features/src/billing-foundation/types.ts +144 -66
- package/packages/bundled-features/src/billing-foundation/validate-options.ts +19 -0
- package/packages/bundled-features/src/billing-foundation/web/billing-plans-panel.tsx +123 -51
- package/packages/bundled-features/src/billing-foundation/web/checkout-consent-dialog.tsx +89 -42
- package/packages/bundled-features/src/billing-foundation/web/constants.ts +4 -0
- package/packages/bundled-features/src/billing-foundation/web/plan-price.ts +55 -0
- package/packages/bundled-features/src/billing-foundation/web/plan-switch-confirm-dialog.tsx +224 -0
- package/packages/bundled-features/src/billing-foundation/webhook-handler.ts +1 -1
- package/packages/bundled-features/src/cap-counter/aggregate-id.ts +8 -9
- package/packages/bundled-features/src/cap-counter/cap-reservation.ts +5 -1
- package/packages/bundled-features/src/cap-counter/changes.json +18 -0
- package/packages/bundled-features/src/cap-counter/constants.ts +11 -12
- package/packages/bundled-features/src/cap-counter/enforce-cap.ts +66 -68
- package/packages/bundled-features/src/cap-counter/entity.ts +29 -29
- package/packages/bundled-features/src/cap-counter/feature.ts +34 -35
- package/packages/bundled-features/src/cap-counter/handlers/increment-rolling.write.ts +31 -31
- package/packages/bundled-features/src/cap-counter/handlers/mark-soft-warned.write.ts +4 -5
- package/packages/bundled-features/src/cap-counter/stock-cap-guard.ts +9 -2
- package/packages/bundled-features/src/cap-counter/with-cap-enforcement.ts +3 -5
- package/packages/bundled-features/src/cap-overview/handlers/caps-usage.query.ts +1 -3
- package/packages/bundled-features/src/cap-overview/handlers/tenant-caps-list.query.ts +1 -1
- package/packages/bundled-features/src/channel-email/changes.json +6 -0
- package/packages/bundled-features/src/channel-email/feature.ts +1 -1
- package/packages/bundled-features/src/channel-email/smtp-transport.ts +34 -31
- package/packages/bundled-features/src/channel-in-app/handlers/unread-count.query.ts +3 -4
- package/packages/bundled-features/src/compliance-profiles/feature.ts +15 -16
- package/packages/bundled-features/src/compliance-profiles/handlers/for-tenant.query.ts +3 -6
- package/packages/bundled-features/src/compliance-profiles/handlers/list-profiles.query.ts +5 -5
- package/packages/bundled-features/src/compliance-profiles/handlers/needs-profile.query.ts +4 -4
- package/packages/bundled-features/src/compliance-profiles/handlers/set-profile.write.ts +13 -14
- package/packages/bundled-features/src/compliance-profiles/handlers/sub-processors.query.ts +9 -11
- package/packages/bundled-features/src/compliance-profiles/resolve-for-tenant.ts +7 -7
- package/packages/bundled-features/src/compliance-profiles/schema/profile-selection.ts +18 -23
- package/packages/bundled-features/src/compliance-profiles/seeding.ts +14 -15
- package/packages/bundled-features/src/compliance-profiles-ops/handlers/tenants-missing-profile.query.ts +3 -3
- package/packages/bundled-features/src/compliance-profiles-ops/index.ts +2 -2
- package/packages/bundled-features/src/config/changes.json +12 -0
- package/packages/bundled-features/src/config/handlers/cascade.query.ts +4 -2
- package/packages/bundled-features/src/config/handlers/readiness.query.ts +7 -7
- package/packages/bundled-features/src/config/handlers/reencrypt.job.ts +4 -5
- package/packages/bundled-features/src/config/handlers/set.write.ts +4 -4
- package/packages/bundled-features/src/config/handlers/values.query.ts +4 -2
- package/packages/bundled-features/src/config/resolver.ts +4 -4
- package/packages/bundled-features/src/config/table.ts +10 -10
- package/packages/bundled-features/src/config/write-helpers.ts +28 -9
- package/packages/bundled-features/src/crypto-shredding/changes.json +6 -0
- package/packages/bundled-features/src/crypto-shredding/handlers/forget-subject.write.ts +23 -21
- package/packages/bundled-features/src/custom-fields/aggregate-id.ts +16 -17
- package/packages/bundled-features/src/custom-fields/entity.ts +49 -47
- package/packages/bundled-features/src/custom-fields/events.ts +7 -8
- package/packages/bundled-features/src/custom-fields/feature.ts +70 -72
- package/packages/bundled-features/src/custom-fields/handlers/clear-custom-field.write.ts +9 -8
- package/packages/bundled-features/src/custom-fields/handlers/define-tenant-field.write.ts +28 -26
- package/packages/bundled-features/src/custom-fields/handlers/delete-system-field.write.ts +6 -7
- package/packages/bundled-features/src/custom-fields/handlers/delete-tenant-field.write.ts +12 -13
- package/packages/bundled-features/src/custom-fields/handlers/set-custom-field.write.ts +25 -28
- package/packages/bundled-features/src/custom-fields/handlers/update-tenant-field.write.ts +6 -6
- package/packages/bundled-features/src/custom-fields/lib/field-access.ts +2 -2
- package/packages/bundled-features/src/custom-fields/lib/parse-serialized-field.ts +1 -1
- package/packages/bundled-features/src/custom-fields/lib/quota.ts +1 -1
- package/packages/bundled-features/src/custom-fields/lib/value-schema.ts +10 -12
- package/packages/bundled-features/src/custom-fields/run-retention.ts +3 -3
- package/packages/bundled-features/src/custom-fields/schemas.ts +14 -14
- package/packages/bundled-features/src/custom-fields/web/client-plugin.tsx +2 -5
- package/packages/bundled-features/src/custom-fields/web/constants.ts +2 -2
- package/packages/bundled-features/src/custom-fields/web/custom-fields-form-section.tsx +26 -26
- package/packages/bundled-features/src/custom-fields/wire-for-entity.ts +65 -67
- package/packages/bundled-features/src/custom-fields/wire-user-data-rights.ts +3 -3
- package/packages/bundled-features/src/data-retention/_internal/parse-override.ts +2 -3
- package/packages/bundled-features/src/data-retention/feature.ts +11 -18
- package/packages/bundled-features/src/data-retention/handlers/policy-for.query.ts +6 -6
- package/packages/bundled-features/src/data-retention/keep-for.ts +17 -17
- package/packages/bundled-features/src/data-retention/override-schema.ts +13 -15
- package/packages/bundled-features/src/data-retention/presets.ts +18 -23
- package/packages/bundled-features/src/data-retention/resolve-for-tenant.ts +1 -1
- package/packages/bundled-features/src/data-retention/resolve-tenant-preset.ts +17 -16
- package/packages/bundled-features/src/data-retention/resolver.ts +33 -32
- package/packages/bundled-features/src/data-retention/run-retention-cleanup.ts +71 -58
- package/packages/bundled-features/src/data-retention/schema/tenant-retention-override.ts +13 -11
- package/packages/bundled-features/src/delivery/changes.json +30 -0
- package/packages/bundled-features/src/delivery/channel-context.ts +9 -3
- package/packages/bundled-features/src/delivery/db/queries/preferences.ts +2 -2
- package/packages/bundled-features/src/delivery/delivery-service.ts +35 -5
- package/packages/bundled-features/src/delivery/feature.ts +3 -3
- package/packages/bundled-features/src/delivery/handlers/log.query.ts +1 -1
- package/packages/bundled-features/src/delivery/handlers/preferences.query.ts +20 -1
- package/packages/bundled-features/src/delivery/handlers/unsubscribe-page-chrome.query.ts +1 -1
- package/packages/bundled-features/src/delivery/tables.ts +9 -8
- package/packages/bundled-features/src/delivery/unsubscribe-link.ts +3 -1
- package/packages/bundled-features/src/delivery/unsubscribe-page-strings.ts +1 -0
- package/packages/bundled-features/src/delivery/unsubscribe-page.ts +6 -0
- package/packages/bundled-features/src/delivery/unsubscribe.ts +40 -19
- package/packages/bundled-features/src/delivery/upsert-preference.ts +3 -4
- package/packages/bundled-features/src/document-ingest-foundation/entity.ts +5 -5
- package/packages/bundled-features/src/document-ingest-foundation/events.ts +2 -2
- package/packages/bundled-features/src/document-ingest-foundation/feature.ts +2 -2
- package/packages/bundled-features/src/document-ingest-foundation/pages.ts +1 -1
- package/packages/bundled-features/src/document-ingest-foundation/tenant-destroy-hook.ts +4 -4
- package/packages/bundled-features/src/feature-toggles/compose-tier-resolver.ts +1 -1
- package/packages/bundled-features/src/feature-toggles/feature.ts +1 -1
- package/packages/bundled-features/src/feature-toggles/global-feature-state-table.ts +1 -1
- package/packages/bundled-features/src/feature-toggles/handlers/registered.query.ts +5 -5
- package/packages/bundled-features/src/feature-toggles/toggle-runtime.ts +1 -1
- package/packages/bundled-features/src/feature-toggles/toggle-sync-signal.ts +1 -1
- package/packages/bundled-features/src/feature-toggles/web/constants.ts +1 -1
- package/packages/bundled-features/src/file-derivatives/feature.ts +2 -2
- package/packages/bundled-features/src/file-derivatives/handlers/public-variant.query.ts +1 -1
- package/packages/bundled-features/src/file-derivatives/presets.ts +1 -1
- package/packages/bundled-features/src/file-foundation/feature.ts +6 -6
- package/packages/bundled-features/src/file-foundation/index.ts +1 -0
- package/packages/bundled-features/src/file-foundation/read-file-ref-content.ts +7 -1
- package/packages/bundled-features/src/file-provider-inmemory/feature.ts +16 -16
- package/packages/bundled-features/src/file-provider-s3/env-named-feature.ts +0 -2
- package/packages/bundled-features/src/file-provider-s3/feature.ts +16 -16
- package/packages/bundled-features/src/file-provider-s3/lib/s3-provider.ts +6 -6
- package/packages/bundled-features/src/files/README.md +1 -1
- package/packages/bundled-features/src/files/feature.ts +6 -6
- package/packages/bundled-features/src/files-tenant-data/handlers/sweep-orphaned-derivatives.job.ts +5 -5
- package/packages/bundled-features/src/files-tenant-data/hooks.ts +24 -26
- package/packages/bundled-features/src/files-tenant-data/index.ts +1 -1
- package/packages/bundled-features/src/folders/feature.ts +1 -1
- package/packages/bundled-features/src/folders/web/folder-manager.tsx +2 -2
- package/packages/bundled-features/src/folders/web/tree.ts +5 -5
- package/packages/bundled-features/src/form-draft/changes.json +6 -0
- package/packages/bundled-features/src/form-draft/db/queries/cleanup.ts +3 -3
- package/packages/bundled-features/src/form-draft/db/queries/owned-file-refs.ts +1 -1
- package/packages/bundled-features/src/form-draft/decrypt-draft.ts +4 -1
- package/packages/bundled-features/src/form-draft/entity.ts +3 -3
- package/packages/bundled-features/src/form-draft/feature.ts +4 -5
- package/packages/bundled-features/src/form-draft/handlers/discard.write.ts +1 -1
- package/packages/bundled-features/src/form-draft/release-file-refs.ts +6 -6
- package/packages/bundled-features/src/form-draft/schemas.ts +3 -3
- package/packages/bundled-features/src/form-draft/web/constants.ts +0 -2
- package/packages/bundled-features/src/host-routing/README.md +1 -1
- package/packages/bundled-features/src/host-routing/changes.json +6 -0
- package/packages/bundled-features/src/host-routing/dispatch.ts +16 -17
- package/packages/bundled-features/src/host-routing/options.ts +2 -1
- package/packages/bundled-features/src/inbound-mail-foundation/aggregate-id.ts +19 -19
- package/packages/bundled-features/src/inbound-mail-foundation/changes.json +18 -0
- package/packages/bundled-features/src/inbound-mail-foundation/connect-routes.ts +4 -3
- package/packages/bundled-features/src/inbound-mail-foundation/constants.ts +30 -33
- package/packages/bundled-features/src/inbound-mail-foundation/entities.ts +47 -48
- package/packages/bundled-features/src/inbound-mail-foundation/events.ts +54 -50
- package/packages/bundled-features/src/inbound-mail-foundation/feature.ts +39 -39
- package/packages/bundled-features/src/inbound-mail-foundation/handlers/account-state.ts +8 -8
- package/packages/bundled-features/src/inbound-mail-foundation/handlers/connect-account.write.ts +25 -26
- package/packages/bundled-features/src/inbound-mail-foundation/handlers/disconnect-account.write.ts +7 -7
- package/packages/bundled-features/src/inbound-mail-foundation/handlers/ingest-message.write.ts +50 -51
- package/packages/bundled-features/src/inbound-mail-foundation/handlers/list-accounts.query.ts +8 -8
- package/packages/bundled-features/src/inbound-mail-foundation/handlers/list-messages.query.ts +10 -10
- package/packages/bundled-features/src/inbound-mail-foundation/handlers/scope-visibility.ts +3 -4
- package/packages/bundled-features/src/inbound-mail-foundation/handlers/update-account.write.ts +27 -15
- package/packages/bundled-features/src/inbound-mail-foundation/i18n.ts +0 -1
- package/packages/bundled-features/src/inbound-mail-foundation/oauth-state.ts +16 -15
- package/packages/bundled-features/src/inbound-mail-foundation/projection.ts +31 -29
- package/packages/bundled-features/src/inbound-mail-foundation/provider-factory.ts +9 -9
- package/packages/bundled-features/src/inbound-mail-foundation/retention-sweep.ts +45 -41
- package/packages/bundled-features/src/inbound-mail-foundation/screens.ts +0 -3
- package/packages/bundled-features/src/inbound-mail-foundation/tenant-destroy-hook.ts +14 -16
- package/packages/bundled-features/src/inbound-mail-foundation/types.ts +70 -70
- package/packages/bundled-features/src/inbound-mail-foundation/watch-supervisor.ts +101 -84
- package/packages/bundled-features/src/inbound-provider-imap/credential-document.ts +13 -13
- package/packages/bundled-features/src/inbound-provider-imap/feature.ts +42 -40
- package/packages/bundled-features/src/inbound-provider-imap/imap-client.ts +20 -19
- package/packages/bundled-features/src/inbound-provider-inmemory/feature.ts +21 -21
- package/packages/bundled-features/src/jobs/changes.json +13 -0
- package/packages/bundled-features/src/jobs/db/queries/retention.ts +2 -2
- package/packages/bundled-features/src/jobs/db/queries/stale-run-sweep.ts +3 -3
- package/packages/bundled-features/src/jobs/events.ts +1 -1
- package/packages/bundled-features/src/jobs/feature.ts +20 -11
- package/packages/bundled-features/src/jobs/handlers/detail.query.ts +4 -4
- package/packages/bundled-features/src/jobs/handlers/list.query.ts +1 -1
- package/packages/bundled-features/src/jobs/handlers/projection-rebuild.job.ts +1 -1
- package/packages/bundled-features/src/jobs/handlers/reindex-entity.job.ts +1 -1
- package/packages/bundled-features/src/jobs/handlers/retry.write.ts +1 -1
- package/packages/bundled-features/src/jobs/handlers/tenant-failures.query.ts +45 -40
- package/packages/bundled-features/src/jobs/handlers/tenant-runs.query.ts +63 -59
- package/packages/bundled-features/src/jobs/job-instant.ts +1 -1
- package/packages/bundled-features/src/jobs/job-run-logger.ts +19 -19
- package/packages/bundled-features/src/jobs/job-run-table.ts +3 -3
- package/packages/bundled-features/src/jobs/screens.ts +1 -1
- 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/entity.ts +5 -5
- package/packages/bundled-features/src/ledger/feature.ts +3 -3
- package/packages/bundled-features/src/ledger/handlers/confirm-schedule-period.write.ts +3 -3
- package/packages/bundled-features/src/ledger/recurring.ts +12 -12
- package/packages/bundled-features/src/ledger/schemas.ts +1 -1
- package/packages/bundled-features/src/ledger/web/constants.ts +2 -2
- package/packages/bundled-features/src/ledger/web/index.ts +7 -7
- package/packages/bundled-features/src/legal-pages/README.md +1 -1
- package/packages/bundled-features/src/legal-pages/feature.ts +40 -41
- package/packages/bundled-features/src/legal-pages/markdown.ts +3 -3
- package/packages/bundled-features/src/legal-pages/web/client-plugin.ts +23 -24
- package/packages/bundled-features/src/legal-pages/web/index.ts +0 -2
- package/packages/bundled-features/src/mail-foundation/changes.json +6 -0
- package/packages/bundled-features/src/mail-foundation/feature.ts +31 -30
- package/packages/bundled-features/src/mail-transport-inmemory/feature.ts +27 -28
- package/packages/bundled-features/src/mail-transport-smtp/feature.ts +20 -20
- package/packages/bundled-features/src/managed-pages/branding.ts +6 -6
- package/packages/bundled-features/src/managed-pages/feature.ts +62 -62
- package/packages/bundled-features/src/managed-pages/handlers/by-slug.query.ts +5 -6
- package/packages/bundled-features/src/managed-pages/handlers/by-tenant-published.query.ts +8 -8
- package/packages/bundled-features/src/managed-pages/handlers/set.write.ts +5 -7
- package/packages/bundled-features/src/managed-pages/screens/page-screens.ts +11 -11
- package/packages/bundled-features/src/managed-pages/seeding.ts +8 -8
- package/packages/bundled-features/src/managed-pages/table.ts +16 -15
- package/packages/bundled-features/src/managed-pages/web/client-plugin.tsx +6 -6
- package/packages/bundled-features/src/managed-pages/web/i18n.ts +1 -1
- package/packages/bundled-features/src/notes-history/entity.ts +4 -4
- package/packages/bundled-features/src/notes-history/feature.ts +2 -2
- package/packages/bundled-features/src/notes-history-user-data/hooks.ts +1 -1
- package/packages/bundled-features/src/page-render/layout.ts +9 -9
- package/packages/bundled-features/src/page-render/markdown.ts +11 -11
- package/packages/bundled-features/src/page-render/security-headers.ts +7 -6
- package/packages/bundled-features/src/personal-access-tokens/feature.ts +9 -6
- package/packages/bundled-features/src/personal-access-tokens/roles.ts +2 -2
- package/packages/bundled-features/src/readiness/handlers/status.query.ts +3 -3
- package/packages/bundled-features/src/renderer-foundation/api.ts +12 -13
- package/packages/bundled-features/src/renderer-foundation/constants.ts +2 -2
- package/packages/bundled-features/src/renderer-foundation/feature.ts +7 -7
- package/packages/bundled-features/src/renderer-foundation/types.ts +11 -12
- package/packages/bundled-features/src/renderer-pdf/changes.json +6 -0
- package/packages/bundled-features/src/renderer-pdf/render-table-pdf.ts +27 -10
- package/packages/bundled-features/src/secrets/feature.ts +9 -9
- package/packages/bundled-features/src/secrets/web/constants.ts +2 -2
- package/packages/bundled-features/src/seo/feature.ts +3 -3
- package/packages/bundled-features/src/sessions/constants.ts +1 -1
- package/packages/bundled-features/src/sessions/feature.ts +16 -11
- package/packages/bundled-features/src/sessions/handlers/revoke-all-for-user.write.ts +2 -2
- package/packages/bundled-features/src/sessions/handlers/revoke-all-others.write.ts +2 -2
- package/packages/bundled-features/src/sessions/handlers/revoke.write.ts +1 -1
- package/packages/bundled-features/src/sessions/schema/user-session.ts +4 -4
- package/packages/bundled-features/src/sessions/screens.ts +2 -2
- package/packages/bundled-features/src/sessions/session-callbacks.ts +2 -2
- package/packages/bundled-features/src/sessions/session-revoked-event.ts +2 -2
- package/packages/bundled-features/src/shared/assert-no-where-rule-in-ownership-write.ts +1 -1
- package/packages/bundled-features/src/shared/decrypt-stored-pii.ts +11 -1
- package/packages/bundled-features/src/shared/encrypt-for-direct-write.ts +1 -1
- package/packages/bundled-features/src/shared/index.ts +1 -1
- package/packages/bundled-features/src/shared/mail-template-body.ts +10 -3
- package/packages/bundled-features/src/shared/map-with-concurrency.ts +5 -4
- package/packages/bundled-features/src/shared/parent-visibility.ts +2 -2
- package/packages/bundled-features/src/shared/password-hashing.ts +1 -1
- package/packages/bundled-features/src/shared/signup-handover.ts +1 -2
- package/packages/bundled-features/src/shared/single-use-token-store.ts +1 -1
- package/packages/bundled-features/src/step-dispatcher/changes.json +6 -0
- package/packages/bundled-features/src/step-dispatcher/feature.ts +1 -0
- package/packages/bundled-features/src/subscription-dodo/changes.json +14 -0
- package/packages/bundled-features/src/subscription-dodo/client.ts +45 -0
- package/packages/bundled-features/src/subscription-dodo/constants.ts +24 -0
- package/packages/bundled-features/src/subscription-dodo/feature.ts +207 -0
- package/packages/bundled-features/src/subscription-dodo/index.ts +16 -0
- package/packages/bundled-features/src/subscription-dodo/plugin-methods.ts +345 -0
- package/packages/bundled-features/src/subscription-dodo/runtime.ts +126 -0
- package/packages/bundled-features/src/subscription-dodo/verify-webhook.ts +262 -0
- package/packages/bundled-features/src/subscription-mollie/constants.ts +15 -17
- package/packages/bundled-features/src/subscription-mollie/feature.ts +41 -42
- package/packages/bundled-features/src/subscription-mollie/index.ts +3 -3
- package/packages/bundled-features/src/subscription-mollie/plugin-methods.ts +17 -16
- package/packages/bundled-features/src/subscription-mollie/verify-webhook.ts +34 -34
- package/packages/bundled-features/src/subscription-stripe/changes.json +12 -0
- package/packages/bundled-features/src/subscription-stripe/constants.ts +4 -6
- package/packages/bundled-features/src/subscription-stripe/feature.ts +45 -43
- package/packages/bundled-features/src/subscription-stripe/index.ts +6 -6
- package/packages/bundled-features/src/subscription-stripe/plugin-methods.ts +22 -27
- package/packages/bundled-features/src/subscription-stripe/runtime.ts +9 -8
- package/packages/bundled-features/src/subscription-stripe/verify-webhook.ts +62 -64
- package/packages/bundled-features/src/tags/feature.ts +3 -3
- package/packages/bundled-features/src/tags/screens.ts +1 -1
- package/packages/bundled-features/src/template-resolver/api.ts +41 -14
- package/packages/bundled-features/src/template-resolver/changes.json +48 -0
- package/packages/bundled-features/src/template-resolver/compile-document-body.ts +1 -0
- package/packages/bundled-features/src/template-resolver/feature.ts +2 -2
- package/packages/bundled-features/src/template-resolver/handlers/bundle-import.ts +3 -2
- package/packages/bundled-features/src/template-resolver/handlers/copy-locale.write.ts +27 -5
- package/packages/bundled-features/src/template-resolver/handlers/diff-to-system.query.ts +3 -2
- package/packages/bundled-features/src/template-resolver/handlers/document-base.ts +3 -2
- package/packages/bundled-features/src/template-resolver/handlers/find-by-id.query.ts +8 -7
- package/packages/bundled-features/src/template-resolver/handlers/list.query.ts +19 -20
- package/packages/bundled-features/src/template-resolver/handlers/overview.query.ts +15 -5
- package/packages/bundled-features/src/template-resolver/handlers/shared.ts +2 -2
- package/packages/bundled-features/src/template-resolver/handlers/template-write.ts +3 -3
- package/packages/bundled-features/src/template-resolver/handlers/tenant-override-counts.query.ts +9 -5
- package/packages/bundled-features/src/template-resolver/handlers/tenant-override-selection.ts +8 -6
- package/packages/bundled-features/src/template-resolver/handlers/tenant-overrides.query.ts +44 -5
- package/packages/bundled-features/src/template-resolver/handlers/upsert-system.write.ts +3 -4
- package/packages/bundled-features/src/template-resolver/handlers/upsert-tenant.write.ts +4 -5
- package/packages/bundled-features/src/template-resolver/handlers/usage.query.ts +37 -26
- package/packages/bundled-features/src/template-resolver/handlers/validate.query.ts +7 -2
- package/packages/bundled-features/src/template-resolver/seeding.ts +36 -18
- package/packages/bundled-features/src/template-resolver/store-template-resource.ts +22 -4
- package/packages/bundled-features/src/template-resolver/table.ts +13 -14
- package/packages/bundled-features/src/template-resolver/template-delivery-policy.ts +0 -11
- package/packages/bundled-features/src/template-resolver/template-meta.ts +12 -4
- package/packages/bundled-features/src/template-resolver/template-preview.ts +11 -3
- package/packages/bundled-features/src/template-resolver/web/client-plugin.tsx +2 -2
- package/packages/bundled-features/src/template-resolver/web/constants.ts +7 -5
- package/packages/bundled-features/src/template-resolver/web/index.ts +7 -0
- package/packages/bundled-features/src/template-resolver/web/mail-layout-constants.ts +8 -0
- package/packages/bundled-features/src/tenant/changes.json +6 -0
- package/packages/bundled-features/src/tenant/feature.ts +7 -7
- package/packages/bundled-features/src/tenant/handlers/member-directory.query.ts +10 -4
- 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/team-list.query.ts +1 -1
- package/packages/bundled-features/src/tenant/handlers/tenant-directory.query.ts +8 -5
- package/packages/bundled-features/src/tenant/invitation-table.ts +37 -40
- package/packages/bundled-features/src/tenant/membership-table.ts +3 -3
- package/packages/bundled-features/src/tenant/schema/tenant.ts +7 -9
- package/packages/bundled-features/src/tenant/screens.ts +1 -1
- package/packages/bundled-features/src/tenant/seeding.ts +19 -18
- package/packages/bundled-features/src/tenant/testing.ts +5 -6
- package/packages/bundled-features/src/tenant/web/constants.ts +2 -2
- package/packages/bundled-features/src/tenant-handover/events.ts +5 -7
- package/packages/bundled-features/src/tenant-handover/feature.ts +1 -1
- package/packages/bundled-features/src/tenant-handover/grant.ts +1 -1
- package/packages/bundled-features/src/tenant-handover/handlers/claim.write.ts +1 -1
- package/packages/bundled-features/src/tenant-handover/move-entity-graph.ts +8 -9
- package/packages/bundled-features/src/tenant-handover/signup-handover-provider.ts +1 -2
- package/packages/bundled-features/src/tenant-handover/transfer-graph.ts +4 -5
- package/packages/bundled-features/src/tenant-lifecycle/boot-checks.ts +1 -1
- package/packages/bundled-features/src/tenant-lifecycle/changes.json +6 -0
- package/packages/bundled-features/src/tenant-lifecycle/constants.ts +5 -0
- package/packages/bundled-features/src/tenant-lifecycle/feature.ts +1 -1
- package/packages/bundled-features/src/tenant-lifecycle/run-tenant-destroy.ts +7 -3
- package/packages/bundled-features/src/tenant-lifecycle/stages.ts +1 -1
- package/packages/bundled-features/src/tenant-settings/constants.ts +1 -1
- package/packages/bundled-features/src/tenant-settings/tenant-defaults.ts +12 -5
- package/packages/bundled-features/src/tier-engine/aggregate-id.ts +15 -16
- package/packages/bundled-features/src/tier-engine/changes.json +6 -0
- package/packages/bundled-features/src/tier-engine/compose-app.ts +14 -14
- package/packages/bundled-features/src/tier-engine/entity.ts +18 -19
- package/packages/bundled-features/src/tier-engine/feature.ts +88 -87
- package/packages/bundled-features/src/tier-engine/handlers/active-tier.query.ts +6 -7
- package/packages/bundled-features/src/tier-engine/handlers/get-tenant-tier.query.ts +4 -4
- package/packages/bundled-features/src/tier-engine/handlers/set-tenant-tier.write.ts +12 -9
- package/packages/bundled-features/src/tier-engine/tier-resolver.ts +2 -2
- package/packages/bundled-features/src/tier-engine/trial.ts +13 -13
- package/packages/bundled-features/src/tier-engine/web/constants.ts +3 -3
- package/packages/bundled-features/src/tier-engine/web/index.ts +4 -4
- package/packages/bundled-features/src/user/db/queries/stream-tenant-backfill.ts +8 -8
- package/packages/bundled-features/src/user/handlers/create.write.ts +4 -4
- package/packages/bundled-features/src/user/schema/user.ts +45 -44
- package/packages/bundled-features/src/user/seeding.ts +40 -40
- package/packages/bundled-features/src/user/testing.ts +3 -3
- package/packages/bundled-features/src/user-data-rights/audit-download.ts +27 -28
- package/packages/bundled-features/src/user-data-rights/boot-checks.ts +1 -1
- package/packages/bundled-features/src/user-data-rights/deletion-token.ts +1 -1
- package/packages/bundled-features/src/user-data-rights/feature.ts +112 -113
- package/packages/bundled-features/src/user-data-rights/handlers/cancel-deletion.write.ts +9 -9
- package/packages/bundled-features/src/user-data-rights/handlers/confirm-deletion-by-token.write.ts +8 -8
- package/packages/bundled-features/src/user-data-rights/handlers/download-by-job.query.ts +29 -30
- package/packages/bundled-features/src/user-data-rights/handlers/download-by-token.query.ts +40 -42
- package/packages/bundled-features/src/user-data-rights/handlers/export-status.query.ts +8 -9
- package/packages/bundled-features/src/user-data-rights/handlers/lift-restriction.write.ts +7 -8
- package/packages/bundled-features/src/user-data-rights/handlers/list-download-attempts.query.ts +2 -2
- package/packages/bundled-features/src/user-data-rights/handlers/my-audit-log.query.ts +4 -4
- package/packages/bundled-features/src/user-data-rights/handlers/request-deletion-by-email.write.ts +30 -29
- package/packages/bundled-features/src/user-data-rights/handlers/request-deletion.write.ts +15 -18
- package/packages/bundled-features/src/user-data-rights/handlers/request-export.write.ts +42 -41
- package/packages/bundled-features/src/user-data-rights/handlers/restrict-account.write.ts +22 -19
- package/packages/bundled-features/src/user-data-rights/handlers/run-forget-cleanup.write.ts +8 -8
- package/packages/bundled-features/src/user-data-rights/index.ts +8 -10
- package/packages/bundled-features/src/user-data-rights/lib/default-mailers.ts +3 -3
- package/packages/bundled-features/src/user-data-rights/lib/update-user-lifecycle.ts +24 -27
- package/packages/bundled-features/src/user-data-rights/run-export-jobs.ts +169 -280
- package/packages/bundled-features/src/user-data-rights/run-forget-cleanup.ts +87 -108
- package/packages/bundled-features/src/user-data-rights/run-user-export.ts +67 -69
- package/packages/bundled-features/src/user-data-rights/schema/download-attempt.ts +11 -11
- package/packages/bundled-features/src/user-data-rights/schema/download-token.ts +37 -41
- package/packages/bundled-features/src/user-data-rights/schema/export-job.ts +65 -67
- package/packages/bundled-features/src/user-data-rights/token-helpers.ts +21 -25
- package/packages/bundled-features/src/user-data-rights/web/client-plugin.tsx +6 -6
- package/packages/bundled-features/src/user-data-rights/web/confirm-deletion-screen.tsx +6 -5
- package/packages/bundled-features/src/user-data-rights/web/constants.ts +1 -1
- package/packages/bundled-features/src/user-data-rights/web/index.ts +5 -5
- package/packages/bundled-features/src/user-data-rights/web/privacy-center-screen.tsx +14 -13
- package/packages/bundled-features/src/user-data-rights/web/public-deletion-gate.tsx +13 -12
- package/packages/bundled-features/src/user-data-rights/web/request-deletion-screen.tsx +6 -6
- package/packages/bundled-features/src/user-data-rights/zip-path.ts +41 -40
- package/packages/bundled-features/src/user-data-rights-defaults/feature.ts +15 -15
- package/packages/bundled-features/src/user-data-rights-defaults/hooks/delivery-attempt.userdata-hook.ts +3 -5
- package/packages/bundled-features/src/user-data-rights-defaults/hooks/file-ref.userdata-hook.ts +45 -51
- package/packages/bundled-features/src/user-data-rights-defaults/hooks/job-run.userdata-hook.ts +2 -2
- package/packages/bundled-features/src/user-data-rights-defaults/hooks/notification-preference.userdata-hook.ts +1 -1
- package/packages/bundled-features/src/user-data-rights-defaults/hooks/user-session.userdata-hook.ts +3 -3
- package/packages/bundled-features/src/user-data-rights-defaults/hooks/user.userdata-hook.ts +32 -31
- package/packages/bundled-features/src/user-profile/handlers/change-email.write.ts +13 -13
- package/packages/bundled-features/src/user-profile/index.ts +3 -3
- package/packages/bundled-features/src/user-profile/web/client-plugin.ts +1 -1
- package/packages/bundled-features/src/user-profile/web/constants.ts +1 -1
- package/packages/bundled-features/src/user-profile/web/profile-screen.tsx +1 -1
- package/packages/bundled-features/src/waitlist/changes.json +18 -0
- package/packages/bundled-features/src/waitlist/handlers/export-purge.write.ts +2 -1
- package/packages/bundled-features/src/waitlist/handlers/submit.write.ts +5 -5
- package/packages/bundled-features/src/waitlist/import.ts +5 -0
- package/packages/bundled-features/src/waitlist/legacy-migrate.ts +22 -3
- package/packages/bundled-features/src/waitlist/link-on-signup.ts +20 -10
- package/packages/bundled-features/src/waitlist/options.ts +3 -0
- package/packages/bundled-features/src/workflow-runner/aggregate-id.ts +1 -1
- package/packages/bundled-features/src/workflow-runner/event-subscriber.ts +4 -5
- package/packages/bundled-features/src/workflow-runner/event-trigger.ts +4 -15
- package/packages/bundled-features/src/workflow-runner/feature.ts +2 -2
- package/packages/bundled-features/src/workflow-runner/handlers/resume-run.write.ts +4 -4
- package/packages/bundled-features/src/workflow-runner/pending-projection.ts +6 -7
- package/packages/bundled-features/src/workflow-runner/runner.ts +6 -6
- package/packages/bundled-features/src/workflow-runner/tables.ts +6 -6
- package/packages/bundled-features/src/workflow-runner/workflow-registry.ts +1 -1
- package/samples/apps/styleguide/src/features/widgets/i18n.ts +11 -0
- package/samples/apps/styleguide/src/features/widgets/web/Widgets.tsx +24 -0
- package/samples/apps/use-all-bundled/e2e-billing-gallery/gallery-constants.ts +5 -0
- package/samples/apps/use-all-bundled/e2e-billing-gallery/gallery-features.ts +90 -15
- package/samples/apps/use-all-bundled/e2e-billing-gallery/server.ts +16 -6
- package/samples/apps/use-all-bundled/feature-manifest.json +120 -3
- package/samples/apps/use-all-bundled/src/run-config.ts +9 -3
- package/samples/recipes/auth-mfa/README.md +5 -0
- package/samples/recipes/auth-mfa/package.json +5 -2
- package/samples/recipes/auth-mfa/src/web/confirmation-code-step.tsx +49 -0
- package/samples/recipes/auth-mfa/tsconfig.json +8 -0
- package/samples/recipes/env-composition/README.md +3 -1
- package/samples/recipes/env-composition/src/env.ts +2 -0
- package/samples/recipes/legal-pages/README.md +1 -1
- package/samples/recipes/rate-limiting/README.md +18 -0
- package/samples/recipes/rate-limiting/src/feature.ts +12 -0
- package/packages/bundled-features/src/billing-foundation/consumer-protection/stored-user-email.ts +0 -12
|
@@ -1,17 +1,16 @@
|
|
|
1
|
-
// createInboundMailSupervisor —
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
//
|
|
1
|
+
// createInboundMailSupervisor — the long-lived Foundation sync process:
|
|
2
|
+
// starts `plugin.watch()` (IMAP IDLE, seconds-level push) for each active
|
|
3
|
+
// account with reconnect backoff, and also runs the periodic reconciliation
|
|
4
|
+
// poll (default: 5 min) through `plugin.fetch()`.
|
|
5
|
+
// Deduplication in the ingest handler makes watch/poll overlap idempotent:
|
|
6
|
+
// the poll is the correctness anchor, while watch only reduces latency.
|
|
7
7
|
//
|
|
8
|
-
// **
|
|
9
|
-
// cron trigger.
|
|
10
|
-
//
|
|
11
|
-
//
|
|
12
|
-
//
|
|
13
|
-
//
|
|
14
|
-
// app owner still wires the supervisor in bin/server.ts:
|
|
8
|
+
// **Documented deviation:** the poll was originally intended as an `r.job`
|
|
9
|
+
// cron trigger. JobContext previously had no dispatcher (see
|
|
10
|
+
// run-export-jobs.ts), but now has one (`ctx.write`/`ctx.queryAs`,
|
|
11
|
+
// job-runner.ts). Converting the poll to a cron job is a separate migration
|
|
12
|
+
// because it changes the concurrency model, so this dispatcher contract
|
|
13
|
+
// remains in place. The app owner wires the supervisor in bin/server.ts:
|
|
15
14
|
//
|
|
16
15
|
// const supervisor = createInboundMailSupervisor({
|
|
17
16
|
// providerCtx: { registry: deps.registry, secrets, metricsFor: deps.metricsFor },
|
|
@@ -20,29 +19,29 @@
|
|
|
20
19
|
// deps.dispatchSystemWrite({ handlerQn, payload, tenantId: tenantId as TenantId }),
|
|
21
20
|
// // Multi-worker deployments: share one DistributedLock (same Redis,
|
|
22
21
|
// // same key prefix) across every worker process so only one of them
|
|
23
|
-
// // holds the IMAP IDLE connection per account
|
|
22
|
+
// // holds the IMAP IDLE connection per account. Omit `lock`
|
|
24
23
|
// // for a single-process deployment — every active account gets
|
|
25
|
-
// // watched locally,
|
|
24
|
+
// // watched locally, as before.
|
|
26
25
|
// lock: createDistributedLock(redis, `${RedisKeys.lock}inbound-mail:watch:`),
|
|
27
26
|
// });
|
|
28
27
|
// await supervisor.start();
|
|
29
28
|
// // shutdown-hook: await supervisor.stop();
|
|
30
29
|
//
|
|
31
|
-
// **IDLE operational risk
|
|
32
|
-
//
|
|
33
|
-
//
|
|
30
|
+
// **IDLE operational risk:** long-lived sockets run in-process.
|
|
31
|
+
// Backoff restart on onError, clean shutdown of every watcher, and the poll
|
|
32
|
+
// covering every gap mitigate this risk.
|
|
34
33
|
//
|
|
35
|
-
// **Multi-worker coordination
|
|
36
|
-
//
|
|
37
|
-
//
|
|
38
|
-
//
|
|
39
|
-
//
|
|
40
|
-
// the account
|
|
41
|
-
//
|
|
42
|
-
//
|
|
43
|
-
//
|
|
44
|
-
//
|
|
45
|
-
//
|
|
34
|
+
// **Multi-worker coordination:** the reconciliation poll remains deliberately
|
|
35
|
+
// N-fold: every worker polls every active account, which is idempotent and
|
|
36
|
+
// cheap. Only `plugin.watch()` holds a long-lived connection, and only one
|
|
37
|
+
// worker may hold it per account. With `deps.lock`, `ensureWatcher` claims a
|
|
38
|
+
// TTL lease (`lock.acquire`) before connecting; a worker that fails to claim
|
|
39
|
+
// it leaves the account to its current holder, while the poll still covers
|
|
40
|
+
// it. The holder renews the lease via heartbeat (`lock.renew`, every ttl/3)
|
|
41
|
+
// for as long as the watcher state exists, including reconnect backoff, not
|
|
42
|
+
// only while the connection is open. Losing the claim (for example through
|
|
43
|
+
// a Redis outage or expired TTL) tears down the local watcher instead of
|
|
44
|
+
// racing a new holder.
|
|
46
45
|
|
|
47
46
|
import {
|
|
48
47
|
configuredPiiSubjectKms,
|
|
@@ -85,18 +84,20 @@ const DEFAULT_MAX_MESSAGES_PER_POLL = 200;
|
|
|
85
84
|
const DEFAULT_WATCH_BACKOFF_INITIAL_MS = 5_000;
|
|
86
85
|
const DEFAULT_WATCH_BACKOFF_MAX_MS = 5 * 60 * 1000;
|
|
87
86
|
const DEFAULT_WATCH_LEASE_TTL_SECONDS = 90;
|
|
88
|
-
/**
|
|
89
|
-
*
|
|
87
|
+
/** One cursor per account (one mailbox inbox) for now; multi-folder support
|
|
88
|
+
* can add further scopes later without a schema change. */
|
|
90
89
|
const CURSOR_SCOPE = "default";
|
|
91
90
|
|
|
91
|
+
const SEEN_LOOKUP_CHUNK_SIZE = 1000;
|
|
92
|
+
|
|
92
93
|
export type InboundMailSupervisorDeps = {
|
|
93
|
-
/** Slim
|
|
94
|
-
*
|
|
94
|
+
/** Slim context for provider calls (`registry` is required; `secrets` is
|
|
95
|
+
* used for provider credential reads). */
|
|
95
96
|
readonly providerCtx: InboundMailContext;
|
|
96
|
-
/** App
|
|
97
|
-
*
|
|
97
|
+
/** App database — direct reads from read_mail_accounts (account snapshot)
|
|
98
|
+
* and direct writes to store_mail_sync_cursors (unmanaged store). */
|
|
98
99
|
readonly db: DbConnection;
|
|
99
|
-
/** Standard
|
|
100
|
+
/** Standard dispatcher with SystemUser — carries ingest-message and
|
|
100
101
|
* update-account. */
|
|
101
102
|
readonly dispatchWrite: (args: {
|
|
102
103
|
readonly handlerQn: string;
|
|
@@ -107,8 +108,8 @@ export type InboundMailSupervisorDeps = {
|
|
|
107
108
|
readonly data?: unknown;
|
|
108
109
|
readonly error?: unknown;
|
|
109
110
|
}>;
|
|
110
|
-
/**
|
|
111
|
-
*
|
|
111
|
+
/** Persists the raw MIME body (file-foundation) and returns its bodyRef
|
|
112
|
+
* for the event. Without this hook, runs in snippet-only mode
|
|
112
113
|
* (bodyRef = ""). */
|
|
113
114
|
readonly storeBody?: (account: MailAccountRecord, msg: RawInboundMessage) => Promise<string>;
|
|
114
115
|
readonly pollIntervalMs?: number;
|
|
@@ -120,7 +121,7 @@ export type InboundMailSupervisorDeps = {
|
|
|
120
121
|
* before `plugin.watch()`, ensureWatcher claims a TTL lease for the
|
|
121
122
|
* account via `lock.acquire` and renews it via `lock.renew` while the
|
|
122
123
|
* watcher lives. Omit for single-process deployments — every active
|
|
123
|
-
* account is watched locally, same as before
|
|
124
|
+
* account is watched locally, same as before. */
|
|
124
125
|
readonly lock?: DistributedLock;
|
|
125
126
|
/** Access-token lifecycle for OAuth accounts. Defaults to a manager built
|
|
126
127
|
* from `db` + `providerCtx.secrets`; pass the instance shared with
|
|
@@ -136,8 +137,8 @@ type WatcherState = {
|
|
|
136
137
|
stop: (() => Promise<void>) | null;
|
|
137
138
|
backoffMs: number;
|
|
138
139
|
restartTimer: ReturnType<typeof setTimeout> | null;
|
|
139
|
-
/**
|
|
140
|
-
*
|
|
140
|
+
/** Incremented by stop() to prevent a delayed restart from reviving
|
|
141
|
+
* an already stopped watcher. */
|
|
141
142
|
generation: number;
|
|
142
143
|
/** Token from `deps.lock.acquire` while this worker owns the account's
|
|
143
144
|
* watch lease; null when unclaimed (no `deps.lock`, or claim lost). */
|
|
@@ -153,8 +154,8 @@ type WatchLeaseResult = "acquired" | "no-token" | "stale";
|
|
|
153
154
|
|
|
154
155
|
export type InboundMailSupervisor = {
|
|
155
156
|
readonly start: () => Promise<void>;
|
|
156
|
-
/**
|
|
157
|
-
*
|
|
157
|
+
/** Reconciliation run across all active accounts; also usable standalone
|
|
158
|
+
* for tests and manual operational triggers. */
|
|
158
159
|
readonly pollOnce: () => Promise<void>;
|
|
159
160
|
readonly stop: () => Promise<void>;
|
|
160
161
|
};
|
|
@@ -214,8 +215,8 @@ export function createInboundMailSupervisor(
|
|
|
214
215
|
const watchers = new Map<string, WatcherState>();
|
|
215
216
|
|
|
216
217
|
// ---------------------------------------------------------------
|
|
217
|
-
// Account
|
|
218
|
-
// (
|
|
218
|
+
// Account snapshot: active accounts of all tenants with the address
|
|
219
|
+
// decrypted (providers never work with ciphertext).
|
|
219
220
|
// ---------------------------------------------------------------
|
|
220
221
|
async function listActiveAccounts(): Promise<readonly MailAccountRecord[]> {
|
|
221
222
|
const rows = await selectMany<ActiveMailAccountRow>(deps.db, mailAccountsProjectionTable, {
|
|
@@ -224,11 +225,19 @@ export function createInboundMailSupervisor(
|
|
|
224
225
|
const piiKms = configuredPiiSubjectKms();
|
|
225
226
|
const records: MailAccountRecord[] = [];
|
|
226
227
|
for (const row of rows) {
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
228
|
+
let decrypted: Record<string, unknown>;
|
|
229
|
+
try {
|
|
230
|
+
decrypted = piiKms
|
|
231
|
+
? await decryptPiiFieldValues(row, MAIL_ACCOUNT_PII_FIELDS, piiKms, {
|
|
232
|
+
requestId: "inbound-mail-foundation:supervisor:list-accounts",
|
|
233
|
+
})
|
|
234
|
+
: row;
|
|
235
|
+
} catch (error) {
|
|
236
|
+
// Class name only: the message may carry ciphertext or key details.
|
|
237
|
+
const errorName = error instanceof Error ? error.name : "unknown error";
|
|
238
|
+
log(`inbound-mail: account ${row.id} skipped — address did not decrypt (${errorName})`);
|
|
239
|
+
continue;
|
|
240
|
+
}
|
|
232
241
|
const decryptedAddress = decrypted["address"];
|
|
233
242
|
// Falling back to row.address would hand the ciphertext to a provider.
|
|
234
243
|
// One undecryptable account must not stop polling of every other tenant's accounts.
|
|
@@ -264,7 +273,7 @@ export function createInboundMailSupervisor(
|
|
|
264
273
|
const parsed: unknown = JSON.parse(row.cursor);
|
|
265
274
|
return isPlainObject(parsed) ? parsed : null;
|
|
266
275
|
} catch {
|
|
267
|
-
return null; //
|
|
276
|
+
return null; // a corrupt cursor counts as none → backfill; dedup catches duplicates
|
|
268
277
|
}
|
|
269
278
|
}
|
|
270
279
|
|
|
@@ -286,7 +295,7 @@ export function createInboundMailSupervisor(
|
|
|
286
295
|
{ cursor: serialized, updatedAt: now },
|
|
287
296
|
{ accountId, scope: CURSOR_SCOPE },
|
|
288
297
|
);
|
|
289
|
-
// skip: update
|
|
298
|
+
// skip: the update path is complete; do not fall through to the insert branch.
|
|
290
299
|
return;
|
|
291
300
|
}
|
|
292
301
|
await insertOne(deps.db, syncCursorTable, {
|
|
@@ -301,8 +310,8 @@ export function createInboundMailSupervisor(
|
|
|
301
310
|
}
|
|
302
311
|
|
|
303
312
|
async function resetCursor(accountId: string): Promise<void> {
|
|
304
|
-
//
|
|
305
|
-
//
|
|
313
|
+
// No deleteMany import is needed: an empty cursor parses to null, so the
|
|
314
|
+
// next fetch runs as a backfill.
|
|
306
315
|
await updateMany(
|
|
307
316
|
deps.db,
|
|
308
317
|
syncCursorTable,
|
|
@@ -312,7 +321,7 @@ export function createInboundMailSupervisor(
|
|
|
312
321
|
}
|
|
313
322
|
|
|
314
323
|
// ---------------------------------------------------------------
|
|
315
|
-
// Ingest
|
|
324
|
+
// Ingest: every message goes through the standard write handler.
|
|
316
325
|
// ---------------------------------------------------------------
|
|
317
326
|
async function ingestBatch(
|
|
318
327
|
account: MailAccountRecord,
|
|
@@ -344,9 +353,9 @@ export function createInboundMailSupervisor(
|
|
|
344
353
|
},
|
|
345
354
|
});
|
|
346
355
|
if (!result.isSuccess) {
|
|
347
|
-
//
|
|
348
|
-
//
|
|
349
|
-
//
|
|
356
|
+
// A single-message failure aborts the batch without persisting the cursor.
|
|
357
|
+
// The next tick refetches from the old cursor, and deduplication skips
|
|
358
|
+
// messages that were already processed.
|
|
350
359
|
throw new Error(
|
|
351
360
|
`ingest-message failed for account ${account.id}: ${JSON.stringify(result.error ?? {})}`,
|
|
352
361
|
);
|
|
@@ -367,8 +376,8 @@ export function createInboundMailSupervisor(
|
|
|
367
376
|
}
|
|
368
377
|
|
|
369
378
|
// ---------------------------------------------------------------
|
|
370
|
-
//
|
|
371
|
-
//
|
|
379
|
+
// Error semantics, shared by poll and watch. Returns true when the
|
|
380
|
+
// account may keep running.
|
|
372
381
|
// ---------------------------------------------------------------
|
|
373
382
|
async function handleSyncError(account: MailAccountRecord, err: unknown): Promise<boolean> {
|
|
374
383
|
if (isInboundAuthError(err)) {
|
|
@@ -383,14 +392,14 @@ export function createInboundMailSupervisor(
|
|
|
383
392
|
}
|
|
384
393
|
if (isInboundRateLimitError(err)) {
|
|
385
394
|
log(`inbound-mail: account ${account.id} rate-limited (retryAfter ${err.retryAfterMs}ms)`);
|
|
386
|
-
return true; //
|
|
395
|
+
return true; // The next tick retries it
|
|
387
396
|
}
|
|
388
397
|
if (isInboundCursorInvalidError(err)) {
|
|
389
398
|
log(`inbound-mail: account ${account.id} cursor invalid — full resync in backfill window`);
|
|
390
399
|
await resetCursor(account.id);
|
|
391
400
|
return true;
|
|
392
401
|
}
|
|
393
|
-
// Transient
|
|
402
|
+
// Transient or unknown error: log it and retry on the next tick.
|
|
394
403
|
log(`inbound-mail: account ${account.id} sync error: ${describeError(err)}`);
|
|
395
404
|
return true;
|
|
396
405
|
}
|
|
@@ -401,17 +410,23 @@ export function createInboundMailSupervisor(
|
|
|
401
410
|
function unknownMessageIdsFilterFor(account: MailAccountRecord) {
|
|
402
411
|
return async (providerMessageIds: readonly string[]): Promise<ReadonlySet<string>> => {
|
|
403
412
|
if (providerMessageIds.length === 0) return new Set();
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
{
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
413
|
+
// One bind parameter per id: a full-mailbox listing would exceed Postgres' 65535 limit.
|
|
414
|
+
const seenIds = new Set<string>();
|
|
415
|
+
for (let start = 0; start < providerMessageIds.length; start += SEEN_LOOKUP_CHUNK_SIZE) {
|
|
416
|
+
const seen = await selectMany<{ providerMessageId: string }>(
|
|
417
|
+
deps.db,
|
|
418
|
+
seenMessageTable,
|
|
419
|
+
{
|
|
420
|
+
tenantId: account.tenantId,
|
|
421
|
+
accountId: account.id,
|
|
422
|
+
providerMessageId: {
|
|
423
|
+
in: providerMessageIds.slice(start, start + SEEN_LOOKUP_CHUNK_SIZE),
|
|
424
|
+
},
|
|
425
|
+
},
|
|
426
|
+
{ fields: ["providerMessageId"] },
|
|
427
|
+
);
|
|
428
|
+
for (const row of seen) seenIds.add(row.providerMessageId);
|
|
429
|
+
}
|
|
415
430
|
return new Set(providerMessageIds.filter((id) => !seenIds.has(id)));
|
|
416
431
|
};
|
|
417
432
|
}
|
|
@@ -423,7 +438,8 @@ export function createInboundMailSupervisor(
|
|
|
423
438
|
let cursor = await loadCursor(account.id);
|
|
424
439
|
let budget = maxMessagesPerPoll;
|
|
425
440
|
try {
|
|
426
|
-
// hasMore
|
|
441
|
+
// hasMore loop: paginate within this tick until the message budget is
|
|
442
|
+
// exhausted.
|
|
427
443
|
for (;;) {
|
|
428
444
|
const accessToken = await resolveAccessToken(account, plugin);
|
|
429
445
|
const result = await plugin.fetch(deps.providerCtx, account, cursor, {
|
|
@@ -456,7 +472,7 @@ export function createInboundMailSupervisor(
|
|
|
456
472
|
await pollAccount(account, plugin);
|
|
457
473
|
if (running) await ensureWatcher(account, plugin);
|
|
458
474
|
}
|
|
459
|
-
//
|
|
475
|
+
// Remove watchers for accounts that are no longer active.
|
|
460
476
|
const activeIds = new Set(accounts.map((a) => a.id));
|
|
461
477
|
for (const accountId of watchers.keys()) {
|
|
462
478
|
if (!activeIds.has(accountId)) await stopWatcher(accountId);
|
|
@@ -464,7 +480,7 @@ export function createInboundMailSupervisor(
|
|
|
464
480
|
}
|
|
465
481
|
|
|
466
482
|
// ---------------------------------------------------------------
|
|
467
|
-
// Watch
|
|
483
|
+
// Watch lifecycle with backoff restart.
|
|
468
484
|
// ---------------------------------------------------------------
|
|
469
485
|
// Heartbeat for a held watch lease. Independent of the connection's own
|
|
470
486
|
// lifecycle — it keeps renewing across reconnect backoff, not just while
|
|
@@ -538,7 +554,7 @@ export function createInboundMailSupervisor(
|
|
|
538
554
|
}
|
|
539
555
|
}
|
|
540
556
|
|
|
541
|
-
// kumiko-lint-ignore complexity-budget yield-point stale-map check after acquire is intentional
|
|
557
|
+
// kumiko-lint-ignore complexity-budget yield-point stale-map check after acquire is intentional
|
|
542
558
|
async function acquireWatchLease(
|
|
543
559
|
account: MailAccountRecord,
|
|
544
560
|
state: WatcherState,
|
|
@@ -565,10 +581,10 @@ export function createInboundMailSupervisor(
|
|
|
565
581
|
account: MailAccountRecord,
|
|
566
582
|
plugin: InboundMailProviderPlugin,
|
|
567
583
|
): Promise<void> {
|
|
568
|
-
// skip:
|
|
584
|
+
// skip: provider without live push; the reconciliation poll covers the account.
|
|
569
585
|
if (!plugin.watch) return;
|
|
570
586
|
const existing = watchers.get(account.id);
|
|
571
|
-
// skip:
|
|
587
|
+
// skip: the watcher is already running, starting, or scheduled for restart.
|
|
572
588
|
if (existing?.stop || existing?.restartTimer || existing?.starting) return;
|
|
573
589
|
|
|
574
590
|
const state: WatcherState = existing ?? {
|
|
@@ -601,7 +617,8 @@ export function createInboundMailSupervisor(
|
|
|
601
617
|
const generation = state.generation;
|
|
602
618
|
|
|
603
619
|
const scheduleRestart = (err: unknown) => {
|
|
604
|
-
// skip:
|
|
620
|
+
// skip: the supervisor stopped or the watcher generation changed; a restart
|
|
621
|
+
// would create a zombie watcher.
|
|
605
622
|
if (!running || state.generation !== generation) return;
|
|
606
623
|
state.stop = null;
|
|
607
624
|
const delay = state.backoffMs;
|
|
@@ -647,7 +664,7 @@ export function createInboundMailSupervisor(
|
|
|
647
664
|
);
|
|
648
665
|
if (state.generation !== generation || !running) {
|
|
649
666
|
await stop();
|
|
650
|
-
// skip: stop()
|
|
667
|
+
// skip: stop() ran while connecting; the watcher was torn down right away.
|
|
651
668
|
return;
|
|
652
669
|
}
|
|
653
670
|
state.stop = stop;
|
|
@@ -674,7 +691,7 @@ export function createInboundMailSupervisor(
|
|
|
674
691
|
|
|
675
692
|
async function stopWatcher(accountId: string): Promise<void> {
|
|
676
693
|
const state = watchers.get(accountId);
|
|
677
|
-
// skip:
|
|
694
|
+
// skip: no watcher state for this account; it is already torn down.
|
|
678
695
|
if (!state) return;
|
|
679
696
|
state.generation += 1;
|
|
680
697
|
if (state.restartTimer) {
|
|
@@ -715,7 +732,7 @@ export function createInboundMailSupervisor(
|
|
|
715
732
|
// Lifecycle.
|
|
716
733
|
// ---------------------------------------------------------------
|
|
717
734
|
function scheduleNextPoll(): void {
|
|
718
|
-
// skip:
|
|
735
|
+
// skip: the supervisor is stopped; do not schedule another poll tick.
|
|
719
736
|
if (!running) return;
|
|
720
737
|
pollTimer = setTimeout(() => {
|
|
721
738
|
pollTimer = null;
|
|
@@ -730,7 +747,7 @@ export function createInboundMailSupervisor(
|
|
|
730
747
|
|
|
731
748
|
return {
|
|
732
749
|
async start() {
|
|
733
|
-
// skip:
|
|
750
|
+
// skip: already started; start() is idempotent.
|
|
734
751
|
if (running) return;
|
|
735
752
|
running = true;
|
|
736
753
|
await pollOnce();
|
|
@@ -1,19 +1,19 @@
|
|
|
1
|
-
// Connection
|
|
2
|
-
// (inboundCredentialSecretKey(accountId)
|
|
1
|
+
// Connection document stored in the per-account secret slot
|
|
2
|
+
// (inboundCredentialSecretKey(accountId)).
|
|
3
3
|
//
|
|
4
|
-
// **
|
|
5
|
-
// mail-transport-smtp (
|
|
6
|
-
//
|
|
7
|
-
//
|
|
8
|
-
//
|
|
9
|
-
//
|
|
4
|
+
// **Why JSON in the secret instead of tenant config keys:** unlike
|
|
5
|
+
// mail-transport-smtp (one SMTP configuration per tenant), inbound mail
|
|
6
|
+
// supports multiple accounts per tenant with different hosts, which
|
|
7
|
+
// per-tenant config keys cannot represent. Host, port, and user belong
|
|
8
|
+
// with the password in one connection document; the slot is encrypted
|
|
9
|
+
// at rest.
|
|
10
10
|
//
|
|
11
|
-
//
|
|
11
|
+
// The account's authMethod determines the interpretation:
|
|
12
12
|
// password → { host, port, secure, user, password }
|
|
13
|
-
// xoauth2 → { host, port, secure, user, accessToken } — V1
|
|
14
|
-
//
|
|
15
|
-
//
|
|
16
|
-
//
|
|
13
|
+
// xoauth2 → { host, port, secure, user, accessToken } — V1 fallback:
|
|
14
|
+
// OAuth providers (M365/Gmail) handle token acquisition and
|
|
15
|
+
// refresh; the imap provider only consumes a valid access
|
|
16
|
+
// token from the slot.
|
|
17
17
|
|
|
18
18
|
import * as z from "zod";
|
|
19
19
|
|
|
@@ -1,27 +1,27 @@
|
|
|
1
1
|
// kumiko-feature-version: 1
|
|
2
2
|
//
|
|
3
|
-
// inbound-provider-imap — IMAP
|
|
4
|
-
// foundation
|
|
5
|
-
// Strato, GMX/Web.de, mailbox.org, Hetzner, all-inkl) via
|
|
6
|
-
//
|
|
7
|
-
// M365
|
|
8
|
-
//
|
|
3
|
+
// inbound-provider-imap — IMAP implementation of the inbound-mail-
|
|
4
|
+
// foundation plugin API. Covers the DACH long tail (IONOS, Telekom,
|
|
5
|
+
// Strato, GMX/Web.de, mailbox.org, Hetzner, all-inkl) via password/
|
|
6
|
+
// app-password authentication, without OAuth. XOAUTH2 is available as
|
|
7
|
+
// a fallback for M365/Gmail accounts (access token from the secret slot;
|
|
8
|
+
// token refresh is handled by the OAuth providers).
|
|
9
9
|
//
|
|
10
|
-
// **
|
|
11
|
-
// 1. Plugin
|
|
10
|
+
// **What this feature provides:**
|
|
11
|
+
// 1. Plugin registration via r.useExtension("inboundMailProvider",
|
|
12
12
|
// "imap", { verify, fetch, watch }).
|
|
13
|
-
// 2. verify: connect+logout
|
|
14
|
-
// 3. fetch:
|
|
15
|
-
// UIDVALIDITY
|
|
16
|
-
//
|
|
17
|
-
// SEARCH since=backfillWindowDays.
|
|
18
|
-
// 4. watch: IMAP IDLE via imapflow — 'exists'
|
|
19
|
-
//
|
|
20
|
-
//
|
|
13
|
+
// 2. verify: connect+logout with typed error mapping.
|
|
14
|
+
// 3. fetch: incremental sync using a UIDVALIDITY:lastUid cursor —
|
|
15
|
+
// a UIDVALIDITY change throws InboundCursorInvalidError (the
|
|
16
|
+
// foundation resets and resyncs within the backfill window). Initial
|
|
17
|
+
// population uses SEARCH since=backfillWindowDays.
|
|
18
|
+
// 4. watch: IMAP IDLE via imapflow — the 'exists' event triggers fetching
|
|
19
|
+
// new UIDs and then onMessages; connection loss triggers onError (the
|
|
20
|
+
// supervisor restarts with backoff; the provider does not reconnect).
|
|
21
21
|
//
|
|
22
|
-
// Credentials: JSON
|
|
23
|
-
// (credential-document.ts)
|
|
24
|
-
//
|
|
22
|
+
// Credentials: JSON document in the per-account secret slot
|
|
23
|
+
// (credential-document.ts), not tenant configuration, because one tenant
|
|
24
|
+
// can have multiple accounts with different hosts.
|
|
25
25
|
|
|
26
26
|
import { mailAllowedPrivateHostsEnvSchema } from "@cosmicdrift/kumiko-bundled-features/foundation-shared";
|
|
27
27
|
import {
|
|
@@ -102,17 +102,17 @@ async function fetchMessages(
|
|
|
102
102
|
const parsedCursor = parseImapCursor(cursor);
|
|
103
103
|
assertUidValidity(parsedCursor, serverUidValidity);
|
|
104
104
|
|
|
105
|
-
// UID
|
|
106
|
-
//
|
|
105
|
+
// Determine the UID set: incrementally from lastUid+1, or use a backfill
|
|
106
|
+
// via SEARCH since (the window limits the initial population).
|
|
107
107
|
let uids: number[];
|
|
108
108
|
if (parsedCursor) {
|
|
109
109
|
const found = await client.search({ uid: `${parsedCursor.lastUid + 1}:*` }, { uid: true });
|
|
110
|
-
// `N
|
|
111
|
-
//
|
|
110
|
+
// `N:*` gotcha: when N exceeds the highest UID, IMAP still returns the
|
|
111
|
+
// last message, so filter it out.
|
|
112
112
|
uids = (found || []).filter((uid) => uid > parsedCursor.lastUid);
|
|
113
113
|
} else {
|
|
114
114
|
const since = Temporal.Now.instant().subtract({ hours: opts.backfillWindowDays * 24 });
|
|
115
|
-
// imapflow
|
|
115
|
+
// imapflow API boundary: search() requires a JS Date.
|
|
116
116
|
uids = (await client.search({ since: instantToLegacyDate(since) }, { uid: true })) || [];
|
|
117
117
|
}
|
|
118
118
|
uids.sort((a, b) => a - b);
|
|
@@ -155,8 +155,8 @@ async function fetchMessages(
|
|
|
155
155
|
}
|
|
156
156
|
|
|
157
157
|
// =============================================================================
|
|
158
|
-
// watch — IMAP IDLE (imapflow
|
|
159
|
-
//
|
|
158
|
+
// watch — IMAP IDLE (imapflow idles automatically while the mailbox is open
|
|
159
|
+
// and emits 'exists' for new messages).
|
|
160
160
|
// =============================================================================
|
|
161
161
|
|
|
162
162
|
async function watchMailbox(
|
|
@@ -175,7 +175,8 @@ async function watchMailbox(
|
|
|
175
175
|
await client.connect();
|
|
176
176
|
const mailbox = (await client.mailboxOpen(IMAP_MAILBOX)) as MailboxObject;
|
|
177
177
|
const uidValidity = String(mailbox.uidValidity);
|
|
178
|
-
// uidNext
|
|
178
|
+
// uidNext is the next UID to be assigned, so everything from here is
|
|
179
|
+
// "new".
|
|
179
180
|
let nextUid = Number(mailbox.uidNext ?? 1);
|
|
180
181
|
|
|
181
182
|
const drainNew = async () => {
|
|
@@ -186,7 +187,7 @@ async function watchMailbox(
|
|
|
186
187
|
{ uid: true, internalDate: true, source: true },
|
|
187
188
|
{ uid: true },
|
|
188
189
|
)) {
|
|
189
|
-
// `N
|
|
190
|
+
// `N:*` gotcha (see fetch): filter out the previous last message.
|
|
190
191
|
if (msg.uid < nextUid || !msg.source) continue;
|
|
191
192
|
messages.push(
|
|
192
193
|
await toRawInboundMessage({
|
|
@@ -203,14 +204,14 @@ async function watchMailbox(
|
|
|
203
204
|
};
|
|
204
205
|
|
|
205
206
|
client.on("exists", () => {
|
|
206
|
-
// skip:
|
|
207
|
+
// skip: ignore delayed exists events after the watcher has stopped.
|
|
207
208
|
if (stopped) return;
|
|
208
209
|
void drainNew().catch((err) => {
|
|
209
210
|
if (!stopped) handlers.onError(mapImapError(err, doc.host));
|
|
210
211
|
});
|
|
211
212
|
});
|
|
212
213
|
const die = (err: unknown) => {
|
|
213
|
-
// skip:
|
|
214
|
+
// skip: already stopped or terminated; fire onError only once.
|
|
214
215
|
if (stopped) return;
|
|
215
216
|
stopped = true;
|
|
216
217
|
handlers.onError(mapImapError(err, doc.host));
|
|
@@ -218,17 +219,18 @@ async function watchMailbox(
|
|
|
218
219
|
client.on("error", die);
|
|
219
220
|
client.on("close", () => die(new Error("connection closed")));
|
|
220
221
|
|
|
221
|
-
// IDLE
|
|
222
|
-
// (
|
|
223
|
-
//
|
|
224
|
-
//
|
|
222
|
+
// IDLE loop: 'exists' fires ONLY while client.idle() is running
|
|
223
|
+
// (verified empirically against greenmail). Each intervening command
|
|
224
|
+
// (drainNew-fetch) ends idle(), so the loop re-enters idle() until
|
|
225
|
+
// stop() closes the connection.
|
|
225
226
|
void (async () => {
|
|
226
227
|
while (!stopped) {
|
|
227
228
|
try {
|
|
228
229
|
await client.idle();
|
|
229
230
|
} catch (err) {
|
|
230
231
|
if (!stopped) die(err);
|
|
231
|
-
// skip: idle
|
|
232
|
+
// skip: the idle loop ends; die() has informed the supervisor (or stop()
|
|
233
|
+
// was the cause).
|
|
232
234
|
return;
|
|
233
235
|
}
|
|
234
236
|
}
|
|
@@ -251,8 +253,8 @@ async function watchMailbox(
|
|
|
251
253
|
// Plugin + Feature-definition
|
|
252
254
|
// =============================================================================
|
|
253
255
|
|
|
254
|
-
/**
|
|
255
|
-
*
|
|
256
|
+
/** Exported for the live test (imap-live.integration.test.ts) —
|
|
257
|
+
* applications use the feature, never the plugin directly. */
|
|
256
258
|
export const imapInboundMailPlugin: InboundMailProviderPlugin = {
|
|
257
259
|
verify: async (ctx, account) => {
|
|
258
260
|
const doc = await readCredentialDocument(ctx, account);
|
|
@@ -267,9 +269,9 @@ export const imapInboundMailPlugin: InboundMailProviderPlugin = {
|
|
|
267
269
|
},
|
|
268
270
|
fetch: fetchMessages,
|
|
269
271
|
watch: watchMailbox,
|
|
270
|
-
//
|
|
271
|
-
//
|
|
272
|
-
//
|
|
272
|
+
// No oauth block: password authentication runs through the credential
|
|
273
|
+
// form (connect-account + Secret-Write); XOAUTH2 token flows belong to
|
|
274
|
+
// the OAuth providers (M365/Gmail).
|
|
273
275
|
};
|
|
274
276
|
|
|
275
277
|
export function createInboundProviderImapFeature() {
|