@aglyn/tenant-data-admin 1.0.0-beta.220 → 1.0.0-beta.222
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +7 -7
- package/src/lib/server/api-keys.js +3 -3
- package/src/lib/server/api-keys.js.map +1 -1
- package/src/lib/server/api-v1-resources.d.ts +44 -3
- package/src/lib/server/api-v1-resources.js +82 -5
- package/src/lib/server/api-v1-resources.js.map +1 -1
- package/src/lib/server/collection-live-pages.d.ts +2 -2
- package/src/lib/server/collection-live-pages.js +1 -1
- package/src/lib/server/collection-live-pages.js.map +1 -1
- package/src/lib/server/email-delivery-log.d.ts +17 -73
- package/src/lib/server/email-delivery-log.js +44 -222
- package/src/lib/server/email-delivery-log.js.map +1 -1
- package/src/lib/server/email-flow-gate.js +27 -38
- package/src/lib/server/email-flow-gate.js.map +1 -1
- package/src/lib/server/erase-person.d.ts +34 -54
- package/src/lib/server/erase-person.js +44 -239
- package/src/lib/server/erase-person.js.map +1 -1
- package/src/lib/server/host-visitor-records.d.ts +12 -4
- package/src/lib/server/host-visitor-records.js +20 -10
- package/src/lib/server/host-visitor-records.js.map +1 -1
- package/src/lib/server/hosted-page-review.d.ts +7 -0
- package/src/lib/server/hosted-page-review.js +144 -20
- package/src/lib/server/hosted-page-review.js.map +1 -1
- package/src/lib/server/leaving-notice.d.ts +98 -0
- package/src/lib/server/leaving-notice.js +250 -0
- package/src/lib/server/leaving-notice.js.map +1 -0
- package/src/lib/server/link-reputation-review.d.ts +58 -0
- package/src/lib/server/link-reputation-review.js +185 -0
- package/src/lib/server/link-reputation-review.js.map +1 -0
- package/src/lib/server/live-page-drops.d.ts +67 -0
- package/src/lib/server/live-page-drops.js +57 -0
- package/src/lib/server/live-page-drops.js.map +1 -0
- package/src/lib/server/media-bandwidth-cap.d.ts +78 -0
- package/src/lib/server/media-bandwidth-cap.js +180 -0
- package/src/lib/server/media-bandwidth-cap.js.map +1 -0
- package/src/lib/server/media-cdn-icon.d.ts +77 -0
- package/src/lib/server/media-cdn-icon.js +140 -0
- package/src/lib/server/media-cdn-icon.js.map +1 -0
- package/src/lib/server/media-delivery-regeneration.d.ts +132 -0
- package/src/lib/server/media-delivery-regeneration.js +216 -0
- package/src/lib/server/media-delivery-regeneration.js.map +1 -0
- package/src/lib/server/media-delivery.d.ts +2 -0
- package/src/lib/server/media-delivery.js +2 -1
- package/src/lib/server/media-delivery.js.map +1 -1
- package/src/lib/server/media-serve-count.d.ts +50 -0
- package/src/lib/server/media-serve-count.js +62 -0
- package/src/lib/server/media-serve-count.js.map +1 -0
- package/src/lib/server/media-signing.d.ts +13 -2
- package/src/lib/server/media-signing.js +12 -8
- package/src/lib/server/media-signing.js.map +1 -1
- package/src/lib/server/media-tombstone.js +7 -5
- package/src/lib/server/media-tombstone.js.map +1 -1
- package/src/lib/server/media-variants.d.ts +151 -20
- package/src/lib/server/media-variants.js +204 -27
- package/src/lib/server/media-variants.js.map +1 -1
- package/src/lib/server/org-age.d.ts +15 -6
- package/src/lib/server/org-age.js +20 -8
- package/src/lib/server/org-age.js.map +1 -1
- package/src/lib/server/organizations.d.ts +102 -2
- package/src/lib/server/organizations.js +388 -58
- package/src/lib/server/organizations.js.map +1 -1
- package/src/lib/server/outbound-send-review.js +24 -6
- package/src/lib/server/outbound-send-review.js.map +1 -1
- package/src/lib/server/page-security-hold.d.ts +93 -0
- package/src/lib/server/page-security-hold.js +180 -0
- package/src/lib/server/page-security-hold.js.map +1 -0
- package/src/lib/server/paged-sweeps.d.ts +51 -0
- package/src/lib/server/paged-sweeps.js +79 -0
- package/src/lib/server/paged-sweeps.js.map +1 -0
- package/src/lib/server/platform-marketing-consent.d.ts +0 -49
- package/src/lib/server/platform-marketing-consent.js +27 -11
- package/src/lib/server/platform-marketing-consent.js.map +1 -1
- package/src/lib/server/serve-media-cdn.d.ts +69 -1
- package/src/lib/server/serve-media-cdn.js +424 -70
- package/src/lib/server/serve-media-cdn.js.map +1 -1
- package/src/lib/server/site-icon-render.d.ts +68 -0
- package/src/lib/server/site-icon-render.js +157 -0
- package/src/lib/server/site-icon-render.js.map +1 -0
- package/src/lib/server/staff-seat.d.ts +50 -0
- package/src/lib/server/staff-seat.js +118 -0
- package/src/lib/server/staff-seat.js.map +1 -0
- package/src/lib/server/transfer-export.d.ts +46 -0
- package/src/lib/server/transfer-export.js +221 -0
- package/src/lib/server/transfer-export.js.map +1 -0
- package/src/lib/server/transfer-jobs.d.ts +385 -0
- package/src/lib/server/transfer-jobs.js +2368 -0
- package/src/lib/server/transfer-jobs.js.map +1 -0
- package/src/lib/server/transfer-packages.d.ts +127 -0
- package/src/lib/server/transfer-packages.js +1036 -0
- package/src/lib/server/transfer-packages.js.map +1 -0
- package/src/lib/server/upgrade-proposal.d.ts +97 -0
- package/src/lib/server/upgrade-proposal.js +312 -0
- package/src/lib/server/upgrade-proposal.js.map +1 -0
- package/src/lib/server/upsert-contact.d.ts +17 -8
- package/src/lib/server/upsert-contact.js +82 -16
- package/src/lib/server/upsert-contact.js.map +1 -1
- package/src/lib/server/web-risk.d.ts +139 -0
- package/src/lib/server/web-risk.js +521 -0
- package/src/lib/server/web-risk.js.map +1 -0
- package/src/tenancy.d.ts +3 -1
- package/src/tenancy.js +5 -1
- package/src/tenancy.js.map +1 -1
- package/src/lib/server/campaign-attribution-store.d.ts +0 -55
- package/src/lib/server/campaign-attribution-store.js +0 -95
- package/src/lib/server/campaign-attribution-store.js.map +0 -1
- package/src/lib/server/campaign-conversion-attribution.d.ts +0 -283
- package/src/lib/server/campaign-conversion-attribution.js +0 -450
- package/src/lib/server/campaign-conversion-attribution.js.map +0 -1
- package/src/lib/server/dataset-live-pages.d.ts +0 -179
- package/src/lib/server/dataset-live-pages.js +0 -421
- package/src/lib/server/dataset-live-pages.js.map +0 -1
- package/src/lib/server/email-revenue-attribution.d.ts +0 -185
- package/src/lib/server/email-revenue-attribution.js +0 -321
- package/src/lib/server/email-revenue-attribution.js.map +0 -1
package/src/tenancy.js
CHANGED
|
@@ -71,7 +71,6 @@ export * from "./lib/server/email-metering.js";
|
|
|
71
71
|
// Side-effecting on purpose (AGL-2409): evaluating this module INSTALLS the
|
|
72
72
|
// platform send-rate governor on `sendEmail`. Every server surface already
|
|
73
73
|
// imports this barrel, so nothing has to remember to call an installer.
|
|
74
|
-
export * from "./lib/server/campaign-conversion-attribution.js";
|
|
75
74
|
export * from "./lib/server/email-send-rate.js";
|
|
76
75
|
// Side-effecting on purpose, for the same reason (AGL-3328): evaluating this
|
|
77
76
|
// module INSTALLS the deliverability preflight on `sendEmail`, so a recipient
|
|
@@ -104,6 +103,10 @@ export * from "./lib/server/operator-alerts.js";
|
|
|
104
103
|
export * from "./lib/server/operator-health.js";
|
|
105
104
|
export * from "./lib/server/org-billing.js";
|
|
106
105
|
export * from "./lib/server/organizations.js";
|
|
106
|
+
// Platform staff take no customer seat, and a workspace is asked to upgrade
|
|
107
|
+
// as a separate step after it is handed over (AGL-3466).
|
|
108
|
+
export * from "./lib/server/staff-seat.js";
|
|
109
|
+
export * from "./lib/server/upgrade-proposal.js";
|
|
107
110
|
export * from "./lib/server/duplicate-activity.js";
|
|
108
111
|
export * from "./lib/server/duplicate-resource.js";
|
|
109
112
|
export * from "./lib/server/workspace-domains.js";
|
|
@@ -129,6 +132,7 @@ export * from "./lib/server/media-storage-path.js";
|
|
|
129
132
|
export * from "./lib/server/safe-equal.js";
|
|
130
133
|
export * from "./lib/server/media-strong-digest.js";
|
|
131
134
|
export * from "./lib/server/media-tombstone.js";
|
|
135
|
+
export * from "./lib/server/media-delivery-regeneration.js";
|
|
132
136
|
export * from "./lib/server/media-variants.js";
|
|
133
137
|
export * from "./lib/server/serve-media-cdn.js";
|
|
134
138
|
export * from "./lib/server/serve-plugin-fetch.js";
|
package/src/tenancy.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../libs/tenant/data/admin/src/tenancy.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2022 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nexport * from './lib/server/analytics-retention'\nexport * from './lib/server/api-http'\nexport * from './lib/server/api-keys'\nexport * from './lib/server/account-emails'\nexport * from './lib/server/auth-pools'\nexport * from './lib/server/client-error-report'\n// The merchant's payment account, as a contract. Its adapter is not exported:\n// a plugin asks the contract, and the doors that only need the readiness rule\n// import `@aglyn/tenant-data-admin/server/payment-provider` directly, which\n// loads no Firebase.\nexport {\n merchantAccountIsReady,\n merchantAccountReadiness,\n paymentProvider,\n} from './lib/server/payment-provider'\nexport type {\n MerchantAccountReadiness,\n MerchantAccountReadinessInput,\n PaymentMode,\n PaymentProvider,\n PaymentProviderEvent,\n} from './lib/server/payment-provider'\nexport * from './lib/server/auth-handoff'\nexport * from './lib/server/console-domains'\nexport * from './lib/server/dns-probe'\nexport * from './lib/server/sending-domains'\nexport * from './lib/server/sender-readiness'\nexport * from './lib/server/host-sending-domain'\nexport * from './lib/server/sending-domain-debt'\nexport * from './lib/server/sending-domain-recheck'\n// The hosting seam itself, so a door can ask whether this deployment is able\n// to register a name at all before it promises the caller one.\n// `workspace-domains` re-exports the pieces its own callers share with it; the\n// provider behind it is not among them.\nexport { domainProvider } from './lib/server/domain-provider'\nexport type {\n DomainProvider,\n DomainProviderId,\n DomainScope,\n} from './lib/server/domain-provider'\nexport * from './lib/server/platform-domain-names'\nexport * from './lib/server/consume-once'\nexport * from './lib/server/csp-aggregate'\nexport * from './lib/server/data-storage-gate'\nexport * from './lib/server/contact-suppression'\nexport * from './lib/server/email-suppression'\n// The double opt-in quarantine, after the module whose keying it shares and\n// whose `filterTopicSendable` is the reader that makes it a quarantine rather\n// than a stored fact.\nexport * from './lib/server/topic-subscriptions'\nexport * from './lib/server/email-unsubscribe-link'\nexport * from './lib/server/email-flow-gate'\n// Side-effecting on purpose, for the reason `email-send-rate` below is:\n// evaluating this module INSTALLS the marketing gate on `sendEmail`, so the\n// unsubscribe header, the suppression check and the frequency ceiling reach\n// every bulk sender without a call at each server entrypoint.\nexport * from './lib/server/email-marketing-gate'\n// A consent group change (AGL-3320): the carries of the three stores above,\n// and the executor that runs a change around them.\nexport * from './lib/server/consent-group-carry'\nexport * from './lib/server/consent-group-change'\n// A person's refusal kept after the record it was written on is deleted\n// (AGL-3338): the delete's write, the list gate's read, and the change's carry.\nexport * from './lib/server/retained-refusals'\nexport * from './lib/server/sms-keywords'\nexport * from './lib/server/document-id'\nexport * from './lib/server/collection-preview-token'\nexport * from './lib/server/edit-access-authz'\nexport * from './lib/server/edit-access-token'\nexport * from './lib/server/edit-hint-token'\nexport * from './lib/server/email-metering'\n// Side-effecting on purpose (AGL-2409): evaluating this module INSTALLS the\n// platform send-rate governor on `sendEmail`. Every server surface already\n// imports this barrel, so nothing has to remember to call an installer.\nexport * from './lib/server/campaign-conversion-attribution'\nexport * from './lib/server/email-send-rate'\n// Side-effecting on purpose, for the same reason (AGL-3328): evaluating this\n// module INSTALLS the deliverability preflight on `sendEmail`, so a recipient\n// whose domain takes no mail is refused, and bulk mail behind a gateway that\n// refused its sending domain twice is held, on every send path at once.\nexport * from './lib/server/email-deliverability'\nexport * from './lib/server/email-sender-reputation'\nexport * from './lib/server/erase'\nexport * from './lib/server/erase-person'\nexport * from './lib/server/dynamic-list-materialize'\nexport * from './lib/server/firebase-admin'\nexport * from './lib/server/free-workspace-cap'\nexport * from './lib/server/ga4-measurement-protocol'\nexport * from './lib/server/host-memberships'\nexport * from './lib/server/host-visitor-records'\nexport * from './lib/server/list-members'\nexport * from './lib/server/lockdown'\nexport * from './lib/server/token-revocation'\nexport * from './lib/server/tenant-write-lockdown'\nexport * from './lib/server/outbound-send-review'\n// The same review for a published page, read where it is composed (AGL-3356).\nexport * from './lib/server/hosted-page-review'\n// Every hold, flag, lock and pause tells owners and staff through one seam (AGL-3368).\nexport * from './lib/server/risk-notice'\nexport * from './lib/server/host-email-tokens'\nexport * from './lib/server/notifications'\n// Every operator alert raises through one entry point, and every health check\n// remembers its state (AGL-3377).\nexport * from './lib/server/operator-alerts'\nexport * from './lib/server/operator-health'\nexport * from './lib/server/org-billing'\nexport * from './lib/server/organizations'\nexport * from './lib/server/duplicate-activity'\nexport * from './lib/server/duplicate-resource'\nexport * from './lib/server/workspace-domains'\nexport * from './lib/server/membership-recover-throttle'\nexport * from './lib/server/password-reset-throttle'\nexport * from './lib/server/rate-limit-store'\nexport * from './lib/server/verify-email-cooldown'\nexport * from './lib/server/vercel-drain-signature'\nexport * from './lib/server/vercel-log-drain'\nexport * from './lib/server/visitor-write-rate-limit'\nexport * from './lib/server/card-payment-velocity'\nexport * from './lib/server/console-api-rate-limit'\nexport * from './lib/server/realm-plugins'\nexport * from './lib/server/release-flags'\nexport * from './lib/server/resolve-people'\nexport * from './lib/server/member-photo'\nexport * from './lib/server/personal-data-export'\nexport * from './lib/server/media-delivery'\nexport * from './lib/server/media-download-tokens'\nexport * from './lib/server/media-quarantine'\nexport * from './lib/server/media-signing'\nexport * from './lib/server/media-storage-path'\nexport * from './lib/server/safe-equal'\nexport * from './lib/server/media-strong-digest'\nexport * from './lib/server/media-tombstone'\nexport * from './lib/server/media-variants'\nexport * from './lib/server/serve-media-cdn'\nexport * from './lib/server/serve-plugin-fetch'\nexport * from './lib/server/configured-url-fetch'\nexport * from './lib/server/sso-break-glass-owners'\nexport * from './lib/server/sso-domain-policy'\nexport * from './lib/server/sso-enforcement'\nexport * from './lib/server/sso-provisioning'\nexport * from './lib/server/update-existing'\nexport * from './lib/server/contact-company-link'\nexport * from './lib/server/contact-email-index'\nexport * from './lib/server/upsert-contact'\nexport * from './lib/server/crm-records'\n// The `nextTaskAtMs` a contact, a company and a deal carry, recomputed from\n// their open tasks by every server-side task writer (AGL-2661).\nexport * from './lib/server/crm-next-activity'\n// Where a one-to-one email's activity row lives and how the delivery\n// webhook advances it (AGL-2615).\nexport * from './lib/server/crm-email-activity'\n// A member's own addresses in a workspace, and the signed link that\n// confirms one (AGL-2975).\nexport * from './lib/server/member-email-aliases'\nexport * from './lib/server/user-profiles'\n// The one \"New account\" staff notice per account, keyed on the auth\n// record's creation (AGL-3225).\nexport * from './lib/server/new-account-announcement'\nexport * from './lib/server/legal-acceptance'\n// The person's decision about the platform's own product email, on their\n// document and on the operator's marketing contact (AGL-3185).\nexport * from './lib/server/platform-marketing-consent'\n"],"names":["merchantAccountIsReady","merchantAccountReadiness","paymentProvider","domainProvider"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../../../../libs/tenant/data/admin/src/tenancy.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2022 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nexport * from './lib/server/analytics-retention'\nexport * from './lib/server/api-http'\nexport * from './lib/server/api-keys'\nexport * from './lib/server/account-emails'\nexport * from './lib/server/auth-pools'\nexport * from './lib/server/client-error-report'\n// The merchant's payment account, as a contract. Its adapter is not exported:\n// a plugin asks the contract, and the doors that only need the readiness rule\n// import `@aglyn/tenant-data-admin/server/payment-provider` directly, which\n// loads no Firebase.\nexport {\n merchantAccountIsReady,\n merchantAccountReadiness,\n paymentProvider,\n} from './lib/server/payment-provider'\nexport type {\n MerchantAccountReadiness,\n MerchantAccountReadinessInput,\n PaymentMode,\n PaymentProvider,\n PaymentProviderEvent,\n} from './lib/server/payment-provider'\nexport * from './lib/server/auth-handoff'\nexport * from './lib/server/console-domains'\nexport * from './lib/server/dns-probe'\nexport * from './lib/server/sending-domains'\nexport * from './lib/server/sender-readiness'\nexport * from './lib/server/host-sending-domain'\nexport * from './lib/server/sending-domain-debt'\nexport * from './lib/server/sending-domain-recheck'\n// The hosting seam itself, so a door can ask whether this deployment is able\n// to register a name at all before it promises the caller one.\n// `workspace-domains` re-exports the pieces its own callers share with it; the\n// provider behind it is not among them.\nexport { domainProvider } from './lib/server/domain-provider'\nexport type {\n DomainProvider,\n DomainProviderId,\n DomainScope,\n} from './lib/server/domain-provider'\nexport * from './lib/server/platform-domain-names'\nexport * from './lib/server/consume-once'\nexport * from './lib/server/csp-aggregate'\nexport * from './lib/server/data-storage-gate'\nexport * from './lib/server/contact-suppression'\nexport * from './lib/server/email-suppression'\n// The double opt-in quarantine, after the module whose keying it shares and\n// whose `filterTopicSendable` is the reader that makes it a quarantine rather\n// than a stored fact.\nexport * from './lib/server/topic-subscriptions'\nexport * from './lib/server/email-unsubscribe-link'\nexport * from './lib/server/email-flow-gate'\n// Side-effecting on purpose, for the reason `email-send-rate` below is:\n// evaluating this module INSTALLS the marketing gate on `sendEmail`, so the\n// unsubscribe header, the suppression check and the frequency ceiling reach\n// every bulk sender without a call at each server entrypoint.\nexport * from './lib/server/email-marketing-gate'\n// A consent group change (AGL-3320): the carries of the three stores above,\n// and the executor that runs a change around them.\nexport * from './lib/server/consent-group-carry'\nexport * from './lib/server/consent-group-change'\n// A person's refusal kept after the record it was written on is deleted\n// (AGL-3338): the delete's write, the list gate's read, and the change's carry.\nexport * from './lib/server/retained-refusals'\nexport * from './lib/server/sms-keywords'\nexport * from './lib/server/document-id'\nexport * from './lib/server/collection-preview-token'\nexport * from './lib/server/edit-access-authz'\nexport * from './lib/server/edit-access-token'\nexport * from './lib/server/edit-hint-token'\nexport * from './lib/server/email-metering'\n// Side-effecting on purpose (AGL-2409): evaluating this module INSTALLS the\n// platform send-rate governor on `sendEmail`. Every server surface already\n// imports this barrel, so nothing has to remember to call an installer.\nexport * from './lib/server/email-send-rate'\n// Side-effecting on purpose, for the same reason (AGL-3328): evaluating this\n// module INSTALLS the deliverability preflight on `sendEmail`, so a recipient\n// whose domain takes no mail is refused, and bulk mail behind a gateway that\n// refused its sending domain twice is held, on every send path at once.\nexport * from './lib/server/email-deliverability'\nexport * from './lib/server/email-sender-reputation'\nexport * from './lib/server/erase'\nexport * from './lib/server/erase-person'\nexport * from './lib/server/dynamic-list-materialize'\nexport * from './lib/server/firebase-admin'\nexport * from './lib/server/free-workspace-cap'\nexport * from './lib/server/ga4-measurement-protocol'\nexport * from './lib/server/host-memberships'\nexport * from './lib/server/host-visitor-records'\nexport * from './lib/server/list-members'\nexport * from './lib/server/lockdown'\nexport * from './lib/server/token-revocation'\nexport * from './lib/server/tenant-write-lockdown'\nexport * from './lib/server/outbound-send-review'\n// The same review for a published page, read where it is composed (AGL-3356).\nexport * from './lib/server/hosted-page-review'\n// Every hold, flag, lock and pause tells owners and staff through one seam (AGL-3368).\nexport * from './lib/server/risk-notice'\nexport * from './lib/server/host-email-tokens'\nexport * from './lib/server/notifications'\n// Every operator alert raises through one entry point, and every health check\n// remembers its state (AGL-3377).\nexport * from './lib/server/operator-alerts'\nexport * from './lib/server/operator-health'\nexport * from './lib/server/org-billing'\nexport * from './lib/server/organizations'\n// Platform staff take no customer seat, and a workspace is asked to upgrade\n// as a separate step after it is handed over (AGL-3466).\nexport * from './lib/server/staff-seat'\nexport * from './lib/server/upgrade-proposal'\nexport * from './lib/server/duplicate-activity'\nexport * from './lib/server/duplicate-resource'\nexport * from './lib/server/workspace-domains'\nexport * from './lib/server/membership-recover-throttle'\nexport * from './lib/server/password-reset-throttle'\nexport * from './lib/server/rate-limit-store'\nexport * from './lib/server/verify-email-cooldown'\nexport * from './lib/server/vercel-drain-signature'\nexport * from './lib/server/vercel-log-drain'\nexport * from './lib/server/visitor-write-rate-limit'\nexport * from './lib/server/card-payment-velocity'\nexport * from './lib/server/console-api-rate-limit'\nexport * from './lib/server/realm-plugins'\nexport * from './lib/server/release-flags'\nexport * from './lib/server/resolve-people'\nexport * from './lib/server/member-photo'\nexport * from './lib/server/personal-data-export'\nexport * from './lib/server/media-delivery'\nexport * from './lib/server/media-download-tokens'\nexport * from './lib/server/media-quarantine'\nexport * from './lib/server/media-signing'\nexport * from './lib/server/media-storage-path'\nexport * from './lib/server/safe-equal'\nexport * from './lib/server/media-strong-digest'\nexport * from './lib/server/media-tombstone'\nexport * from './lib/server/media-delivery-regeneration'\nexport * from './lib/server/media-variants'\nexport * from './lib/server/serve-media-cdn'\nexport * from './lib/server/serve-plugin-fetch'\nexport * from './lib/server/configured-url-fetch'\nexport * from './lib/server/sso-break-glass-owners'\nexport * from './lib/server/sso-domain-policy'\nexport * from './lib/server/sso-enforcement'\nexport * from './lib/server/sso-provisioning'\nexport * from './lib/server/update-existing'\nexport * from './lib/server/contact-company-link'\nexport * from './lib/server/contact-email-index'\nexport * from './lib/server/upsert-contact'\nexport * from './lib/server/crm-records'\n// The `nextTaskAtMs` a contact, a company and a deal carry, recomputed from\n// their open tasks by every server-side task writer (AGL-2661).\nexport * from './lib/server/crm-next-activity'\n// Where a one-to-one email's activity row lives and how the delivery\n// webhook advances it (AGL-2615).\nexport * from './lib/server/crm-email-activity'\n// A member's own addresses in a workspace, and the signed link that\n// confirms one (AGL-2975).\nexport * from './lib/server/member-email-aliases'\nexport * from './lib/server/user-profiles'\n// The one \"New account\" staff notice per account, keyed on the auth\n// record's creation (AGL-3225).\nexport * from './lib/server/new-account-announcement'\nexport * from './lib/server/legal-acceptance'\n// The person's decision about the platform's own product email, on their\n// document and on the operator's marketing contact (AGL-3185).\nexport * from './lib/server/platform-marketing-consent'\n"],"names":["merchantAccountIsReady","merchantAccountReadiness","paymentProvider","domainProvider"],"mappings":"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"}
|
|
@@ -1,55 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @license
|
|
3
|
-
* Copyright 2026 Aglyn LLC
|
|
4
|
-
*
|
|
5
|
-
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
-
* you may not use this file except in compliance with the License.
|
|
7
|
-
* You may obtain a copy of the License at
|
|
8
|
-
*
|
|
9
|
-
* http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
-
*
|
|
11
|
-
* Unless required by applicable law or agreed to in writing, software
|
|
12
|
-
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
-
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
-
* See the License for the specific language governing permissions and
|
|
15
|
-
* limitations under the License.
|
|
16
|
-
*/
|
|
17
|
-
/** The per-host collection of conversion attribution records. */
|
|
18
|
-
export declare const CAMPAIGN_ATTRIBUTIONS_COLLECTION = "campaignAttributions";
|
|
19
|
-
/**
|
|
20
|
-
* Removes every conversion attributed to one person, across every site.
|
|
21
|
-
*
|
|
22
|
-
* ## Why an erasure reaches these at all
|
|
23
|
-
*
|
|
24
|
-
* An attribution is a claim about a person: "this human came from that
|
|
25
|
-
* campaign and then did this". It is the same personal fact as the click
|
|
26
|
-
* stamp it was derived from, and the erasure path already deletes those —
|
|
27
|
-
* `eraseEmailDeliveriesForAddresses` drops `campaignTouches` alongside the
|
|
28
|
-
* engagement stamps and says why. A summary that outlived its source would
|
|
29
|
-
* leave an erasure that removed the evidence and kept the conclusion.
|
|
30
|
-
*
|
|
31
|
-
* ## Why a collection-group query
|
|
32
|
-
*
|
|
33
|
-
* The record is per HOST, because that is where a site's own reporting reads
|
|
34
|
-
* it. An erasure arrives as an ADDRESS and knows nothing about which sites
|
|
35
|
-
* that person ever visited. The collection group is the join between the two,
|
|
36
|
-
* and `personKey` is on the record precisely so one exists — declared in
|
|
37
|
-
* `cloud/firebase-firestore.indexes.json` with `COLLECTION_GROUP` scope,
|
|
38
|
-
* because a collection-group query gets no automatic single-field index and
|
|
39
|
-
* would otherwise fail with FAILED_PRECONDITION at the moment somebody asked
|
|
40
|
-
* to be forgotten.
|
|
41
|
-
*
|
|
42
|
-
* ## The order's own record is NOT touched
|
|
43
|
-
*
|
|
44
|
-
* `emailAttributions` holds a commercial fact about a sale, keyed by the
|
|
45
|
-
* order's id and carrying no address — the revenue join says so where it
|
|
46
|
-
* writes it. This holds a record of a person's behavior under a key derived
|
|
47
|
-
* from their address. Different facts, different answers, and the split is
|
|
48
|
-
* why both are defensible.
|
|
49
|
-
*
|
|
50
|
-
* Never throws: an erasure that fails on one leg must still complete the
|
|
51
|
-
* others, and the caller reports what it removed.
|
|
52
|
-
*
|
|
53
|
-
* @returns how many records were removed.
|
|
54
|
-
*/
|
|
55
|
-
export declare function eraseCampaignAttributionsForPersonKey(key: string | null | undefined, firestore?: any): Promise<number>;
|
|
@@ -1,95 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @license
|
|
3
|
-
* Copyright 2026 Aglyn LLC
|
|
4
|
-
*
|
|
5
|
-
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
-
* you may not use this file except in compliance with the License.
|
|
7
|
-
* You may obtain a copy of the License at
|
|
8
|
-
*
|
|
9
|
-
* http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
-
*
|
|
11
|
-
* Unless required by applicable law or agreed to in writing, software
|
|
12
|
-
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
-
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
-
* See the License for the specific language governing permissions and
|
|
15
|
-
* limitations under the License.
|
|
16
|
-
*/ /**
|
|
17
|
-
* Where conversion attributions are kept, and how an erasure reaches them.
|
|
18
|
-
*
|
|
19
|
-
* ## Why this is a leaf module and not part of the join
|
|
20
|
-
*
|
|
21
|
-
* The erasure runs from `email-delivery-log.ts`, in the same function that
|
|
22
|
-
* drops a person's campaign touches — which is the point, because an
|
|
23
|
-
* attribution is a conclusion drawn from those touches and a conclusion that
|
|
24
|
-
* outlives its evidence is an erasure that did not happen. But the join
|
|
25
|
-
* itself READS that module (`readEmailCampaignTouch` is one half of the
|
|
26
|
-
* last-touch comparison), so putting the erasure beside the join would make
|
|
27
|
-
* the two modules import each other. Under jest that cycle resolves to
|
|
28
|
-
* `undefined` at import time rather than failing loudly, which is how an
|
|
29
|
-
* erasure comes to silently do nothing while its test passes.
|
|
30
|
-
*
|
|
31
|
-
* So the collection name and the sweep live here, importing nothing but
|
|
32
|
-
* Firestore, and both sides import this.
|
|
33
|
-
*/ import firebaseAdmin from "./firebase-admin.js";
|
|
34
|
-
const defaultFirestore = ()=>firebaseAdmin.app().firestore();
|
|
35
|
-
/** The per-host collection of conversion attribution records. */ export const CAMPAIGN_ATTRIBUTIONS_COLLECTION = 'campaignAttributions';
|
|
36
|
-
/** How many records one erasure pass removes per batch. */ const ERASURE_BATCH = 400;
|
|
37
|
-
/**
|
|
38
|
-
* Removes every conversion attributed to one person, across every site.
|
|
39
|
-
*
|
|
40
|
-
* ## Why an erasure reaches these at all
|
|
41
|
-
*
|
|
42
|
-
* An attribution is a claim about a person: "this human came from that
|
|
43
|
-
* campaign and then did this". It is the same personal fact as the click
|
|
44
|
-
* stamp it was derived from, and the erasure path already deletes those —
|
|
45
|
-
* `eraseEmailDeliveriesForAddresses` drops `campaignTouches` alongside the
|
|
46
|
-
* engagement stamps and says why. A summary that outlived its source would
|
|
47
|
-
* leave an erasure that removed the evidence and kept the conclusion.
|
|
48
|
-
*
|
|
49
|
-
* ## Why a collection-group query
|
|
50
|
-
*
|
|
51
|
-
* The record is per HOST, because that is where a site's own reporting reads
|
|
52
|
-
* it. An erasure arrives as an ADDRESS and knows nothing about which sites
|
|
53
|
-
* that person ever visited. The collection group is the join between the two,
|
|
54
|
-
* and `personKey` is on the record precisely so one exists — declared in
|
|
55
|
-
* `cloud/firebase-firestore.indexes.json` with `COLLECTION_GROUP` scope,
|
|
56
|
-
* because a collection-group query gets no automatic single-field index and
|
|
57
|
-
* would otherwise fail with FAILED_PRECONDITION at the moment somebody asked
|
|
58
|
-
* to be forgotten.
|
|
59
|
-
*
|
|
60
|
-
* ## The order's own record is NOT touched
|
|
61
|
-
*
|
|
62
|
-
* `emailAttributions` holds a commercial fact about a sale, keyed by the
|
|
63
|
-
* order's id and carrying no address — the revenue join says so where it
|
|
64
|
-
* writes it. This holds a record of a person's behavior under a key derived
|
|
65
|
-
* from their address. Different facts, different answers, and the split is
|
|
66
|
-
* why both are defensible.
|
|
67
|
-
*
|
|
68
|
-
* Never throws: an erasure that fails on one leg must still complete the
|
|
69
|
-
* others, and the caller reports what it removed.
|
|
70
|
-
*
|
|
71
|
-
* @returns how many records were removed.
|
|
72
|
-
*/ export async function eraseCampaignAttributionsForPersonKey(key, firestore) {
|
|
73
|
-
if (!key || typeof key !== 'string') return 0;
|
|
74
|
-
const db = firestore != null ? firestore : defaultFirestore();
|
|
75
|
-
let removed = 0;
|
|
76
|
-
try {
|
|
77
|
-
// Bounded rather than `while (true)`: a pathological volume must not be
|
|
78
|
-
// able to hold an erasure request open indefinitely, and the pass that
|
|
79
|
-
// runs next picks up whatever a bounded one left.
|
|
80
|
-
for(let pass = 0; pass < 20; pass += 1){
|
|
81
|
-
const snapshot = await db.collectionGroup(CAMPAIGN_ATTRIBUTIONS_COLLECTION).where('personKey', '==', key).limit(ERASURE_BATCH).get();
|
|
82
|
-
if (snapshot.empty) break;
|
|
83
|
-
const batch = db.batch();
|
|
84
|
-
snapshot.docs.forEach((doc)=>batch.delete(doc.ref));
|
|
85
|
-
await batch.commit();
|
|
86
|
-
removed += snapshot.size;
|
|
87
|
-
if (snapshot.size < ERASURE_BATCH) break;
|
|
88
|
-
}
|
|
89
|
-
} catch (error) {
|
|
90
|
-
console.error('eraseCampaignAttributionsForPersonKey failed', error);
|
|
91
|
-
}
|
|
92
|
-
return removed;
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
//# sourceMappingURL=campaign-attribution-store.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../../../libs/tenant/data/admin/src/lib/server/campaign-attribution-store.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/**\n * Where conversion attributions are kept, and how an erasure reaches them.\n *\n * ## Why this is a leaf module and not part of the join\n *\n * The erasure runs from `email-delivery-log.ts`, in the same function that\n * drops a person's campaign touches — which is the point, because an\n * attribution is a conclusion drawn from those touches and a conclusion that\n * outlives its evidence is an erasure that did not happen. But the join\n * itself READS that module (`readEmailCampaignTouch` is one half of the\n * last-touch comparison), so putting the erasure beside the join would make\n * the two modules import each other. Under jest that cycle resolves to\n * `undefined` at import time rather than failing loudly, which is how an\n * erasure comes to silently do nothing while its test passes.\n *\n * So the collection name and the sweep live here, importing nothing but\n * Firestore, and both sides import this.\n */\n\nimport firebaseAdmin from './firebase-admin'\n\nconst defaultFirestore = () => firebaseAdmin.app().firestore()\n\n/** The per-host collection of conversion attribution records. */\nexport const CAMPAIGN_ATTRIBUTIONS_COLLECTION = 'campaignAttributions'\n\n/** How many records one erasure pass removes per batch. */\nconst ERASURE_BATCH = 400\n\n/**\n * Removes every conversion attributed to one person, across every site.\n *\n * ## Why an erasure reaches these at all\n *\n * An attribution is a claim about a person: \"this human came from that\n * campaign and then did this\". It is the same personal fact as the click\n * stamp it was derived from, and the erasure path already deletes those —\n * `eraseEmailDeliveriesForAddresses` drops `campaignTouches` alongside the\n * engagement stamps and says why. A summary that outlived its source would\n * leave an erasure that removed the evidence and kept the conclusion.\n *\n * ## Why a collection-group query\n *\n * The record is per HOST, because that is where a site's own reporting reads\n * it. An erasure arrives as an ADDRESS and knows nothing about which sites\n * that person ever visited. The collection group is the join between the two,\n * and `personKey` is on the record precisely so one exists — declared in\n * `cloud/firebase-firestore.indexes.json` with `COLLECTION_GROUP` scope,\n * because a collection-group query gets no automatic single-field index and\n * would otherwise fail with FAILED_PRECONDITION at the moment somebody asked\n * to be forgotten.\n *\n * ## The order's own record is NOT touched\n *\n * `emailAttributions` holds a commercial fact about a sale, keyed by the\n * order's id and carrying no address — the revenue join says so where it\n * writes it. This holds a record of a person's behavior under a key derived\n * from their address. Different facts, different answers, and the split is\n * why both are defensible.\n *\n * Never throws: an erasure that fails on one leg must still complete the\n * others, and the caller reports what it removed.\n *\n * @returns how many records were removed.\n */\nexport async function eraseCampaignAttributionsForPersonKey(\n key: string | null | undefined,\n firestore?: any,\n): Promise<number> {\n if (!key || typeof key !== 'string') return 0\n const db = firestore ?? defaultFirestore()\n let removed = 0\n try {\n // Bounded rather than `while (true)`: a pathological volume must not be\n // able to hold an erasure request open indefinitely, and the pass that\n // runs next picks up whatever a bounded one left.\n for (let pass = 0; pass < 20; pass += 1) {\n const snapshot = await db\n .collectionGroup(CAMPAIGN_ATTRIBUTIONS_COLLECTION)\n .where('personKey', '==', key)\n .limit(ERASURE_BATCH)\n .get()\n if (snapshot.empty) break\n const batch = db.batch()\n snapshot.docs.forEach((doc: any) => batch.delete(doc.ref))\n await batch.commit()\n removed += snapshot.size\n if (snapshot.size < ERASURE_BATCH) break\n }\n } catch (error) {\n console.error('eraseCampaignAttributionsForPersonKey failed', error)\n }\n return removed\n}\n"],"names":["firebaseAdmin","defaultFirestore","app","firestore","CAMPAIGN_ATTRIBUTIONS_COLLECTION","ERASURE_BATCH","eraseCampaignAttributionsForPersonKey","key","db","removed","pass","snapshot","collectionGroup","where","limit","get","empty","batch","docs","forEach","doc","delete","ref","commit","size","error","console"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAED;;;;;;;;;;;;;;;;;CAiBC,GAED,OAAOA,mBAAmB,sBAAkB;AAE5C,MAAMC,mBAAmB,IAAMD,cAAcE,GAAG,GAAGC,SAAS;AAE5D,+DAA+D,GAC/D,OAAO,MAAMC,mCAAmC,uBAAsB;AAEtE,yDAAyD,GACzD,MAAMC,gBAAgB;AAEtB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAmCC,GACD,OAAO,eAAeC,sCACpBC,GAA8B,EAC9BJ,SAAe;IAEf,IAAI,CAACI,OAAO,OAAOA,QAAQ,UAAU,OAAO;IAC5C,MAAMC,KAAKL,oBAAAA,YAAaF;IACxB,IAAIQ,UAAU;IACd,IAAI;QACF,wEAAwE;QACxE,uEAAuE;QACvE,kDAAkD;QAClD,IAAK,IAAIC,OAAO,GAAGA,OAAO,IAAIA,QAAQ,EAAG;YACvC,MAAMC,WAAW,MAAMH,GACpBI,eAAe,CAACR,kCAChBS,KAAK,CAAC,aAAa,MAAMN,KACzBO,KAAK,CAACT,eACNU,GAAG;YACN,IAAIJ,SAASK,KAAK,EAAE;YACpB,MAAMC,QAAQT,GAAGS,KAAK;YACtBN,SAASO,IAAI,CAACC,OAAO,CAAC,CAACC,MAAaH,MAAMI,MAAM,CAACD,IAAIE,GAAG;YACxD,MAAML,MAAMM,MAAM;YAClBd,WAAWE,SAASa,IAAI;YACxB,IAAIb,SAASa,IAAI,GAAGnB,eAAe;QACrC;IACF,EAAE,OAAOoB,OAAO;QACdC,QAAQD,KAAK,CAAC,gDAAgDA;IAChE;IACA,OAAOhB;AACT"}
|
|
@@ -1,283 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @license
|
|
3
|
-
* Copyright 2026 Aglyn LLC
|
|
4
|
-
*
|
|
5
|
-
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
-
* you may not use this file except in compliance with the License.
|
|
7
|
-
* You may obtain a copy of the License at
|
|
8
|
-
*
|
|
9
|
-
* http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
-
*
|
|
11
|
-
* Unless required by applicable law or agreed to in writing, software
|
|
12
|
-
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
-
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
-
* See the License for the specific language governing permissions and
|
|
15
|
-
* limitations under the License.
|
|
16
|
-
*/
|
|
17
|
-
import { type DocumentReference, type Firestore } from 'firebase-admin/firestore';
|
|
18
|
-
import { CAMPAIGN_ATTRIBUTIONS_COLLECTION, eraseCampaignAttributionsForPersonKey } from './campaign-attribution-store';
|
|
19
|
-
/**
|
|
20
|
-
* THE IDENTIFY-MOMENT JOIN — a form, a lead, a contact or a booking, credited
|
|
21
|
-
* to the campaign that led to it.
|
|
22
|
-
*
|
|
23
|
-
* ## Why there is a second join at all, and why it is not a second SCHEME
|
|
24
|
-
*
|
|
25
|
-
* `email-revenue-attribution.ts` credits an ORDER by looking the buyer's
|
|
26
|
-
* address up in the touch map a click wrote. That works because an order
|
|
27
|
-
* names its buyer. Every other outcome a campaign causes is produced by
|
|
28
|
-
* somebody who was anonymous until the instant they produced it: they arrive
|
|
29
|
-
* from a campaign link, browse, and only become identifiable when they submit
|
|
30
|
-
* a form, sign up, book or check out. There is no address to look up until
|
|
31
|
-
* the very moment being attributed.
|
|
32
|
-
*
|
|
33
|
-
* So the touch is carried on the VISITOR (`utm-touch.ts`) and attached
|
|
34
|
-
* here, at each moment they become somebody. What is NOT rebuilt is the
|
|
35
|
-
* model: the same seven-day window, the same last-click rule, the same
|
|
36
|
-
* `model`/`windowDays` stamped onto every record so a report can say what it
|
|
37
|
-
* counted. The email channel is not re-derived either — it is READ from the
|
|
38
|
-
* touch the delivery webhook already wrote, so a campaign email and a
|
|
39
|
-
* campaign ad are two inputs to one comparison rather than two schemes with
|
|
40
|
-
* two answers.
|
|
41
|
-
*
|
|
42
|
-
* ## Last touch, across both channels
|
|
43
|
-
*
|
|
44
|
-
* At the identify moment there can be two candidate touches:
|
|
45
|
-
*
|
|
46
|
-
* - the WEB touch the visitor's device carried, from an ad, a partner link
|
|
47
|
-
* or a social post; and
|
|
48
|
-
* - the EMAIL touch on `emailDeliveries/{personKey}.campaignTouches[hostId]`,
|
|
49
|
-
* written server-side when they clicked a campaign's mail.
|
|
50
|
-
*
|
|
51
|
-
* Both are window-checked and the LATER one wins. Splitting the outcome
|
|
52
|
-
* between them is the multi-touch model the revenue work rejected for the
|
|
53
|
-
* reason that applies here unchanged: a rule the merchant did not choose
|
|
54
|
-
* produces a figure nobody can check. One outcome, one campaign, and a rule
|
|
55
|
-
* stated on the record.
|
|
56
|
-
*
|
|
57
|
-
* ## No campaign touch means NO RECORD
|
|
58
|
-
*
|
|
59
|
-
* Direct traffic writes nothing at all, and there is deliberately no fallback
|
|
60
|
-
* — no "most recent campaign on this site", no referrer inference, no
|
|
61
|
-
* `utm_source=direct`. A conversion nobody can be credited with is a
|
|
62
|
-
* conversion nobody is credited with, and the absence of a record is how the
|
|
63
|
-
* report says so. The cost of a miss is one uncounted outcome; the cost of a
|
|
64
|
-
* guess is a campaign that reads as effective because it was the last one to
|
|
65
|
-
* run.
|
|
66
|
-
*
|
|
67
|
-
* ## Three reads at most, and usually one
|
|
68
|
-
*
|
|
69
|
-
* Resolving a touch costs ONE keyed document read, and only when the visitor
|
|
70
|
-
* gave an address — no query, no index, nothing that can be truncated. A door
|
|
71
|
-
* resolves once per request and hands the result to every writer beneath it,
|
|
72
|
-
* so a form submission that creates a submission, a contact and a lead pays
|
|
73
|
-
* for the lookup once rather than three times.
|
|
74
|
-
*
|
|
75
|
-
* ## Never throws
|
|
76
|
-
*
|
|
77
|
-
* The same contract as the revenue join and for the same reason: the visitor
|
|
78
|
-
* has already done the thing being attributed and the record of it is already
|
|
79
|
-
* written. A lost attribution understates a campaign; a thrown one loses a
|
|
80
|
-
* lead.
|
|
81
|
-
*/
|
|
82
|
-
/**
|
|
83
|
-
* The collection and the erasure sweep, re-exported so a caller needs one
|
|
84
|
-
* import for the whole join. They are DEFINED in a leaf module because the
|
|
85
|
-
* sweep runs from `email-delivery-log.ts`, which this file reads — see
|
|
86
|
-
* `campaign-attribution-store.ts` for why that cycle is not merely untidy.
|
|
87
|
-
*/
|
|
88
|
-
export { CAMPAIGN_ATTRIBUTIONS_COLLECTION, eraseCampaignAttributionsForPersonKey, };
|
|
89
|
-
/** The single conversion rollup document under an email campaign. */
|
|
90
|
-
export declare const CAMPAIGN_CONVERSIONS_REPORT_DOC = "conversions";
|
|
91
|
-
/**
|
|
92
|
-
* Which identify moment a record credits.
|
|
93
|
-
*
|
|
94
|
-
* Kept apart rather than summed, which is the whole reason the kind is on the
|
|
95
|
-
* record. One form submission by a new person creates a submission, a contact
|
|
96
|
-
* and possibly a lead — three true statements about one visitor action — and
|
|
97
|
-
* a report that added them would treble every campaign's conversions. A
|
|
98
|
-
* reader asks for one kind at a time, exactly as the revenue rollup buckets
|
|
99
|
-
* by currency and never totals across them.
|
|
100
|
-
*/
|
|
101
|
-
export type CampaignConversionKind = 'form' | 'lead' | 'contact' | 'booking';
|
|
102
|
-
/**
|
|
103
|
-
* Which channel the credited touch arrived through.
|
|
104
|
-
*
|
|
105
|
-
* `sequence` (AGL-3254) is a click on, or the sending of, a one-to-one
|
|
106
|
-
* email a rep's sequence sent. It is read off the same touch map as the
|
|
107
|
-
* email channel — the Outreach click route stamps it there with the
|
|
108
|
-
* sequence and the enrollment beside the campaign the sequence is in — so
|
|
109
|
-
* a booking made from a sequence link is credited by the door that credits
|
|
110
|
-
* every other booking, and nothing in this join has to know what a
|
|
111
|
-
* sequence is.
|
|
112
|
-
*/
|
|
113
|
-
export type CampaignTouchChannel = 'email' | 'web' | 'sequence';
|
|
114
|
-
/** The touch a conversion is credited to, once both channels have been asked. */
|
|
115
|
-
export interface ResolvedCampaignTouch {
|
|
116
|
-
channel: CampaignTouchChannel;
|
|
117
|
-
/**
|
|
118
|
-
* The campaign document, when the touch was a click on our own mail — or
|
|
119
|
-
* the campaign CONTAINER (`emailCampaigns/{id}`) a sequence is in, for a
|
|
120
|
-
* sequence touch.
|
|
121
|
-
*/
|
|
122
|
-
campaignId?: string;
|
|
123
|
-
/** The sequence the touch came through, for a `sequence` touch. */
|
|
124
|
-
sequenceId?: string;
|
|
125
|
-
/** The enrollment the touch came through, for a `sequence` touch. */
|
|
126
|
-
enrollmentId?: string;
|
|
127
|
-
/** `utm_source`, when the touch was a link on the web. */
|
|
128
|
-
source?: string;
|
|
129
|
-
/** `utm_medium`, when the touch was a link on the web. */
|
|
130
|
-
medium?: string;
|
|
131
|
-
/** `utm_campaign`, when the touch was a link on the web. */
|
|
132
|
-
campaign?: string;
|
|
133
|
-
/** When the visitor followed the campaign link, epoch ms. */
|
|
134
|
-
touchedAtMs: number;
|
|
135
|
-
/**
|
|
136
|
-
* `sha256` of the normalized address the touch was resolved against, when
|
|
137
|
-
* the visitor gave one. The handle an erasure erases by — see
|
|
138
|
-
* {@link eraseCampaignAttributionsForPersonKey}.
|
|
139
|
-
*/
|
|
140
|
-
personKey?: string;
|
|
141
|
-
}
|
|
142
|
-
/** What one conversion attribution record holds. */
|
|
143
|
-
export interface CampaignConversionRecord extends ResolvedCampaignTouch {
|
|
144
|
-
kind: CampaignConversionKind;
|
|
145
|
-
/** The submission, lead, contact or booking this credits. */
|
|
146
|
-
refId: string;
|
|
147
|
-
/** When the visitor became identifiable. */
|
|
148
|
-
convertedAtMs: number;
|
|
149
|
-
/** The model this credit was decided under. */
|
|
150
|
-
model: string;
|
|
151
|
-
/** The window, in days, it was decided inside. */
|
|
152
|
-
windowDays: number;
|
|
153
|
-
}
|
|
154
|
-
/**
|
|
155
|
-
* The document id for one conversion.
|
|
156
|
-
*
|
|
157
|
-
* `{kind}:{refId}`, so the same submission credited twice is the same
|
|
158
|
-
* document — which is what makes {@link attributeCampaignConversion}'s
|
|
159
|
-
* `create()` idempotent. Scoped by kind because the ids come from different
|
|
160
|
-
* collections and a booking id has no reason to be distinct from a form
|
|
161
|
-
* submission's.
|
|
162
|
-
*/
|
|
163
|
-
export declare function campaignConversionId(kind: CampaignConversionKind, refId: string): string;
|
|
164
|
-
/**
|
|
165
|
-
* The campaign touch to credit a conversion happening now, or `null`.
|
|
166
|
-
*
|
|
167
|
-
* Called ONCE per conversion request, at the door, and the result handed to
|
|
168
|
-
* every writer beneath it. See the module docblock for the cost argument.
|
|
169
|
-
*
|
|
170
|
-
* @returns the winning touch, or `null` when neither channel has one inside
|
|
171
|
-
* the window — which is the ordinary case and writes nothing.
|
|
172
|
-
*/
|
|
173
|
-
export declare function resolveCampaignTouch(options: {
|
|
174
|
-
hostId: string;
|
|
175
|
-
/**
|
|
176
|
-
* The wire form the conversion request carried, when the door reports
|
|
177
|
-
* one. Re-parsed through the allowlist that wrote it rather than trusted:
|
|
178
|
-
* it arrives from the visitor's browser, so it may claim no more than an
|
|
179
|
-
* inbound URL could.
|
|
180
|
-
*/
|
|
181
|
-
wire?: unknown;
|
|
182
|
-
/** The address the visitor identified with, raw. */
|
|
183
|
-
email?: unknown;
|
|
184
|
-
/** When they identified themselves. Defaults to now. */
|
|
185
|
-
atMs?: number;
|
|
186
|
-
}, firestore?: any): Promise<ResolvedCampaignTouch | null>;
|
|
187
|
-
/**
|
|
188
|
-
* Credits one conversion to the campaign the visitor last came from.
|
|
189
|
-
*
|
|
190
|
-
* ## Why the record is a document and not a field
|
|
191
|
-
*
|
|
192
|
-
* The revenue join's three reasons, and the first is decisive here too.
|
|
193
|
-
* `create()` fails when the document already exists, which is exact
|
|
194
|
-
* idempotency for free: a retried form POST, a redelivered booking webhook
|
|
195
|
-
* and a re-run capture all land here a second time and must all leave the
|
|
196
|
-
* rollup where they found it. Second, the four kinds live in four different
|
|
197
|
-
* collections — a contact is org-scoped and shared across every site in the
|
|
198
|
-
* org — so there is no one document a field could go on. Third, the
|
|
199
|
-
* submission, the lead and the booking are read by the inbox, the export and
|
|
200
|
-
* the fulfilment path, and none of them wants a field about marketing.
|
|
201
|
-
*
|
|
202
|
-
* ## The rollup exists for the EMAIL channel only
|
|
203
|
-
*
|
|
204
|
-
* A campaign document is a real entity with a real id, so its conversions
|
|
205
|
-
* roll up under it beside the revenue the same join already credits it with.
|
|
206
|
-
* A web campaign is a LABEL the marketer typed into a URL, with no document,
|
|
207
|
-
* no id and no bound on how many distinct values exist — the same unbounded
|
|
208
|
-
* key space the analytics collector caps its per-day label map against. A
|
|
209
|
-
* rollup keyed on it would be a map anybody who can vary a query string can
|
|
210
|
-
* grow. So the web channel's records stand on their own and a report reads
|
|
211
|
-
* them; see the module's report seam note.
|
|
212
|
-
*
|
|
213
|
-
* @returns the record written, or `null` when nothing was credited.
|
|
214
|
-
*/
|
|
215
|
-
export declare function attributeCampaignConversion(options: {
|
|
216
|
-
hostId: string;
|
|
217
|
-
kind: CampaignConversionKind;
|
|
218
|
-
/** The submission, lead, contact or booking being credited. */
|
|
219
|
-
refId: string;
|
|
220
|
-
/** The touch {@link resolveCampaignTouch} picked, or `null` for direct. */
|
|
221
|
-
touch: ResolvedCampaignTouch | null | undefined;
|
|
222
|
-
/** When the visitor became identifiable. Defaults to now. */
|
|
223
|
-
convertedAtMs?: number;
|
|
224
|
-
}, firestore?: any): Promise<CampaignConversionRecord | null>;
|
|
225
|
-
/**
|
|
226
|
-
* The per-org collection of sequence rollups. Restated from the reader
|
|
227
|
-
* (`campaign-report.ts` in the campaigns UI library, which this package may
|
|
228
|
-
* not import) and asserted equal by the reader's spec.
|
|
229
|
-
*/
|
|
230
|
-
export declare const CAMPAIGN_SEQUENCE_REPORTS_COLLECTION = "campaignSequenceReports";
|
|
231
|
-
/** The outcomes the runtime credits, in funnel order. */
|
|
232
|
-
export declare const CAMPAIGN_SEQUENCE_OUTCOMES: readonly ["enrolled", "sent", "replied", "meetings", "converted"];
|
|
233
|
-
export type CampaignSequenceOutcome = (typeof CAMPAIGN_SEQUENCE_OUTCOMES)[number];
|
|
234
|
-
/**
|
|
235
|
-
* Credits one outcome, for one enrollment, to every campaign named.
|
|
236
|
-
*
|
|
237
|
-
* Never throws, and never refuses the batch for one bad id: the outcome
|
|
238
|
-
* has already happened, and a campaign that is gone gains an orphaned
|
|
239
|
-
* report rather than blocking the credit to one that is not — the
|
|
240
|
-
* conversions rollup's own argument for a merge-set that creates.
|
|
241
|
-
*
|
|
242
|
-
* @returns how many campaigns were credited.
|
|
243
|
-
*/
|
|
244
|
-
export declare function creditCampaignSequenceOutcome(options: {
|
|
245
|
-
/** The site the enrollment emails for; resolves the org when none is given. */
|
|
246
|
-
hostId: string;
|
|
247
|
-
/**
|
|
248
|
-
* The org whose containers the campaigns are, when the caller holds it —
|
|
249
|
-
* an enrollment is an org document, so its routes do. Saves the
|
|
250
|
-
* `hostIndex` read.
|
|
251
|
-
*/
|
|
252
|
-
orgId?: string | null;
|
|
253
|
-
/** The campaigns the enrollment carries; unusable ids are skipped. */
|
|
254
|
-
campaignIds: readonly string[];
|
|
255
|
-
outcome: CampaignSequenceOutcome;
|
|
256
|
-
/** When the outcome happened. Defaults to now. */
|
|
257
|
-
atMs?: number;
|
|
258
|
-
}, firestore?: any): Promise<number>;
|
|
259
|
-
/** `orgs/{orgId}/campaigns/{sendId}`. */
|
|
260
|
-
export declare function orgCampaignSendRef(orgId: string, sendId: string, firestore?: Firestore): DocumentReference;
|
|
261
|
-
/** `orgs/{orgId}/campaignSequenceReports/{campaignId}`. */
|
|
262
|
-
export declare function orgCampaignSequenceReportRef(orgId: string, campaignId: string, firestore?: Firestore): DocumentReference;
|
|
263
|
-
export interface ResolveCampaignSendRefInput {
|
|
264
|
-
/** The site the link or the message tag names. */
|
|
265
|
-
hostId: string;
|
|
266
|
-
/** The send id (`cid` on a link, `campaignId` on a message tag). */
|
|
267
|
-
sendId: string;
|
|
268
|
-
/**
|
|
269
|
-
* The org, when the caller already has it — a message tag minted since
|
|
270
|
-
* AGL-3273 carries it. Saves the `hostIndex` read.
|
|
271
|
-
*/
|
|
272
|
-
orgId?: string | null;
|
|
273
|
-
firestore?: Firestore;
|
|
274
|
-
}
|
|
275
|
-
/**
|
|
276
|
-
* The send's document, wherever it currently is. Null when neither location
|
|
277
|
-
* holds it — a send id from a link whose send was discarded, or a forged one.
|
|
278
|
-
*
|
|
279
|
-
* Costs one read after the migration (the org document), plus the
|
|
280
|
-
* `hostIndex` lookup when the caller has no org id; that lookup is
|
|
281
|
-
* request-cached.
|
|
282
|
-
*/
|
|
283
|
-
export declare function resolveCampaignSendRef(input: ResolveCampaignSendRefInput): Promise<DocumentReference | null>;
|