@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,485 @@
1
+ import { _ as _extends } from "@swc/helpers/_/_extends";
2
+ /**
3
+ * @license
4
+ * Copyright 2026 Aglyn LLC
5
+ *
6
+ * Licensed under the Apache License, Version 2.0 (the "License");
7
+ * you may not use this file except in compliance with the License.
8
+ * You may obtain a copy of the License at
9
+ *
10
+ * http://www.apache.org/licenses/LICENSE-2.0
11
+ *
12
+ * Unless required by applicable law or agreed to in writing, software
13
+ * distributed under the License is distributed on an "AS IS" BASIS,
14
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ * See the License for the specific language governing permissions and
16
+ * limitations under the License.
17
+ */ import { Readable } from "node:stream";
18
+ import { mediaRenditionObjectPath, parseMediaRenditions } from "@aglyn/aglyn/app-utils/media-ref";
19
+ import { mediaDeliveryProvider } from "@aglyn/aglyn/plugin-manager/media-delivery-provider";
20
+ import { firebaseAdmin } from "./firebase-admin.js";
21
+ import { MEDIA_SIGNATURE_MAX_TTL_MS } from "./media-signing.js";
22
+ import { mediaStoragePathInScope } from "./media-storage-path.js";
23
+ import { isServerReleaseFlagOnForOrg } from "./release-flags.js";
24
+ /**
25
+ * Video delivered from the configured delivery provider (AGL-2824): the
26
+ * platform's half.
27
+ *
28
+ * `serveMediaCdn` keeps every access decision it has always made. When a
29
+ * video request passes all of them, and this module finds a current copy of
30
+ * the requested representation at the delivery provider, the CDN answers
31
+ * with a `302` to a short-lived signed URL on the provider's host instead of
32
+ * streaming the bytes itself. The commerce stream mints the same URL for a
33
+ * buyer. Everything else — images, posters, downloads, and every video with
34
+ * no current copy — serves exactly as it did.
35
+ *
36
+ * ## Three switches, all of which must be on
37
+ *
38
+ * 1. **A provider.** A plugin registers one against core's contract
39
+ * (`media-delivery-provider`), and it must be configured for the
40
+ * capability in question: `store` to copy or remove, `deliver` to mint.
41
+ * With none, every function here returns before any I/O.
42
+ * 2. **The release flag**, per org: {@link MEDIA_DELIVERY_RELEASE_FLAG}. It
43
+ * gates the COPY as well as the redirect, because a copy is customer data
44
+ * at the provider. Removal is never gated: a copy made while the flag was
45
+ * on must still go when its asset does.
46
+ * 3. **A current copy**, recorded on the media document under
47
+ * {@link MEDIA_DELIVERY_COPIES_FIELD}. A copy is current when it was made
48
+ * from the asset's present `contentHash` and its key is the one this
49
+ * module derives for the asset. A replace changes the hash, so every copy
50
+ * of the previous bytes stops being served in the same write, before
51
+ * anything at the provider is removed.
52
+ *
53
+ * ## The platform stays the system of record
54
+ *
55
+ * The bucket keeps every byte it kept before, and the storage counter moves
56
+ * exactly as it did: a copy's size is recorded on the copy and never added to
57
+ * `counters/media`, so storage is billed once. Making the provider's copy the
58
+ * only one is a later step, and the storage cost model depends on it (see
59
+ * AGL-2825); nothing here deletes a platform object.
60
+ *
61
+ * ## Keys carry the content hash
62
+ *
63
+ * `{hosts|orgs}/{scopeId}/{mediaId}/{sourceHash}/{representation}/{objectHash}`.
64
+ * The source hash is the asset's `contentHash`; the object hash is the
65
+ * copied object's own digest (for the master the two are the same). A
66
+ * replaced file or a re-encoded rendition therefore gets a new key, so a URL
67
+ * minted for old bytes can never be answered with new ones, and the whole
68
+ * asset is one prefix for removal. The record is written by the server only,
69
+ * but the key it holds is checked against the derivation before it is used:
70
+ * a record copied onto another document names a key that is not that
71
+ * document's own, and is ignored.
72
+ */ /** The per-org release flag both halves — copy and redirect — ask. */ export const MEDIA_DELIVERY_RELEASE_FLAG = 'release_video_delivery';
73
+ /** The media document field that records the copies at the provider. */ export const MEDIA_DELIVERY_COPIES_FIELD = 'deliveryCopies';
74
+ /** The representation name of the uploaded file itself. */ export const MEDIA_DELIVERY_MASTER = 'master';
75
+ /**
76
+ * The shortest delivery URL lifetime. A delivery URL is a bearer capability,
77
+ * so it is short, but a player sends its range requests to the URL it was
78
+ * redirected to, so it must outlive a sitting: seeking, pausing and resuming
79
+ * all reuse it.
80
+ */ export const MEDIA_DELIVERY_MIN_TTL_MS = 15 * 60 * 1000;
81
+ /** The lifetime when the film's duration is unknown. */ export const MEDIA_DELIVERY_DEFAULT_TTL_MS = 60 * 60 * 1000;
82
+ /** The longest lifetime, shared with every other signed media link. */ export const MEDIA_DELIVERY_MAX_TTL_MS = MEDIA_SIGNATURE_MAX_TTL_MS;
83
+ /**
84
+ * How long a delivery URL for a film of `durationMs` lives: twice the film,
85
+ * so a viewer who pauses halfway still finishes it, between
86
+ * {@link MEDIA_DELIVERY_MIN_TTL_MS} and {@link MEDIA_DELIVERY_MAX_TTL_MS}.
87
+ */ export function mediaDeliveryTtlMs(durationMs) {
88
+ const duration = Number(durationMs);
89
+ if (!Number.isFinite(duration) || duration <= 0) {
90
+ return MEDIA_DELIVERY_DEFAULT_TTL_MS;
91
+ }
92
+ return Math.min(MEDIA_DELIVERY_MAX_TTL_MS, Math.max(MEDIA_DELIVERY_MIN_TTL_MS, Math.round(duration * 2)));
93
+ }
94
+ /** An id segment, as the CDN route accepts one. */ const ID_SEGMENT = /^[A-Za-z0-9_-]{1,64}$/;
95
+ /** A content digest: `contentHash` is 16 hex, a full digest up to 64. */ const DIGEST = /^[a-f0-9]{16,64}$/;
96
+ /** `master`, or `r-` and a rendition key. */ const REPRESENTATION = /^(?:master|r-[a-z0-9][a-z0-9-]{0,23})$/;
97
+ /** The representation a rendition (or the master, for none) is copied as. */ export function mediaDeliveryRepresentation(rendition) {
98
+ return rendition ? `r-${rendition.key}` : MEDIA_DELIVERY_MASTER;
99
+ }
100
+ function validAsset(asset) {
101
+ return (asset.collection === 'hosts' || asset.collection === 'orgs') && ID_SEGMENT.test(asset.scopeId) && ID_SEGMENT.test(asset.mediaId);
102
+ }
103
+ /**
104
+ * The prefix every copy of one asset lives under, or null for an asset whose
105
+ * identity does not fit the grammar. Ends in `/`, so one asset's prefix is
106
+ * never another's.
107
+ */ export function mediaDeliveryAssetPrefix(asset) {
108
+ if (!validAsset(asset)) return null;
109
+ return `${asset.collection}/${asset.scopeId}/${asset.mediaId}/`;
110
+ }
111
+ /** The prefix every copy of one library lives under, for an erasure. */ export function mediaDeliveryScopePrefix(collection, scopeId) {
112
+ if (collection !== 'hosts' && collection !== 'orgs') return null;
113
+ if (!ID_SEGMENT.test(scopeId)) return null;
114
+ return `${collection}/${scopeId}/`;
115
+ }
116
+ /** The object key of one copy, or null when a part does not fit the grammar. */ export function mediaDeliveryObjectKey(asset, copy) {
117
+ const prefix = mediaDeliveryAssetPrefix(asset);
118
+ if (!prefix) return null;
119
+ if (!DIGEST.test(copy.sourceHash) || !DIGEST.test(copy.objectHash)) return null;
120
+ if (!REPRESENTATION.test(copy.representation)) return null;
121
+ return `${prefix}${copy.sourceHash}/${copy.representation}/${copy.objectHash}`;
122
+ }
123
+ /**
124
+ * The copies a media document records that belong to THIS asset: each entry
125
+ * well formed, and its key exactly the one {@link mediaDeliveryObjectKey}
126
+ * derives from its own fields. Anything else is dropped rather than trusted.
127
+ */ export function parseMediaDeliveryCopies(raw, asset) {
128
+ const copies = {};
129
+ if (!raw || typeof raw !== 'object' || Array.isArray(raw)) return copies;
130
+ for (const [representation, value] of Object.entries(raw)){
131
+ var _entry_sourceHash, _entry_objectHash, _entry_contentType;
132
+ if (!value || typeof value !== 'object') continue;
133
+ const entry = value;
134
+ const sourceHash = String((_entry_sourceHash = entry['sourceHash']) != null ? _entry_sourceHash : '');
135
+ const objectHash = String((_entry_objectHash = entry['objectHash']) != null ? _entry_objectHash : '');
136
+ const key = mediaDeliveryObjectKey(asset, {
137
+ sourceHash,
138
+ representation,
139
+ objectHash
140
+ });
141
+ if (!key || entry['key'] !== key) continue;
142
+ const contentType = String((_entry_contentType = entry['contentType']) != null ? _entry_contentType : '');
143
+ if (!/^video\/[\w.+-]{1,64}$/.test(contentType)) continue;
144
+ const sizeBytes = Number(entry['sizeBytes']);
145
+ copies[representation] = {
146
+ key,
147
+ sourceHash,
148
+ objectHash,
149
+ contentType,
150
+ sizeBytes: Number.isFinite(sizeBytes) && sizeBytes > 0 ? Math.round(sizeBytes) : 0,
151
+ copiedAtMs: Number(entry['copiedAtMs']) || 0
152
+ };
153
+ }
154
+ return copies;
155
+ }
156
+ /** Whether a served type is one this module delivers: video, nothing else. */ export function isMediaDeliveryType(contentType) {
157
+ return String(contentType != null ? contentType : '').split(';')[0].trim().toLowerCase().startsWith('video/');
158
+ }
159
+ function currentContentHash(document) {
160
+ const hash = document.get('contentHash');
161
+ return typeof hash === 'string' && DIGEST.test(hash) ? hash : null;
162
+ }
163
+ /*===========================================================================
164
+ * The owning org, for the flag.
165
+ *==========================================================================*/ const ORG_CACHE_TTL_MS = 60000;
166
+ const hostOrgCache = new Map();
167
+ /** Drops the host → org cache. Tests need it between cases. */ export function invalidateMediaDeliveryOrgCache() {
168
+ hostOrgCache.clear();
169
+ }
170
+ /**
171
+ * The org that owns a library: the id itself for an org library, and the
172
+ * host's `hostIndex` entry for a site's. Cached for a minute, like the
173
+ * release-flag reads it feeds; a failed read answers null, which the flag
174
+ * treats as "no org" and so refuses anything short of a full release.
175
+ */ export async function mediaDeliveryOrgIdFor(collection, scopeId) {
176
+ if (collection === 'orgs') return scopeId || null;
177
+ const hit = hostOrgCache.get(scopeId);
178
+ if (hit && Date.now() - hit.at < ORG_CACHE_TTL_MS) return hit.orgId;
179
+ const orgId = await (async ()=>{
180
+ try {
181
+ const firestore = firebaseAdmin.app().firestore();
182
+ const [index] = await firestore.getAll(firestore.collection('hostIndex').doc(scopeId), {
183
+ fieldMask: [
184
+ 'orgId'
185
+ ]
186
+ });
187
+ const value = index == null ? void 0 : index.get('orgId');
188
+ return typeof value === 'string' && value ? value : null;
189
+ } catch (unused) {
190
+ return null;
191
+ }
192
+ })();
193
+ hostOrgCache.set(scopeId, {
194
+ at: Date.now(),
195
+ orgId
196
+ });
197
+ return orgId;
198
+ }
199
+ /** The flag verdict for one org. */ export function mediaDeliveryEnabledForOrg(orgId) {
200
+ return isServerReleaseFlagOnForOrg(MEDIA_DELIVERY_RELEASE_FLAG, orgId);
201
+ }
202
+ /**
203
+ * The delivery URL for one representation of one asset, or null when the
204
+ * request must be served the way it always was.
205
+ *
206
+ * Called only after the caller has made every access decision. Null for: a
207
+ * type that is not video; an asset with no current copy of this
208
+ * representation; the flag off for the owning org; a lifetime that has
209
+ * already run out; and a provider that fails to mint. The cheap answers come
210
+ * first, so an asset with no copy costs no read at all.
211
+ */ export async function mediaDeliveryRedirect(input) {
212
+ var _input_deliveryEnabled, _input_nowMs, _input_ttlMs;
213
+ if (!isMediaDeliveryType(input.servedType)) return null;
214
+ const sourceHash = currentContentHash(input.document);
215
+ if (!sourceHash) return null;
216
+ const copies = parseMediaDeliveryCopies(input.document.get(MEDIA_DELIVERY_COPIES_FIELD), input.asset);
217
+ const copy = copies[mediaDeliveryRepresentation(input.rendition)];
218
+ if (!copy || copy.sourceHash !== sourceHash) return null;
219
+ const orgId = await input.orgId();
220
+ const enabled = await ((_input_deliveryEnabled = input.deliveryEnabled) != null ? _input_deliveryEnabled : mediaDeliveryEnabledForOrg)(orgId);
221
+ if (!enabled) return null;
222
+ const nowMs = (_input_nowMs = input.nowMs) != null ? _input_nowMs : Date.now();
223
+ const video = input.document.get('video');
224
+ const lifetime = Math.min(MEDIA_DELIVERY_MAX_TTL_MS, (_input_ttlMs = input.ttlMs) != null ? _input_ttlMs : mediaDeliveryTtlMs(video == null ? void 0 : video.durationMs));
225
+ let expiresAtMs = nowMs + lifetime;
226
+ if (input.notAfterMs !== undefined) {
227
+ // A bound that cannot be read is no bound, and a URL with none could
228
+ // outlive what the caller was entitled to; serve from the platform.
229
+ if (!Number.isFinite(input.notAfterMs)) return null;
230
+ expiresAtMs = Math.min(expiresAtMs, input.notAfterMs);
231
+ }
232
+ if (!(expiresAtMs > nowMs)) return null;
233
+ try {
234
+ const location = await input.provider.deliveryUrl({
235
+ key: copy.key,
236
+ expiresAtMs,
237
+ claims: _extends({}, input.claims, {
238
+ orgId
239
+ })
240
+ });
241
+ return {
242
+ location,
243
+ expiresAtMs,
244
+ key: copy.key
245
+ };
246
+ } catch (error) {
247
+ console.error('[media-delivery] could not mint a delivery URL', JSON.stringify({
248
+ key: copy.key
249
+ }), error);
250
+ return null;
251
+ }
252
+ }
253
+ /** The md5 Storage records for an object, as hex. */ function md5Hex(metadata) {
254
+ const raw = metadata['md5Hash'];
255
+ if (typeof raw !== 'string' || !raw) return null;
256
+ const hex = Buffer.from(raw, 'base64').toString('hex');
257
+ return DIGEST.test(hex) ? hex : null;
258
+ }
259
+ /** One `getMetadata()` answer, unwrapped from the SDK's tuple. */ async function readMetadata(file) {
260
+ const response = await file.getMetadata();
261
+ const metadata = Array.isArray(response) ? response[0] : response;
262
+ return metadata != null ? metadata : {};
263
+ }
264
+ /**
265
+ * Brings the provider's copies of one video in line with its document: every
266
+ * representation the document serves — the master and each recorded
267
+ * rendition — gets a copy made from its current bytes, and a copy nothing
268
+ * current names is removed.
269
+ *
270
+ * Every copy path runs through here: an upload's finalize, a replace, a
271
+ * restore and the rendition producer. It is safe to run again at any time.
272
+ * The master's copy is current when it was made from the asset's
273
+ * `contentHash`; a rendition's when it was made from that hash AND from the
274
+ * rendition object Storage holds now, since the producer can encode a key
275
+ * again without the master changing.
276
+ *
277
+ * The record is written in a transaction that re-reads the document, and
278
+ * only while the asset still has the content hash the copies were made from.
279
+ * An asset replaced or deleted while its copies were uploading keeps that
280
+ * state, and the copies just made are removed rather than recorded against
281
+ * bytes they are not.
282
+ */ export async function syncMediaDeliveryCopies(options) {
283
+ var _options_deliveryEnabled, _options_nowMs;
284
+ const result = {
285
+ status: 'synced',
286
+ copied: [],
287
+ kept: [],
288
+ removed: [],
289
+ failed: []
290
+ };
291
+ const done = (status)=>_extends({}, result, {
292
+ status
293
+ });
294
+ const provider = options.provider === undefined ? mediaDeliveryProvider('store') : options.provider;
295
+ if (!provider) return done('no-provider');
296
+ const { asset, bucket, docRef } = options;
297
+ if (!mediaDeliveryAssetPrefix(asset)) return done('no-asset');
298
+ const snapshot = await docRef.get();
299
+ if (!snapshot.exists || snapshot.get('deletedAt')) return done('no-asset');
300
+ if (!isMediaDeliveryType(snapshot.get('contentType'))) return done('not-video');
301
+ const sourceHash = currentContentHash(snapshot);
302
+ if (!sourceHash) return done('no-hash');
303
+ const orgId = options.orgId === undefined ? await mediaDeliveryOrgIdFor(asset.collection, asset.scopeId) : options.orgId;
304
+ const enabled = await ((_options_deliveryEnabled = options.deliveryEnabled) != null ? _options_deliveryEnabled : mediaDeliveryEnabledForOrg)(orgId);
305
+ if (!enabled) return done('disabled');
306
+ const now = (_options_nowMs = options.nowMs) != null ? _options_nowMs : Date.now;
307
+ const basePath = mediaStoragePathInScope({
308
+ storagePath: snapshot.get('storagePath'),
309
+ base: `${asset.collection}/${asset.scopeId}`,
310
+ mediaId: asset.mediaId
311
+ });
312
+ const wanted = [
313
+ {
314
+ objectPath: basePath,
315
+ rendition: null
316
+ },
317
+ ...parseMediaRenditions(snapshot.get('videoRenditions')).map((rendition)=>({
318
+ objectPath: mediaRenditionObjectPath(basePath, rendition),
319
+ rendition
320
+ }))
321
+ ];
322
+ const recorded = parseMediaDeliveryCopies(snapshot.get(MEDIA_DELIVERY_COPIES_FIELD), asset);
323
+ const next = {};
324
+ for (const { objectPath, rendition } of wanted){
325
+ const representation = mediaDeliveryRepresentation(rendition);
326
+ const existing = recorded[representation];
327
+ try {
328
+ var _ref, _metadata_contentType;
329
+ const file = bucket.file(objectPath);
330
+ // The master's digest is on the document already, so a current copy of
331
+ // it costs no Storage read. A rendition's is read every time.
332
+ if (!rendition && (existing == null ? void 0 : existing.sourceHash) === sourceHash) {
333
+ next[representation] = existing;
334
+ result.kept.push(representation);
335
+ continue;
336
+ }
337
+ const metadata = await readMetadata(file);
338
+ const objectHash = rendition ? md5Hex(metadata) : sourceHash;
339
+ if (!objectHash) throw new Error('the object reports no digest');
340
+ if (existing && existing.sourceHash === sourceHash && existing.objectHash === objectHash) {
341
+ next[representation] = existing;
342
+ result.kept.push(representation);
343
+ continue;
344
+ }
345
+ const sizeBytes = Number(metadata['size']);
346
+ if (!Number.isSafeInteger(sizeBytes) || sizeBytes <= 0) {
347
+ throw new Error('the object reports no size');
348
+ }
349
+ const contentType = rendition ? rendition.contentType : String((_ref = (_metadata_contentType = metadata['contentType']) != null ? _metadata_contentType : snapshot.get('contentType')) != null ? _ref : '');
350
+ if (!isMediaDeliveryType(contentType)) throw new Error('the object is not a video');
351
+ const key = mediaDeliveryObjectKey(asset, {
352
+ sourceHash,
353
+ representation,
354
+ objectHash
355
+ });
356
+ if (!key) throw new Error('no key fits the asset');
357
+ await provider.putObject({
358
+ key,
359
+ body: Readable.toWeb(file.createReadStream()),
360
+ contentLength: sizeBytes,
361
+ contentType
362
+ });
363
+ next[representation] = {
364
+ key,
365
+ sourceHash,
366
+ objectHash,
367
+ contentType,
368
+ sizeBytes,
369
+ copiedAtMs: now()
370
+ };
371
+ result.copied.push(representation);
372
+ } catch (error) {
373
+ result.failed.push(representation);
374
+ console.error('[media-delivery] copy failed; it keeps serving from the platform', JSON.stringify(_extends({}, asset, {
375
+ representation
376
+ })), error);
377
+ }
378
+ }
379
+ const nextKeys = new Set(Object.values(next).map((copy)=>copy.key));
380
+ const retired = Object.values(recorded).map((copy)=>copy.key).filter((key)=>!nextKeys.has(key));
381
+ const madeNow = result.copied.map((representation)=>{
382
+ var _next_representation;
383
+ return (_next_representation = next[representation]) == null ? void 0 : _next_representation.key;
384
+ }).filter((key)=>Boolean(key));
385
+ if (result.copied.length || retired.length) {
386
+ const written = await docRef.firestore.runTransaction(async (transaction)=>{
387
+ const fresh = await transaction.get(docRef);
388
+ if (!fresh.exists || fresh.get('deletedAt')) return false;
389
+ if (currentContentHash(fresh) !== sourceHash) return false;
390
+ transaction.update(docRef, {
391
+ [MEDIA_DELIVERY_COPIES_FIELD]: next
392
+ });
393
+ return true;
394
+ });
395
+ if (!written) {
396
+ // Replaced or deleted while the copies were uploading. Nothing records
397
+ // what was just made, so it goes rather than lingering unreferenced.
398
+ await removeKeys(provider, madeNow, result);
399
+ return done('replaced');
400
+ }
401
+ }
402
+ await removeKeys(provider, retired, result);
403
+ return done('synced');
404
+ }
405
+ async function removeKeys(provider, keys, result) {
406
+ for (const key of keys){
407
+ try {
408
+ await provider.deleteObject(key);
409
+ result.removed.push(key);
410
+ } catch (error) {
411
+ console.error('[media-delivery] could not remove a retired copy', JSON.stringify({
412
+ key
413
+ }), error);
414
+ }
415
+ }
416
+ }
417
+ /**
418
+ * Removes every copy of one asset from the provider: a delete, a takedown and
419
+ * a replace all end the life of the bytes the copies were made from.
420
+ *
421
+ * By prefix rather than by the document's record, so a copy the record lost
422
+ * track of goes too. Never gated on the flag, since copies made while it was
423
+ * on must still go, and never throws: the caller's own action has already
424
+ * happened and must not be reported as failed because a copy lingered. Null
425
+ * when no provider is configured to store.
426
+ */ export async function removeMediaDeliveryCopies(options) {
427
+ const provider = options.provider === undefined ? mediaDeliveryProvider('store') : options.provider;
428
+ if (!provider) return null;
429
+ const prefix = mediaDeliveryAssetPrefix(options.asset);
430
+ if (!prefix) return {
431
+ removed: 0,
432
+ failed: false
433
+ };
434
+ try {
435
+ return {
436
+ removed: await provider.deleteObjectsWithPrefix(prefix),
437
+ failed: false
438
+ };
439
+ } catch (error) {
440
+ console.error('[media-delivery] could not remove an asset\'s copies', JSON.stringify(options.asset), error);
441
+ return {
442
+ removed: 0,
443
+ failed: true
444
+ };
445
+ }
446
+ }
447
+ /**
448
+ * Removes every copy a library holds at the provider — the erasure of a site
449
+ * or an org. Same posture as {@link removeMediaDeliveryCopies}.
450
+ */ export async function eraseMediaDeliveryScope(options) {
451
+ const provider = options.provider === undefined ? mediaDeliveryProvider('store') : options.provider;
452
+ if (!provider) return null;
453
+ const prefix = mediaDeliveryScopePrefix(options.collection, options.scopeId);
454
+ if (!prefix) return {
455
+ removed: 0,
456
+ failed: false
457
+ };
458
+ try {
459
+ return {
460
+ removed: await provider.deleteObjectsWithPrefix(prefix),
461
+ failed: false
462
+ };
463
+ } catch (error) {
464
+ console.error('[media-delivery] could not erase a library\'s copies', JSON.stringify({
465
+ collection: options.collection,
466
+ scopeId: options.scopeId
467
+ }), error);
468
+ return {
469
+ removed: 0,
470
+ failed: true
471
+ };
472
+ }
473
+ }
474
+ /**
475
+ * A media document without its copy record, for a restore: the delete that
476
+ * made the tombstone removed the copies, so the record describes objects
477
+ * that are gone.
478
+ */ export function withoutMediaDeliveryCopies(media) {
479
+ if (!(MEDIA_DELIVERY_COPIES_FIELD in media)) return media;
480
+ const rest = _extends({}, media);
481
+ delete rest[MEDIA_DELIVERY_COPIES_FIELD];
482
+ return rest;
483
+ }
484
+
485
+ //# sourceMappingURL=media-delivery.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../../../../../libs/tenant/data/admin/src/lib/server/media-delivery.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 { Readable } from 'node:stream'\nimport {\n type MediaVideoRendition,\n mediaRenditionObjectPath,\n parseMediaRenditions,\n} from '@aglyn/aglyn/app-utils/media-ref'\nimport type { ReleaseFlagKey } from '@aglyn/aglyn/app-utils/release-flags'\nimport {\n type MediaDeliveryClaims,\n type MediaDeliveryProvider,\n mediaDeliveryProvider,\n} from '@aglyn/aglyn/plugin-manager/media-delivery-provider'\nimport { firebaseAdmin } from './firebase-admin'\nimport { MEDIA_SIGNATURE_MAX_TTL_MS } from './media-signing'\nimport { mediaStoragePathInScope } from './media-storage-path'\nimport { isServerReleaseFlagOnForOrg } from './release-flags'\n\n/**\n * Video delivered from the configured delivery provider (AGL-2824): the\n * platform's half.\n *\n * `serveMediaCdn` keeps every access decision it has always made. When a\n * video request passes all of them, and this module finds a current copy of\n * the requested representation at the delivery provider, the CDN answers\n * with a `302` to a short-lived signed URL on the provider's host instead of\n * streaming the bytes itself. The commerce stream mints the same URL for a\n * buyer. Everything else — images, posters, downloads, and every video with\n * no current copy — serves exactly as it did.\n *\n * ## Three switches, all of which must be on\n *\n * 1. **A provider.** A plugin registers one against core's contract\n * (`media-delivery-provider`), and it must be configured for the\n * capability in question: `store` to copy or remove, `deliver` to mint.\n * With none, every function here returns before any I/O.\n * 2. **The release flag**, per org: {@link MEDIA_DELIVERY_RELEASE_FLAG}. It\n * gates the COPY as well as the redirect, because a copy is customer data\n * at the provider. Removal is never gated: a copy made while the flag was\n * on must still go when its asset does.\n * 3. **A current copy**, recorded on the media document under\n * {@link MEDIA_DELIVERY_COPIES_FIELD}. A copy is current when it was made\n * from the asset's present `contentHash` and its key is the one this\n * module derives for the asset. A replace changes the hash, so every copy\n * of the previous bytes stops being served in the same write, before\n * anything at the provider is removed.\n *\n * ## The platform stays the system of record\n *\n * The bucket keeps every byte it kept before, and the storage counter moves\n * exactly as it did: a copy's size is recorded on the copy and never added to\n * `counters/media`, so storage is billed once. Making the provider's copy the\n * only one is a later step, and the storage cost model depends on it (see\n * AGL-2825); nothing here deletes a platform object.\n *\n * ## Keys carry the content hash\n *\n * `{hosts|orgs}/{scopeId}/{mediaId}/{sourceHash}/{representation}/{objectHash}`.\n * The source hash is the asset's `contentHash`; the object hash is the\n * copied object's own digest (for the master the two are the same). A\n * replaced file or a re-encoded rendition therefore gets a new key, so a URL\n * minted for old bytes can never be answered with new ones, and the whole\n * asset is one prefix for removal. The record is written by the server only,\n * but the key it holds is checked against the derivation before it is used:\n * a record copied onto another document names a key that is not that\n * document's own, and is ignored.\n */\n\n/** The per-org release flag both halves — copy and redirect — ask. */\nexport const MEDIA_DELIVERY_RELEASE_FLAG: ReleaseFlagKey = 'release_video_delivery'\n\n/** The media document field that records the copies at the provider. */\nexport const MEDIA_DELIVERY_COPIES_FIELD = 'deliveryCopies'\n\n/** The representation name of the uploaded file itself. */\nexport const MEDIA_DELIVERY_MASTER = 'master'\n\n/**\n * The shortest delivery URL lifetime. A delivery URL is a bearer capability,\n * so it is short, but a player sends its range requests to the URL it was\n * redirected to, so it must outlive a sitting: seeking, pausing and resuming\n * all reuse it.\n */\nexport const MEDIA_DELIVERY_MIN_TTL_MS = 15 * 60 * 1000\n\n/** The lifetime when the film's duration is unknown. */\nexport const MEDIA_DELIVERY_DEFAULT_TTL_MS = 60 * 60 * 1000\n\n/** The longest lifetime, shared with every other signed media link. */\nexport const MEDIA_DELIVERY_MAX_TTL_MS = MEDIA_SIGNATURE_MAX_TTL_MS\n\n/**\n * How long a delivery URL for a film of `durationMs` lives: twice the film,\n * so a viewer who pauses halfway still finishes it, between\n * {@link MEDIA_DELIVERY_MIN_TTL_MS} and {@link MEDIA_DELIVERY_MAX_TTL_MS}.\n */\nexport function mediaDeliveryTtlMs(durationMs: unknown): number {\n const duration = Number(durationMs)\n if (!Number.isFinite(duration) || duration <= 0) {\n return MEDIA_DELIVERY_DEFAULT_TTL_MS\n }\n return Math.min(\n MEDIA_DELIVERY_MAX_TTL_MS,\n Math.max(MEDIA_DELIVERY_MIN_TTL_MS, Math.round(duration * 2)),\n )\n}\n\n/** The asset a copy belongs to. */\nexport interface MediaDeliveryAsset {\n collection: 'hosts' | 'orgs'\n scopeId: string\n mediaId: string\n}\n\n/** An id segment, as the CDN route accepts one. */\nconst ID_SEGMENT = /^[A-Za-z0-9_-]{1,64}$/\n/** A content digest: `contentHash` is 16 hex, a full digest up to 64. */\nconst DIGEST = /^[a-f0-9]{16,64}$/\n/** `master`, or `r-` and a rendition key. */\nconst REPRESENTATION = /^(?:master|r-[a-z0-9][a-z0-9-]{0,23})$/\n\n/** The representation a rendition (or the master, for none) is copied as. */\nexport function mediaDeliveryRepresentation(\n rendition: Pick<MediaVideoRendition, 'key'> | null | undefined,\n): string {\n return rendition ? `r-${rendition.key}` : MEDIA_DELIVERY_MASTER\n}\n\nfunction validAsset(asset: MediaDeliveryAsset): boolean {\n return (\n (asset.collection === 'hosts' || asset.collection === 'orgs') &&\n ID_SEGMENT.test(asset.scopeId) &&\n ID_SEGMENT.test(asset.mediaId)\n )\n}\n\n/**\n * The prefix every copy of one asset lives under, or null for an asset whose\n * identity does not fit the grammar. Ends in `/`, so one asset's prefix is\n * never another's.\n */\nexport function mediaDeliveryAssetPrefix(asset: MediaDeliveryAsset): string | null {\n if (!validAsset(asset)) return null\n return `${asset.collection}/${asset.scopeId}/${asset.mediaId}/`\n}\n\n/** The prefix every copy of one library lives under, for an erasure. */\nexport function mediaDeliveryScopePrefix(\n collection: 'hosts' | 'orgs',\n scopeId: string,\n): string | null {\n if (collection !== 'hosts' && collection !== 'orgs') return null\n if (!ID_SEGMENT.test(scopeId)) return null\n return `${collection}/${scopeId}/`\n}\n\n/** The object key of one copy, or null when a part does not fit the grammar. */\nexport function mediaDeliveryObjectKey(\n asset: MediaDeliveryAsset,\n copy: { sourceHash: string; representation: string; objectHash: string },\n): string | null {\n const prefix = mediaDeliveryAssetPrefix(asset)\n if (!prefix) return null\n if (!DIGEST.test(copy.sourceHash) || !DIGEST.test(copy.objectHash)) return null\n if (!REPRESENTATION.test(copy.representation)) return null\n return `${prefix}${copy.sourceHash}/${copy.representation}/${copy.objectHash}`\n}\n\n/** One copy at the provider, as the media document records it. */\nexport interface MediaDeliveryCopy {\n key: string\n /** The asset's `contentHash` when the copy was made. */\n sourceHash: string\n /** The copied object's own digest. */\n objectHash: string\n contentType: string\n sizeBytes: number\n copiedAtMs: number\n}\n\n/** The recorded copies of one asset, by representation. */\nexport type MediaDeliveryCopies = Record<string, MediaDeliveryCopy>\n\n/**\n * The copies a media document records that belong to THIS asset: each entry\n * well formed, and its key exactly the one {@link mediaDeliveryObjectKey}\n * derives from its own fields. Anything else is dropped rather than trusted.\n */\nexport function parseMediaDeliveryCopies(\n raw: unknown,\n asset: MediaDeliveryAsset,\n): MediaDeliveryCopies {\n const copies: MediaDeliveryCopies = {}\n if (!raw || typeof raw !== 'object' || Array.isArray(raw)) return copies\n for (const [representation, value] of Object.entries(raw as Record<string, unknown>)) {\n if (!value || typeof value !== 'object') continue\n const entry = value as Record<string, unknown>\n const sourceHash = String(entry['sourceHash'] ?? '')\n const objectHash = String(entry['objectHash'] ?? '')\n const key = mediaDeliveryObjectKey(asset, { sourceHash, representation, objectHash })\n if (!key || entry['key'] !== key) continue\n const contentType = String(entry['contentType'] ?? '')\n if (!/^video\\/[\\w.+-]{1,64}$/.test(contentType)) continue\n const sizeBytes = Number(entry['sizeBytes'])\n copies[representation] = {\n key,\n sourceHash,\n objectHash,\n contentType,\n sizeBytes: Number.isFinite(sizeBytes) && sizeBytes > 0 ? Math.round(sizeBytes) : 0,\n copiedAtMs: Number(entry['copiedAtMs']) || 0,\n }\n }\n return copies\n}\n\n/** Whether a served type is one this module delivers: video, nothing else. */\nexport function isMediaDeliveryType(contentType: unknown): boolean {\n return String(contentType ?? '')\n .split(';')[0]\n .trim()\n .toLowerCase()\n .startsWith('video/')\n}\n\nfunction currentContentHash(document: { get(field: string): unknown }): string | null {\n const hash = document.get('contentHash')\n return typeof hash === 'string' && DIGEST.test(hash) ? hash : null\n}\n\n/*===========================================================================\n * The owning org, for the flag.\n *==========================================================================*/\n\nconst ORG_CACHE_TTL_MS = 60_000\nconst hostOrgCache = new Map<string, { at: number; orgId: string | null }>()\n\n/** Drops the host → org cache. Tests need it between cases. */\nexport function invalidateMediaDeliveryOrgCache(): void {\n hostOrgCache.clear()\n}\n\n/**\n * The org that owns a library: the id itself for an org library, and the\n * host's `hostIndex` entry for a site's. Cached for a minute, like the\n * release-flag reads it feeds; a failed read answers null, which the flag\n * treats as \"no org\" and so refuses anything short of a full release.\n */\nexport async function mediaDeliveryOrgIdFor(\n collection: 'hosts' | 'orgs',\n scopeId: string,\n): Promise<string | null> {\n if (collection === 'orgs') return scopeId || null\n const hit = hostOrgCache.get(scopeId)\n if (hit && Date.now() - hit.at < ORG_CACHE_TTL_MS) return hit.orgId\n const orgId = await (async (): Promise<string | null> => {\n try {\n const firestore = firebaseAdmin.app().firestore()\n const [index] = await firestore.getAll(\n firestore.collection('hostIndex').doc(scopeId),\n { fieldMask: ['orgId'] },\n )\n const value = index?.get('orgId')\n return typeof value === 'string' && value ? value : null\n } catch {\n return null\n }\n })()\n hostOrgCache.set(scopeId, { at: Date.now(), orgId })\n return orgId\n}\n\n/** The flag verdict for one org. */\nexport function mediaDeliveryEnabledForOrg(orgId: string | null): Promise<boolean> {\n return isServerReleaseFlagOnForOrg(MEDIA_DELIVERY_RELEASE_FLAG, orgId)\n}\n\n/*===========================================================================\n * The redirect.\n *==========================================================================*/\n\nexport interface MediaDeliveryRedirect {\n location: string\n expiresAtMs: number\n key: string\n}\n\n/**\n * The delivery URL for one representation of one asset, or null when the\n * request must be served the way it always was.\n *\n * Called only after the caller has made every access decision. Null for: a\n * type that is not video; an asset with no current copy of this\n * representation; the flag off for the owning org; a lifetime that has\n * already run out; and a provider that fails to mint. The cheap answers come\n * first, so an asset with no copy costs no read at all.\n */\nexport async function mediaDeliveryRedirect(input: {\n provider: MediaDeliveryProvider\n asset: MediaDeliveryAsset\n document: { get(field: string): unknown }\n /** The rendition being served, or none for the master. */\n rendition?: Pick<MediaVideoRendition, 'key'> | null\n servedType: unknown\n claims: Omit<MediaDeliveryClaims, 'orgId'>\n /** The owning org, asked only once a current copy is found. */\n orgId: () => Promise<string | null>\n /** Defaults to the release flag. */\n deliveryEnabled?: (orgId: string | null) => Promise<boolean>\n /** The URL may not outlive this, e.g. the signature the request carried. */\n notAfterMs?: number\n /** A fixed lifetime; otherwise {@link mediaDeliveryTtlMs} of the film. */\n ttlMs?: number\n nowMs?: number\n}): Promise<MediaDeliveryRedirect | null> {\n if (!isMediaDeliveryType(input.servedType)) return null\n const sourceHash = currentContentHash(input.document)\n if (!sourceHash) return null\n const copies = parseMediaDeliveryCopies(\n input.document.get(MEDIA_DELIVERY_COPIES_FIELD),\n input.asset,\n )\n const copy = copies[mediaDeliveryRepresentation(input.rendition)]\n if (!copy || copy.sourceHash !== sourceHash) return null\n\n const orgId = await input.orgId()\n const enabled = await (input.deliveryEnabled ?? mediaDeliveryEnabledForOrg)(orgId)\n if (!enabled) return null\n\n const nowMs = input.nowMs ?? Date.now()\n const video = input.document.get('video') as { durationMs?: unknown } | undefined\n const lifetime = Math.min(\n MEDIA_DELIVERY_MAX_TTL_MS,\n input.ttlMs ?? mediaDeliveryTtlMs(video?.durationMs),\n )\n let expiresAtMs = nowMs + lifetime\n if (input.notAfterMs !== undefined) {\n // A bound that cannot be read is no bound, and a URL with none could\n // outlive what the caller was entitled to; serve from the platform.\n if (!Number.isFinite(input.notAfterMs)) return null\n expiresAtMs = Math.min(expiresAtMs, input.notAfterMs)\n }\n if (!(expiresAtMs > nowMs)) return null\n try {\n const location = await input.provider.deliveryUrl({\n key: copy.key,\n expiresAtMs,\n claims: { ...input.claims, orgId },\n })\n return { location, expiresAtMs, key: copy.key }\n } catch (error) {\n console.error(\n '[media-delivery] could not mint a delivery URL',\n JSON.stringify({ key: copy.key }),\n error,\n )\n return null\n }\n}\n\n/*===========================================================================\n * Copies.\n *==========================================================================*/\n\n/** The slice of a Storage file the copy reads. */\nexport interface MediaDeliverySourceFile {\n getMetadata(): Promise<unknown>\n createReadStream(): NodeJS.ReadableStream\n}\n\n/** The slice of a Storage bucket the copy reads. */\nexport interface MediaDeliverySourceBucket {\n file(path: string): MediaDeliverySourceFile\n}\n\nexport type MediaDeliverySyncStatus =\n | 'no-provider'\n | 'no-asset'\n | 'not-video'\n | 'no-hash'\n | 'disabled'\n | 'replaced'\n | 'synced'\n\nexport interface MediaDeliverySyncResult {\n status: MediaDeliverySyncStatus\n /** Representations copied by this call. */\n copied: string[]\n /** Representations whose current copy was already there. */\n kept: string[]\n /** Keys removed from the provider because nothing current names them. */\n removed: string[]\n /** Representations whose copy failed; they keep serving from the platform. */\n failed: string[]\n}\n\n/** The md5 Storage records for an object, as hex. */\nfunction md5Hex(metadata: Record<string, unknown>): string | null {\n const raw = metadata['md5Hash']\n if (typeof raw !== 'string' || !raw) return null\n const hex = Buffer.from(raw, 'base64').toString('hex')\n return DIGEST.test(hex) ? hex : null\n}\n\n/** One `getMetadata()` answer, unwrapped from the SDK's tuple. */\nasync function readMetadata(file: MediaDeliverySourceFile): Promise<Record<string, unknown>> {\n const response = await file.getMetadata()\n const metadata = Array.isArray(response) ? response[0] : response\n return (metadata ?? {}) as Record<string, unknown>\n}\n\n/**\n * Brings the provider's copies of one video in line with its document: every\n * representation the document serves — the master and each recorded\n * rendition — gets a copy made from its current bytes, and a copy nothing\n * current names is removed.\n *\n * Every copy path runs through here: an upload's finalize, a replace, a\n * restore and the rendition producer. It is safe to run again at any time.\n * The master's copy is current when it was made from the asset's\n * `contentHash`; a rendition's when it was made from that hash AND from the\n * rendition object Storage holds now, since the producer can encode a key\n * again without the master changing.\n *\n * The record is written in a transaction that re-reads the document, and\n * only while the asset still has the content hash the copies were made from.\n * An asset replaced or deleted while its copies were uploading keeps that\n * state, and the copies just made are removed rather than recorded against\n * bytes they are not.\n */\nexport async function syncMediaDeliveryCopies(options: {\n docRef: FirebaseFirestore.DocumentReference\n asset: MediaDeliveryAsset\n bucket: MediaDeliverySourceBucket\n /** The owning org, when the caller has it; otherwise resolved. */\n orgId?: string | null\n /** Defaults to the registered provider configured to store. */\n provider?: MediaDeliveryProvider | null\n /** Defaults to the release flag. */\n deliveryEnabled?: (orgId: string | null) => Promise<boolean>\n nowMs?: () => number\n}): Promise<MediaDeliverySyncResult> {\n const result: MediaDeliverySyncResult = {\n status: 'synced',\n copied: [],\n kept: [],\n removed: [],\n failed: [],\n }\n const done = (status: MediaDeliverySyncStatus): MediaDeliverySyncResult => ({\n ...result,\n status,\n })\n const provider =\n options.provider === undefined ? mediaDeliveryProvider('store') : options.provider\n if (!provider) return done('no-provider')\n const { asset, bucket, docRef } = options\n if (!mediaDeliveryAssetPrefix(asset)) return done('no-asset')\n\n const snapshot = await docRef.get()\n if (!snapshot.exists || snapshot.get('deletedAt')) return done('no-asset')\n if (!isMediaDeliveryType(snapshot.get('contentType'))) return done('not-video')\n const sourceHash = currentContentHash(snapshot)\n if (!sourceHash) return done('no-hash')\n\n const orgId =\n options.orgId === undefined\n ? await mediaDeliveryOrgIdFor(asset.collection, asset.scopeId)\n : options.orgId\n const enabled = await (options.deliveryEnabled ?? mediaDeliveryEnabledForOrg)(orgId)\n if (!enabled) return done('disabled')\n\n const now = options.nowMs ?? Date.now\n const basePath = mediaStoragePathInScope({\n storagePath: snapshot.get('storagePath'),\n base: `${asset.collection}/${asset.scopeId}`,\n mediaId: asset.mediaId,\n })\n const wanted: Array<{ objectPath: string; rendition: MediaVideoRendition | null }> = [\n { objectPath: basePath, rendition: null },\n ...parseMediaRenditions(snapshot.get('videoRenditions')).map((rendition) => ({\n objectPath: mediaRenditionObjectPath(basePath, rendition),\n rendition,\n })),\n ]\n\n const recorded = parseMediaDeliveryCopies(snapshot.get(MEDIA_DELIVERY_COPIES_FIELD), asset)\n const next: MediaDeliveryCopies = {}\n for (const { objectPath, rendition } of wanted) {\n const representation = mediaDeliveryRepresentation(rendition)\n const existing = recorded[representation]\n try {\n const file = bucket.file(objectPath)\n // The master's digest is on the document already, so a current copy of\n // it costs no Storage read. A rendition's is read every time.\n if (!rendition && existing?.sourceHash === sourceHash) {\n next[representation] = existing\n result.kept.push(representation)\n continue\n }\n const metadata = await readMetadata(file)\n const objectHash = rendition ? md5Hex(metadata) : sourceHash\n if (!objectHash) throw new Error('the object reports no digest')\n if (\n existing &&\n existing.sourceHash === sourceHash &&\n existing.objectHash === objectHash\n ) {\n next[representation] = existing\n result.kept.push(representation)\n continue\n }\n const sizeBytes = Number(metadata['size'])\n if (!Number.isSafeInteger(sizeBytes) || sizeBytes <= 0) {\n throw new Error('the object reports no size')\n }\n const contentType = rendition\n ? rendition.contentType\n : String(metadata['contentType'] ?? snapshot.get('contentType') ?? '')\n if (!isMediaDeliveryType(contentType)) throw new Error('the object is not a video')\n const key = mediaDeliveryObjectKey(asset, { sourceHash, representation, objectHash })\n if (!key) throw new Error('no key fits the asset')\n await provider.putObject({\n key,\n body: Readable.toWeb(file.createReadStream() as Readable) as ReadableStream<Uint8Array>,\n contentLength: sizeBytes,\n contentType,\n })\n next[representation] = {\n key,\n sourceHash,\n objectHash,\n contentType,\n sizeBytes,\n copiedAtMs: now(),\n }\n result.copied.push(representation)\n } catch (error) {\n result.failed.push(representation)\n console.error(\n '[media-delivery] copy failed; it keeps serving from the platform',\n JSON.stringify({ ...asset, representation }),\n error,\n )\n }\n }\n\n const nextKeys = new Set(Object.values(next).map((copy) => copy.key))\n const retired = Object.values(recorded)\n .map((copy) => copy.key)\n .filter((key) => !nextKeys.has(key))\n const madeNow = result.copied\n .map((representation) => next[representation]?.key)\n .filter((key): key is string => Boolean(key))\n\n if (result.copied.length || retired.length) {\n const written = await docRef.firestore.runTransaction(async (transaction) => {\n const fresh = await transaction.get(docRef)\n if (!fresh.exists || fresh.get('deletedAt')) return false\n if (currentContentHash(fresh) !== sourceHash) return false\n transaction.update(docRef, { [MEDIA_DELIVERY_COPIES_FIELD]: next })\n return true\n })\n if (!written) {\n // Replaced or deleted while the copies were uploading. Nothing records\n // what was just made, so it goes rather than lingering unreferenced.\n await removeKeys(provider, madeNow, result)\n return done('replaced')\n }\n }\n await removeKeys(provider, retired, result)\n return done('synced')\n}\n\nasync function removeKeys(\n provider: MediaDeliveryProvider,\n keys: string[],\n result: Pick<MediaDeliverySyncResult, 'removed'>,\n): Promise<void> {\n for (const key of keys) {\n try {\n await provider.deleteObject(key)\n result.removed.push(key)\n } catch (error) {\n console.error('[media-delivery] could not remove a retired copy', JSON.stringify({ key }), error)\n }\n }\n}\n\n/**\n * Removes every copy of one asset from the provider: a delete, a takedown and\n * a replace all end the life of the bytes the copies were made from.\n *\n * By prefix rather than by the document's record, so a copy the record lost\n * track of goes too. Never gated on the flag, since copies made while it was\n * on must still go, and never throws: the caller's own action has already\n * happened and must not be reported as failed because a copy lingered. Null\n * when no provider is configured to store.\n */\nexport async function removeMediaDeliveryCopies(options: {\n asset: MediaDeliveryAsset\n provider?: MediaDeliveryProvider | null\n}): Promise<{ removed: number; failed: boolean } | null> {\n const provider =\n options.provider === undefined ? mediaDeliveryProvider('store') : options.provider\n if (!provider) return null\n const prefix = mediaDeliveryAssetPrefix(options.asset)\n if (!prefix) return { removed: 0, failed: false }\n try {\n return { removed: await provider.deleteObjectsWithPrefix(prefix), failed: false }\n } catch (error) {\n console.error(\n '[media-delivery] could not remove an asset\\'s copies',\n JSON.stringify(options.asset),\n error,\n )\n return { removed: 0, failed: true }\n }\n}\n\n/**\n * Removes every copy a library holds at the provider — the erasure of a site\n * or an org. Same posture as {@link removeMediaDeliveryCopies}.\n */\nexport async function eraseMediaDeliveryScope(options: {\n collection: 'hosts' | 'orgs'\n scopeId: string\n provider?: MediaDeliveryProvider | null\n}): Promise<{ removed: number; failed: boolean } | null> {\n const provider =\n options.provider === undefined ? mediaDeliveryProvider('store') : options.provider\n if (!provider) return null\n const prefix = mediaDeliveryScopePrefix(options.collection, options.scopeId)\n if (!prefix) return { removed: 0, failed: false }\n try {\n return { removed: await provider.deleteObjectsWithPrefix(prefix), failed: false }\n } catch (error) {\n console.error(\n '[media-delivery] could not erase a library\\'s copies',\n JSON.stringify({ collection: options.collection, scopeId: options.scopeId }),\n error,\n )\n return { removed: 0, failed: true }\n }\n}\n\n/**\n * A media document without its copy record, for a restore: the delete that\n * made the tombstone removed the copies, so the record describes objects\n * that are gone.\n */\nexport function withoutMediaDeliveryCopies(\n media: Record<string, unknown>,\n): Record<string, unknown> {\n if (!(MEDIA_DELIVERY_COPIES_FIELD in media)) return media\n const rest = { ...media }\n delete rest[MEDIA_DELIVERY_COPIES_FIELD]\n return rest\n}\n"],"names":["Readable","mediaRenditionObjectPath","parseMediaRenditions","mediaDeliveryProvider","firebaseAdmin","MEDIA_SIGNATURE_MAX_TTL_MS","mediaStoragePathInScope","isServerReleaseFlagOnForOrg","MEDIA_DELIVERY_RELEASE_FLAG","MEDIA_DELIVERY_COPIES_FIELD","MEDIA_DELIVERY_MASTER","MEDIA_DELIVERY_MIN_TTL_MS","MEDIA_DELIVERY_DEFAULT_TTL_MS","MEDIA_DELIVERY_MAX_TTL_MS","mediaDeliveryTtlMs","durationMs","duration","Number","isFinite","Math","min","max","round","ID_SEGMENT","DIGEST","REPRESENTATION","mediaDeliveryRepresentation","rendition","key","validAsset","asset","collection","test","scopeId","mediaId","mediaDeliveryAssetPrefix","mediaDeliveryScopePrefix","mediaDeliveryObjectKey","copy","prefix","sourceHash","objectHash","representation","parseMediaDeliveryCopies","raw","copies","Array","isArray","value","Object","entries","entry","String","contentType","sizeBytes","copiedAtMs","isMediaDeliveryType","split","trim","toLowerCase","startsWith","currentContentHash","document","hash","get","ORG_CACHE_TTL_MS","hostOrgCache","Map","invalidateMediaDeliveryOrgCache","clear","mediaDeliveryOrgIdFor","hit","Date","now","at","orgId","firestore","app","index","getAll","doc","fieldMask","set","mediaDeliveryEnabledForOrg","mediaDeliveryRedirect","input","servedType","enabled","deliveryEnabled","nowMs","video","lifetime","ttlMs","expiresAtMs","notAfterMs","undefined","location","provider","deliveryUrl","claims","error","console","JSON","stringify","md5Hex","metadata","hex","Buffer","from","toString","readMetadata","file","response","getMetadata","syncMediaDeliveryCopies","options","result","status","copied","kept","removed","failed","done","bucket","docRef","snapshot","exists","basePath","storagePath","base","wanted","objectPath","map","recorded","next","existing","push","Error","isSafeInteger","putObject","body","toWeb","createReadStream","contentLength","nextKeys","Set","values","retired","filter","has","madeNow","Boolean","length","written","runTransaction","transaction","fresh","update","removeKeys","keys","deleteObject","removeMediaDeliveryCopies","deleteObjectsWithPrefix","eraseMediaDeliveryScope","withoutMediaDeliveryCopies","media","rest"],"mappings":"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"}