@openlup/platform 0.15.0 → 0.17.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/CHANGELOG.md +124 -0
- package/README.md +633 -6
- package/dist/communications/accountingDocumentDelivery.d.ts +35 -0
- package/dist/communications/accountingDocumentDelivery.js +33 -0
- package/dist/communications/authEmailError.d.ts +5 -0
- package/dist/communications/authEmailError.js +8 -0
- package/dist/communications/authEmailPorts.d.ts +2 -0
- package/dist/communications/authEmailPorts.js +1 -0
- package/dist/communications/authSendEmailHook.d.ts +35 -0
- package/dist/communications/authSendEmailHook.js +250 -0
- package/dist/communications/capturedTransactionalDelivery.d.ts +13 -0
- package/dist/communications/capturedTransactionalDelivery.js +14 -0
- package/dist/communications/communicationSync.d.ts +11 -0
- package/dist/communications/communicationSync.js +79 -0
- package/dist/communications/communicationSyncWorker.d.ts +68 -0
- package/dist/communications/communicationSyncWorker.js +218 -0
- package/dist/communications/consentPolicy.d.ts +19 -0
- package/dist/communications/consentPolicy.js +43 -0
- package/dist/communications/consentPurposeRegistry.d.ts +4 -0
- package/dist/communications/consentPurposeRegistry.js +24 -0
- package/dist/communications/contracts/abandonedCart.d.ts +27 -0
- package/dist/communications/contracts/abandonedCart.js +25 -0
- package/dist/communications/contracts/authEmail.d.ts +62 -0
- package/dist/communications/contracts/authEmail.js +1 -0
- package/dist/communications/contracts/backInStock.d.ts +28 -0
- package/dist/communications/contracts/backInStock.js +28 -0
- package/dist/communications/contracts/blocks.d.ts +24 -0
- package/dist/communications/contracts/blocks.js +23 -0
- package/dist/communications/contracts/checkoutExpired.d.ts +22 -0
- package/dist/communications/contracts/checkoutExpired.js +25 -0
- package/dist/communications/contracts/checkoutRecovery.d.ts +26 -0
- package/dist/communications/contracts/checkoutRecovery.js +37 -0
- package/dist/communications/contracts/commerceEmailContent.d.ts +176 -0
- package/dist/communications/contracts/commerceEmailContent.js +1 -0
- package/dist/communications/contracts/commerceEngagementEmailContent.d.ts +95 -0
- package/dist/communications/contracts/commerceEngagementEmailContent.js +1 -0
- package/dist/communications/contracts/consent.d.ts +12 -0
- package/dist/communications/contracts/consent.js +1 -0
- package/dist/communications/contracts/email.d.ts +182 -0
- package/dist/communications/contracts/email.js +1 -0
- package/dist/communications/contracts/guideCard.d.ts +3 -0
- package/dist/communications/contracts/guideCard.js +26 -0
- package/dist/communications/contracts/index.d.ts +51 -0
- package/dist/communications/contracts/index.js +42 -0
- package/dist/communications/contracts/inquiryContent.d.ts +32 -0
- package/dist/communications/contracts/inquiryContent.js +33 -0
- package/dist/communications/contracts/invoiceDocumentContent.d.ts +26 -0
- package/dist/communications/contracts/invoiceDocumentContent.js +36 -0
- package/dist/communications/contracts/marketingAutomation.d.ts +52 -0
- package/dist/communications/contracts/marketingAutomation.js +1 -0
- package/dist/communications/contracts/newsletter.d.ts +63 -0
- package/dist/communications/contracts/newsletter.js +1 -0
- package/dist/communications/contracts/newsletterWebhook.d.ts +26 -0
- package/dist/communications/contracts/newsletterWebhook.js +1 -0
- package/dist/communications/contracts/optOutContent.d.ts +11 -0
- package/dist/communications/contracts/optOutContent.js +17 -0
- package/dist/communications/contracts/orderCanceled.d.ts +25 -0
- package/dist/communications/contracts/orderCanceled.js +27 -0
- package/dist/communications/contracts/orderDraft.d.ts +46 -0
- package/dist/communications/contracts/orderDraft.js +46 -0
- package/dist/communications/contracts/orderPaid.d.ts +53 -0
- package/dist/communications/contracts/orderPaid.js +71 -0
- package/dist/communications/contracts/orderRefunded.d.ts +24 -0
- package/dist/communications/contracts/orderRefunded.js +27 -0
- package/dist/communications/contracts/paymentFailed.d.ts +24 -0
- package/dist/communications/contracts/paymentFailed.js +24 -0
- package/dist/communications/contracts/publicIntake.d.ts +49 -0
- package/dist/communications/contracts/publicIntake.js +1 -0
- package/dist/communications/contracts/render.d.ts +2 -0
- package/dist/communications/contracts/render.js +229 -0
- package/dist/communications/contracts/reorderReminder.d.ts +24 -0
- package/dist/communications/contracts/reorderReminder.js +24 -0
- package/dist/communications/contracts/returnEmails.d.ts +25 -0
- package/dist/communications/contracts/returnEmails.js +62 -0
- package/dist/communications/contracts/reviewEffects.d.ts +26 -0
- package/dist/communications/contracts/reviewEffects.js +25 -0
- package/dist/communications/contracts/reviewRequest.d.ts +26 -0
- package/dist/communications/contracts/reviewRequest.js +24 -0
- package/dist/communications/contracts/roleNotificationContent.d.ts +25 -0
- package/dist/communications/contracts/roleNotificationContent.js +46 -0
- package/dist/communications/contracts/shipmentDelivered.d.ts +23 -0
- package/dist/communications/contracts/shipmentDelivered.js +24 -0
- package/dist/communications/contracts/shipmentDispatched.d.ts +27 -0
- package/dist/communications/contracts/shipmentDispatched.js +35 -0
- package/dist/communications/contracts/shipmentException.d.ts +21 -0
- package/dist/communications/contracts/shipmentException.js +26 -0
- package/dist/communications/contracts/subscriptionActivationActionRequired.d.ts +17 -0
- package/dist/communications/contracts/subscriptionActivationActionRequired.js +22 -0
- package/dist/communications/contracts/subscriptionAddressChanged.d.ts +19 -0
- package/dist/communications/contracts/subscriptionAddressChanged.js +25 -0
- package/dist/communications/contracts/subscriptionCancelled.d.ts +19 -0
- package/dist/communications/contracts/subscriptionCancelled.js +25 -0
- package/dist/communications/contracts/subscriptionCycleSkipped.d.ts +25 -0
- package/dist/communications/contracts/subscriptionCycleSkipped.js +40 -0
- package/dist/communications/contracts/subscriptionDeliveryRescheduled.d.ts +25 -0
- package/dist/communications/contracts/subscriptionDeliveryRescheduled.js +39 -0
- package/dist/communications/contracts/subscriptionEmailContent.d.ts +145 -0
- package/dist/communications/contracts/subscriptionEmailContent.js +1 -0
- package/dist/communications/contracts/subscriptionPackageChanged.d.ts +20 -0
- package/dist/communications/contracts/subscriptionPackageChanged.js +26 -0
- package/dist/communications/contracts/subscriptionPauseReminder.d.ts +23 -0
- package/dist/communications/contracts/subscriptionPauseReminder.js +29 -0
- package/dist/communications/contracts/subscriptionPaused.d.ts +21 -0
- package/dist/communications/contracts/subscriptionPaused.js +25 -0
- package/dist/communications/contracts/subscriptionPaymentExpired.d.ts +34 -0
- package/dist/communications/contracts/subscriptionPaymentExpired.js +37 -0
- package/dist/communications/contracts/subscriptionPaymentFailed.d.ts +49 -0
- package/dist/communications/contracts/subscriptionPaymentFailed.js +56 -0
- package/dist/communications/contracts/subscriptionPaymentRecovered.d.ts +19 -0
- package/dist/communications/contracts/subscriptionPaymentRecovered.js +22 -0
- package/dist/communications/contracts/subscriptionRenewalAtRisk.d.ts +36 -0
- package/dist/communications/contracts/subscriptionRenewalAtRisk.js +36 -0
- package/dist/communications/contracts/subscriptionRenewalUpcoming.d.ts +49 -0
- package/dist/communications/contracts/subscriptionRenewalUpcoming.js +43 -0
- package/dist/communications/contracts/subscriptionResumed.d.ts +23 -0
- package/dist/communications/contracts/subscriptionResumed.js +25 -0
- package/dist/communications/contracts/subscriptionWelcome.d.ts +44 -0
- package/dist/communications/contracts/subscriptionWelcome.js +45 -0
- package/dist/communications/contracts/subscriptionWinback.d.ts +21 -0
- package/dist/communications/contracts/subscriptionWinback.js +28 -0
- package/dist/communications/contracts/transactionalDelivery.d.ts +42 -0
- package/dist/communications/contracts/transactionalDelivery.js +1 -0
- package/dist/communications/contracts/types.d.ts +46 -0
- package/dist/communications/contracts/types.js +1 -0
- package/dist/communications/contribution.d.ts +10 -0
- package/dist/communications/contribution.js +24 -0
- package/dist/communications/deliveryReconciliation.d.ts +64 -0
- package/dist/communications/deliveryReconciliation.js +60 -0
- package/dist/communications/emailEgress.d.ts +38 -0
- package/dist/communications/emailEgress.js +100 -0
- package/dist/communications/emailHealthAssessment.d.ts +39 -0
- package/dist/communications/emailHealthAssessment.js +59 -0
- package/dist/communications/emailHealthMetricsPort.d.ts +17 -0
- package/dist/communications/emailHealthMetricsPort.js +1 -0
- package/dist/communications/emailNotificationControl.d.ts +27 -0
- package/dist/communications/emailNotificationControl.js +10 -0
- package/dist/communications/emailObservation.d.ts +28 -0
- package/dist/communications/emailObservation.js +23 -0
- package/dist/communications/emailTransport.d.ts +82 -0
- package/dist/communications/emailTransport.js +95 -0
- package/dist/communications/errors.d.ts +15 -0
- package/dist/communications/errors.js +30 -0
- package/dist/communications/index.d.ts +46 -0
- package/dist/communications/index.js +38 -0
- package/dist/communications/integrationEventSignature.d.ts +15 -0
- package/dist/communications/integrationEventSignature.js +37 -0
- package/dist/communications/managedTransactionalDeliveryStore.d.ts +12 -0
- package/dist/communications/managedTransactionalDeliveryStore.js +49 -0
- package/dist/communications/nativeOperations.d.ts +79 -0
- package/dist/communications/nativeOperations.js +3 -0
- package/dist/communications/newsletterIntegrationContracts.d.ts +130 -0
- package/dist/communications/newsletterIntegrationContracts.js +128 -0
- package/dist/communications/newsletterWebhookPort.d.ts +11 -0
- package/dist/communications/newsletterWebhookPort.js +84 -0
- package/dist/communications/newsletterWebhookProcessing.d.ts +17 -0
- package/dist/communications/newsletterWebhookProcessing.js +71 -0
- package/dist/communications/originPolicy.d.ts +40 -0
- package/dist/communications/originPolicy.js +100 -0
- package/dist/communications/postgresCommunicationSync.d.ts +79 -0
- package/dist/communications/postgresCommunicationSync.js +111 -0
- package/dist/communications/postgresConsentEvaluator.d.ts +19 -0
- package/dist/communications/postgresConsentEvaluator.js +43 -0
- package/dist/communications/postgresDeliveryReconciliation.d.ts +3 -0
- package/dist/communications/postgresDeliveryReconciliation.js +113 -0
- package/dist/communications/postgresNativeOperations.d.ts +13 -0
- package/dist/communications/postgresNativeOperations.js +63 -0
- package/dist/communications/postgresNotificationRecipients.d.ts +37 -0
- package/dist/communications/postgresNotificationRecipients.js +60 -0
- package/dist/communications/postgresTransactionalDeliveryStore.d.ts +7 -0
- package/dist/communications/postgresTransactionalDeliveryStore.js +55 -0
- package/dist/communications/preferencesToken.d.ts +10 -0
- package/dist/communications/preferencesToken.js +40 -0
- package/dist/communications/publicPreferenceToken.d.ts +24 -0
- package/dist/communications/publicPreferenceToken.js +53 -0
- package/dist/communications/shipmentNotifications.d.ts +34 -0
- package/dist/communications/shipmentNotifications.js +61 -0
- package/dist/communications/sql.d.ts +26 -0
- package/dist/communications/sql.js +7 -0
- package/dist/communications/standardWebhooks.d.ts +14 -0
- package/dist/communications/standardWebhooks.js +73 -0
- package/dist/communications/status.d.ts +11 -0
- package/dist/communications/status.js +36 -0
- package/dist/communications/transactionalDelivery.d.ts +19 -0
- package/dist/communications/transactionalDelivery.js +96 -0
- package/dist/communications/transactionalDeliveryIdentifier.d.ts +3 -0
- package/dist/communications/transactionalDeliveryIdentifier.js +6 -0
- package/dist/communications/unsubscribeToken.d.ts +77 -0
- package/dist/communications/unsubscribeToken.js +150 -0
- package/dist/communications/validation.d.ts +2 -0
- package/dist/communications/validation.js +3 -0
- package/dist/http/index.d.ts +1 -0
- package/dist/http/index.js +1 -0
- package/dist/http/webhookSignature.d.ts +42 -0
- package/dist/http/webhookSignature.js +86 -0
- package/dist/identity/adminAuthBinding.d.ts +28 -0
- package/dist/identity/adminAuthBinding.js +66 -0
- package/dist/identity/adminAuthorization.d.ts +34 -0
- package/dist/identity/adminAuthorization.js +72 -0
- package/dist/identity/adminContracts.d.ts +70 -0
- package/dist/identity/adminContracts.js +43 -0
- package/dist/identity/adminMagicLinkHandler.d.ts +39 -0
- package/dist/identity/adminMagicLinkHandler.js +94 -0
- package/dist/identity/adminMeHandler.d.ts +15 -0
- package/dist/identity/adminMeHandler.js +49 -0
- package/dist/identity/adminPreflight.d.ts +16 -0
- package/dist/identity/adminPreflight.js +38 -0
- package/dist/identity/adminUserActionPorts.d.ts +31 -0
- package/dist/identity/adminUserActionPorts.js +51 -0
- package/dist/identity/adminUserHandlers.d.ts +38 -0
- package/dist/identity/adminUserHandlers.js +140 -0
- package/dist/identity/authVerifyUrl.d.ts +6 -0
- package/dist/identity/authVerifyUrl.js +54 -0
- package/dist/identity/contracts/contracts.d.ts +8 -0
- package/dist/identity/contracts/contracts.js +2 -0
- package/dist/identity/contracts/emailTrust.d.ts +3 -0
- package/dist/identity/contracts/emailTrust.js +4 -0
- package/dist/identity/contracts/index.d.ts +8 -0
- package/dist/identity/contracts/index.js +8 -0
- package/dist/identity/contracts/oidc.d.ts +3 -0
- package/dist/identity/contracts/oidc.js +9 -0
- package/dist/identity/contracts/operatorAuth.d.ts +35 -0
- package/dist/identity/contracts/operatorAuth.js +53 -0
- package/dist/identity/contracts/operatorInvitations.d.ts +37 -0
- package/dist/identity/contracts/operatorInvitations.js +1 -0
- package/dist/identity/contracts/ports.d.ts +22 -0
- package/dist/identity/contracts/ports.js +1 -0
- package/dist/identity/contracts/privacy.d.ts +62 -0
- package/dist/identity/contracts/privacy.js +99 -0
- package/dist/identity/contracts/providerList.d.ts +3 -0
- package/dist/identity/contracts/providerList.js +11 -0
- package/dist/identity/contracts/types.d.ts +16 -0
- package/dist/identity/contracts/types.js +4 -0
- package/dist/identity/contribution.d.ts +8 -0
- package/dist/identity/contribution.js +22 -0
- package/dist/identity/customerAuth.d.ts +8 -0
- package/dist/identity/customerAuth.js +1 -0
- package/dist/identity/customerAuthorization.d.ts +5 -0
- package/dist/identity/customerAuthorization.js +22 -0
- package/dist/identity/customerContracts.d.ts +42 -0
- package/dist/identity/customerContracts.js +43 -0
- package/dist/identity/customerIdentityBinding.d.ts +44 -0
- package/dist/identity/customerIdentityBinding.js +60 -0
- package/dist/identity/customerIdentityVerifier.d.ts +8 -0
- package/dist/identity/customerIdentityVerifier.js +101 -0
- package/dist/identity/customerMagicLinkHandler.d.ts +38 -0
- package/dist/identity/customerMagicLinkHandler.js +115 -0
- package/dist/identity/customerMeHandler.d.ts +11 -0
- package/dist/identity/customerMeHandler.js +46 -0
- package/dist/identity/customerPorts.d.ts +4 -0
- package/dist/identity/customerPorts.js +1 -0
- package/dist/identity/customerSessionBinding.d.ts +38 -0
- package/dist/identity/customerSessionBinding.js +80 -0
- package/dist/identity/customerSessionIssuer.d.ts +10 -0
- package/dist/identity/customerSessionIssuer.js +57 -0
- package/dist/identity/customerVerifyOtpHandler.d.ts +8 -0
- package/dist/identity/customerVerifyOtpHandler.js +41 -0
- package/dist/identity/directOperatorInvitationStore.d.ts +19 -0
- package/dist/identity/directOperatorInvitationStore.js +193 -0
- package/dist/identity/emailTrustPolicy.d.ts +14 -0
- package/dist/identity/emailTrustPolicy.js +16 -0
- package/dist/identity/identityPersonalDataContributor.d.ts +7 -0
- package/dist/identity/identityPersonalDataContributor.js +46 -0
- package/dist/identity/identityReadiness.d.ts +10 -0
- package/dist/identity/identityReadiness.js +25 -0
- package/dist/identity/index.d.ts +67 -0
- package/dist/identity/index.js +66 -0
- package/dist/identity/inviteAdminUserUseCase.d.ts +89 -0
- package/dist/identity/inviteAdminUserUseCase.js +75 -0
- package/dist/identity/jwtLibrary.d.ts +54 -0
- package/dist/identity/jwtLibrary.js +1 -0
- package/dist/identity/machineActorWriteFence.d.ts +4 -0
- package/dist/identity/machineActorWriteFence.js +13 -0
- package/dist/identity/magicLinkPorts.d.ts +28 -0
- package/dist/identity/magicLinkPorts.js +1 -0
- package/dist/identity/managedAccountLinkStore.d.ts +2 -0
- package/dist/identity/managedAccountLinkStore.js +93 -0
- package/dist/identity/managedAdminAuthVerifier.d.ts +7 -0
- package/dist/identity/managedAdminAuthVerifier.js +27 -0
- package/dist/identity/managedAdminUserGateways.d.ts +123 -0
- package/dist/identity/managedAdminUserGateways.js +127 -0
- package/dist/identity/managedClient.d.ts +90 -0
- package/dist/identity/managedClient.js +10 -0
- package/dist/identity/managedClientAuthLink.d.ts +3 -0
- package/dist/identity/managedClientAuthLink.js +115 -0
- package/dist/identity/managedCustomerMe.d.ts +13 -0
- package/dist/identity/managedCustomerMe.js +30 -0
- package/dist/identity/managedEmailTrustPolicy.d.ts +11 -0
- package/dist/identity/managedEmailTrustPolicy.js +105 -0
- package/dist/identity/managedIdentityVerifier.d.ts +2 -0
- package/dist/identity/managedIdentityVerifier.js +16 -0
- package/dist/identity/managedPrivacy.d.ts +4 -0
- package/dist/identity/managedPrivacy.js +39 -0
- package/dist/identity/messages.d.ts +29 -0
- package/dist/identity/messages.js +39 -0
- package/dist/identity/oidcIdentityVerifier.d.ts +45 -0
- package/dist/identity/oidcIdentityVerifier.js +309 -0
- package/dist/identity/oidcPrincipal.d.ts +2 -0
- package/dist/identity/oidcPrincipal.js +10 -0
- package/dist/identity/operatorAdminAuth.d.ts +6 -0
- package/dist/identity/operatorAdminAuth.js +13 -0
- package/dist/identity/operatorAuthHandlers.d.ts +18 -0
- package/dist/identity/operatorAuthHandlers.js +71 -0
- package/dist/identity/operatorIdentityVerifier.d.ts +8 -0
- package/dist/identity/operatorIdentityVerifier.js +19 -0
- package/dist/identity/operatorInvitationLifecycle.d.ts +7 -0
- package/dist/identity/operatorInvitationLifecycle.js +15 -0
- package/dist/identity/operatorInvitationPorts.d.ts +31 -0
- package/dist/identity/operatorInvitationPorts.js +1 -0
- package/dist/identity/operatorPorts.d.ts +37 -0
- package/dist/identity/operatorPorts.js +1 -0
- package/dist/identity/operatorSession.d.ts +13 -0
- package/dist/identity/operatorSession.js +31 -0
- package/dist/identity/operatorTokenAdminAuth.d.ts +15 -0
- package/dist/identity/operatorTokenAdminAuth.js +50 -0
- package/dist/identity/personalDataRegistry.d.ts +4 -0
- package/dist/identity/personalDataRegistry.js +11 -0
- package/dist/identity/platformAdminAuth.d.ts +15 -0
- package/dist/identity/platformAdminAuth.js +1 -0
- package/dist/identity/ports.d.ts +127 -0
- package/dist/identity/ports.js +38 -0
- package/dist/identity/postgresAccountLinkStore.d.ts +7 -0
- package/dist/identity/postgresAccountLinkStore.js +52 -0
- package/dist/identity/postgresCustomerMe.d.ts +3 -0
- package/dist/identity/postgresCustomerMe.js +21 -0
- package/dist/identity/postgresCustomerRecovery.d.ts +11 -0
- package/dist/identity/postgresCustomerRecovery.js +57 -0
- package/dist/identity/postgresOperatorIdentity.d.ts +5 -0
- package/dist/identity/postgresOperatorIdentity.js +137 -0
- package/dist/identity/postgresPrivacy.d.ts +7 -0
- package/dist/identity/postgresPrivacy.js +126 -0
- package/dist/identity/privacyHandlers.d.ts +10 -0
- package/dist/identity/privacyHandlers.js +35 -0
- package/dist/identity/privacyPorts.d.ts +72 -0
- package/dist/identity/privacyPorts.js +8 -0
- package/dist/identity/privacyUseCases.d.ts +5 -0
- package/dist/identity/privacyUseCases.js +55 -0
- package/dist/identity/reconcileAccountForPrincipal.d.ts +20 -0
- package/dist/identity/reconcileAccountForPrincipal.js +66 -0
- package/dist/identity/reconcileAccountHandler.d.ts +11 -0
- package/dist/identity/reconcileAccountHandler.js +30 -0
- package/dist/identity/removeAdminUserUseCase.d.ts +31 -0
- package/dist/identity/removeAdminUserUseCase.js +60 -0
- package/dist/identity/response.d.ts +3 -0
- package/dist/identity/response.js +2 -0
- package/dist/identity/sql.d.ts +44 -0
- package/dist/identity/sql.js +10 -0
- package/package.json +22 -2
- package/release-gates.json +63 -8
- package/sql/20260807190000_comms_outbox_rail.sql +329 -0
- package/sql/20260810120001_transactional_delivery_receipts.sql +209 -0
- package/sql/20260810180001_self_host_actor_auth.sql +40 -0
- package/sql/20260810204057_customer_identity_read.sql +31 -0
- package/sql/20260813120000_communications_control_plane.sql +568 -0
- package/sql/20260814090000_customer_selfservice_recovery.sql +477 -0
- package/sql/20260901143000_admin_membership_authority_foundation.sql +234 -0
- package/sql/20261018120000_identity_operator_invitations.sql +42 -0
- package/sql/20261018180000_identity_operator_challenges.sql +56 -0
- package/sql/20261018190000_identity_privacy_erasures.sql +15 -0
- package/sql/20261019200000_communications_native_operations.sql +68 -0
- package/src/communications/accountingDocumentDelivery.ts +63 -0
- package/src/communications/authEmailError.ts +10 -0
- package/src/communications/authEmailPorts.ts +2 -0
- package/src/communications/authSendEmailHook.ts +310 -0
- package/src/communications/capturedTransactionalDelivery.ts +29 -0
- package/src/communications/communicationSync.ts +96 -0
- package/src/communications/communicationSyncWorker.ts +302 -0
- package/src/communications/consentPolicy.ts +82 -0
- package/src/communications/consentPurposeRegistry.ts +31 -0
- package/src/communications/contracts/abandonedCart.ts +64 -0
- package/src/communications/contracts/authEmail.ts +73 -0
- package/src/communications/contracts/backInStock.ts +69 -0
- package/src/communications/contracts/blocks.ts +47 -0
- package/src/communications/contracts/checkoutExpired.ts +62 -0
- package/src/communications/contracts/checkoutRecovery.ts +68 -0
- package/src/communications/contracts/commerceEmailContent.ts +189 -0
- package/src/communications/contracts/commerceEngagementEmailContent.ts +106 -0
- package/src/communications/contracts/consent.ts +13 -0
- package/src/communications/contracts/email.ts +210 -0
- package/src/communications/contracts/guideCard.ts +34 -0
- package/src/communications/contracts/index.ts +51 -0
- package/src/communications/contracts/inquiryContent.ts +53 -0
- package/src/communications/contracts/invoiceDocumentContent.ts +57 -0
- package/src/communications/contracts/marketingAutomation.ts +45 -0
- package/src/communications/contracts/newsletter.ts +78 -0
- package/src/communications/contracts/newsletterWebhook.ts +23 -0
- package/src/communications/contracts/optOutContent.ts +23 -0
- package/src/communications/contracts/orderCanceled.ts +64 -0
- package/src/communications/contracts/orderDraft.ts +112 -0
- package/src/communications/contracts/orderPaid.ts +147 -0
- package/src/communications/contracts/orderRefunded.ts +63 -0
- package/src/communications/contracts/paymentFailed.ts +59 -0
- package/src/communications/contracts/publicIntake.ts +53 -0
- package/src/communications/contracts/render.ts +271 -0
- package/src/communications/contracts/reorderReminder.ts +59 -0
- package/src/communications/contracts/returnEmails.ts +113 -0
- package/src/communications/contracts/reviewEffects.ts +60 -0
- package/src/communications/contracts/reviewRequest.ts +59 -0
- package/src/communications/contracts/roleNotificationContent.ts +65 -0
- package/src/communications/contracts/shipmentDelivered.ts +59 -0
- package/src/communications/contracts/shipmentDispatched.ts +77 -0
- package/src/communications/contracts/shipmentException.ts +62 -0
- package/src/communications/contracts/subscriptionActivationActionRequired.ts +37 -0
- package/src/communications/contracts/subscriptionAddressChanged.ts +56 -0
- package/src/communications/contracts/subscriptionCancelled.ts +57 -0
- package/src/communications/contracts/subscriptionCycleSkipped.ts +80 -0
- package/src/communications/contracts/subscriptionDeliveryRescheduled.ts +79 -0
- package/src/communications/contracts/subscriptionEmailContent.ts +163 -0
- package/src/communications/contracts/subscriptionPackageChanged.ts +59 -0
- package/src/communications/contracts/subscriptionPauseReminder.ts +63 -0
- package/src/communications/contracts/subscriptionPaused.ts +58 -0
- package/src/communications/contracts/subscriptionPaymentExpired.ts +83 -0
- package/src/communications/contracts/subscriptionPaymentFailed.ts +117 -0
- package/src/communications/contracts/subscriptionPaymentRecovered.ts +50 -0
- package/src/communications/contracts/subscriptionRenewalAtRisk.ts +85 -0
- package/src/communications/contracts/subscriptionRenewalUpcoming.ts +103 -0
- package/src/communications/contracts/subscriptionResumed.ts +60 -0
- package/src/communications/contracts/subscriptionWelcome.ts +102 -0
- package/src/communications/contracts/subscriptionWinback.ts +62 -0
- package/src/communications/contracts/transactionalDelivery.ts +43 -0
- package/src/communications/contracts/types.ts +58 -0
- package/src/communications/contribution.ts +33 -0
- package/src/communications/deliveryReconciliation.ts +108 -0
- package/src/communications/emailEgress.ts +111 -0
- package/src/communications/emailHealthAssessment.ts +106 -0
- package/src/communications/emailHealthMetricsPort.ts +17 -0
- package/src/communications/emailNotificationControl.ts +28 -0
- package/src/communications/emailObservation.ts +43 -0
- package/src/communications/emailTransport.ts +167 -0
- package/src/communications/errors.ts +34 -0
- package/src/communications/index.ts +46 -0
- package/src/communications/integrationEventSignature.ts +49 -0
- package/src/communications/managedTransactionalDeliveryStore.ts +59 -0
- package/src/communications/nativeOperations.ts +40 -0
- package/src/communications/newsletterIntegrationContracts.ts +154 -0
- package/src/communications/newsletterWebhookPort.ts +104 -0
- package/src/communications/newsletterWebhookProcessing.ts +102 -0
- package/src/communications/originPolicy.ts +151 -0
- package/src/communications/postgresCommunicationSync.ts +127 -0
- package/src/communications/postgresConsentEvaluator.ts +49 -0
- package/src/communications/postgresDeliveryReconciliation.ts +101 -0
- package/src/communications/postgresNativeOperations.ts +55 -0
- package/src/communications/postgresNotificationRecipients.ts +57 -0
- package/src/communications/postgresTransactionalDeliveryStore.ts +79 -0
- package/src/communications/preferencesToken.ts +62 -0
- package/src/communications/publicPreferenceToken.ts +68 -0
- package/src/communications/shipmentNotifications.ts +78 -0
- package/src/communications/sql.ts +7 -0
- package/src/communications/standardWebhooks.ts +84 -0
- package/src/communications/status.ts +51 -0
- package/src/communications/transactionalDelivery.ts +114 -0
- package/src/communications/transactionalDeliveryIdentifier.ts +9 -0
- package/src/communications/unsubscribeToken.ts +186 -0
- package/src/communications/validation.ts +3 -0
- package/src/http/index.ts +1 -0
- package/src/http/webhookSignature.ts +96 -0
- package/src/identity/adminAuthBinding.ts +76 -0
- package/src/identity/adminAuthorization.ts +106 -0
- package/src/identity/adminContracts.ts +52 -0
- package/src/identity/adminMagicLinkHandler.ts +132 -0
- package/src/identity/adminMeHandler.ts +64 -0
- package/src/identity/adminPreflight.ts +46 -0
- package/src/identity/adminUserActionPorts.ts +78 -0
- package/src/identity/adminUserHandlers.ts +174 -0
- package/src/identity/authVerifyUrl.ts +56 -0
- package/src/identity/contracts/contracts.ts +15 -0
- package/src/identity/contracts/emailTrust.ts +5 -0
- package/src/identity/contracts/index.ts +8 -0
- package/src/identity/contracts/oidc.ts +10 -0
- package/src/identity/contracts/operatorAuth.ts +61 -0
- package/src/identity/contracts/operatorInvitations.ts +32 -0
- package/src/identity/contracts/ports.ts +25 -0
- package/src/identity/contracts/privacy.ts +86 -0
- package/src/identity/contracts/providerList.ts +13 -0
- package/src/identity/contracts/types.ts +25 -0
- package/src/identity/contribution.ts +29 -0
- package/src/identity/customerAuth.ts +3 -0
- package/src/identity/customerAuthorization.ts +38 -0
- package/src/identity/customerContracts.ts +52 -0
- package/src/identity/customerIdentityBinding.ts +125 -0
- package/src/identity/customerIdentityVerifier.ts +141 -0
- package/src/identity/customerMagicLinkHandler.ts +171 -0
- package/src/identity/customerMeHandler.ts +64 -0
- package/src/identity/customerPorts.ts +2 -0
- package/src/identity/customerSessionBinding.ts +114 -0
- package/src/identity/customerSessionIssuer.ts +71 -0
- package/src/identity/customerVerifyOtpHandler.ts +44 -0
- package/src/identity/directOperatorInvitationStore.ts +178 -0
- package/src/identity/emailTrustPolicy.ts +16 -0
- package/src/identity/identityPersonalDataContributor.ts +48 -0
- package/src/identity/identityReadiness.ts +26 -0
- package/src/identity/index.ts +69 -0
- package/src/identity/inviteAdminUserUseCase.ts +134 -0
- package/src/identity/jwtLibrary.ts +24 -0
- package/src/identity/machineActorWriteFence.ts +19 -0
- package/src/identity/magicLinkPorts.ts +7 -0
- package/src/identity/managedAccountLinkStore.ts +105 -0
- package/src/identity/managedAdminAuthVerifier.ts +40 -0
- package/src/identity/managedAdminUserGateways.ts +164 -0
- package/src/identity/managedClient.ts +58 -0
- package/src/identity/managedClientAuthLink.ts +120 -0
- package/src/identity/managedCustomerMe.ts +52 -0
- package/src/identity/managedEmailTrustPolicy.ts +90 -0
- package/src/identity/managedIdentityVerifier.ts +20 -0
- package/src/identity/managedPrivacy.ts +37 -0
- package/src/identity/messages.ts +41 -0
- package/src/identity/oidcIdentityVerifier.ts +229 -0
- package/src/identity/oidcPrincipal.ts +10 -0
- package/src/identity/operatorAdminAuth.ts +14 -0
- package/src/identity/operatorAuthHandlers.ts +52 -0
- package/src/identity/operatorIdentityVerifier.ts +19 -0
- package/src/identity/operatorInvitationLifecycle.ts +14 -0
- package/src/identity/operatorInvitationPorts.ts +11 -0
- package/src/identity/operatorPorts.ts +11 -0
- package/src/identity/operatorSession.ts +30 -0
- package/src/identity/operatorTokenAdminAuth.ts +79 -0
- package/src/identity/personalDataRegistry.ts +8 -0
- package/src/identity/platformAdminAuth.ts +7 -0
- package/src/identity/ports.ts +158 -0
- package/src/identity/postgresAccountLinkStore.ts +49 -0
- package/src/identity/postgresCustomerMe.ts +33 -0
- package/src/identity/postgresCustomerRecovery.ts +72 -0
- package/src/identity/postgresOperatorIdentity.ts +123 -0
- package/src/identity/postgresPrivacy.ts +117 -0
- package/src/identity/privacyHandlers.ts +33 -0
- package/src/identity/privacyPorts.ts +52 -0
- package/src/identity/privacyUseCases.ts +48 -0
- package/src/identity/reconcileAccountForPrincipal.ts +99 -0
- package/src/identity/reconcileAccountHandler.ts +36 -0
- package/src/identity/removeAdminUserUseCase.ts +85 -0
- package/src/identity/response.ts +3 -0
- package/src/identity/sql.ts +10 -0
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import { z } from "./validation.js";
|
|
2
|
+
export const NEWSLETTER_MARKETING_PURPOSES = [
|
|
3
|
+
"marketing_launch_offer",
|
|
4
|
+
"marketing_newsletter",
|
|
5
|
+
];
|
|
6
|
+
export const NEWSLETTER_SYNC_EVENT_TYPES = [
|
|
7
|
+
"subscribe",
|
|
8
|
+
"update",
|
|
9
|
+
"unsubscribe",
|
|
10
|
+
"suppress",
|
|
11
|
+
];
|
|
12
|
+
export const NEWSLETTER_WEBHOOK_EVENT_TYPES = [
|
|
13
|
+
...NEWSLETTER_SYNC_EVENT_TYPES,
|
|
14
|
+
"complaint",
|
|
15
|
+
"bounce",
|
|
16
|
+
"email_change",
|
|
17
|
+
"profile_update",
|
|
18
|
+
"ignored",
|
|
19
|
+
];
|
|
20
|
+
export const NEWSLETTER_PROVIDER_CAPABILITY_KEYS = [
|
|
21
|
+
"subscribe",
|
|
22
|
+
"unsubscribe",
|
|
23
|
+
"suppress",
|
|
24
|
+
"unsuppress",
|
|
25
|
+
"doubleOptIn",
|
|
26
|
+
"emailChange",
|
|
27
|
+
"listMembership",
|
|
28
|
+
"webhookIdempotency",
|
|
29
|
+
"retryAfter",
|
|
30
|
+
];
|
|
31
|
+
export const defaultNewsletterProviderCapabilities = {
|
|
32
|
+
subscribe: false,
|
|
33
|
+
unsubscribe: false,
|
|
34
|
+
suppress: false,
|
|
35
|
+
unsuppress: false,
|
|
36
|
+
doubleOptIn: false,
|
|
37
|
+
emailChange: false,
|
|
38
|
+
listMembership: false,
|
|
39
|
+
webhookIdempotency: false,
|
|
40
|
+
retryAfter: false,
|
|
41
|
+
};
|
|
42
|
+
export const fullNewsletterProviderCapabilities = {
|
|
43
|
+
subscribe: true,
|
|
44
|
+
unsubscribe: true,
|
|
45
|
+
suppress: true,
|
|
46
|
+
unsuppress: true,
|
|
47
|
+
doubleOptIn: true,
|
|
48
|
+
emailChange: true,
|
|
49
|
+
listMembership: true,
|
|
50
|
+
webhookIdempotency: true,
|
|
51
|
+
retryAfter: true,
|
|
52
|
+
};
|
|
53
|
+
export const newsletterProviderKindSchema = z
|
|
54
|
+
.string()
|
|
55
|
+
.trim()
|
|
56
|
+
.min(1)
|
|
57
|
+
.max(80)
|
|
58
|
+
.regex(/^[a-z0-9][a-z0-9_-]*$/);
|
|
59
|
+
export const newsletterMarketingPurposeSchema = z.enum(NEWSLETTER_MARKETING_PURPOSES);
|
|
60
|
+
export const newsletterSyncEventTypeSchema = z.enum(NEWSLETTER_SYNC_EVENT_TYPES);
|
|
61
|
+
export const newsletterWebhookEventTypeSchema = z.enum(NEWSLETTER_WEBHOOK_EVENT_TYPES);
|
|
62
|
+
export const newsletterProviderCapabilitiesSchema = z.object({
|
|
63
|
+
subscribe: z.boolean(),
|
|
64
|
+
unsubscribe: z.boolean(),
|
|
65
|
+
suppress: z.boolean(),
|
|
66
|
+
unsuppress: z.boolean(),
|
|
67
|
+
doubleOptIn: z.boolean(),
|
|
68
|
+
emailChange: z.boolean(),
|
|
69
|
+
listMembership: z.boolean(),
|
|
70
|
+
webhookIdempotency: z.boolean(),
|
|
71
|
+
retryAfter: z.boolean(),
|
|
72
|
+
});
|
|
73
|
+
export const canonicalNewsletterWebhookEventSchema = z.object({
|
|
74
|
+
providerKind: newsletterProviderKindSchema,
|
|
75
|
+
providerEventId: z.string().trim().min(1).max(200),
|
|
76
|
+
eventType: newsletterWebhookEventTypeSchema,
|
|
77
|
+
email: z.string().trim().email().nullable().optional(),
|
|
78
|
+
remoteProfileId: z.string().trim().min(1).max(200).nullable().optional(),
|
|
79
|
+
remoteListId: z.string().trim().min(1).max(200).nullable().optional(),
|
|
80
|
+
purpose: newsletterMarketingPurposeSchema.nullable().optional(),
|
|
81
|
+
explicitOptInEvidence: z.boolean().optional(),
|
|
82
|
+
doubleOptInStatus: z.enum(["confirmed", "pending", "unknown"]).optional(),
|
|
83
|
+
occurredAt: z.string().datetime({ offset: true }).nullable().optional(),
|
|
84
|
+
payload: z.record(z.string(), z.unknown()).optional(),
|
|
85
|
+
});
|
|
86
|
+
export function normalizeCanonicalNewsletterWebhookEvent(event) {
|
|
87
|
+
return {
|
|
88
|
+
providerKind: event.providerKind,
|
|
89
|
+
providerEventId: event.providerEventId,
|
|
90
|
+
eventType: event.eventType,
|
|
91
|
+
email: event.email ?? null,
|
|
92
|
+
remoteProfileId: event.remoteProfileId ?? null,
|
|
93
|
+
remoteListId: event.remoteListId ?? null,
|
|
94
|
+
purpose: event.purpose ?? null,
|
|
95
|
+
explicitOptInEvidence: event.explicitOptInEvidence === true,
|
|
96
|
+
doubleOptInStatus: event.doubleOptInStatus ?? "unknown",
|
|
97
|
+
occurredAt: event.occurredAt ?? null,
|
|
98
|
+
rawPayload: event.payload ?? {},
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
export function requiredNewsletterCapabilityForEvent(eventType) {
|
|
102
|
+
if (eventType === "subscribe")
|
|
103
|
+
return "subscribe";
|
|
104
|
+
if (eventType === "unsubscribe")
|
|
105
|
+
return "unsubscribe";
|
|
106
|
+
if (eventType === "suppress")
|
|
107
|
+
return "suppress";
|
|
108
|
+
return "listMembership";
|
|
109
|
+
}
|
|
110
|
+
export function supportsNewsletterSyncEvent(capabilities, eventType) {
|
|
111
|
+
return capabilities[requiredNewsletterCapabilityForEvent(eventType)] === true;
|
|
112
|
+
}
|
|
113
|
+
/** Explicit registry binding; legacy schemas above retain their two-purpose default. */
|
|
114
|
+
export function createNewsletterIntegrationSchemas(registry) {
|
|
115
|
+
const purpose = z.custom((value) => registry.has(value), "unregistered_communication_purpose");
|
|
116
|
+
return {
|
|
117
|
+
purpose,
|
|
118
|
+
webhookEvent: canonicalNewsletterWebhookEventSchema.extend({ purpose: purpose.nullable().optional() }),
|
|
119
|
+
syncEvent: z.object({
|
|
120
|
+
id: z.string().min(1), claimToken: z.string().min(1), contactId: z.string().min(1),
|
|
121
|
+
normalizedEmail: z.string().email(), eventType: newsletterSyncEventTypeSchema,
|
|
122
|
+
purpose, payload: z.record(z.string(), z.unknown()), attemptCount: z.number().int().min(0),
|
|
123
|
+
originProviderKind: newsletterProviderKindSchema.nullable().optional(),
|
|
124
|
+
originProviderEventId: z.string().nullable().optional(), remoteProfileId: z.string().nullable().optional(),
|
|
125
|
+
remoteListId: z.string().nullable().optional(),
|
|
126
|
+
}),
|
|
127
|
+
};
|
|
128
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { NewsletterWebhookPort } from "./contracts/newsletterWebhook.js";
|
|
2
|
+
export interface NewsletterWebhookRpcClient {
|
|
3
|
+
rpc(functionName: string, args: Record<string, unknown>): PromiseLike<{
|
|
4
|
+
data: unknown;
|
|
5
|
+
error: {
|
|
6
|
+
code?: string;
|
|
7
|
+
message?: string;
|
|
8
|
+
} | null;
|
|
9
|
+
}>;
|
|
10
|
+
}
|
|
11
|
+
export declare function createSupabaseNewsletterWebhookPort(client: NewsletterWebhookRpcClient): NewsletterWebhookPort;
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
export function createSupabaseNewsletterWebhookPort(client) {
|
|
2
|
+
return {
|
|
3
|
+
async recordProviderEvent(input) {
|
|
4
|
+
const { data, error } = await client.rpc("communication_record_provider_event", {
|
|
5
|
+
p_provider_kind: input.providerKind,
|
|
6
|
+
p_provider_event_id: input.providerEventId,
|
|
7
|
+
p_event_type: input.eventType,
|
|
8
|
+
p_email: input.email,
|
|
9
|
+
p_remote_profile_id: input.remoteProfileId,
|
|
10
|
+
p_purpose: input.purpose,
|
|
11
|
+
p_state: input.state,
|
|
12
|
+
p_payload: sanitizePayload(input.rawPayload),
|
|
13
|
+
p_metadata: providerEventMetadata(input),
|
|
14
|
+
p_processing_status: input.processingStatus,
|
|
15
|
+
});
|
|
16
|
+
if (error)
|
|
17
|
+
throw rpcFailure("communication_record_provider_event", error);
|
|
18
|
+
const record = isRecord(data) ? data : {};
|
|
19
|
+
return {
|
|
20
|
+
providerEventId: readString(record.providerEventId) ?? "",
|
|
21
|
+
contactId: readString(record.contactId),
|
|
22
|
+
inserted: record.inserted === true,
|
|
23
|
+
};
|
|
24
|
+
},
|
|
25
|
+
async recordPermission(input) {
|
|
26
|
+
const { error } = await client.rpc("communication_record_permission_event", {
|
|
27
|
+
p_contact_id: input.contactId,
|
|
28
|
+
p_purpose: input.purpose,
|
|
29
|
+
p_state: input.state,
|
|
30
|
+
p_source: "newsletter_provider_webhook",
|
|
31
|
+
p_source_ref: {
|
|
32
|
+
providerKind: input.providerKind,
|
|
33
|
+
providerEventId: input.providerEventId,
|
|
34
|
+
},
|
|
35
|
+
p_reason: input.reason,
|
|
36
|
+
p_metadata: {
|
|
37
|
+
...input.metadata,
|
|
38
|
+
actorType: "provider_webhook",
|
|
39
|
+
captureMethod: "newsletter_webhook",
|
|
40
|
+
originProviderKind: input.providerKind,
|
|
41
|
+
originProviderEventId: input.providerEventId,
|
|
42
|
+
},
|
|
43
|
+
p_captured_at: new Date().toISOString(),
|
|
44
|
+
p_create_sync_event: true,
|
|
45
|
+
});
|
|
46
|
+
if (error)
|
|
47
|
+
throw rpcFailure("communication_record_permission_event", error);
|
|
48
|
+
},
|
|
49
|
+
async markProviderEvent(input) {
|
|
50
|
+
const { error } = await client.rpc("communication_mark_provider_event_processed", {
|
|
51
|
+
p_provider_event_id: input.providerEventId,
|
|
52
|
+
p_processing_status: input.status,
|
|
53
|
+
p_metadata: input.metadata,
|
|
54
|
+
});
|
|
55
|
+
if (error)
|
|
56
|
+
throw rpcFailure("communication_mark_provider_event_processed", error);
|
|
57
|
+
},
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
function providerEventMetadata(input) {
|
|
61
|
+
return {
|
|
62
|
+
occurredAt: input.occurredAt,
|
|
63
|
+
explicitOptInEvidence: input.explicitOptInEvidence,
|
|
64
|
+
doubleOptInStatus: input.doubleOptInStatus,
|
|
65
|
+
remoteListId: input.remoteListId,
|
|
66
|
+
processingStatus: input.processingStatus,
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
function sanitizePayload(payload) {
|
|
70
|
+
const sanitized = { ...payload };
|
|
71
|
+
delete sanitized.apiKey;
|
|
72
|
+
delete sanitized.token;
|
|
73
|
+
delete sanitized.secret;
|
|
74
|
+
return sanitized;
|
|
75
|
+
}
|
|
76
|
+
function rpcFailure(rpcName, error) {
|
|
77
|
+
return new Error(`${rpcName}_failed: ${error.message ?? error.code ?? "unknown"}`);
|
|
78
|
+
}
|
|
79
|
+
function isRecord(value) {
|
|
80
|
+
return Boolean(value && typeof value === "object" && !Array.isArray(value));
|
|
81
|
+
}
|
|
82
|
+
function readString(value) {
|
|
83
|
+
return typeof value === "string" && value.trim() ? value.trim() : null;
|
|
84
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { NewsletterMarketingPurpose, NewsletterWebhookEvent } from "./contracts/newsletter.js";
|
|
2
|
+
import type { NewsletterWebhookPort } from "./contracts/newsletterWebhook.js";
|
|
3
|
+
import type { CommunicationPurposeRegistry } from "./contracts/consent.js";
|
|
4
|
+
export interface NewsletterWebhookProcessResult {
|
|
5
|
+
provider: string;
|
|
6
|
+
providerEventId: string;
|
|
7
|
+
replayed?: boolean;
|
|
8
|
+
status?: "processed" | "ignored";
|
|
9
|
+
reason?: string;
|
|
10
|
+
localState?: "granted" | "suppressed";
|
|
11
|
+
}
|
|
12
|
+
export declare function processNewsletterWebhookEvent<Purpose extends string = NewsletterMarketingPurpose>(input: {
|
|
13
|
+
providerKind: string;
|
|
14
|
+
event: NewsletterWebhookEvent<Purpose>;
|
|
15
|
+
port: NewsletterWebhookPort<Purpose | NewsletterMarketingPurpose>;
|
|
16
|
+
registry?: CommunicationPurposeRegistry<Purpose>;
|
|
17
|
+
}): Promise<NewsletterWebhookProcessResult>;
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
const MARKETING_PURPOSES = ["marketing_launch_offer", "marketing_newsletter"];
|
|
2
|
+
export async function processNewsletterWebhookEvent(input) {
|
|
3
|
+
const { providerKind, event, port } = input;
|
|
4
|
+
if (input.registry && event.purpose !== null && !input.registry.has(event.purpose)) {
|
|
5
|
+
throw new Error("unregistered_communication_purpose");
|
|
6
|
+
}
|
|
7
|
+
const action = localAction(event);
|
|
8
|
+
const providerEvent = await port.recordProviderEvent({
|
|
9
|
+
...event,
|
|
10
|
+
state: action.state,
|
|
11
|
+
processingStatus: action.kind === "ignore" ? "ignored" : "received",
|
|
12
|
+
});
|
|
13
|
+
if (!providerEvent.inserted) {
|
|
14
|
+
return { provider: providerKind, providerEventId: event.providerEventId, replayed: true };
|
|
15
|
+
}
|
|
16
|
+
if (action.kind === "ignore" || !providerEvent.contactId) {
|
|
17
|
+
await port.markProviderEvent({
|
|
18
|
+
providerEventId: providerEvent.providerEventId,
|
|
19
|
+
status: "ignored",
|
|
20
|
+
metadata: { reason: action.reason },
|
|
21
|
+
});
|
|
22
|
+
return {
|
|
23
|
+
provider: providerKind,
|
|
24
|
+
providerEventId: event.providerEventId,
|
|
25
|
+
status: "ignored",
|
|
26
|
+
reason: action.reason,
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
for (const purpose of event.purpose ? [event.purpose] : MARKETING_PURPOSES) {
|
|
30
|
+
await port.recordPermission({
|
|
31
|
+
contactId: providerEvent.contactId,
|
|
32
|
+
providerKind,
|
|
33
|
+
providerEventId: event.providerEventId,
|
|
34
|
+
purpose,
|
|
35
|
+
state: action.state,
|
|
36
|
+
reason: action.reason,
|
|
37
|
+
metadata: {
|
|
38
|
+
source: "newsletter_provider_webhook",
|
|
39
|
+
providerKind,
|
|
40
|
+
providerEventId: event.providerEventId,
|
|
41
|
+
remoteProfileId: event.remoteProfileId,
|
|
42
|
+
remoteListId: event.remoteListId,
|
|
43
|
+
doubleOptInStatus: event.doubleOptInStatus,
|
|
44
|
+
explicitOptInEvidence: event.explicitOptInEvidence,
|
|
45
|
+
},
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
await port.markProviderEvent({
|
|
49
|
+
providerEventId: providerEvent.providerEventId,
|
|
50
|
+
status: "processed",
|
|
51
|
+
metadata: { localState: action.state, reason: action.reason },
|
|
52
|
+
});
|
|
53
|
+
return {
|
|
54
|
+
provider: providerKind,
|
|
55
|
+
providerEventId: event.providerEventId,
|
|
56
|
+
status: "processed",
|
|
57
|
+
localState: action.state,
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
function localAction(event) {
|
|
61
|
+
if (["unsubscribe", "suppress", "complaint"].includes(event.eventType)) {
|
|
62
|
+
return { kind: "apply", state: "suppressed", reason: `provider_${event.eventType}` };
|
|
63
|
+
}
|
|
64
|
+
if (event.eventType === "subscribe") {
|
|
65
|
+
if (event.explicitOptInEvidence || event.doubleOptInStatus === "confirmed") {
|
|
66
|
+
return { kind: "apply", state: "granted", reason: "provider_verified_opt_in" };
|
|
67
|
+
}
|
|
68
|
+
return { kind: "ignore", state: null, reason: "provider_subscribe_without_verified_opt_in" };
|
|
69
|
+
}
|
|
70
|
+
return { kind: "ignore", state: null, reason: "provider_event_not_permission_mutating" };
|
|
71
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
export type EmailOriginSource = "explicit" | "default";
|
|
2
|
+
export type EmailEnvironment = "hidden_preview" | "preview" | "staging" | "production" | "production_or_default" | "unknown";
|
|
3
|
+
export type EmailOriginPolicyError = "hidden_preview_origin_required" | "hidden_preview_origin_must_not_be_production" | "invalid_email_origin";
|
|
4
|
+
export interface ResolvedEmailOrigin {
|
|
5
|
+
origin: string;
|
|
6
|
+
source: EmailOriginSource;
|
|
7
|
+
environment: EmailEnvironment;
|
|
8
|
+
production: boolean;
|
|
9
|
+
}
|
|
10
|
+
export type EmailOriginResolution = {
|
|
11
|
+
ok: true;
|
|
12
|
+
origin: string;
|
|
13
|
+
source: EmailOriginSource;
|
|
14
|
+
resolved: ResolvedEmailOrigin;
|
|
15
|
+
} | {
|
|
16
|
+
ok: false;
|
|
17
|
+
error: EmailOriginPolicyError;
|
|
18
|
+
};
|
|
19
|
+
export interface EmailOriginInput {
|
|
20
|
+
/** Brand/configured default origin used when no explicit origin is set. */
|
|
21
|
+
defaultOrigin: string;
|
|
22
|
+
/** Hosts treated as production by the policy (brand-supplied). */
|
|
23
|
+
productionEmailHosts: string[];
|
|
24
|
+
explicitBaseUrl?: string | null;
|
|
25
|
+
customerAuthRedirectOrigin?: string | null;
|
|
26
|
+
siteUrl?: string | null;
|
|
27
|
+
hiddenPreviewEnabled?: boolean;
|
|
28
|
+
environment?: string | null;
|
|
29
|
+
}
|
|
30
|
+
export declare const EMAIL_DEFAULT_ORIGIN_ENV = "EMAIL_DEFAULT_ORIGIN";
|
|
31
|
+
export declare const EMAIL_PRODUCTION_ORIGIN_HOSTS_ENV = "EMAIL_PRODUCTION_ORIGIN_HOSTS";
|
|
32
|
+
export declare const COMMS_PRODUCTION_PROJECT_REF_ENV = "COMMS_PRODUCTION_PROJECT_REF";
|
|
33
|
+
export interface EmailOriginConfiguration {
|
|
34
|
+
defaultOrigin: string;
|
|
35
|
+
productionEmailHosts: string[];
|
|
36
|
+
}
|
|
37
|
+
export declare function normalizeEmailOrigin(value: string): string | null;
|
|
38
|
+
export declare function isProductionEmailOrigin(origin: string, productionHosts: string[]): boolean;
|
|
39
|
+
export declare function readEmailOriginConfiguration(env: Record<string, string | undefined>, fallback: EmailOriginConfiguration): EmailOriginConfiguration;
|
|
40
|
+
export declare function resolveEmailOrigin(input: EmailOriginInput): EmailOriginResolution;
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
export const EMAIL_DEFAULT_ORIGIN_ENV = "EMAIL_DEFAULT_ORIGIN";
|
|
2
|
+
export const EMAIL_PRODUCTION_ORIGIN_HOSTS_ENV = "EMAIL_PRODUCTION_ORIGIN_HOSTS";
|
|
3
|
+
export const COMMS_PRODUCTION_PROJECT_REF_ENV = "COMMS_PRODUCTION_PROJECT_REF";
|
|
4
|
+
function clean(value) {
|
|
5
|
+
const trimmed = value?.trim();
|
|
6
|
+
return trimmed ? trimmed : null;
|
|
7
|
+
}
|
|
8
|
+
function resolveEnvironment(input) {
|
|
9
|
+
if (input.hiddenPreviewEnabled)
|
|
10
|
+
return "hidden_preview";
|
|
11
|
+
const value = clean(input.environment)?.toLowerCase().replace(/-/g, "_");
|
|
12
|
+
if (value === "hidden_preview" ||
|
|
13
|
+
value === "preview" ||
|
|
14
|
+
value === "staging" ||
|
|
15
|
+
value === "production" ||
|
|
16
|
+
value === "production_or_default") {
|
|
17
|
+
return value;
|
|
18
|
+
}
|
|
19
|
+
return "production_or_default";
|
|
20
|
+
}
|
|
21
|
+
export function normalizeEmailOrigin(value) {
|
|
22
|
+
try {
|
|
23
|
+
const url = new URL(value);
|
|
24
|
+
if (url.protocol !== "https:" && url.protocol !== "http:")
|
|
25
|
+
return null;
|
|
26
|
+
if (!url.hostname)
|
|
27
|
+
return null;
|
|
28
|
+
return url.origin.replace(/\/+$/, "");
|
|
29
|
+
}
|
|
30
|
+
catch {
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
export function isProductionEmailOrigin(origin, productionHosts) {
|
|
35
|
+
try {
|
|
36
|
+
return productionHosts.includes(new URL(origin).hostname.toLowerCase());
|
|
37
|
+
}
|
|
38
|
+
catch {
|
|
39
|
+
return false;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
export function readEmailOriginConfiguration(env, fallback) {
|
|
43
|
+
const configuredDefault = env[EMAIL_DEFAULT_ORIGIN_ENV];
|
|
44
|
+
const configuredHosts = env[EMAIL_PRODUCTION_ORIGIN_HOSTS_ENV];
|
|
45
|
+
return {
|
|
46
|
+
defaultOrigin: configuredDefault === undefined ? fallback.defaultOrigin : configuredDefault,
|
|
47
|
+
productionEmailHosts: configuredHosts === undefined
|
|
48
|
+
? fallback.productionEmailHosts
|
|
49
|
+
: configuredHosts
|
|
50
|
+
.split(/[,\s;]+/)
|
|
51
|
+
.map((host) => host.trim().toLowerCase())
|
|
52
|
+
.filter(Boolean),
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
function requiresExplicitNonProductionOrigin(environment) {
|
|
56
|
+
return environment === "hidden_preview" || environment === "preview" || environment === "staging";
|
|
57
|
+
}
|
|
58
|
+
export function resolveEmailOrigin(input) {
|
|
59
|
+
const environment = resolveEnvironment(input);
|
|
60
|
+
const explicit = clean(input.explicitBaseUrl) ??
|
|
61
|
+
clean(input.customerAuthRedirectOrigin) ??
|
|
62
|
+
clean(input.siteUrl);
|
|
63
|
+
if (!explicit) {
|
|
64
|
+
if (requiresExplicitNonProductionOrigin(environment)) {
|
|
65
|
+
return { ok: false, error: "hidden_preview_origin_required" };
|
|
66
|
+
}
|
|
67
|
+
const defaultOrigin = normalizeEmailOrigin(input.defaultOrigin);
|
|
68
|
+
if (!defaultOrigin)
|
|
69
|
+
return { ok: false, error: "invalid_email_origin" };
|
|
70
|
+
return {
|
|
71
|
+
ok: true,
|
|
72
|
+
origin: defaultOrigin,
|
|
73
|
+
source: "default",
|
|
74
|
+
resolved: {
|
|
75
|
+
origin: defaultOrigin,
|
|
76
|
+
source: "default",
|
|
77
|
+
environment,
|
|
78
|
+
production: isProductionEmailOrigin(defaultOrigin, input.productionEmailHosts),
|
|
79
|
+
},
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
const normalized = normalizeEmailOrigin(explicit);
|
|
83
|
+
if (!normalized)
|
|
84
|
+
return { ok: false, error: "invalid_email_origin" };
|
|
85
|
+
const production = isProductionEmailOrigin(normalized, input.productionEmailHosts);
|
|
86
|
+
if (requiresExplicitNonProductionOrigin(environment) && production) {
|
|
87
|
+
return { ok: false, error: "hidden_preview_origin_must_not_be_production" };
|
|
88
|
+
}
|
|
89
|
+
return {
|
|
90
|
+
ok: true,
|
|
91
|
+
origin: normalized,
|
|
92
|
+
source: "explicit",
|
|
93
|
+
resolved: {
|
|
94
|
+
origin: normalized,
|
|
95
|
+
source: "explicit",
|
|
96
|
+
environment,
|
|
97
|
+
production,
|
|
98
|
+
},
|
|
99
|
+
};
|
|
100
|
+
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import type { SqlExecutor } from "@openlup/core/readiness";
|
|
2
|
+
import type { PostgresDataGatewayEnv } from "../data/index.js";
|
|
3
|
+
import type { CommunicationSyncStore } from "./communicationSyncWorker.js";
|
|
4
|
+
import type { NewsletterSyncEventType, NewsletterMarketingPurpose } from "./contracts/newsletter.js";
|
|
5
|
+
import type { NativeCommunicationsOptions } from "./nativeOperations.js";
|
|
6
|
+
/** Structural seam for the existing outbox store, scoped to this operation's transaction. */
|
|
7
|
+
export interface NativeSyncRail {
|
|
8
|
+
claimBatch(input: {
|
|
9
|
+
eventTypes: string[];
|
|
10
|
+
knownEventTypes: string[];
|
|
11
|
+
batchSize: number;
|
|
12
|
+
visibilitySeconds: number;
|
|
13
|
+
maxAttempts: number;
|
|
14
|
+
}): Promise<{
|
|
15
|
+
id: string;
|
|
16
|
+
attempts: number;
|
|
17
|
+
metadata: Record<string, unknown>;
|
|
18
|
+
}[]>;
|
|
19
|
+
markProcessed(input: {
|
|
20
|
+
eventId: string;
|
|
21
|
+
claimToken: string;
|
|
22
|
+
metadata?: Record<string, unknown>;
|
|
23
|
+
}): Promise<{
|
|
24
|
+
applied: boolean;
|
|
25
|
+
}>;
|
|
26
|
+
markFailed(input: {
|
|
27
|
+
eventId: string;
|
|
28
|
+
claimToken: string;
|
|
29
|
+
error: string;
|
|
30
|
+
outcome: "retry" | "discard";
|
|
31
|
+
baseDelaySeconds: number;
|
|
32
|
+
maxDelaySeconds: number;
|
|
33
|
+
maxAttempts: number;
|
|
34
|
+
snoozeSeconds: number;
|
|
35
|
+
}): Promise<{
|
|
36
|
+
status: string;
|
|
37
|
+
}>;
|
|
38
|
+
}
|
|
39
|
+
export interface NativeSyncRegistration {
|
|
40
|
+
sourceKey: string;
|
|
41
|
+
contactId: string;
|
|
42
|
+
normalizedEmail: string;
|
|
43
|
+
eventType: NewsletterSyncEventType;
|
|
44
|
+
purpose: NewsletterMarketingPurpose;
|
|
45
|
+
payload?: Record<string, unknown>;
|
|
46
|
+
originProviderKind?: string | null;
|
|
47
|
+
originProviderEventId?: string | null;
|
|
48
|
+
remoteProfileId?: string | null;
|
|
49
|
+
remoteListId?: string | null;
|
|
50
|
+
}
|
|
51
|
+
export interface NativeSyncOptions extends NativeCommunicationsOptions {
|
|
52
|
+
providerKind: string;
|
|
53
|
+
rail: (db: SqlExecutor) => NativeSyncRail;
|
|
54
|
+
}
|
|
55
|
+
export interface NativeCommunicationSyncStore extends CommunicationSyncStore {
|
|
56
|
+
registerWork(input: NativeSyncRegistration): Promise<{
|
|
57
|
+
id: string;
|
|
58
|
+
}>;
|
|
59
|
+
registerPreference(input: {
|
|
60
|
+
email: string;
|
|
61
|
+
purpose: "tester_program" | NewsletterMarketingPurpose;
|
|
62
|
+
state: "denied" | "suppressed";
|
|
63
|
+
}): Promise<{
|
|
64
|
+
updated: true;
|
|
65
|
+
email: string;
|
|
66
|
+
purpose: "tester_program" | NewsletterMarketingPurpose;
|
|
67
|
+
state: "denied" | "suppressed";
|
|
68
|
+
workId: string | null;
|
|
69
|
+
alreadySuppressed: boolean;
|
|
70
|
+
}>;
|
|
71
|
+
close(): Promise<void>;
|
|
72
|
+
}
|
|
73
|
+
export declare const nativeSyncEventType: (binding: string) => string;
|
|
74
|
+
/** Trusted host transaction seam; registration validation remains shared with the standalone store. */
|
|
75
|
+
export declare function registerNativeSyncWorkInTransaction(db: SqlExecutor, options: Pick<NativeSyncOptions, "binding" | "providerKind">, input: NativeSyncRegistration): Promise<{
|
|
76
|
+
id: string;
|
|
77
|
+
}>;
|
|
78
|
+
/** No sidecar claim/lease/drainer. Only runCommunicationSyncDispatch executes membership effects. */
|
|
79
|
+
export declare function createPostgresCommunicationSyncStore(env: PostgresDataGatewayEnv, options: NativeSyncOptions): NativeCommunicationSyncStore;
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { nativeEmail, nativeFingerprint, nativeInteger, nativeJson, nativeStamp, nativeText, nativeTransactions } from "./postgresNativeOperations.js";
|
|
2
|
+
export const nativeSyncEventType = (binding) => `communication.native.sync.${nativeFingerprint(nativeText(binding))}`;
|
|
3
|
+
function nativeSyncRegistration(options, input) {
|
|
4
|
+
const binding = nativeText(options.binding), provider = nativeText(options.providerKind), eventType = nativeSyncEventType(binding);
|
|
5
|
+
const email = nativeEmail(input.normalizedEmail), source = nativeText(input.sourceKey), contact = nativeText(input.contactId);
|
|
6
|
+
if (!["subscribe", "update", "unsubscribe", "suppress"].includes(input.eventType) || !["marketing_launch_offer", "marketing_newsletter"].includes(input.purpose))
|
|
7
|
+
throw new Error("native_sync_event_invalid");
|
|
8
|
+
const payload = nativeJson(input.payload ?? {});
|
|
9
|
+
const refs = [input.originProviderKind ?? null, input.originProviderEventId ?? null, input.remoteProfileId ?? null, input.remoteListId ?? null].map(value => value === null ? null : nativeText(value));
|
|
10
|
+
const fact = nativeFingerprint(JSON.stringify([provider, contact, email, input.eventType, input.purpose, payload, ...refs]));
|
|
11
|
+
// Reuse the rail's ordering barrier for successive preferences of one contact/target.
|
|
12
|
+
const aggregate = nativeFingerprint(JSON.stringify([binding, provider, contact])).slice(0, 32);
|
|
13
|
+
return async (db) => {
|
|
14
|
+
// One rail identity for each source event and exact target binding.
|
|
15
|
+
await db.query(`INSERT INTO public.outbox_events (aggregate_type,aggregate_id,event_type,idempotency_key,payload)
|
|
16
|
+
VALUES ('communication_native_sync',$4::uuid,$1,$2,$3::jsonb) ON CONFLICT (event_type,idempotency_key) DO NOTHING`, [eventType, source, payload, aggregate]);
|
|
17
|
+
const work = (await db.query("SELECT id FROM public.outbox_events WHERE event_type=$1 AND idempotency_key=$2 FOR UPDATE", [eventType, source])).rows[0];
|
|
18
|
+
await db.query(`INSERT INTO public.communication_native_sync_state
|
|
19
|
+
(work_id,binding,provider_kind,contact_id,normalized_email,purpose,event_kind,origin_provider_kind,origin_provider_event_id,remote_profile_id,remote_list_id,command_fingerprint)
|
|
20
|
+
VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12) ON CONFLICT (work_id) DO NOTHING`, [work.id, binding, provider, contact, email, input.purpose, input.eventType, ...refs, fact]);
|
|
21
|
+
const state = (await db.query("SELECT command_fingerprint FROM public.communication_native_sync_state WHERE work_id=$1", [work.id])).rows[0];
|
|
22
|
+
if (state?.command_fingerprint !== fact)
|
|
23
|
+
throw new Error("native_sync_registration_conflict");
|
|
24
|
+
return { id: nativeText(work.id) };
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
/** Trusted host transaction seam; registration validation remains shared with the standalone store. */
|
|
28
|
+
export async function registerNativeSyncWorkInTransaction(db, options, input) {
|
|
29
|
+
return nativeSyncRegistration(options, input)(db);
|
|
30
|
+
}
|
|
31
|
+
/** No sidecar claim/lease/drainer. Only runCommunicationSyncDispatch executes membership effects. */
|
|
32
|
+
export function createPostgresCommunicationSyncStore(env, options) {
|
|
33
|
+
const binding = nativeText(options.binding), provider = nativeText(options.providerKind), eventType = nativeSyncEventType(binding);
|
|
34
|
+
const lane = nativeTransactions(env, options, ["public.communication_native_sync_state", "public.outbox_events", "public.communication_recipient_consents"]);
|
|
35
|
+
const checkProvider = (value) => { if (value !== provider)
|
|
36
|
+
throw new Error("native_sync_binding_conflict"); };
|
|
37
|
+
return {
|
|
38
|
+
close: lane.close,
|
|
39
|
+
async registerWork(input) {
|
|
40
|
+
const register = nativeSyncRegistration(options, input);
|
|
41
|
+
return lane.run(db => register(db));
|
|
42
|
+
},
|
|
43
|
+
registerPreference(input) {
|
|
44
|
+
const email = nativeEmail(input.email), fingerprint = nativeFingerprint(email), purpose = nativeText(input.purpose);
|
|
45
|
+
if (!["tester_program", "marketing_launch_offer", "marketing_newsletter"].includes(purpose) || !["denied", "suppressed"].includes(input.state))
|
|
46
|
+
throw new Error("native_preference_invalid");
|
|
47
|
+
return lane.run(async (db) => {
|
|
48
|
+
await db.query("SELECT pg_advisory_xact_lock(hashtextextended($1,0))", [JSON.stringify([fingerprint, purpose])]);
|
|
49
|
+
const old = (await db.query("SELECT state,updated_at FROM public.communication_recipient_consents WHERE recipient_fingerprint=$1 AND purpose=$2 FOR UPDATE", [fingerprint, purpose])).rows[0];
|
|
50
|
+
const row = old?.state === input.state ? old : (await db.query(`INSERT INTO public.communication_recipient_consents (recipient_fingerprint,purpose,state)
|
|
51
|
+
VALUES ($1,$2,$3) ON CONFLICT (recipient_fingerprint,purpose) DO UPDATE SET state=excluded.state,updated_at=clock_timestamp() RETURNING state,updated_at`, [fingerprint, purpose, input.state])).rows[0];
|
|
52
|
+
let workId = null;
|
|
53
|
+
if (purpose !== "tester_program") {
|
|
54
|
+
const sourceKey = nativeFingerprint(JSON.stringify([fingerprint, purpose, input.state, nativeStamp(row.updated_at)]));
|
|
55
|
+
workId = (await registerNativeSyncWorkInTransaction(db, options, { sourceKey, contactId: fingerprint, normalizedEmail: email, eventType: input.state === "suppressed" ? "suppress" : "unsubscribe", purpose: purpose })).id;
|
|
56
|
+
}
|
|
57
|
+
return { updated: true, email, purpose: purpose, state: input.state, workId, alreadySuppressed: old?.state === input.state };
|
|
58
|
+
});
|
|
59
|
+
},
|
|
60
|
+
claimBatch(input) {
|
|
61
|
+
checkProvider(input.providerKind);
|
|
62
|
+
nativeInteger(input.batchSize, 1, 100);
|
|
63
|
+
nativeInteger(input.visibilitySeconds, 60, 3600);
|
|
64
|
+
nativeInteger(input.maxAttempts, 1, 20);
|
|
65
|
+
return lane.run(async (db) => {
|
|
66
|
+
const work = await options.rail(db).claimBatch({ eventTypes: [eventType], knownEventTypes: [eventType], batchSize: input.batchSize, visibilitySeconds: input.visibilitySeconds, maxAttempts: input.maxAttempts });
|
|
67
|
+
const events = [];
|
|
68
|
+
for (const row of work) {
|
|
69
|
+
const state = (await db.query("UPDATE public.communication_native_sync_state SET max_attempts=$4 WHERE work_id=$1 AND binding=$2 AND provider_kind=$3 RETURNING *", [row.id, binding, provider, input.maxAttempts])).rows[0];
|
|
70
|
+
if (!state)
|
|
71
|
+
throw new Error("native_sync_sidecar_missing");
|
|
72
|
+
const payload = (await db.query("SELECT payload FROM public.outbox_events WHERE id=$1", [row.id])).rows[0].payload;
|
|
73
|
+
nativeJson(payload);
|
|
74
|
+
events.push({ id: nativeText(row.id), claimToken: nativeText(row.metadata.claimToken), contactId: nativeText(state.contact_id), normalizedEmail: nativeEmail(state.normalized_email), eventType: state.event_kind,
|
|
75
|
+
purpose: state.purpose, payload: payload, attemptCount: nativeInteger(row.attempts - 1, 0, 19),
|
|
76
|
+
originProviderKind: state.origin_provider_kind, originProviderEventId: state.origin_provider_event_id, remoteProfileId: state.remote_profile_id, remoteListId: state.remote_list_id });
|
|
77
|
+
}
|
|
78
|
+
return events;
|
|
79
|
+
});
|
|
80
|
+
},
|
|
81
|
+
markSent(input) {
|
|
82
|
+
checkProvider(input.providerKind);
|
|
83
|
+
const summary = nativeJson(input.responseSummary ?? {}), remote = input.remoteProfileId == null ? null : nativeText(input.remoteProfileId);
|
|
84
|
+
return lane.run(async (db) => {
|
|
85
|
+
const row = (await db.query(`SELECT e.id FROM public.outbox_events e JOIN public.communication_native_sync_state s ON s.work_id=e.id
|
|
86
|
+
WHERE e.id=$1 AND s.binding=$2 AND s.provider_kind=$3 AND e.event_type=$4 AND e.status='processing' AND e.metadata->>'claimToken'=$5 FOR UPDATE OF e,s`, [input.eventId, binding, provider, eventType, input.claimToken])).rows[0];
|
|
87
|
+
if (!row)
|
|
88
|
+
return false;
|
|
89
|
+
if (!(await options.rail(db).markProcessed({ eventId: input.eventId, claimToken: input.claimToken })).applied)
|
|
90
|
+
throw new Error("native_sync_ack_lost");
|
|
91
|
+
await db.query("UPDATE public.communication_native_sync_state SET remote_profile_id=$2,response_summary=$3::jsonb WHERE work_id=$1", [input.eventId, remote, summary]);
|
|
92
|
+
return true;
|
|
93
|
+
});
|
|
94
|
+
},
|
|
95
|
+
markFailed(input) {
|
|
96
|
+
checkProvider(input.providerKind);
|
|
97
|
+
nativeInteger(input.backoffSeconds, 0, 86400);
|
|
98
|
+
return lane.run(async (db) => {
|
|
99
|
+
const row = (await db.query(`SELECT s.max_attempts FROM public.outbox_events e JOIN public.communication_native_sync_state s ON s.work_id=e.id
|
|
100
|
+
WHERE e.id=$1 AND s.binding=$2 AND s.provider_kind=$3 AND e.event_type=$4 AND e.status='processing' AND e.metadata->>'claimToken'=$5 FOR UPDATE OF e,s`, [input.eventId, binding, provider, eventType, input.claimToken])).rows[0];
|
|
101
|
+
if (!row)
|
|
102
|
+
return false;
|
|
103
|
+
const result = await options.rail(db).markFailed({ eventId: input.eventId, claimToken: input.claimToken, error: nativeText(input.error, 2000), outcome: input.retry ? "retry" : "discard", baseDelaySeconds: input.backoffSeconds, maxDelaySeconds: input.backoffSeconds, maxAttempts: nativeInteger(Number(row.max_attempts), 1, 20), snoozeSeconds: 300 });
|
|
104
|
+
if (!["failed", "discarded"].includes(result.status))
|
|
105
|
+
throw new Error("native_sync_ack_lost");
|
|
106
|
+
await db.query("UPDATE public.communication_native_sync_state SET response_summary=$2::jsonb WHERE work_id=$1", [input.eventId, JSON.stringify({ status: result.status })]);
|
|
107
|
+
return true;
|
|
108
|
+
});
|
|
109
|
+
},
|
|
110
|
+
};
|
|
111
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { PostgresDataGatewayEnv, PostgresDataGatewayOptions } from "../data/index.js";
|
|
2
|
+
import type { CommunicationPurposeRegistry } from "./contracts/consent.js";
|
|
3
|
+
import { type NativeConsentDecision, type NativeConsentInput } from "./nativeOperations.js";
|
|
4
|
+
export interface NativeConsentOptions extends PostgresDataGatewayOptions {
|
|
5
|
+
registry?: CommunicationPurposeRegistry<string>;
|
|
6
|
+
/** Host-owned authoritative tester pause, read in the decision transaction. Missing means unpaused. */
|
|
7
|
+
testerPaused?: (db: import("../data/index.js").PgQueryExecutor, input: NativeConsentInput) => Promise<boolean>;
|
|
8
|
+
}
|
|
9
|
+
export interface NativeConsentEvaluator {
|
|
10
|
+
evaluate(input: NativeConsentInput): Promise<NativeConsentDecision>;
|
|
11
|
+
close(): Promise<void>;
|
|
12
|
+
readonly guarantees: {
|
|
13
|
+
readonly managedFrequencyCaps: false;
|
|
14
|
+
readonly legacyContactTouching: false;
|
|
15
|
+
readonly sendTimeFence: false;
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
/** Bounded P4 policy, authoritative native permission and durable decision. Evaluation never sends. */
|
|
19
|
+
export declare function createPostgresConsentEvaluator(env: PostgresDataGatewayEnv, options?: NativeConsentOptions): NativeConsentEvaluator;
|