@aglyn/tenant-data-admin 1.0.0-beta.143

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.
Files changed (374) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +7 -0
  3. package/jest.integration.config.d.ts +39 -0
  4. package/jest.integration.setup.d.ts +17 -0
  5. package/package.json +51 -0
  6. package/src/index.d.ts +17 -0
  7. package/src/index.js +18 -0
  8. package/src/index.js.map +1 -0
  9. package/src/lib/render-cache.d.ts +184 -0
  10. package/src/lib/render-cache.js +198 -0
  11. package/src/lib/render-cache.js.map +1 -0
  12. package/src/lib/server/account-addresses.d.ts +160 -0
  13. package/src/lib/server/account-addresses.js +311 -0
  14. package/src/lib/server/account-addresses.js.map +1 -0
  15. package/src/lib/server/account-emails.d.ts +219 -0
  16. package/src/lib/server/account-emails.js +640 -0
  17. package/src/lib/server/account-emails.js.map +1 -0
  18. package/src/lib/server/admin-audit.d.ts +180 -0
  19. package/src/lib/server/admin-audit.js +233 -0
  20. package/src/lib/server/admin-audit.js.map +1 -0
  21. package/src/lib/server/analytics-retention.d.ts +63 -0
  22. package/src/lib/server/analytics-retention.js +66 -0
  23. package/src/lib/server/analytics-retention.js.map +1 -0
  24. package/src/lib/server/api-http.d.ts +159 -0
  25. package/src/lib/server/api-http.js +230 -0
  26. package/src/lib/server/api-http.js.map +1 -0
  27. package/src/lib/server/api-keys.d.ts +117 -0
  28. package/src/lib/server/api-keys.js +298 -0
  29. package/src/lib/server/api-keys.js.map +1 -0
  30. package/src/lib/server/auth-handoff.d.ts +165 -0
  31. package/src/lib/server/auth-handoff.js +482 -0
  32. package/src/lib/server/auth-handoff.js.map +1 -0
  33. package/src/lib/server/auth-pools.d.ts +246 -0
  34. package/src/lib/server/auth-pools.js +551 -0
  35. package/src/lib/server/auth-pools.js.map +1 -0
  36. package/src/lib/server/billing-addons.d.ts +171 -0
  37. package/src/lib/server/billing-addons.js +277 -0
  38. package/src/lib/server/billing-addons.js.map +1 -0
  39. package/src/lib/server/campaign-attribution-store.d.ts +55 -0
  40. package/src/lib/server/campaign-attribution-store.js +95 -0
  41. package/src/lib/server/campaign-attribution-store.js.map +1 -0
  42. package/src/lib/server/campaign-conversion-attribution.d.ts +205 -0
  43. package/src/lib/server/campaign-conversion-attribution.js +279 -0
  44. package/src/lib/server/campaign-conversion-attribution.js.map +1 -0
  45. package/src/lib/server/client-error-report.d.ts +301 -0
  46. package/src/lib/server/client-error-report.js +755 -0
  47. package/src/lib/server/client-error-report.js.map +1 -0
  48. package/src/lib/server/connect-account-status.d.ts +74 -0
  49. package/src/lib/server/connect-account-status.js +94 -0
  50. package/src/lib/server/connect-account-status.js.map +1 -0
  51. package/src/lib/server/connect-payout-failure.d.ts +103 -0
  52. package/src/lib/server/connect-payout-failure.js +127 -0
  53. package/src/lib/server/connect-payout-failure.js.map +1 -0
  54. package/src/lib/server/console-api-rate-limit.d.ts +81 -0
  55. package/src/lib/server/console-api-rate-limit.js +111 -0
  56. package/src/lib/server/console-api-rate-limit.js.map +1 -0
  57. package/src/lib/server/console-domains.d.ts +326 -0
  58. package/src/lib/server/console-domains.js +779 -0
  59. package/src/lib/server/console-domains.js.map +1 -0
  60. package/src/lib/server/consume-once.d.ts +91 -0
  61. package/src/lib/server/consume-once.js +96 -0
  62. package/src/lib/server/consume-once.js.map +1 -0
  63. package/src/lib/server/contact-company-link.d.ts +85 -0
  64. package/src/lib/server/contact-company-link.js +105 -0
  65. package/src/lib/server/contact-company-link.js.map +1 -0
  66. package/src/lib/server/contact-email-engagement.d.ts +32 -0
  67. package/src/lib/server/contact-email-engagement.js +145 -0
  68. package/src/lib/server/contact-email-engagement.js.map +1 -0
  69. package/src/lib/server/contact-email-index.d.ts +56 -0
  70. package/src/lib/server/contact-email-index.js +158 -0
  71. package/src/lib/server/contact-email-index.js.map +1 -0
  72. package/src/lib/server/contact-lifecycle-floor.d.ts +89 -0
  73. package/src/lib/server/contact-lifecycle-floor.js +88 -0
  74. package/src/lib/server/contact-lifecycle-floor.js.map +1 -0
  75. package/src/lib/server/contact-merge.d.ts +55 -0
  76. package/src/lib/server/contact-merge.js +279 -0
  77. package/src/lib/server/contact-merge.js.map +1 -0
  78. package/src/lib/server/contact-suppression.d.ts +153 -0
  79. package/src/lib/server/contact-suppression.js +259 -0
  80. package/src/lib/server/contact-suppression.js.map +1 -0
  81. package/src/lib/server/crm-booking-activity.d.ts +86 -0
  82. package/src/lib/server/crm-booking-activity.js +251 -0
  83. package/src/lib/server/crm-booking-activity.js.map +1 -0
  84. package/src/lib/server/crm-email-activity.d.ts +112 -0
  85. package/src/lib/server/crm-email-activity.js +143 -0
  86. package/src/lib/server/crm-email-activity.js.map +1 -0
  87. package/src/lib/server/crm-inbound-email.d.ts +145 -0
  88. package/src/lib/server/crm-inbound-email.js +287 -0
  89. package/src/lib/server/crm-inbound-email.js.map +1 -0
  90. package/src/lib/server/crm-next-activity.d.ts +74 -0
  91. package/src/lib/server/crm-next-activity.js +194 -0
  92. package/src/lib/server/crm-next-activity.js.map +1 -0
  93. package/src/lib/server/crm-records.d.ts +144 -0
  94. package/src/lib/server/crm-records.js +218 -0
  95. package/src/lib/server/crm-records.js.map +1 -0
  96. package/src/lib/server/csp-aggregate.d.ts +62 -0
  97. package/src/lib/server/csp-aggregate.js +276 -0
  98. package/src/lib/server/csp-aggregate.js.map +1 -0
  99. package/src/lib/server/data-storage-gate.d.ts +66 -0
  100. package/src/lib/server/data-storage-gate.js +48 -0
  101. package/src/lib/server/data-storage-gate.js.map +1 -0
  102. package/src/lib/server/dataset-live-pages.d.ts +179 -0
  103. package/src/lib/server/dataset-live-pages.js +421 -0
  104. package/src/lib/server/dataset-live-pages.js.map +1 -0
  105. package/src/lib/server/dns-probe.d.ts +53 -0
  106. package/src/lib/server/dns-probe.js +184 -0
  107. package/src/lib/server/dns-probe.js.map +1 -0
  108. package/src/lib/server/document-id.d.ts +29 -0
  109. package/src/lib/server/document-id.js +100 -0
  110. package/src/lib/server/document-id.js.map +1 -0
  111. package/src/lib/server/domain-provider-vercel.d.ts +33 -0
  112. package/src/lib/server/domain-provider-vercel.js +298 -0
  113. package/src/lib/server/domain-provider-vercel.js.map +1 -0
  114. package/src/lib/server/domain-provider-webhook.d.ts +73 -0
  115. package/src/lib/server/domain-provider-webhook.js +223 -0
  116. package/src/lib/server/domain-provider-webhook.js.map +1 -0
  117. package/src/lib/server/domain-provider-wildcard.d.ts +54 -0
  118. package/src/lib/server/domain-provider-wildcard.js +163 -0
  119. package/src/lib/server/domain-provider-wildcard.js.map +1 -0
  120. package/src/lib/server/domain-provider.d.ts +197 -0
  121. package/src/lib/server/domain-provider.js +221 -0
  122. package/src/lib/server/domain-provider.js.map +1 -0
  123. package/src/lib/server/duplicate-activity.d.ts +54 -0
  124. package/src/lib/server/duplicate-activity.js +67 -0
  125. package/src/lib/server/duplicate-activity.js.map +1 -0
  126. package/src/lib/server/duplicate-resource.d.ts +55 -0
  127. package/src/lib/server/duplicate-resource.js +474 -0
  128. package/src/lib/server/duplicate-resource.js.map +1 -0
  129. package/src/lib/server/dynamic-list-materialize.d.ts +153 -0
  130. package/src/lib/server/dynamic-list-materialize.js +581 -0
  131. package/src/lib/server/dynamic-list-materialize.js.map +1 -0
  132. package/src/lib/server/edit-access-authz.d.ts +56 -0
  133. package/src/lib/server/edit-access-authz.js +107 -0
  134. package/src/lib/server/edit-access-authz.js.map +1 -0
  135. package/src/lib/server/edit-access-token.d.ts +72 -0
  136. package/src/lib/server/edit-access-token.js +116 -0
  137. package/src/lib/server/edit-access-token.js.map +1 -0
  138. package/src/lib/server/edit-hint-token.d.ts +83 -0
  139. package/src/lib/server/edit-hint-token.js +119 -0
  140. package/src/lib/server/edit-hint-token.js.map +1 -0
  141. package/src/lib/server/email-delivery-log.d.ts +509 -0
  142. package/src/lib/server/email-delivery-log.js +1043 -0
  143. package/src/lib/server/email-delivery-log.js.map +1 -0
  144. package/src/lib/server/email-flow-gate.d.ts +80 -0
  145. package/src/lib/server/email-flow-gate.js +153 -0
  146. package/src/lib/server/email-flow-gate.js.map +1 -0
  147. package/src/lib/server/email-marketing-gate.d.ts +333 -0
  148. package/src/lib/server/email-marketing-gate.js +639 -0
  149. package/src/lib/server/email-marketing-gate.js.map +1 -0
  150. package/src/lib/server/email-metering.d.ts +293 -0
  151. package/src/lib/server/email-metering.js +359 -0
  152. package/src/lib/server/email-metering.js.map +1 -0
  153. package/src/lib/server/email-revenue-attribution.d.ts +185 -0
  154. package/src/lib/server/email-revenue-attribution.js +300 -0
  155. package/src/lib/server/email-revenue-attribution.js.map +1 -0
  156. package/src/lib/server/email-send-rate.d.ts +232 -0
  157. package/src/lib/server/email-send-rate.js +417 -0
  158. package/src/lib/server/email-send-rate.js.map +1 -0
  159. package/src/lib/server/email-sender-reputation.d.ts +317 -0
  160. package/src/lib/server/email-sender-reputation.js +464 -0
  161. package/src/lib/server/email-sender-reputation.js.map +1 -0
  162. package/src/lib/server/email-suppression.d.ts +490 -0
  163. package/src/lib/server/email-suppression.js +648 -0
  164. package/src/lib/server/email-suppression.js.map +1 -0
  165. package/src/lib/server/email-topic-confirmation.d.ts +150 -0
  166. package/src/lib/server/email-topic-confirmation.js +272 -0
  167. package/src/lib/server/email-topic-confirmation.js.map +1 -0
  168. package/src/lib/server/email-unsubscribe-link.d.ts +174 -0
  169. package/src/lib/server/email-unsubscribe-link.js +236 -0
  170. package/src/lib/server/email-unsubscribe-link.js.map +1 -0
  171. package/src/lib/server/erase-person.d.ts +116 -0
  172. package/src/lib/server/erase-person.js +272 -0
  173. package/src/lib/server/erase-person.js.map +1 -0
  174. package/src/lib/server/erase.d.ts +458 -0
  175. package/src/lib/server/erase.js +1506 -0
  176. package/src/lib/server/erase.js.map +1 -0
  177. package/src/lib/server/firebase-admin.d.ts +71 -0
  178. package/src/lib/server/firebase-admin.js +343 -0
  179. package/src/lib/server/firebase-admin.js.map +1 -0
  180. package/src/lib/server/form-dataset-binding-token.d.ts +35 -0
  181. package/src/lib/server/form-dataset-binding-token.js +107 -0
  182. package/src/lib/server/form-dataset-binding-token.js.map +1 -0
  183. package/src/lib/server/free-workspace-cap.d.ts +201 -0
  184. package/src/lib/server/free-workspace-cap.js +404 -0
  185. package/src/lib/server/free-workspace-cap.js.map +1 -0
  186. package/src/lib/server/ga4-measurement-protocol.d.ts +256 -0
  187. package/src/lib/server/ga4-measurement-protocol.js +558 -0
  188. package/src/lib/server/ga4-measurement-protocol.js.map +1 -0
  189. package/src/lib/server/host-email-tokens.d.ts +47 -0
  190. package/src/lib/server/host-email-tokens.js +72 -0
  191. package/src/lib/server/host-email-tokens.js.map +1 -0
  192. package/src/lib/server/host-memberships.d.ts +54 -0
  193. package/src/lib/server/host-memberships.js +194 -0
  194. package/src/lib/server/host-memberships.js.map +1 -0
  195. package/src/lib/server/host-sending-domain.d.ts +292 -0
  196. package/src/lib/server/host-sending-domain.js +451 -0
  197. package/src/lib/server/host-sending-domain.js.map +1 -0
  198. package/src/lib/server/host-visitor-records.d.ts +150 -0
  199. package/src/lib/server/host-visitor-records.js +262 -0
  200. package/src/lib/server/host-visitor-records.js.map +1 -0
  201. package/src/lib/server/id-token-refusal.d.ts +28 -0
  202. package/src/lib/server/id-token-refusal.js +111 -0
  203. package/src/lib/server/id-token-refusal.js.map +1 -0
  204. package/src/lib/server/legal-acceptance.d.ts +214 -0
  205. package/src/lib/server/legal-acceptance.js +305 -0
  206. package/src/lib/server/legal-acceptance.js.map +1 -0
  207. package/src/lib/server/list-members.d.ts +211 -0
  208. package/src/lib/server/list-members.js +176 -0
  209. package/src/lib/server/list-members.js.map +1 -0
  210. package/src/lib/server/live-page-usage.d.ts +202 -0
  211. package/src/lib/server/live-page-usage.js +300 -0
  212. package/src/lib/server/live-page-usage.js.map +1 -0
  213. package/src/lib/server/lockdown.d.ts +313 -0
  214. package/src/lib/server/lockdown.js +643 -0
  215. package/src/lib/server/lockdown.js.map +1 -0
  216. package/src/lib/server/media-cdn-rate-limit.d.ts +188 -0
  217. package/src/lib/server/media-cdn-rate-limit.js +204 -0
  218. package/src/lib/server/media-cdn-rate-limit.js.map +1 -0
  219. package/src/lib/server/media-delivery.d.ts +267 -0
  220. package/src/lib/server/media-delivery.js +485 -0
  221. package/src/lib/server/media-delivery.js.map +1 -0
  222. package/src/lib/server/media-download-tokens.d.ts +161 -0
  223. package/src/lib/server/media-download-tokens.js +386 -0
  224. package/src/lib/server/media-download-tokens.js.map +1 -0
  225. package/src/lib/server/media-quarantine.d.ts +127 -0
  226. package/src/lib/server/media-quarantine.js +170 -0
  227. package/src/lib/server/media-quarantine.js.map +1 -0
  228. package/src/lib/server/media-signing.d.ts +126 -0
  229. package/src/lib/server/media-signing.js +159 -0
  230. package/src/lib/server/media-signing.js.map +1 -0
  231. package/src/lib/server/media-storage-path.d.ts +96 -0
  232. package/src/lib/server/media-storage-path.js +111 -0
  233. package/src/lib/server/media-storage-path.js.map +1 -0
  234. package/src/lib/server/media-strong-digest.d.ts +85 -0
  235. package/src/lib/server/media-strong-digest.js +190 -0
  236. package/src/lib/server/media-strong-digest.js.map +1 -0
  237. package/src/lib/server/media-tombstone.d.ts +276 -0
  238. package/src/lib/server/media-tombstone.js +421 -0
  239. package/src/lib/server/media-tombstone.js.map +1 -0
  240. package/src/lib/server/media-variants.d.ts +309 -0
  241. package/src/lib/server/media-variants.js +428 -0
  242. package/src/lib/server/media-variants.js.map +1 -0
  243. package/src/lib/server/member-email-aliases.d.ts +180 -0
  244. package/src/lib/server/member-email-aliases.js +420 -0
  245. package/src/lib/server/member-email-aliases.js.map +1 -0
  246. package/src/lib/server/member-photo.d.ts +88 -0
  247. package/src/lib/server/member-photo.js +185 -0
  248. package/src/lib/server/member-photo.js.map +1 -0
  249. package/src/lib/server/membership-recover-throttle.d.ts +211 -0
  250. package/src/lib/server/membership-recover-throttle.js +231 -0
  251. package/src/lib/server/membership-recover-throttle.js.map +1 -0
  252. package/src/lib/server/notifications.d.ts +55 -0
  253. package/src/lib/server/notifications.js +134 -0
  254. package/src/lib/server/notifications.js.map +1 -0
  255. package/src/lib/server/org-billing.d.ts +97 -0
  256. package/src/lib/server/org-billing.js +236 -0
  257. package/src/lib/server/org-billing.js.map +1 -0
  258. package/src/lib/server/organizations.d.ts +837 -0
  259. package/src/lib/server/organizations.js +1814 -0
  260. package/src/lib/server/organizations.js.map +1 -0
  261. package/src/lib/server/password-reset-throttle.d.ts +84 -0
  262. package/src/lib/server/password-reset-throttle.js +102 -0
  263. package/src/lib/server/password-reset-throttle.js.map +1 -0
  264. package/src/lib/server/personal-data-export.d.ts +241 -0
  265. package/src/lib/server/personal-data-export.js +858 -0
  266. package/src/lib/server/personal-data-export.js.map +1 -0
  267. package/src/lib/server/platform-domain-names.d.ts +62 -0
  268. package/src/lib/server/platform-domain-names.js +197 -0
  269. package/src/lib/server/platform-domain-names.js.map +1 -0
  270. package/src/lib/server/platform-marketing-consent.d.ts +133 -0
  271. package/src/lib/server/platform-marketing-consent.js +209 -0
  272. package/src/lib/server/platform-marketing-consent.js.map +1 -0
  273. package/src/lib/server/rate-limit-store.d.ts +702 -0
  274. package/src/lib/server/rate-limit-store.js +1220 -0
  275. package/src/lib/server/rate-limit-store.js.map +1 -0
  276. package/src/lib/server/realm-plugins.d.ts +67 -0
  277. package/src/lib/server/realm-plugins.js +250 -0
  278. package/src/lib/server/realm-plugins.js.map +1 -0
  279. package/src/lib/server/recaptcha-allowlist.d.ts +90 -0
  280. package/src/lib/server/recaptcha-allowlist.js +409 -0
  281. package/src/lib/server/recaptcha-allowlist.js.map +1 -0
  282. package/src/lib/server/release-flags.d.ts +105 -0
  283. package/src/lib/server/release-flags.js +183 -0
  284. package/src/lib/server/release-flags.js.map +1 -0
  285. package/src/lib/server/replay-erasures.d.ts +126 -0
  286. package/src/lib/server/replay-erasures.js +217 -0
  287. package/src/lib/server/replay-erasures.js.map +1 -0
  288. package/src/lib/server/resolve-people.d.ts +66 -0
  289. package/src/lib/server/resolve-people.js +112 -0
  290. package/src/lib/server/resolve-people.js.map +1 -0
  291. package/src/lib/server/safe-equal.d.ts +45 -0
  292. package/src/lib/server/safe-equal.js +58 -0
  293. package/src/lib/server/safe-equal.js.map +1 -0
  294. package/src/lib/server/sending-domain-debt.d.ts +127 -0
  295. package/src/lib/server/sending-domain-debt.js +268 -0
  296. package/src/lib/server/sending-domain-debt.js.map +1 -0
  297. package/src/lib/server/sending-domain-recheck.d.ts +151 -0
  298. package/src/lib/server/sending-domain-recheck.js +253 -0
  299. package/src/lib/server/sending-domain-recheck.js.map +1 -0
  300. package/src/lib/server/sending-domains.d.ts +364 -0
  301. package/src/lib/server/sending-domains.js +683 -0
  302. package/src/lib/server/sending-domains.js.map +1 -0
  303. package/src/lib/server/serve-media-cdn.d.ts +433 -0
  304. package/src/lib/server/serve-media-cdn.js +1370 -0
  305. package/src/lib/server/serve-media-cdn.js.map +1 -0
  306. package/src/lib/server/serve-plugin-fetch.d.ts +61 -0
  307. package/src/lib/server/serve-plugin-fetch.js +294 -0
  308. package/src/lib/server/serve-plugin-fetch.js.map +1 -0
  309. package/src/lib/server/sms-keywords.d.ts +63 -0
  310. package/src/lib/server/sms-keywords.js +146 -0
  311. package/src/lib/server/sms-keywords.js.map +1 -0
  312. package/src/lib/server/sso-break-glass-owners.d.ts +91 -0
  313. package/src/lib/server/sso-break-glass-owners.js +227 -0
  314. package/src/lib/server/sso-break-glass-owners.js.map +1 -0
  315. package/src/lib/server/sso-domain-policy.d.ts +140 -0
  316. package/src/lib/server/sso-domain-policy.js +239 -0
  317. package/src/lib/server/sso-domain-policy.js.map +1 -0
  318. package/src/lib/server/sso-drift-logic.d.ts +106 -0
  319. package/src/lib/server/sso-drift-logic.js +95 -0
  320. package/src/lib/server/sso-drift-logic.js.map +1 -0
  321. package/src/lib/server/sso-enforcement.d.ts +204 -0
  322. package/src/lib/server/sso-enforcement.js +309 -0
  323. package/src/lib/server/sso-enforcement.js.map +1 -0
  324. package/src/lib/server/sso-provisioning.d.ts +320 -0
  325. package/src/lib/server/sso-provisioning.js +715 -0
  326. package/src/lib/server/sso-provisioning.js.map +1 -0
  327. package/src/lib/server/staff-alert-email.d.ts +41 -0
  328. package/src/lib/server/staff-alert-email.js +63 -0
  329. package/src/lib/server/staff-alert-email.js.map +1 -0
  330. package/src/lib/server/stripe-account-mode.d.ts +156 -0
  331. package/src/lib/server/stripe-account-mode.js +162 -0
  332. package/src/lib/server/stripe-account-mode.js.map +1 -0
  333. package/src/lib/server/tenant-write-lockdown.d.ts +131 -0
  334. package/src/lib/server/tenant-write-lockdown.js +176 -0
  335. package/src/lib/server/tenant-write-lockdown.js.map +1 -0
  336. package/src/lib/server/test-firestore.d.ts +45 -0
  337. package/src/lib/server/test-firestore.js +124 -0
  338. package/src/lib/server/test-firestore.js.map +1 -0
  339. package/src/lib/server/token-revocation.d.ts +140 -0
  340. package/src/lib/server/token-revocation.js +255 -0
  341. package/src/lib/server/token-revocation.js.map +1 -0
  342. package/src/lib/server/update-existing.d.ts +32 -0
  343. package/src/lib/server/update-existing.js +73 -0
  344. package/src/lib/server/update-existing.js.map +1 -0
  345. package/src/lib/server/upload-cors-reconcile.d.ts +118 -0
  346. package/src/lib/server/upload-cors-reconcile.js +234 -0
  347. package/src/lib/server/upload-cors-reconcile.js.map +1 -0
  348. package/src/lib/server/upsert-contact.d.ts +262 -0
  349. package/src/lib/server/upsert-contact.js +546 -0
  350. package/src/lib/server/upsert-contact.js.map +1 -0
  351. package/src/lib/server/usage-invoice.d.ts +203 -0
  352. package/src/lib/server/usage-invoice.js +397 -0
  353. package/src/lib/server/usage-invoice.js.map +1 -0
  354. package/src/lib/server/user-profiles.d.ts +151 -0
  355. package/src/lib/server/user-profiles.js +364 -0
  356. package/src/lib/server/user-profiles.js.map +1 -0
  357. package/src/lib/server/vercel-drain-signature.d.ts +30 -0
  358. package/src/lib/server/vercel-drain-signature.js +61 -0
  359. package/src/lib/server/vercel-drain-signature.js.map +1 -0
  360. package/src/lib/server/vercel-log-drain.d.ts +259 -0
  361. package/src/lib/server/vercel-log-drain.js +479 -0
  362. package/src/lib/server/vercel-log-drain.js.map +1 -0
  363. package/src/lib/server/verify-email-cooldown.d.ts +102 -0
  364. package/src/lib/server/verify-email-cooldown.js +114 -0
  365. package/src/lib/server/verify-email-cooldown.js.map +1 -0
  366. package/src/lib/server/visitor-write-rate-limit.d.ts +87 -0
  367. package/src/lib/server/visitor-write-rate-limit.js +117 -0
  368. package/src/lib/server/visitor-write-rate-limit.js.map +1 -0
  369. package/src/lib/server/workspace-domains.d.ts +156 -0
  370. package/src/lib/server/workspace-domains.js +271 -0
  371. package/src/lib/server/workspace-domains.js.map +1 -0
  372. package/src/tenancy.d.ts +115 -0
  373. package/src/tenancy.js +142 -0
  374. package/src/tenancy.js.map +1 -0
@@ -0,0 +1,119 @@
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
+ */ import { createHmac, timingSafeEqual } from "crypto";
17
+ import { tokenSigningSecret } from "./media-signing.js";
18
+ /**
19
+ * Editor-presence HINT tokens for the tenant admin bar on `*.aglyn.app`
20
+ * (AGL-1842, the cross-site half AGL-1829 could not reach).
21
+ *
22
+ * The console session lives on `app.aglyn.com`; `*.aglyn.app` is a different
23
+ * registrable domain, so neither the AGL-1829 hint cookie nor the silent
24
+ * iframe probe can cross over — storage partitioning keeps the two worlds
25
+ * apart, correctly. What CAN cross is a top-level navigation: at console
26
+ * sign-in the browser is bounced once through
27
+ * `https://console.aglyn.app/api/edit-hint/set?sig=<bounce token>` and back,
28
+ * and in that first-party moment the tenant app plants a signed hint cookie
29
+ * on `Domain=.aglyn.app` that every tenant subdomain can present to its own
30
+ * server afterwards.
31
+ *
32
+ * Two kinds, deliberately non-interchangeable:
33
+ *
34
+ * - `bounce` — rides the redirect URL once. URLs are logged and shareable,
35
+ * so its TTL is seconds ({@link EDIT_HINT_BOUNCE_TTL_MS}).
36
+ * - `cookie` — the planted `aglyn_edit_hint` value, HttpOnly, living the
37
+ * {@link EDIT_HINT_COOKIE_TTL_MS} window between console visits.
38
+ *
39
+ * The kind is INSIDE the signed context string, so a leaked 60-second bounce
40
+ * URL can never be replayed as a week-long cookie, and a stolen cookie value
41
+ * can never be planted elsewhere through the bounce endpoint. The `edit-hint:`
42
+ * prefix domain-separates both from the `edit-bar:`/`media:`/commerce
43
+ * signatures sharing `TOKEN_SIGNING_SECRET`.
44
+ *
45
+ * A hint is NOT edit access: it names a uid and nothing else. Everything
46
+ * real happens server-side at the tenant's `/api/edit-access/exchange`,
47
+ * which re-authorizes that uid against the specific host being viewed with
48
+ * the same membership gate the console's token mint applies. A forged,
49
+ * expired, or stale hint costs one refused POST.
50
+ */ /** Seconds, not minutes: the bounce URL is single-use inside one redirect. */ export const EDIT_HINT_BOUNCE_TTL_MS = 60 * 1000;
51
+ /** 7 days — matches the AGL-1829 marker cookie's refresh cadence. */ export const EDIT_HINT_COOKIE_TTL_MS = 7 * 24 * 60 * 60 * 1000;
52
+ /**
53
+ * The HttpOnly signed hint cookie on `Domain=.aglyn.app`. Sibling of the
54
+ * JS-visible `aglyn_editor=1` marker (`EDITOR_HINT_COOKIE`), which the
55
+ * admin-bar stub reads to arm; this one only ever travels back to the
56
+ * tenant's own exchange route, where it is verified server-side.
57
+ */ export const EDIT_HINT_COOKIE = 'aglyn_edit_hint';
58
+ /** Version tag, mirroring the edit-access token's debuggability rationale. */ const TOKEN_PREFIX = 'aglyn-edit-hint-v1';
59
+ function signPayload(kind, payloadB64) {
60
+ return createHmac('sha256', tokenSigningSecret()).update(`edit-hint:${kind}:${payloadB64}`).digest('base64url');
61
+ }
62
+ /**
63
+ * Mints a hint of the given kind for a uid the caller has ALREADY verified
64
+ * (the console route verifies a Firebase ID token; the bounce endpoint
65
+ * verifies a `bounce` hint). The mint itself checks nothing.
66
+ */ export function mintEditHintToken(kind, uid, nowMs = Date.now()) {
67
+ if (!uid) throw new Error('uid is required');
68
+ const exp = nowMs + (kind === 'bounce' ? EDIT_HINT_BOUNCE_TTL_MS : EDIT_HINT_COOKIE_TTL_MS);
69
+ const claims = {
70
+ uid,
71
+ exp
72
+ };
73
+ const payload = Buffer.from(JSON.stringify(claims), 'utf8').toString('base64url');
74
+ return {
75
+ token: `${TOKEN_PREFIX}.${payload}.${signPayload(kind, payload)}`,
76
+ expiresAtMs: exp
77
+ };
78
+ }
79
+ /**
80
+ * Verifies a presented hint AS the stated kind and returns its claims, or
81
+ * `null` for anything else — expired, tampered, malformed, wrong version,
82
+ * wrong KIND (the replay wall), or a deploy with no signing secret (fail
83
+ * closed, like every signature in this family).
84
+ */ export function verifyEditHintToken(kind, token, nowMs = Date.now()) {
85
+ if (typeof token !== 'string' || token.length === 0 || token.length > 4096) {
86
+ return null;
87
+ }
88
+ const parts = token.split('.');
89
+ if (parts.length !== 3 || parts[0] !== TOKEN_PREFIX) return null;
90
+ const [, payload, sig] = parts;
91
+ let expected;
92
+ try {
93
+ expected = signPayload(kind, payload);
94
+ } catch (unused) {
95
+ // Secret missing — refuse rather than trusting anything.
96
+ return null;
97
+ }
98
+ // Length-checked before timingSafeEqual, which THROWS on attacker-length
99
+ // input — a 500 where a refusal belongs (same note as edit-access-token).
100
+ if (sig.length !== expected.length) return null;
101
+ if (!timingSafeEqual(new Uint8Array(Buffer.from(sig)), new Uint8Array(Buffer.from(expected)))) {
102
+ return null;
103
+ }
104
+ let claims;
105
+ try {
106
+ claims = JSON.parse(Buffer.from(payload, 'base64url').toString('utf8'));
107
+ } catch (unused) {
108
+ return null;
109
+ }
110
+ const exp = Number(claims == null ? void 0 : claims.exp);
111
+ if (!Number.isFinite(exp) || exp <= nowMs) return null;
112
+ if (typeof claims.uid !== 'string' || !claims.uid) return null;
113
+ return {
114
+ uid: claims.uid,
115
+ exp
116
+ };
117
+ }
118
+
119
+ //# sourceMappingURL=edit-hint-token.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../../../../../libs/tenant/data/admin/src/lib/server/edit-hint-token.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\nimport { createHmac, timingSafeEqual } from 'crypto'\nimport { tokenSigningSecret } from './media-signing'\n\n/**\n * Editor-presence HINT tokens for the tenant admin bar on `*.aglyn.app`\n * (AGL-1842, the cross-site half AGL-1829 could not reach).\n *\n * The console session lives on `app.aglyn.com`; `*.aglyn.app` is a different\n * registrable domain, so neither the AGL-1829 hint cookie nor the silent\n * iframe probe can cross over — storage partitioning keeps the two worlds\n * apart, correctly. What CAN cross is a top-level navigation: at console\n * sign-in the browser is bounced once through\n * `https://console.aglyn.app/api/edit-hint/set?sig=<bounce token>` and back,\n * and in that first-party moment the tenant app plants a signed hint cookie\n * on `Domain=.aglyn.app` that every tenant subdomain can present to its own\n * server afterwards.\n *\n * Two kinds, deliberately non-interchangeable:\n *\n * - `bounce` — rides the redirect URL once. URLs are logged and shareable,\n * so its TTL is seconds ({@link EDIT_HINT_BOUNCE_TTL_MS}).\n * - `cookie` — the planted `aglyn_edit_hint` value, HttpOnly, living the\n * {@link EDIT_HINT_COOKIE_TTL_MS} window between console visits.\n *\n * The kind is INSIDE the signed context string, so a leaked 60-second bounce\n * URL can never be replayed as a week-long cookie, and a stolen cookie value\n * can never be planted elsewhere through the bounce endpoint. The `edit-hint:`\n * prefix domain-separates both from the `edit-bar:`/`media:`/commerce\n * signatures sharing `TOKEN_SIGNING_SECRET`.\n *\n * A hint is NOT edit access: it names a uid and nothing else. Everything\n * real happens server-side at the tenant's `/api/edit-access/exchange`,\n * which re-authorizes that uid against the specific host being viewed with\n * the same membership gate the console's token mint applies. A forged,\n * expired, or stale hint costs one refused POST.\n */\n\n/** Seconds, not minutes: the bounce URL is single-use inside one redirect. */\nexport const EDIT_HINT_BOUNCE_TTL_MS = 60 * 1000\n\n/** 7 days — matches the AGL-1829 marker cookie's refresh cadence. */\nexport const EDIT_HINT_COOKIE_TTL_MS = 7 * 24 * 60 * 60 * 1000\n\n/**\n * The HttpOnly signed hint cookie on `Domain=.aglyn.app`. Sibling of the\n * JS-visible `aglyn_editor=1` marker (`EDITOR_HINT_COOKIE`), which the\n * admin-bar stub reads to arm; this one only ever travels back to the\n * tenant's own exchange route, where it is verified server-side.\n */\nexport const EDIT_HINT_COOKIE = 'aglyn_edit_hint'\n\n/** Version tag, mirroring the edit-access token's debuggability rationale. */\nconst TOKEN_PREFIX = 'aglyn-edit-hint-v1'\n\nexport type EditHintKind = 'bounce' | 'cookie'\n\nexport interface EditHintClaims {\n uid: string\n /** Expiry, epoch ms. */\n exp: number\n}\n\nfunction signPayload(kind: EditHintKind, payloadB64: string): string {\n return createHmac('sha256', tokenSigningSecret())\n .update(`edit-hint:${kind}:${payloadB64}`)\n .digest('base64url')\n}\n\nexport interface MintedEditHintToken {\n token: string\n expiresAtMs: number\n}\n\n/**\n * Mints a hint of the given kind for a uid the caller has ALREADY verified\n * (the console route verifies a Firebase ID token; the bounce endpoint\n * verifies a `bounce` hint). The mint itself checks nothing.\n */\nexport function mintEditHintToken(\n kind: EditHintKind,\n uid: string,\n nowMs: number = Date.now(),\n): MintedEditHintToken {\n if (!uid) throw new Error('uid is required')\n const exp =\n nowMs +\n (kind === 'bounce' ? EDIT_HINT_BOUNCE_TTL_MS : EDIT_HINT_COOKIE_TTL_MS)\n const claims: EditHintClaims = { uid, exp }\n const payload = Buffer.from(JSON.stringify(claims), 'utf8').toString(\n 'base64url',\n )\n return {\n token: `${TOKEN_PREFIX}.${payload}.${signPayload(kind, payload)}`,\n expiresAtMs: exp,\n }\n}\n\n/**\n * Verifies a presented hint AS the stated kind and returns its claims, or\n * `null` for anything else — expired, tampered, malformed, wrong version,\n * wrong KIND (the replay wall), or a deploy with no signing secret (fail\n * closed, like every signature in this family).\n */\nexport function verifyEditHintToken(\n kind: EditHintKind,\n token: unknown,\n nowMs: number = Date.now(),\n): EditHintClaims | null {\n if (typeof token !== 'string' || token.length === 0 || token.length > 4096) {\n return null\n }\n const parts = token.split('.')\n if (parts.length !== 3 || parts[0] !== TOKEN_PREFIX) return null\n const [, payload, sig] = parts\n let expected: string\n try {\n expected = signPayload(kind, payload)\n } catch {\n // Secret missing — refuse rather than trusting anything.\n return null\n }\n // Length-checked before timingSafeEqual, which THROWS on attacker-length\n // input — a 500 where a refusal belongs (same note as edit-access-token).\n if (sig.length !== expected.length) return null\n if (\n !timingSafeEqual(\n new Uint8Array(Buffer.from(sig)),\n new Uint8Array(Buffer.from(expected)),\n )\n ) {\n return null\n }\n let claims: EditHintClaims\n try {\n claims = JSON.parse(Buffer.from(payload, 'base64url').toString('utf8'))\n } catch {\n return null\n }\n const exp = Number(claims?.exp)\n if (!Number.isFinite(exp) || exp <= nowMs) return null\n if (typeof claims.uid !== 'string' || !claims.uid) return null\n return { uid: claims.uid, exp }\n}\n"],"names":["createHmac","timingSafeEqual","tokenSigningSecret","EDIT_HINT_BOUNCE_TTL_MS","EDIT_HINT_COOKIE_TTL_MS","EDIT_HINT_COOKIE","TOKEN_PREFIX","signPayload","kind","payloadB64","update","digest","mintEditHintToken","uid","nowMs","Date","now","Error","exp","claims","payload","Buffer","from","JSON","stringify","toString","token","expiresAtMs","verifyEditHintToken","length","parts","split","sig","expected","Uint8Array","parse","Number","isFinite"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAED,SAASA,UAAU,EAAEC,eAAe,QAAQ,SAAQ;AACpD,SAASC,kBAAkB,QAAQ,qBAAiB;AAEpD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAgCC,GAED,4EAA4E,GAC5E,OAAO,MAAMC,0BAA0B,KAAK,KAAI;AAEhD,mEAAmE,GACnE,OAAO,MAAMC,0BAA0B,IAAI,KAAK,KAAK,KAAK,KAAI;AAE9D;;;;;CAKC,GACD,OAAO,MAAMC,mBAAmB,kBAAiB;AAEjD,4EAA4E,GAC5E,MAAMC,eAAe;AAUrB,SAASC,YAAYC,IAAkB,EAAEC,UAAkB;IACzD,OAAOT,WAAW,UAAUE,sBACzBQ,MAAM,CAAC,CAAC,UAAU,EAAEF,KAAK,CAAC,EAAEC,YAAY,EACxCE,MAAM,CAAC;AACZ;AAOA;;;;CAIC,GACD,OAAO,SAASC,kBACdJ,IAAkB,EAClBK,GAAW,EACXC,QAAgBC,KAAKC,GAAG,EAAE;IAE1B,IAAI,CAACH,KAAK,MAAM,IAAII,MAAM;IAC1B,MAAMC,MACJJ,QACCN,CAAAA,SAAS,WAAWL,0BAA0BC,uBAAsB;IACvE,MAAMe,SAAyB;QAAEN;QAAKK;IAAI;IAC1C,MAAME,UAAUC,OAAOC,IAAI,CAACC,KAAKC,SAAS,CAACL,SAAS,QAAQM,QAAQ,CAClE;IAEF,OAAO;QACLC,OAAO,GAAGpB,aAAa,CAAC,EAAEc,QAAQ,CAAC,EAAEb,YAAYC,MAAMY,UAAU;QACjEO,aAAaT;IACf;AACF;AAEA;;;;;CAKC,GACD,OAAO,SAASU,oBACdpB,IAAkB,EAClBkB,KAAc,EACdZ,QAAgBC,KAAKC,GAAG,EAAE;IAE1B,IAAI,OAAOU,UAAU,YAAYA,MAAMG,MAAM,KAAK,KAAKH,MAAMG,MAAM,GAAG,MAAM;QAC1E,OAAO;IACT;IACA,MAAMC,QAAQJ,MAAMK,KAAK,CAAC;IAC1B,IAAID,MAAMD,MAAM,KAAK,KAAKC,KAAK,CAAC,EAAE,KAAKxB,cAAc,OAAO;IAC5D,MAAM,GAAGc,SAASY,IAAI,GAAGF;IACzB,IAAIG;IACJ,IAAI;QACFA,WAAW1B,YAAYC,MAAMY;IAC/B,EAAE,eAAM;QACN,yDAAyD;QACzD,OAAO;IACT;IACA,yEAAyE;IACzE,0EAA0E;IAC1E,IAAIY,IAAIH,MAAM,KAAKI,SAASJ,MAAM,EAAE,OAAO;IAC3C,IACE,CAAC5B,gBACC,IAAIiC,WAAWb,OAAOC,IAAI,CAACU,OAC3B,IAAIE,WAAWb,OAAOC,IAAI,CAACW,aAE7B;QACA,OAAO;IACT;IACA,IAAId;IACJ,IAAI;QACFA,SAASI,KAAKY,KAAK,CAACd,OAAOC,IAAI,CAACF,SAAS,aAAaK,QAAQ,CAAC;IACjE,EAAE,eAAM;QACN,OAAO;IACT;IACA,MAAMP,MAAMkB,OAAOjB,0BAAAA,OAAQD,GAAG;IAC9B,IAAI,CAACkB,OAAOC,QAAQ,CAACnB,QAAQA,OAAOJ,OAAO,OAAO;IAClD,IAAI,OAAOK,OAAON,GAAG,KAAK,YAAY,CAACM,OAAON,GAAG,EAAE,OAAO;IAC1D,OAAO;QAAEA,KAAKM,OAAON,GAAG;QAAEK;IAAI;AAChC"}
@@ -0,0 +1,509 @@
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 EmailDeliveryEvent, type EmailDeliveryEventType, type EmailDeliveryHistorySource, type EmailDeliverySnapshot } from '@aglyn/shared-util-email';
18
+ export declare const EMAIL_DELIVERIES_COLLECTION = "emailDeliveries";
19
+ export declare const EMAIL_DELIVERY_MESSAGES_COLLECTION = "messages";
20
+ /** The most messages one staff read will return. */
21
+ export declare const EMAIL_DELIVERY_READ_LIMIT = 50;
22
+ /**
23
+ * The most distinct links one message records.
24
+ *
25
+ * A newsletter with forty links clicked by one reader must not grow the
26
+ * document without bound; the first few tell a staffer what they need.
27
+ */
28
+ export declare const EMAIL_DELIVERY_MAX_LINKS = 10;
29
+ /** One message as the staff view reads it. */
30
+ export interface EmailDeliveryRecord {
31
+ /** The provider's message id — also the document id. */
32
+ messageId: string;
33
+ provider: string;
34
+ to: string;
35
+ subject: string | null;
36
+ /** The sender label, e.g. `'invite'`. Null for a send that carried none. */
37
+ context: string | null;
38
+ /** Furthest-along (worst) lifecycle state seen. */
39
+ status: EmailDeliveryEventType;
40
+ /** Epoch ms per state, absent for states that never happened. */
41
+ timestamps: Partial<Record<EmailDeliveryEventType, number>>;
42
+ /** First event we saw for this message. Always present — the sort key. */
43
+ firstSeenAtMs: number;
44
+ openCount: number;
45
+ clickCount: number;
46
+ /** Distinct destinations followed, capped. */
47
+ clickedLinks: string[];
48
+ bounceType: string | null;
49
+ detail: string | null;
50
+ hostId: string | null;
51
+ campaignId: string | null;
52
+ }
53
+ /**
54
+ * What one {@link recordEmailDeliveryEvent} call did.
55
+ *
56
+ * `firstOfType` exists so a CAMPAIGN counter can be incremented once per
57
+ * recipient without buying a read of its own. This transaction already holds
58
+ * the message's prior state, and "has this message ever been opened before"
59
+ * is the fact a distinct-openers count needs — deriving it here costs
60
+ * nothing, and deriving it anywhere else costs a document read per event.
61
+ *
62
+ * It is also what makes those counters idempotent, on the same reasoning the
63
+ * webhook's replay guard rests on: a redelivered or replayed event finds the
64
+ * state already recorded and reports `false`, so the counter cannot be
65
+ * incremented twice for one message's first open.
66
+ */
67
+ export interface EmailDeliveryEventOutcome {
68
+ /**
69
+ * No event of this TYPE had been recorded against this message before.
70
+ *
71
+ * Read off `timestamps`, which is written for every event type, rather than
72
+ * off `openCount`/`clickCount`, which exist for two of them.
73
+ */
74
+ firstOfType: boolean;
75
+ /** The message this event was recorded against. */
76
+ providerMessageId: string;
77
+ /** The recipient, lowercased — the person the event is about. */
78
+ to: string;
79
+ /** Which event this was. */
80
+ type: EmailDeliveryEventType;
81
+ /** When it happened, epoch ms. */
82
+ at: number;
83
+ }
84
+ /**
85
+ * Records one normalized event against its message.
86
+ *
87
+ * A transaction rather than a merge-set, for one property that matters to the
88
+ * reader: `firstSeenAtMs` must be written exactly once and must never be
89
+ * absent. Events arrive out of order — an `opened` can beat its own `sent`
90
+ * through the queue — so "create with the first event's time, then leave it
91
+ * alone" needs a read in the same atomic step as the write. A document missing
92
+ * that field would be dropped from the `orderBy` read entirely and the message
93
+ * would simply not appear, which is the failure mode a delivery log can least
94
+ * afford.
95
+ *
96
+ * @returns the outcome, or `null` when nothing was written. `null` is the
97
+ * ordinary answer for an address that is not an address; it is never
98
+ * an error.
99
+ */
100
+ export declare function recordEmailDeliveryEvent(event: EmailDeliveryEvent, firestore?: any): Promise<EmailDeliveryEventOutcome | null>;
101
+ /**
102
+ * Records one message the PROVIDER already knows about — the history import.
103
+ *
104
+ * ## Why this is not just `recordEmailDeliveryEvent` with a made-up event
105
+ *
106
+ * A snapshot is weaker evidence than an event, in two specific ways, and
107
+ * writing it as an event would silently promote it:
108
+ *
109
+ * - **It carries no counts.** A provider's list reports one `last_event` per
110
+ * message and no engagement detail, so `opened` means "at least once" and
111
+ * can never mean "three times". Incrementing `openCount` from a snapshot
112
+ * would invent a number, and re-running the import would invent it again.
113
+ * - **It can be STALER than what we already hold.** The event feed is live;
114
+ * an import is a page of results fetched some time ago. So the status is
115
+ * merged with {@link worstDeliveryStatus} rather than assigned, and a row
116
+ * the webhook has already advanced is never walked backwards.
117
+ *
118
+ * Everything else it fills is a gap-fill only: `subject` and `sentAt` are
119
+ * written when absent and left alone when present. The net effect is that
120
+ * importing history is idempotent and can be run as often as you like, and a
121
+ * message the event feed has covered is untouched by it.
122
+ *
123
+ * `context` is deliberately NOT recoverable here. It comes from a send tag,
124
+ * and the list endpoint does not return tags — so an imported row shows the
125
+ * subject and the status but cannot say which of our senders produced it. The
126
+ * card renders that absence rather than guessing.
127
+ *
128
+ * @returns whether a row was written or updated.
129
+ */
130
+ export declare function recordEmailDeliverySnapshot(snapshot: EmailDeliverySnapshot, firestore?: any): Promise<boolean>;
131
+ /**
132
+ * Records a batch, independently — one bad event must not lose the others.
133
+ *
134
+ * @returns one outcome per event that was WRITTEN; events that wrote nothing
135
+ * are absent, so the length is still the count the old return value
136
+ * reported.
137
+ */
138
+ export declare function recordEmailDeliveryEvents(events: EmailDeliveryEvent[], firestore?: any): Promise<EmailDeliveryEventOutcome[]>;
139
+ /** What one person's mail says about whether they are still listening. */
140
+ export interface EmailPersonEngagement {
141
+ /** The later of {@link lastOpenedAtMs} and {@link lastClickedAtMs}. */
142
+ lastEngagedAtMs: number | null;
143
+ lastOpenedAtMs: number | null;
144
+ /**
145
+ * Clicks are the metric to lean on. Apple's Mail Privacy Protection
146
+ * prefetches images, so an open is partly a statement about the recipient's
147
+ * mail client; a click is a statement about the recipient.
148
+ */
149
+ lastClickedAtMs: number | null;
150
+ }
151
+ /** The empty answer, so a caller never has to invent one. */
152
+ export declare const NO_PERSON_ENGAGEMENT: EmailPersonEngagement;
153
+ /**
154
+ * Advances the engagement stamps for the people these outcomes are about.
155
+ *
156
+ * A transaction, and it buys exactly one property: the stamps only ever move
157
+ * FORWARD. Provider events are not ordered, and a replay of an event whose
158
+ * first delivery never landed can carry an instant from months ago — a blind
159
+ * merge-set would let that overwrite a fresh stamp and quietly make an active
160
+ * subscriber look cold to a control whose whole job is refusing to mail cold
161
+ * people. Reading before writing is a cheaper unit than the write beside it,
162
+ * and it happens at most once per message per event type.
163
+ *
164
+ * Never throws, for the same reason nothing else in this file does: a rollup
165
+ * that failed loses a stamp, and a rollup that threw would lose the webhook's
166
+ * acknowledgement and teach the provider to retry the whole event.
167
+ *
168
+ * @returns how many person documents were written.
169
+ */
170
+ export declare function recordPersonEngagement(outcomes: readonly EmailDeliveryEventOutcome[], firestore?: any): Promise<number>;
171
+ /**
172
+ * One person's engagement, by address. Never throws.
173
+ *
174
+ * Returns {@link NO_PERSON_ENGAGEMENT} for an address we hold nothing about,
175
+ * AND for a read that failed. The two are deliberately the same answer here:
176
+ * every caller uses this to decide whether to REFUSE something, and both
177
+ * readings must resolve to "we have no evidence this person is cold", which
178
+ * is the only safe direction for a control that stops mail.
179
+ */
180
+ export declare function readPersonEngagement(email: string | null | undefined, firestore?: any): Promise<EmailPersonEngagement>;
181
+ /**
182
+ * Engagement for many people at once, keyed by their person key.
183
+ *
184
+ * A `getAll` rather than a query: these are keyed document reads, so this
185
+ * needs no index, cannot be truncated by a `limit`, and cannot drop somebody
186
+ * for missing a field the way an `orderBy` would. The audience materializer
187
+ * calls it a page at a time and counts every read against its scan budget.
188
+ *
189
+ * A key with no document is present in the result with
190
+ * {@link NO_PERSON_ENGAGEMENT}, so a caller never has to tell "absent" from
191
+ * "not read" — and a failure returns every requested key that way for the
192
+ * same reason {@link readPersonEngagement} does.
193
+ */
194
+ export declare function readPersonEngagementByKeys(keys: readonly string[], firestore?: any): Promise<Map<string, EmailPersonEngagement>>;
195
+ /** The field on `emailDeliveries/{key}` holding the per-host touches. */
196
+ export declare const EMAIL_TOUCH_FIELD = "campaignTouches";
197
+ /**
198
+ * How many sites' touches one person's document keeps.
199
+ *
200
+ * A cap, not a page size: the map lives in a document with a 1 MiB ceiling
201
+ * and nothing else bounds how many sites may mail one address.
202
+ */
203
+ export declare const EMAIL_TOUCH_MAX_HOSTS = 10;
204
+ /** The last campaign one person clicked on one site. */
205
+ export interface EmailCampaignTouch {
206
+ hostId: string;
207
+ campaignId: string;
208
+ /** When the click happened, epoch ms — the provider's instant. */
209
+ clickedAtMs: number;
210
+ }
211
+ /**
212
+ * Records that this person clicked this campaign's mail. Never throws.
213
+ *
214
+ * Forward-only, in a transaction, for the reason {@link recordPersonEngagement}
215
+ * is: provider delivery is at-least-once and unordered, so a replayed click
216
+ * from last month must not displace this week's. That same property is what
217
+ * makes this idempotent — a redelivered event finds its own instant already
218
+ * stored and writes nothing.
219
+ *
220
+ * @returns whether the touch moved forward.
221
+ */
222
+ export declare function recordEmailCampaignTouch(touch: {
223
+ email: string | null | undefined;
224
+ hostId: string;
225
+ campaignId: string;
226
+ atMs: number;
227
+ }, firestore?: any): Promise<boolean>;
228
+ /**
229
+ * The last campaign this person clicked on this site, or `null`.
230
+ *
231
+ * One keyed document read — no query, no index, and nothing that can be
232
+ * truncated. `null` for an address we hold no touch for AND for a read that
233
+ * failed, which are the same answer on purpose: both mean "we cannot say
234
+ * which campaign preceded this order", and the only safe thing to do with
235
+ * that is credit nobody.
236
+ */
237
+ export declare function readEmailCampaignTouch(email: string | null | undefined, hostId: string, firestore?: any): Promise<EmailCampaignTouch | null>;
238
+ /** What one {@link importEmailDeliveryHistory} run did. */
239
+ export interface EmailDeliveryImportResult {
240
+ /** Provider messages read. */
241
+ scanned: number;
242
+ /** Per-recipient rows written or refreshed. */
243
+ recorded: number;
244
+ pages: number;
245
+ /** Cursor to resume from, or null when the history was exhausted. */
246
+ nextCursor: string | null;
247
+ /** True when the page budget ran out before the history did. */
248
+ truncated: boolean;
249
+ }
250
+ /** Default page budget for one import run. 100 messages per page. */
251
+ export declare const EMAIL_DELIVERY_IMPORT_MAX_PAGES = 20;
252
+ /**
253
+ * Imports already-sent mail from a provider into the log.
254
+ *
255
+ * Bounded by PAGES rather than run to completion: this is called from a
256
+ * request handler, and an account with a large history would otherwise hold
257
+ * one open until it timed out — losing every page it had already written,
258
+ * because a partial import that reports nothing is indistinguishable from one
259
+ * that did nothing. Instead it stops at the budget, returns `nextCursor`, and
260
+ * the caller resumes. Every page is written before the next is fetched, so an
261
+ * interrupted run keeps its work.
262
+ *
263
+ * Idempotent by construction — see {@link recordEmailDeliverySnapshot}: a
264
+ * message the event feed already covered is not walked backwards, and
265
+ * re-running invents no counts.
266
+ *
267
+ * The `source` is injected rather than constructed here. This module may not
268
+ * know which provider is in use, and a test must be able to run the whole
269
+ * loop — pagination, cursor handling, the stop condition — without a network.
270
+ */
271
+ export declare function importEmailDeliveryHistory(options: {
272
+ source: EmailDeliveryHistorySource;
273
+ cursor?: string | null;
274
+ maxPages?: number;
275
+ firestore?: any;
276
+ }): Promise<EmailDeliveryImportResult>;
277
+ /**
278
+ * The messages sent to one address, newest first.
279
+ *
280
+ * Ordered on `firstSeenAtMs`, which the writer guarantees on creation, rather
281
+ * than on a per-state timestamp that only some rows carry: `orderBy` drops
282
+ * every document missing the field, so ordering on `timestamps.sent` would
283
+ * silently hide any message whose `sent` webhook never arrived — exactly the
284
+ * message a staffer is looking for.
285
+ *
286
+ * @returns the rows, or an empty array. The caller distinguishes "none" from
287
+ * "could not read" through {@link readEmailDeliveryHistory}.
288
+ */
289
+ export declare function readEmailDeliveries(email: string | null | undefined, options?: {
290
+ limit?: number;
291
+ firestore?: any;
292
+ }): Promise<EmailDeliveryRecord[]>;
293
+ /**
294
+ * {@link readEmailDeliveries} with the read failure kept separate from an
295
+ * empty result.
296
+ *
297
+ * The same shape `devices` uses on the staff detail route, for the same
298
+ * reason: "we have no record of any email to this person" and "we could not
299
+ * reach the log" lead a staffer to opposite next actions, and a card that
300
+ * renders both as an empty table sends them down the wrong one.
301
+ */
302
+ export declare function readEmailDeliveryHistory(email: string | null | undefined, options?: {
303
+ limit?: number;
304
+ firestore?: any;
305
+ }): Promise<{
306
+ lookupFailed: boolean;
307
+ rows: EmailDeliveryRecord[];
308
+ }>;
309
+ /** The most recipient rows one campaign-engagement read returns. */
310
+ export declare const EMAIL_CAMPAIGN_ENGAGEMENT_PAGE_SIZE = 25;
311
+ /**
312
+ * How many campaigns one engagement read can span.
313
+ *
314
+ * Firestore's `in` operator takes at most 30 values, and the query below runs
315
+ * as a merge of one sub-query per value — so this is a hard limit of the
316
+ * store rather than a number worth tuning. A design used by more campaigns
317
+ * than this reads its most recent 30, and the caller is told so.
318
+ */
319
+ export declare const EMAIL_CAMPAIGN_ENGAGEMENT_MAX_CAMPAIGNS = 30;
320
+ /** Which recipients a campaign-engagement read returns. */
321
+ export type EmailEngagementFilter = 'all' | 'opened' | 'clicked';
322
+ /** One page of recipient rows. */
323
+ export interface EmailCampaignEngagementPage {
324
+ rows: EmailDeliveryRecord[];
325
+ /**
326
+ * Cursor for the next page, or null at the end.
327
+ *
328
+ * The full document PATH of the last row, which is
329
+ * `emailDeliveries/{sha256(address)}/messages/{messageId}`. It is re-read
330
+ * as a snapshot to resume the query, rather than resuming from the ordered
331
+ * VALUE: a value cursor positions after every document sharing it, so two
332
+ * messages recorded in the same millisecond would lose one of them between
333
+ * pages — silently, and only under load.
334
+ */
335
+ cursor: string | null;
336
+ /** The read failed, as distinct from finding nothing. */
337
+ lookupFailed: boolean;
338
+ /** Campaigns past {@link EMAIL_CAMPAIGN_ENGAGEMENT_MAX_CAMPAIGNS}. */
339
+ campaignsOmitted: number;
340
+ }
341
+ /**
342
+ * The recipients of one site's campaigns, newest message first.
343
+ *
344
+ * ## What each filter orders on, and why it is not one query with a flag
345
+ *
346
+ * `all` orders on `firstSeenAtMs`, which {@link recordEmailDeliveryEvent}
347
+ * guarantees on creation. `opened` and `clicked` carry an inequality —
348
+ * `openCount > 0` — and Firestore requires the first ordering to be on the
349
+ * inequality's own field, so those two order on the count and then on the
350
+ * time. That is not a workaround: a message never opened has no `openCount`
351
+ * field at all, so the inequality is also what excludes it, and the ordering
352
+ * puts the most engaged recipient first, which is the order a merchant reads
353
+ * such a table in.
354
+ *
355
+ * ## Never throws
356
+ *
357
+ * Same contract as the rest of this file: `lookupFailed` distinguishes a read
358
+ * that could not run — a missing index is the likely one — from a campaign
359
+ * nobody opened. Rendering those two the same way is how a merchant concludes
360
+ * their campaign reached nobody.
361
+ */
362
+ export declare function readCampaignEngagement(options: {
363
+ /** The site whose mail this is. The caller must already have proven it. */
364
+ hostId: string;
365
+ /** Campaign ids to read, most recent first. */
366
+ campaignIds: readonly string[];
367
+ filter?: EmailEngagementFilter;
368
+ limit?: number;
369
+ /** A `cursor` from a previous page. */
370
+ cursor?: string | null;
371
+ firestore?: any;
372
+ }): Promise<EmailCampaignEngagementPage>;
373
+ /**
374
+ * A record that delivery data WAS held for an address and has been erased.
375
+ *
376
+ * Written into the parent `emailDeliveries/{emailKey}` document, which the
377
+ * messages subcollection otherwise leaves empty.
378
+ *
379
+ * ⚠️ It carries no address, no subject, no message id and no uid — nothing
380
+ * the erasure was performed to destroy. `count` is a magnitude, which is what
381
+ * makes the row honest without reconstituting anything: it says data existed
382
+ * and is gone, and nothing about what it was.
383
+ */
384
+ export interface EmailDeliveryErasure {
385
+ /** Epoch ms. */
386
+ at: number;
387
+ /** How many messages were removed. */
388
+ count: number;
389
+ }
390
+ /** One account's mail, gathered from every address it holds. */
391
+ export interface EmailDeliveryHistory {
392
+ lookupFailed: boolean;
393
+ rows: EmailDeliveryRecord[];
394
+ /**
395
+ * The addresses actually read, in the order they were given.
396
+ *
397
+ * The card names them. A staffer looking at mail sent to an address that is
398
+ * no longer this account's primary has to be able to see that that is what
399
+ * they are looking at.
400
+ */
401
+ addressesRead: string[];
402
+ /**
403
+ * Erasure tombstones found, keyed by address.
404
+ *
405
+ * An address whose records were erased under somebody's request reads as an
406
+ * empty table otherwise — which is the precise failure this card's copy
407
+ * warns about, recreated by the fix for it.
408
+ */
409
+ erasures: Record<string, EmailDeliveryErasure>;
410
+ }
411
+ /** The tombstone on one address, or null. Never throws. */
412
+ export declare function readEmailDeliveryErasure(email: string | null | undefined, firestore?: any): Promise<EmailDeliveryErasure | null>;
413
+ /**
414
+ * Every message sent to any address this account holds, newest first.
415
+ *
416
+ * Merged and re-sorted rather than concatenated: the rows are one person's
417
+ * mail and a staffer reads them as a timeline, so grouping them by which
418
+ * address happened to receive them would put the answer in two places and
419
+ * make "what was the last thing we sent them" a question about two tables.
420
+ * Each row keeps its own `to`, so the card can still say which address.
421
+ *
422
+ * `lookupFailed` is true when ANY address failed. A partial read of a
423
+ * delivery log is the same hazard as an empty one — it under-reports mail we
424
+ * sent — and reporting it as a clean result is how a staffer comes to tell a
425
+ * customer something untrue.
426
+ */
427
+ export declare function readEmailDeliveryHistoryForAddresses(addresses: readonly string[], options?: {
428
+ limit?: number;
429
+ firestore?: any;
430
+ }): Promise<EmailDeliveryHistory>;
431
+ /** What one multi-address erasure did. */
432
+ export interface EmailDeliveryErasureResult {
433
+ /** Messages removed, across every address that was erased. */
434
+ removed: number;
435
+ /** The addresses actually erased. Tombstoned, one document each. */
436
+ addresses: string[];
437
+ /**
438
+ * Addresses left INTACT because another account is also known to hold them.
439
+ *
440
+ * Never empty and ignorable: a caller erasing an account has to treat a
441
+ * non-empty list as an erasure it did not finish. See
442
+ * {@link eraseEmailDeliveriesForAddresses}.
443
+ */
444
+ contestedAddresses: string[];
445
+ }
446
+ /**
447
+ * Erase the delivery log for every address an account holds, except the ones
448
+ * a second account also holds.
449
+ *
450
+ * ## The shared-address decision
451
+ *
452
+ * The log describes an ADDRESS, not an account. Where one account holds an
453
+ * address, erasing it is simply erasing the subject's mail, and this sweeps
454
+ * it.
455
+ *
456
+ * Where TWO accounts hold one address, the same rows are two people's answer
457
+ * to "what did you send me", and the two readings are incompatible:
458
+ *
459
+ * - **One human, two accounts** — the ordinary live shape, an account whose
460
+ * federated provider address is another account's primary. Erasing is
461
+ * right; the mail is the requester's.
462
+ * - **A genuinely shared mailbox** — `billing@`, `support@`, a role account
463
+ * two different people hold. Erasing destroys the second person's delivery
464
+ * history for an address they legitimately hold, and they asked for
465
+ * nothing.
466
+ *
467
+ * ⛔ **Nothing here can tell those apart.** The difference is a fact about the
468
+ * humans, and the data holds no fact about the humans — only that two account
469
+ * records name one address. So this function does not choose. It erases what
470
+ * it can decide about and reports the rest as CONTESTED, and `eraseUser`
471
+ * refuses the whole erasure rather than half-perform one: destroying the
472
+ * second party's mail has no remedy, and quietly leaving it while reporting
473
+ * the erasure complete is the gap this area exists to close. Refusing is the
474
+ * only outcome that is neither, and it is reversible — a human decides which
475
+ * reading applies, detaches the address or confirms the account, and the
476
+ * erasure runs.
477
+ *
478
+ * ⚠️ A contested address is not tombstoned. The tombstone means "the records
479
+ * here were removed under an erasure request", and writing one over rows that
480
+ * are still present would tell the second holder their mail is gone while it
481
+ * sits underneath — a worse misreading than the blank table, because it is
482
+ * confidently wrong rather than merely empty. Nothing was removed, so their
483
+ * card renders their mail exactly as before.
484
+ *
485
+ * ⚠️ `shared` is one-directional evidence. True proves a second holder; false
486
+ * only means none was found, because there is no lookup for an account
487
+ * holding an address through a federated provider (see
488
+ * `account-addresses.ts`). So the tombstone is still written for EVERY
489
+ * address that IS erased, not only ones believed unshared — it costs one
490
+ * small document and closes the case where a second holder exists behind the
491
+ * gap in the probe and would otherwise meet a blank table.
492
+ *
493
+ * ⛔ Only addresses the account HOLDS, resolved through the one resolver. An
494
+ * address arriving here that the account does not hold erases a stranger's
495
+ * mail, which no erasure request authorises.
496
+ */
497
+ export declare function eraseEmailDeliveriesForAddresses(addresses: readonly {
498
+ address: string;
499
+ shared?: boolean;
500
+ }[], firestore?: any): Promise<EmailDeliveryErasureResult>;
501
+ /**
502
+ * Deletes everything recorded for one address.
503
+ *
504
+ * The log holds an address, the subjects sent to it and when they were opened
505
+ * — personal data by any reading — so the erasure path has to be able to reach
506
+ * it. Batched because a long-lived account can hold hundreds of rows and a
507
+ * single `delete()` per document would be one round trip each.
508
+ */
509
+ export declare function eraseEmailDeliveries(email: string | null | undefined, firestore?: any): Promise<number>;