@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,66 @@
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
+ * Retention for the AGL-82 analytics day docs (AGL-1844).
18
+ *
19
+ * Day docs accumulated forever: every pageview, media serve and redirect hit
20
+ * left a `{hosts|orgs}/{id}/analytics/{YYYY-MM-DD}` (or
21
+ * `hosts/{id}/screenAnalytics/{screenId}:{day}`) document behind, and
22
+ * nothing ever deleted one. Every WRITER now stamps `expiresAt`, and a
23
+ * Firestore TTL policy on that field (declared as `fieldOverrides` in
24
+ * `cloud/firebase-firestore.indexes.json`, on the `analytics` and
25
+ * `screenAnalytics` collection groups) sweeps the old ones.
26
+ *
27
+ * ## Why 400 days
28
+ *
29
+ * Long enough that every reader's window closes with room to spare:
30
+ *
31
+ * - the console's widest range is 90 days;
32
+ * - /api/billing/report-usage meters `analytics/{day}.total` for the CURRENT
33
+ * period — an invoice dispute a year later still finds its days;
34
+ * - a year-over-year comparison (no surface does one yet) would still have
35
+ * both years.
36
+ *
37
+ * Short enough that storage is bounded: ~400 docs per host (plus per-screen
38
+ * and per-org docs) at steady state, instead of unbounded growth.
39
+ *
40
+ * ## Why TTL and NOT monthly rollups (the AGL-1844 decision)
41
+ *
42
+ * Rollups were considered and deliberately not built: no console surface
43
+ * reads more than 90 dailies (bounded, and blunted by the AGL-1440 cache),
44
+ * so a monthly doc would be written by a scheduled job — new moving part,
45
+ * new failure mode — to serve a view nothing renders. If a >90-day console
46
+ * range ever ships, rollups become that feature's first commit.
47
+ *
48
+ * TTL deletion is Firestore's own sweeper: typically within 24h of expiry,
49
+ * no code of ours runs. Docs written BEFORE this shipped carry no
50
+ * `expiresAt` and are never swept — the trickle of writes to a live site
51
+ * stamps its recent days naturally, and old unstamped days are exactly the
52
+ * ones nothing reads.
53
+ */ /** See the module doc for why 400. */ export const ANALYTICS_DAY_RETENTION_DAYS = 400;
54
+ const DAY_MS = 24 * 60 * 60 * 1000;
55
+ /**
56
+ * When the day doc named `day` (YYYY-MM-DD, UTC) may be swept. Anchored on
57
+ * the day itself, not the write clock, so every write to one doc agrees on
58
+ * one expiry. An unparsable day anchors on "now", which only ever shortens
59
+ * retention.
60
+ */ export function analyticsDayExpiresAt(day) {
61
+ const dayStart = Date.parse(`${day}T00:00:00Z`);
62
+ const base = Number.isFinite(dayStart) ? dayStart : Date.now();
63
+ return new Date(base + ANALYTICS_DAY_RETENTION_DAYS * DAY_MS);
64
+ }
65
+
66
+ //# sourceMappingURL=analytics-retention.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../../../../../libs/tenant/data/admin/src/lib/server/analytics-retention.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/**\n * Retention for the AGL-82 analytics day docs (AGL-1844).\n *\n * Day docs accumulated forever: every pageview, media serve and redirect hit\n * left a `{hosts|orgs}/{id}/analytics/{YYYY-MM-DD}` (or\n * `hosts/{id}/screenAnalytics/{screenId}:{day}`) document behind, and\n * nothing ever deleted one. Every WRITER now stamps `expiresAt`, and a\n * Firestore TTL policy on that field (declared as `fieldOverrides` in\n * `cloud/firebase-firestore.indexes.json`, on the `analytics` and\n * `screenAnalytics` collection groups) sweeps the old ones.\n *\n * ## Why 400 days\n *\n * Long enough that every reader's window closes with room to spare:\n *\n * - the console's widest range is 90 days;\n * - /api/billing/report-usage meters `analytics/{day}.total` for the CURRENT\n * period — an invoice dispute a year later still finds its days;\n * - a year-over-year comparison (no surface does one yet) would still have\n * both years.\n *\n * Short enough that storage is bounded: ~400 docs per host (plus per-screen\n * and per-org docs) at steady state, instead of unbounded growth.\n *\n * ## Why TTL and NOT monthly rollups (the AGL-1844 decision)\n *\n * Rollups were considered and deliberately not built: no console surface\n * reads more than 90 dailies (bounded, and blunted by the AGL-1440 cache),\n * so a monthly doc would be written by a scheduled job — new moving part,\n * new failure mode — to serve a view nothing renders. If a >90-day console\n * range ever ships, rollups become that feature's first commit.\n *\n * TTL deletion is Firestore's own sweeper: typically within 24h of expiry,\n * no code of ours runs. Docs written BEFORE this shipped carry no\n * `expiresAt` and are never swept — the trickle of writes to a live site\n * stamps its recent days naturally, and old unstamped days are exactly the\n * ones nothing reads.\n */\n\n/** See the module doc for why 400. */\nexport const ANALYTICS_DAY_RETENTION_DAYS = 400\n\nconst DAY_MS = 24 * 60 * 60 * 1000\n\n/**\n * When the day doc named `day` (YYYY-MM-DD, UTC) may be swept. Anchored on\n * the day itself, not the write clock, so every write to one doc agrees on\n * one expiry. An unparsable day anchors on \"now\", which only ever shortens\n * retention.\n */\nexport function analyticsDayExpiresAt(day: string): Date {\n const dayStart = Date.parse(`${day}T00:00:00Z`)\n const base = Number.isFinite(dayStart) ? dayStart : Date.now()\n return new Date(base + ANALYTICS_DAY_RETENTION_DAYS * DAY_MS)\n}\n"],"names":["ANALYTICS_DAY_RETENTION_DAYS","DAY_MS","analyticsDayExpiresAt","day","dayStart","Date","parse","base","Number","isFinite","now"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAqCC,GAED,oCAAoC,GACpC,OAAO,MAAMA,+BAA+B,IAAG;AAE/C,MAAMC,SAAS,KAAK,KAAK,KAAK;AAE9B;;;;;CAKC,GACD,OAAO,SAASC,sBAAsBC,GAAW;IAC/C,MAAMC,WAAWC,KAAKC,KAAK,CAAC,GAAGH,IAAI,UAAU,CAAC;IAC9C,MAAMI,OAAOC,OAAOC,QAAQ,CAACL,YAAYA,WAAWC,KAAKK,GAAG;IAC5D,OAAO,IAAIL,KAAKE,OAAOP,+BAA+BC;AACxD"}
@@ -0,0 +1,159 @@
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
+ /**
18
+ * HTTP building blocks for the customer REST API (`/api/v1`, AGL-617): a
19
+ * consistent JSON error envelope, list/pagination helpers, and a per-key
20
+ * rate limiter. Everything here is pure (Web `Response` + plain data), so it
21
+ * is unit-tested directly.
22
+ */
23
+ /** Every error response is `{ error: { type, message, code?, fields? } }`. */
24
+ export interface ApiErrorBody {
25
+ error: {
26
+ type: string;
27
+ message: string;
28
+ code?: string;
29
+ /** Per-field reasons on a validation failure (AGL-901). */
30
+ fields?: Record<string, string>;
31
+ };
32
+ }
33
+ export interface ApiResponseInit {
34
+ message?: string;
35
+ code?: string;
36
+ /**
37
+ * Field id → reason, surfaced on `validation_failed`. Additive: clients
38
+ * that only read `type`/`message` are unaffected.
39
+ */
40
+ fields?: Record<string, string>;
41
+ headers?: Record<string, string>;
42
+ }
43
+ /** Canonical error responses. Pass `{ headers }` to carry rate-limit headers. */
44
+ export declare const ApiErrors: {
45
+ unauthorized: (init?: ApiResponseInit) => Response;
46
+ planRequired: (init?: ApiResponseInit) => Response;
47
+ insufficientScope: (scope: string, headers?: Record<string, string>) => Response;
48
+ forbidden: (init?: ApiResponseInit) => Response;
49
+ notFound: (init?: ApiResponseInit) => Response;
50
+ badRequest: (init?: ApiResponseInit) => Response;
51
+ /**
52
+ * The request conflicts with work already in flight (AGL-1709). Used by the
53
+ * idempotency claim for the one case it must fail CLOSED: an identical
54
+ * attempt that has taken the claim but not yet settled. Letting the second
55
+ * caller through is the duplicate the key exists to prevent, so it is
56
+ * refused rather than served — and a client that reads `type` gets a
57
+ * retriable answer rather than a mystery.
58
+ */
59
+ conflict: (init?: ApiResponseInit) => Response;
60
+ methodNotAllowed: (init?: ApiResponseInit) => Response;
61
+ /**
62
+ * The three refusals an upload can produce that a JSON-only API never could
63
+ * (AGL-2463). Built here rather than at the call site so `/v1` keeps ONE
64
+ * error envelope — a client branching on `error.type` must not meet a bare
65
+ * `{ error: '<sentence>' }` on the one surface that handles bytes.
66
+ */
67
+ unsupportedMediaType: (init?: ApiResponseInit) => Response;
68
+ payloadTooLarge: (init?: ApiResponseInit) => Response;
69
+ /**
70
+ * 451, matching the console's own media chokepoints: the asset is refused
71
+ * because its content hash is on the takedown ledger, which is a legal
72
+ * verdict rather than a permission or a quota.
73
+ */
74
+ unavailableForLegalReasons: (init?: ApiResponseInit) => Response;
75
+ rateLimited: (retryAfterSec: number, headers?: Record<string, string>) => Response;
76
+ internal: (init?: ApiResponseInit) => Response;
77
+ };
78
+ /**
79
+ * Re-emit `response` with `headers` merged in — the escape hatch for a
80
+ * refusal built by a shared helper that knows nothing about this request's
81
+ * rate-limit budget (the lockdown 423, AGL-1596). Status, body and the
82
+ * response's own headers are preserved; `set` overwrites rather than
83
+ * appends, so a header named on both sides appears exactly once (the passed
84
+ * value wins) and no client ever reads a doubled `X-RateLimit-Remaining`.
85
+ */
86
+ export declare function withResponseHeaders(response: Response, headers: Record<string, string>): Response;
87
+ /** Success JSON with an optional status + headers (e.g. rate-limit headers). */
88
+ export declare function apiJson(data: unknown, init?: {
89
+ status?: number;
90
+ headers?: Record<string, string>;
91
+ }): Response;
92
+ export declare const DEFAULT_PAGE_LIMIT = 25;
93
+ export declare const MAX_PAGE_LIMIT = 100;
94
+ /** Clamp a `?limit=` value to [1, MAX], defaulting when absent/invalid. */
95
+ export declare function parseLimit(raw: string | null | undefined): number;
96
+ /** Opaque cursor = base64url of an underlying key (e.g. a Firestore doc id). */
97
+ export declare function encodeCursor(value: string): string;
98
+ export declare function decodeCursor(cursor: string | null | undefined): string | undefined;
99
+ /** List envelope: `{ data, next_cursor, has_more }`. */
100
+ export declare function listResponse(data: unknown[], nextCursor: string | null, headers?: Record<string, string>): Response;
101
+ export interface RateLimitState {
102
+ count: number;
103
+ windowStartMs: number;
104
+ }
105
+ export interface RateLimitResult {
106
+ allowed: boolean;
107
+ limit: number;
108
+ remaining: number;
109
+ resetMs: number;
110
+ }
111
+ export interface RateLimitOptions {
112
+ limit?: number;
113
+ windowMs?: number;
114
+ now?: number;
115
+ store?: Map<string, RateLimitState>;
116
+ }
117
+ export declare const DEFAULT_RATE_LIMIT = 120;
118
+ export declare const DEFAULT_RATE_WINDOW_MS = 60000;
119
+ /** Count one request against `key`'s fixed window. `now`/`store` injectable. */
120
+ export declare function checkRateLimit(key: string, options?: RateLimitOptions): RateLimitResult;
121
+ /**
122
+ * Report `key`'s window WITHOUT counting a request against it (AGL-2414).
123
+ *
124
+ * `checkRateLimit` fuses "is there budget" with "spend some", which is the
125
+ * right shape when every call is a request to be counted. It is the wrong
126
+ * shape when the budget meters something the caller has not done *yet* and
127
+ * may never do — `/api/v1` gates the Firestore key lookup on an IP's budget
128
+ * of *unproductive* lookups, so it has to ask before the lookup and only
129
+ * charge if the lookup came back empty. Asking with `checkRateLimit` would
130
+ * charge every request, including the successful ones, which is precisely the
131
+ * customer-visible cap that budget must not become.
132
+ *
133
+ * `allowed` answers "would the NEXT call be admitted", i.e. `count < limit` —
134
+ * one off from `checkRateLimit`'s post-increment `count <= limit`, and the
135
+ * same verdict for the same request. `remaining` is what is left now, so an
136
+ * untouched or expired window reports the full `limit` rather than
137
+ * `limit - 1`.
138
+ */
139
+ export declare function peekRateLimit(key: string, options?: RateLimitOptions): RateLimitResult;
140
+ /**
141
+ * Rate-limit headers, in BOTH spellings (AGL-2727).
142
+ *
143
+ * `X-RateLimit-*` is the de-facto set this API has always sent, and it is kept
144
+ * verbatim — including `Reset` as an epoch second — because integrations are
145
+ * already reading it and a header that changes meaning under a client is worse
146
+ * than one it has to learn.
147
+ *
148
+ * `RateLimit-*` is RFC 9331, which is what a caller that was not written
149
+ * against this specific API looks for, and increasingly what an agent looks
150
+ * for. Same numbers, standard spelling. Its `Reset` is SECONDS REMAINING, not
151
+ * an epoch — that is the RFC's own choice, and the better one: a duration is
152
+ * read against the caller's clock rather than ours, so a skewed clock cannot
153
+ * turn "wait 12 seconds" into "wait until 1970".
154
+ *
155
+ * ⚠️ Both are computed from the SAME `result`, so they can never disagree.
156
+ * Two independently-derived budgets on one response is a bug that reads as a
157
+ * rounding error.
158
+ */
159
+ export declare function rateLimitHeaders(result: RateLimitResult, now?: number): Record<string, string>;
@@ -0,0 +1,230 @@
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
+ * HTTP building blocks for the customer REST API (`/api/v1`, AGL-617): a
18
+ * consistent JSON error envelope, list/pagination helpers, and a per-key
19
+ * rate limiter. Everything here is pure (Web `Response` + plain data), so it
20
+ * is unit-tested directly.
21
+ */ /** Every error response is `{ error: { type, message, code?, fields? } }`. */ import { _ as _extends } from "@swc/helpers/_/_extends";
22
+ function errorResponse(status, type, fallbackMessage, init) {
23
+ var _ref;
24
+ const error = {
25
+ type,
26
+ message: (_ref = init == null ? void 0 : init.message) != null ? _ref : fallbackMessage
27
+ };
28
+ if (init == null ? void 0 : init.code) error.code = init.code;
29
+ if ((init == null ? void 0 : init.fields) && Object.keys(init.fields).length) error.fields = init.fields;
30
+ return Response.json({
31
+ error
32
+ }, {
33
+ status,
34
+ headers: init == null ? void 0 : init.headers
35
+ });
36
+ }
37
+ /** Canonical error responses. Pass `{ headers }` to carry rate-limit headers. */ export const ApiErrors = {
38
+ unauthorized: (init)=>errorResponse(401, 'unauthorized', 'Invalid or missing API key', init),
39
+ planRequired: (init)=>errorResponse(403, 'plan_required', 'API access requires the Business or Advanced plan', init),
40
+ insufficientScope: (scope, headers)=>errorResponse(403, 'insufficient_scope', `Missing the "${scope}" scope`, {
41
+ code: scope,
42
+ headers
43
+ }),
44
+ forbidden: (init)=>errorResponse(403, 'forbidden', 'Not permitted', init),
45
+ notFound: (init)=>errorResponse(404, 'not_found', 'Not found', init),
46
+ badRequest: (init)=>errorResponse(400, 'bad_request', 'Bad request', init),
47
+ /**
48
+ * The request conflicts with work already in flight (AGL-1709). Used by the
49
+ * idempotency claim for the one case it must fail CLOSED: an identical
50
+ * attempt that has taken the claim but not yet settled. Letting the second
51
+ * caller through is the duplicate the key exists to prevent, so it is
52
+ * refused rather than served — and a client that reads `type` gets a
53
+ * retriable answer rather than a mystery.
54
+ */ conflict: (init)=>errorResponse(409, 'conflict', 'Conflicts with the current state', init),
55
+ methodNotAllowed: (init)=>errorResponse(405, 'method_not_allowed', 'Method not allowed', init),
56
+ /**
57
+ * The three refusals an upload can produce that a JSON-only API never could
58
+ * (AGL-2463). Built here rather than at the call site so `/v1` keeps ONE
59
+ * error envelope — a client branching on `error.type` must not meet a bare
60
+ * `{ error: '<sentence>' }` on the one surface that handles bytes.
61
+ */ unsupportedMediaType: (init)=>errorResponse(415, 'unsupported_media_type', 'Unsupported file type', init),
62
+ payloadTooLarge: (init)=>errorResponse(413, 'payload_too_large', 'File too large', init),
63
+ /**
64
+ * 451, matching the console's own media chokepoints: the asset is refused
65
+ * because its content hash is on the takedown ledger, which is a legal
66
+ * verdict rather than a permission or a quota.
67
+ */ unavailableForLegalReasons: (init)=>errorResponse(451, 'unavailable_for_legal_reasons', 'This file has been removed and cannot be uploaded', init),
68
+ rateLimited: (retryAfterSec, headers)=>errorResponse(429, 'rate_limited', 'Too many requests', {
69
+ headers: _extends({
70
+ 'Retry-After': String(Math.max(1, retryAfterSec))
71
+ }, headers)
72
+ }),
73
+ internal: (init)=>errorResponse(500, 'internal_error', 'Something went wrong', init)
74
+ };
75
+ /**
76
+ * Re-emit `response` with `headers` merged in — the escape hatch for a
77
+ * refusal built by a shared helper that knows nothing about this request's
78
+ * rate-limit budget (the lockdown 423, AGL-1596). Status, body and the
79
+ * response's own headers are preserved; `set` overwrites rather than
80
+ * appends, so a header named on both sides appears exactly once (the passed
81
+ * value wins) and no client ever reads a doubled `X-RateLimit-Remaining`.
82
+ */ export function withResponseHeaders(response, headers) {
83
+ const merged = new Headers(response.headers);
84
+ for (const [name, value] of Object.entries(headers))merged.set(name, value);
85
+ return new Response(response.body, {
86
+ status: response.status,
87
+ statusText: response.statusText,
88
+ headers: merged
89
+ });
90
+ }
91
+ /** Success JSON with an optional status + headers (e.g. rate-limit headers). */ export function apiJson(data, init) {
92
+ var _ref;
93
+ return Response.json(data, {
94
+ status: (_ref = init == null ? void 0 : init.status) != null ? _ref : 200,
95
+ headers: init == null ? void 0 : init.headers
96
+ });
97
+ }
98
+ // ── Pagination ──────────────────────────────────────────────────────────────
99
+ export const DEFAULT_PAGE_LIMIT = 25;
100
+ export const MAX_PAGE_LIMIT = 100;
101
+ /** Clamp a `?limit=` value to [1, MAX], defaulting when absent/invalid. */ export function parseLimit(raw) {
102
+ const n = Number(raw);
103
+ if (!Number.isFinite(n) || n < 1) return DEFAULT_PAGE_LIMIT;
104
+ return Math.min(Math.floor(n), MAX_PAGE_LIMIT);
105
+ }
106
+ /** Opaque cursor = base64url of an underlying key (e.g. a Firestore doc id). */ export function encodeCursor(value) {
107
+ return Buffer.from(value, 'utf8').toString('base64url');
108
+ }
109
+ export function decodeCursor(cursor) {
110
+ if (!cursor) return undefined;
111
+ try {
112
+ const decoded = Buffer.from(cursor, 'base64url').toString('utf8');
113
+ return decoded.length > 0 ? decoded : undefined;
114
+ } catch (unused) {
115
+ return undefined;
116
+ }
117
+ }
118
+ /** List envelope: `{ data, next_cursor, has_more }`. */ export function listResponse(data, nextCursor, headers) {
119
+ return apiJson({
120
+ object: 'list',
121
+ data,
122
+ next_cursor: nextCursor,
123
+ has_more: nextCursor != null
124
+ }, {
125
+ headers
126
+ });
127
+ }
128
+ export const DEFAULT_RATE_LIMIT = 120;
129
+ export const DEFAULT_RATE_WINDOW_MS = 60000;
130
+ // Per-instance store. Serverless instances are ephemeral, so this blunts
131
+ // bursts rather than enforcing a global cap (matches the app's other
132
+ // limiters); a durable per-key counter can back it later.
133
+ const defaultStore = new Map();
134
+ /** Count one request against `key`'s fixed window. `now`/`store` injectable. */ export function checkRateLimit(key, options) {
135
+ var _ref, _ref1, _ref2, _ref3;
136
+ const limit = (_ref = options == null ? void 0 : options.limit) != null ? _ref : DEFAULT_RATE_LIMIT;
137
+ const windowMs = (_ref1 = options == null ? void 0 : options.windowMs) != null ? _ref1 : DEFAULT_RATE_WINDOW_MS;
138
+ const now = (_ref2 = options == null ? void 0 : options.now) != null ? _ref2 : Date.now();
139
+ const store = (_ref3 = options == null ? void 0 : options.store) != null ? _ref3 : defaultStore;
140
+ const state = store.get(key);
141
+ if (!state || now - state.windowStartMs >= windowMs) {
142
+ store.set(key, {
143
+ count: 1,
144
+ windowStartMs: now
145
+ });
146
+ return {
147
+ allowed: true,
148
+ limit,
149
+ remaining: limit - 1,
150
+ resetMs: now + windowMs
151
+ };
152
+ }
153
+ state.count += 1;
154
+ return {
155
+ allowed: state.count <= limit,
156
+ limit,
157
+ remaining: Math.max(0, limit - state.count),
158
+ resetMs: state.windowStartMs + windowMs
159
+ };
160
+ }
161
+ /**
162
+ * Report `key`'s window WITHOUT counting a request against it (AGL-2414).
163
+ *
164
+ * `checkRateLimit` fuses "is there budget" with "spend some", which is the
165
+ * right shape when every call is a request to be counted. It is the wrong
166
+ * shape when the budget meters something the caller has not done *yet* and
167
+ * may never do — `/api/v1` gates the Firestore key lookup on an IP's budget
168
+ * of *unproductive* lookups, so it has to ask before the lookup and only
169
+ * charge if the lookup came back empty. Asking with `checkRateLimit` would
170
+ * charge every request, including the successful ones, which is precisely the
171
+ * customer-visible cap that budget must not become.
172
+ *
173
+ * `allowed` answers "would the NEXT call be admitted", i.e. `count < limit` —
174
+ * one off from `checkRateLimit`'s post-increment `count <= limit`, and the
175
+ * same verdict for the same request. `remaining` is what is left now, so an
176
+ * untouched or expired window reports the full `limit` rather than
177
+ * `limit - 1`.
178
+ */ export function peekRateLimit(key, options) {
179
+ var _ref, _ref1, _ref2, _ref3;
180
+ const limit = (_ref = options == null ? void 0 : options.limit) != null ? _ref : DEFAULT_RATE_LIMIT;
181
+ const windowMs = (_ref1 = options == null ? void 0 : options.windowMs) != null ? _ref1 : DEFAULT_RATE_WINDOW_MS;
182
+ const now = (_ref2 = options == null ? void 0 : options.now) != null ? _ref2 : Date.now();
183
+ const store = (_ref3 = options == null ? void 0 : options.store) != null ? _ref3 : defaultStore;
184
+ const state = store.get(key);
185
+ if (!state || now - state.windowStartMs >= windowMs) {
186
+ return {
187
+ allowed: true,
188
+ limit,
189
+ remaining: limit,
190
+ resetMs: now + windowMs
191
+ };
192
+ }
193
+ return {
194
+ allowed: state.count < limit,
195
+ limit,
196
+ remaining: Math.max(0, limit - state.count),
197
+ resetMs: state.windowStartMs + windowMs
198
+ };
199
+ }
200
+ /**
201
+ * Rate-limit headers, in BOTH spellings (AGL-2727).
202
+ *
203
+ * `X-RateLimit-*` is the de-facto set this API has always sent, and it is kept
204
+ * verbatim — including `Reset` as an epoch second — because integrations are
205
+ * already reading it and a header that changes meaning under a client is worse
206
+ * than one it has to learn.
207
+ *
208
+ * `RateLimit-*` is RFC 9331, which is what a caller that was not written
209
+ * against this specific API looks for, and increasingly what an agent looks
210
+ * for. Same numbers, standard spelling. Its `Reset` is SECONDS REMAINING, not
211
+ * an epoch — that is the RFC's own choice, and the better one: a duration is
212
+ * read against the caller's clock rather than ours, so a skewed clock cannot
213
+ * turn "wait 12 seconds" into "wait until 1970".
214
+ *
215
+ * ⚠️ Both are computed from the SAME `result`, so they can never disagree.
216
+ * Two independently-derived budgets on one response is a bug that reads as a
217
+ * rounding error.
218
+ */ export function rateLimitHeaders(result, now = Date.now()) {
219
+ const remainingSeconds = Math.max(0, Math.ceil((result.resetMs - now) / 1000));
220
+ return {
221
+ 'X-RateLimit-Limit': String(result.limit),
222
+ 'X-RateLimit-Remaining': String(result.remaining),
223
+ 'X-RateLimit-Reset': String(Math.ceil(result.resetMs / 1000)),
224
+ 'RateLimit-Limit': String(result.limit),
225
+ 'RateLimit-Remaining': String(result.remaining),
226
+ 'RateLimit-Reset': String(remainingSeconds)
227
+ };
228
+ }
229
+
230
+ //# sourceMappingURL=api-http.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../../../../../libs/tenant/data/admin/src/lib/server/api-http.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/**\n * HTTP building blocks for the customer REST API (`/api/v1`, AGL-617): a\n * consistent JSON error envelope, list/pagination helpers, and a per-key\n * rate limiter. Everything here is pure (Web `Response` + plain data), so it\n * is unit-tested directly.\n */\n\n/** Every error response is `{ error: { type, message, code?, fields? } }`. */\nexport interface ApiErrorBody {\n error: {\n type: string\n message: string\n code?: string\n /** Per-field reasons on a validation failure (AGL-901). */\n fields?: Record<string, string>\n }\n}\n\nexport interface ApiResponseInit {\n message?: string\n code?: string\n /**\n * Field id → reason, surfaced on `validation_failed`. Additive: clients\n * that only read `type`/`message` are unaffected.\n */\n fields?: Record<string, string>\n headers?: Record<string, string>\n}\n\nfunction errorResponse(\n status: number,\n type: string,\n fallbackMessage: string,\n init?: ApiResponseInit,\n): Response {\n const error: ApiErrorBody['error'] = {\n type,\n message: init?.message ?? fallbackMessage,\n }\n if (init?.code) error.code = init.code\n if (init?.fields && Object.keys(init.fields).length) error.fields = init.fields\n return Response.json({ error }, { status, headers: init?.headers })\n}\n\n/** Canonical error responses. Pass `{ headers }` to carry rate-limit headers. */\nexport const ApiErrors = {\n unauthorized: (init?: ApiResponseInit) =>\n errorResponse(401, 'unauthorized', 'Invalid or missing API key', init),\n planRequired: (init?: ApiResponseInit) =>\n errorResponse(\n 403,\n 'plan_required',\n 'API access requires the Business or Advanced plan',\n init,\n ),\n insufficientScope: (scope: string, headers?: Record<string, string>) =>\n errorResponse(403, 'insufficient_scope', `Missing the \"${scope}\" scope`, {\n code: scope,\n headers,\n }),\n forbidden: (init?: ApiResponseInit) =>\n errorResponse(403, 'forbidden', 'Not permitted', init),\n notFound: (init?: ApiResponseInit) =>\n errorResponse(404, 'not_found', 'Not found', init),\n badRequest: (init?: ApiResponseInit) =>\n errorResponse(400, 'bad_request', 'Bad request', init),\n /**\n * The request conflicts with work already in flight (AGL-1709). Used by the\n * idempotency claim for the one case it must fail CLOSED: an identical\n * attempt that has taken the claim but not yet settled. Letting the second\n * caller through is the duplicate the key exists to prevent, so it is\n * refused rather than served — and a client that reads `type` gets a\n * retriable answer rather than a mystery.\n */\n conflict: (init?: ApiResponseInit) =>\n errorResponse(409, 'conflict', 'Conflicts with the current state', init),\n methodNotAllowed: (init?: ApiResponseInit) =>\n errorResponse(405, 'method_not_allowed', 'Method not allowed', init),\n /**\n * The three refusals an upload can produce that a JSON-only API never could\n * (AGL-2463). Built here rather than at the call site so `/v1` keeps ONE\n * error envelope — a client branching on `error.type` must not meet a bare\n * `{ error: '<sentence>' }` on the one surface that handles bytes.\n */\n unsupportedMediaType: (init?: ApiResponseInit) =>\n errorResponse(415, 'unsupported_media_type', 'Unsupported file type', init),\n payloadTooLarge: (init?: ApiResponseInit) =>\n errorResponse(413, 'payload_too_large', 'File too large', init),\n /**\n * 451, matching the console's own media chokepoints: the asset is refused\n * because its content hash is on the takedown ledger, which is a legal\n * verdict rather than a permission or a quota.\n */\n unavailableForLegalReasons: (init?: ApiResponseInit) =>\n errorResponse(\n 451,\n 'unavailable_for_legal_reasons',\n 'This file has been removed and cannot be uploaded',\n init,\n ),\n rateLimited: (retryAfterSec: number, headers?: Record<string, string>) =>\n errorResponse(429, 'rate_limited', 'Too many requests', {\n headers: { 'Retry-After': String(Math.max(1, retryAfterSec)), ...headers },\n }),\n internal: (init?: ApiResponseInit) =>\n errorResponse(500, 'internal_error', 'Something went wrong', init),\n}\n\n/**\n * Re-emit `response` with `headers` merged in — the escape hatch for a\n * refusal built by a shared helper that knows nothing about this request's\n * rate-limit budget (the lockdown 423, AGL-1596). Status, body and the\n * response's own headers are preserved; `set` overwrites rather than\n * appends, so a header named on both sides appears exactly once (the passed\n * value wins) and no client ever reads a doubled `X-RateLimit-Remaining`.\n */\nexport function withResponseHeaders(\n response: Response,\n headers: Record<string, string>,\n): Response {\n const merged = new Headers(response.headers)\n for (const [name, value] of Object.entries(headers)) merged.set(name, value)\n return new Response(response.body, {\n status: response.status,\n statusText: response.statusText,\n headers: merged,\n })\n}\n\n/** Success JSON with an optional status + headers (e.g. rate-limit headers). */\nexport function apiJson(\n data: unknown,\n init?: { status?: number; headers?: Record<string, string> },\n): Response {\n return Response.json(data, {\n status: init?.status ?? 200,\n headers: init?.headers,\n })\n}\n\n// ── Pagination ──────────────────────────────────────────────────────────────\n\nexport const DEFAULT_PAGE_LIMIT = 25\nexport const MAX_PAGE_LIMIT = 100\n\n/** Clamp a `?limit=` value to [1, MAX], defaulting when absent/invalid. */\nexport function parseLimit(raw: string | null | undefined): number {\n const n = Number(raw)\n if (!Number.isFinite(n) || n < 1) return DEFAULT_PAGE_LIMIT\n return Math.min(Math.floor(n), MAX_PAGE_LIMIT)\n}\n\n/** Opaque cursor = base64url of an underlying key (e.g. a Firestore doc id). */\nexport function encodeCursor(value: string): string {\n return Buffer.from(value, 'utf8').toString('base64url')\n}\n\nexport function decodeCursor(\n cursor: string | null | undefined,\n): string | undefined {\n if (!cursor) return undefined\n try {\n const decoded = Buffer.from(cursor, 'base64url').toString('utf8')\n return decoded.length > 0 ? decoded : undefined\n } catch {\n return undefined\n }\n}\n\n/** List envelope: `{ data, next_cursor, has_more }`. */\nexport function listResponse(\n data: unknown[],\n nextCursor: string | null,\n headers?: Record<string, string>,\n): Response {\n return apiJson(\n { object: 'list', data, next_cursor: nextCursor, has_more: nextCursor != null },\n { headers },\n )\n}\n\n// ── Rate limiting (fixed window, per key) ───────────────────────────────────\n\nexport interface RateLimitState {\n count: number\n windowStartMs: number\n}\n\nexport interface RateLimitResult {\n allowed: boolean\n limit: number\n remaining: number\n resetMs: number\n}\n\nexport interface RateLimitOptions {\n limit?: number\n windowMs?: number\n now?: number\n store?: Map<string, RateLimitState>\n}\n\nexport const DEFAULT_RATE_LIMIT = 120\nexport const DEFAULT_RATE_WINDOW_MS = 60_000\n\n// Per-instance store. Serverless instances are ephemeral, so this blunts\n// bursts rather than enforcing a global cap (matches the app's other\n// limiters); a durable per-key counter can back it later.\nconst defaultStore = new Map<string, RateLimitState>()\n\n/** Count one request against `key`'s fixed window. `now`/`store` injectable. */\nexport function checkRateLimit(\n key: string,\n options?: RateLimitOptions,\n): RateLimitResult {\n const limit = options?.limit ?? DEFAULT_RATE_LIMIT\n const windowMs = options?.windowMs ?? DEFAULT_RATE_WINDOW_MS\n const now = options?.now ?? Date.now()\n const store = options?.store ?? defaultStore\n\n const state = store.get(key)\n if (!state || now - state.windowStartMs >= windowMs) {\n store.set(key, { count: 1, windowStartMs: now })\n return { allowed: true, limit, remaining: limit - 1, resetMs: now + windowMs }\n }\n state.count += 1\n return {\n allowed: state.count <= limit,\n limit,\n remaining: Math.max(0, limit - state.count),\n resetMs: state.windowStartMs + windowMs,\n }\n}\n\n/**\n * Report `key`'s window WITHOUT counting a request against it (AGL-2414).\n *\n * `checkRateLimit` fuses \"is there budget\" with \"spend some\", which is the\n * right shape when every call is a request to be counted. It is the wrong\n * shape when the budget meters something the caller has not done *yet* and\n * may never do — `/api/v1` gates the Firestore key lookup on an IP's budget\n * of *unproductive* lookups, so it has to ask before the lookup and only\n * charge if the lookup came back empty. Asking with `checkRateLimit` would\n * charge every request, including the successful ones, which is precisely the\n * customer-visible cap that budget must not become.\n *\n * `allowed` answers \"would the NEXT call be admitted\", i.e. `count < limit` —\n * one off from `checkRateLimit`'s post-increment `count <= limit`, and the\n * same verdict for the same request. `remaining` is what is left now, so an\n * untouched or expired window reports the full `limit` rather than\n * `limit - 1`.\n */\nexport function peekRateLimit(\n key: string,\n options?: RateLimitOptions,\n): RateLimitResult {\n const limit = options?.limit ?? DEFAULT_RATE_LIMIT\n const windowMs = options?.windowMs ?? DEFAULT_RATE_WINDOW_MS\n const now = options?.now ?? Date.now()\n const store = options?.store ?? defaultStore\n\n const state = store.get(key)\n if (!state || now - state.windowStartMs >= windowMs) {\n return { allowed: true, limit, remaining: limit, resetMs: now + windowMs }\n }\n return {\n allowed: state.count < limit,\n limit,\n remaining: Math.max(0, limit - state.count),\n resetMs: state.windowStartMs + windowMs,\n }\n}\n\n/**\n * Rate-limit headers, in BOTH spellings (AGL-2727).\n *\n * `X-RateLimit-*` is the de-facto set this API has always sent, and it is kept\n * verbatim — including `Reset` as an epoch second — because integrations are\n * already reading it and a header that changes meaning under a client is worse\n * than one it has to learn.\n *\n * `RateLimit-*` is RFC 9331, which is what a caller that was not written\n * against this specific API looks for, and increasingly what an agent looks\n * for. Same numbers, standard spelling. Its `Reset` is SECONDS REMAINING, not\n * an epoch — that is the RFC's own choice, and the better one: a duration is\n * read against the caller's clock rather than ours, so a skewed clock cannot\n * turn \"wait 12 seconds\" into \"wait until 1970\".\n *\n * ⚠️ Both are computed from the SAME `result`, so they can never disagree.\n * Two independently-derived budgets on one response is a bug that reads as a\n * rounding error.\n */\nexport function rateLimitHeaders(\n result: RateLimitResult,\n now: number = Date.now(),\n): Record<string, string> {\n const remainingSeconds = Math.max(0, Math.ceil((result.resetMs - now) / 1000))\n return {\n 'X-RateLimit-Limit': String(result.limit),\n 'X-RateLimit-Remaining': String(result.remaining),\n 'X-RateLimit-Reset': String(Math.ceil(result.resetMs / 1000)),\n 'RateLimit-Limit': String(result.limit),\n 'RateLimit-Remaining': String(result.remaining),\n 'RateLimit-Reset': String(remainingSeconds),\n }\n}\n"],"names":["errorResponse","status","type","fallbackMessage","init","error","message","code","fields","Object","keys","length","Response","json","headers","ApiErrors","unauthorized","planRequired","insufficientScope","scope","forbidden","notFound","badRequest","conflict","methodNotAllowed","unsupportedMediaType","payloadTooLarge","unavailableForLegalReasons","rateLimited","retryAfterSec","String","Math","max","internal","withResponseHeaders","response","merged","Headers","name","value","entries","set","body","statusText","apiJson","data","DEFAULT_PAGE_LIMIT","MAX_PAGE_LIMIT","parseLimit","raw","n","Number","isFinite","min","floor","encodeCursor","Buffer","from","toString","decodeCursor","cursor","undefined","decoded","listResponse","nextCursor","object","next_cursor","has_more","DEFAULT_RATE_LIMIT","DEFAULT_RATE_WINDOW_MS","defaultStore","Map","checkRateLimit","key","options","limit","windowMs","now","Date","store","state","get","windowStartMs","count","allowed","remaining","resetMs","peekRateLimit","rateLimitHeaders","result","remainingSeconds","ceil"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAED;;;;;CAKC,GAED,4EAA4E;AAsB5E,SAASA,cACPC,MAAc,EACdC,IAAY,EACZC,eAAuB,EACvBC,IAAsB;;IAEtB,MAAMC,QAA+B;QACnCH;QACAI,OAAO,UAAEF,wBAAAA,KAAME,OAAO,mBAAIH;IAC5B;IACA,IAAIC,wBAAAA,KAAMG,IAAI,EAAEF,MAAME,IAAI,GAAGH,KAAKG,IAAI;IACtC,IAAIH,CAAAA,wBAAAA,KAAMI,MAAM,KAAIC,OAAOC,IAAI,CAACN,KAAKI,MAAM,EAAEG,MAAM,EAAEN,MAAMG,MAAM,GAAGJ,KAAKI,MAAM;IAC/E,OAAOI,SAASC,IAAI,CAAC;QAAER;IAAM,GAAG;QAAEJ;QAAQa,OAAO,EAAEV,wBAAAA,KAAMU,OAAO;IAAC;AACnE;AAEA,+EAA+E,GAC/E,OAAO,MAAMC,YAAY;IACvBC,cAAc,CAACZ,OACbJ,cAAc,KAAK,gBAAgB,8BAA8BI;IACnEa,cAAc,CAACb,OACbJ,cACE,KACA,iBACA,qDACAI;IAEJc,mBAAmB,CAACC,OAAeL,UACjCd,cAAc,KAAK,sBAAsB,CAAC,aAAa,EAAEmB,MAAM,OAAO,CAAC,EAAE;YACvEZ,MAAMY;YACNL;QACF;IACFM,WAAW,CAAChB,OACVJ,cAAc,KAAK,aAAa,iBAAiBI;IACnDiB,UAAU,CAACjB,OACTJ,cAAc,KAAK,aAAa,aAAaI;IAC/CkB,YAAY,CAAClB,OACXJ,cAAc,KAAK,eAAe,eAAeI;IACnD;;;;;;;GAOC,GACDmB,UAAU,CAACnB,OACTJ,cAAc,KAAK,YAAY,oCAAoCI;IACrEoB,kBAAkB,CAACpB,OACjBJ,cAAc,KAAK,sBAAsB,sBAAsBI;IACjE;;;;;GAKC,GACDqB,sBAAsB,CAACrB,OACrBJ,cAAc,KAAK,0BAA0B,yBAAyBI;IACxEsB,iBAAiB,CAACtB,OAChBJ,cAAc,KAAK,qBAAqB,kBAAkBI;IAC5D;;;;GAIC,GACDuB,4BAA4B,CAACvB,OAC3BJ,cACE,KACA,iCACA,qDACAI;IAEJwB,aAAa,CAACC,eAAuBf,UACnCd,cAAc,KAAK,gBAAgB,qBAAqB;YACtDc,SAAS;gBAAE,eAAegB,OAAOC,KAAKC,GAAG,CAAC,GAAGH;eAAoBf;QACnE;IACFmB,UAAU,CAAC7B,OACTJ,cAAc,KAAK,kBAAkB,wBAAwBI;AACjE,EAAC;AAED;;;;;;;CAOC,GACD,OAAO,SAAS8B,oBACdC,QAAkB,EAClBrB,OAA+B;IAE/B,MAAMsB,SAAS,IAAIC,QAAQF,SAASrB,OAAO;IAC3C,KAAK,MAAM,CAACwB,MAAMC,MAAM,IAAI9B,OAAO+B,OAAO,CAAC1B,SAAUsB,OAAOK,GAAG,CAACH,MAAMC;IACtE,OAAO,IAAI3B,SAASuB,SAASO,IAAI,EAAE;QACjCzC,QAAQkC,SAASlC,MAAM;QACvB0C,YAAYR,SAASQ,UAAU;QAC/B7B,SAASsB;IACX;AACF;AAEA,8EAA8E,GAC9E,OAAO,SAASQ,QACdC,IAAa,EACbzC,IAA4D;;IAE5D,OAAOQ,SAASC,IAAI,CAACgC,MAAM;QACzB5C,MAAM,UAAEG,wBAAAA,KAAMH,MAAM,mBAAI;QACxBa,OAAO,EAAEV,wBAAAA,KAAMU,OAAO;IACxB;AACF;AAEA,+EAA+E;AAE/E,OAAO,MAAMgC,qBAAqB,GAAE;AACpC,OAAO,MAAMC,iBAAiB,IAAG;AAEjC,yEAAyE,GACzE,OAAO,SAASC,WAAWC,GAA8B;IACvD,MAAMC,IAAIC,OAAOF;IACjB,IAAI,CAACE,OAAOC,QAAQ,CAACF,MAAMA,IAAI,GAAG,OAAOJ;IACzC,OAAOf,KAAKsB,GAAG,CAACtB,KAAKuB,KAAK,CAACJ,IAAIH;AACjC;AAEA,8EAA8E,GAC9E,OAAO,SAASQ,aAAahB,KAAa;IACxC,OAAOiB,OAAOC,IAAI,CAAClB,OAAO,QAAQmB,QAAQ,CAAC;AAC7C;AAEA,OAAO,SAASC,aACdC,MAAiC;IAEjC,IAAI,CAACA,QAAQ,OAAOC;IACpB,IAAI;QACF,MAAMC,UAAUN,OAAOC,IAAI,CAACG,QAAQ,aAAaF,QAAQ,CAAC;QAC1D,OAAOI,QAAQnD,MAAM,GAAG,IAAImD,UAAUD;IACxC,EAAE,eAAM;QACN,OAAOA;IACT;AACF;AAEA,sDAAsD,GACtD,OAAO,SAASE,aACdlB,IAAe,EACfmB,UAAyB,EACzBlD,OAAgC;IAEhC,OAAO8B,QACL;QAAEqB,QAAQ;QAAQpB;QAAMqB,aAAaF;QAAYG,UAAUH,cAAc;IAAK,GAC9E;QAAElD;IAAQ;AAEd;AAuBA,OAAO,MAAMsD,qBAAqB,IAAG;AACrC,OAAO,MAAMC,yBAAyB,MAAM;AAE5C,yEAAyE;AACzE,qEAAqE;AACrE,0DAA0D;AAC1D,MAAMC,eAAe,IAAIC;AAEzB,8EAA8E,GAC9E,OAAO,SAASC,eACdC,GAAW,EACXC,OAA0B;;IAE1B,MAAMC,gBAAQD,2BAAAA,QAASC,KAAK,mBAAIP;IAChC,MAAMQ,oBAAWF,2BAAAA,QAASE,QAAQ,oBAAIP;IACtC,MAAMQ,eAAMH,2BAAAA,QAASG,GAAG,oBAAIC,KAAKD,GAAG;IACpC,MAAME,iBAAQL,2BAAAA,QAASK,KAAK,oBAAIT;IAEhC,MAAMU,QAAQD,MAAME,GAAG,CAACR;IACxB,IAAI,CAACO,SAASH,MAAMG,MAAME,aAAa,IAAIN,UAAU;QACnDG,MAAMtC,GAAG,CAACgC,KAAK;YAAEU,OAAO;YAAGD,eAAeL;QAAI;QAC9C,OAAO;YAAEO,SAAS;YAAMT;YAAOU,WAAWV,QAAQ;YAAGW,SAAST,MAAMD;QAAS;IAC/E;IACAI,MAAMG,KAAK,IAAI;IACf,OAAO;QACLC,SAASJ,MAAMG,KAAK,IAAIR;QACxBA;QACAU,WAAWtD,KAAKC,GAAG,CAAC,GAAG2C,QAAQK,MAAMG,KAAK;QAC1CG,SAASN,MAAME,aAAa,GAAGN;IACjC;AACF;AAEA;;;;;;;;;;;;;;;;;CAiBC,GACD,OAAO,SAASW,cACdd,GAAW,EACXC,OAA0B;;IAE1B,MAAMC,gBAAQD,2BAAAA,QAASC,KAAK,mBAAIP;IAChC,MAAMQ,oBAAWF,2BAAAA,QAASE,QAAQ,oBAAIP;IACtC,MAAMQ,eAAMH,2BAAAA,QAASG,GAAG,oBAAIC,KAAKD,GAAG;IACpC,MAAME,iBAAQL,2BAAAA,QAASK,KAAK,oBAAIT;IAEhC,MAAMU,QAAQD,MAAME,GAAG,CAACR;IACxB,IAAI,CAACO,SAASH,MAAMG,MAAME,aAAa,IAAIN,UAAU;QACnD,OAAO;YAAEQ,SAAS;YAAMT;YAAOU,WAAWV;YAAOW,SAAST,MAAMD;QAAS;IAC3E;IACA,OAAO;QACLQ,SAASJ,MAAMG,KAAK,GAAGR;QACvBA;QACAU,WAAWtD,KAAKC,GAAG,CAAC,GAAG2C,QAAQK,MAAMG,KAAK;QAC1CG,SAASN,MAAME,aAAa,GAAGN;IACjC;AACF;AAEA;;;;;;;;;;;;;;;;;;CAkBC,GACD,OAAO,SAASY,iBACdC,MAAuB,EACvBZ,MAAcC,KAAKD,GAAG,EAAE;IAExB,MAAMa,mBAAmB3D,KAAKC,GAAG,CAAC,GAAGD,KAAK4D,IAAI,CAAC,AAACF,CAAAA,OAAOH,OAAO,GAAGT,GAAE,IAAK;IACxE,OAAO;QACL,qBAAqB/C,OAAO2D,OAAOd,KAAK;QACxC,yBAAyB7C,OAAO2D,OAAOJ,SAAS;QAChD,qBAAqBvD,OAAOC,KAAK4D,IAAI,CAACF,OAAOH,OAAO,GAAG;QACvD,mBAAmBxD,OAAO2D,OAAOd,KAAK;QACtC,uBAAuB7C,OAAO2D,OAAOJ,SAAS;QAC9C,mBAAmBvD,OAAO4D;IAC5B;AACF"}
@@ -0,0 +1,117 @@
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
+ /**
18
+ * Scopes a key can be granted, as `resource:action`. Every entry must be
19
+ * enforced by a real endpoint — a mintable scope that grants nothing reads
20
+ * as a broken permission (AGL-899 removed `contacts:write` on those grounds,
21
+ * and AGL-2276 re-added it in the same change that shipped the writes, which
22
+ * is the condition AGL-899 set).
23
+ */
24
+ export declare const API_SCOPES: readonly ["datasets:read", "datasets:write", "contacts:read", "contacts:write", "sites:read", "sites:publish", "sites:write", "forms:read", "forms:write", "orders:read", "orders:write", "products:read", "media:read", "media:write", "crm:read", "crm:write"];
25
+ export type ApiScope = (typeof API_SCOPES)[number];
26
+ /** Live-key token prefix. A `_test_` variant can follow the same shape later. */
27
+ export declare const API_KEY_TOKEN_PREFIX = "aglyn_sk_";
28
+ /** Top-level collection; doc id = the token hash. */
29
+ export declare const API_KEYS_COLLECTION = "apiKeys";
30
+ /** Stored document. `createdAt`/`lastUsedAt`/etc. are Firestore Timestamps. */
31
+ export interface ApiKeyDocument {
32
+ /** Public, stable id shown in the UI and used for revoke (never the hash). */
33
+ keyId: string;
34
+ orgId: string;
35
+ name: string;
36
+ /** e.g. `aglyn_sk_ab12cd…` — enough to recognize a key, not to use it. */
37
+ keyPrefix: string;
38
+ scopes: ApiScope[];
39
+ createdBy: string;
40
+ createdAt: FirebaseFirestore.Timestamp;
41
+ lastUsedAt: FirebaseFirestore.Timestamp | null;
42
+ revokedAt: FirebaseFirestore.Timestamp | null;
43
+ expiresAt: FirebaseFirestore.Timestamp | null;
44
+ }
45
+ /** Metadata safe to return to a client (no hash, no raw token). */
46
+ export interface PublicApiKey {
47
+ keyId: string;
48
+ name: string;
49
+ keyPrefix: string;
50
+ scopes: ApiScope[];
51
+ createdAt: string | null;
52
+ lastUsedAt: string | null;
53
+ revokedAt: string | null;
54
+ expiresAt: string | null;
55
+ }
56
+ /** The resolved principal after a successful key verification. */
57
+ export interface VerifiedApiKey {
58
+ orgId: string;
59
+ keyId: string;
60
+ scopes: ApiScope[];
61
+ /**
62
+ * The name the organization gave the key — what a feed attributes the
63
+ * key's writes to (AGL-2632). Every minted key carries one; empty only
64
+ * for a document written before the field was required.
65
+ */
66
+ name: string;
67
+ }
68
+ /** True if `value` is one of the known scopes. */
69
+ export declare function isApiScope(value: string): value is ApiScope;
70
+ /** Keep only valid, de-duplicated scopes, preserving `API_SCOPES` order. */
71
+ export declare function normalizeScopes(scopes: readonly string[]): ApiScope[];
72
+ /** SHA-256 hex of a raw token — the Firestore doc id and lookup key. */
73
+ export declare function hashApiKey(token: string): string;
74
+ /** Structural check before hashing/looking up — cheap reject of junk input. */
75
+ export declare function isValidApiKeyFormat(token: string): boolean;
76
+ /**
77
+ * Generate a fresh token plus the derived hash and display prefix. The raw
78
+ * `token` is the only time the secret exists in plaintext — persist the hash,
79
+ * hand the token to the user once.
80
+ */
81
+ export declare function generateApiKeyToken(): {
82
+ token: string;
83
+ hash: string;
84
+ keyPrefix: string;
85
+ };
86
+ /** True when the granted scopes include the one an operation requires. */
87
+ export declare function hasScope(granted: readonly ApiScope[], required: ApiScope): boolean;
88
+ /** A random public key id (`key_…`), distinct from the secret and its hash. */
89
+ export declare function generateKeyId(): string;
90
+ /** Public projection of a stored key — never includes the hash/doc id. */
91
+ export declare function toPublicApiKey(doc: ApiKeyDocument): PublicApiKey;
92
+ export interface MintApiKeyInput {
93
+ orgId: string;
94
+ name: string;
95
+ scopes: readonly string[];
96
+ createdBy: string;
97
+ /** Optional absolute expiry, epoch ms. */
98
+ expiresAtMs?: number | null;
99
+ }
100
+ /**
101
+ * Create a key for an org. Returns the raw `token` (show once) and the public
102
+ * metadata. Invalid scopes are dropped; at least one valid scope is required.
103
+ */
104
+ export declare function mintApiKey(input: MintApiKeyInput): Promise<{
105
+ token: string;
106
+ key: PublicApiKey;
107
+ }>;
108
+ /**
109
+ * Resolve a raw token to its org/scopes, or `null` if it is malformed,
110
+ * unknown, revoked, or expired. Refreshes `lastUsedAt` at most once a minute
111
+ * (fire-and-forget) to avoid a write on every request.
112
+ */
113
+ export declare function verifyApiKey(token: string): Promise<VerifiedApiKey | null>;
114
+ /** All keys for an org, newest first (metadata only). */
115
+ export declare function listApiKeys(orgId: string): Promise<PublicApiKey[]>;
116
+ /** Revoke a key by its public id. Returns false if not found/already revoked. */
117
+ export declare function revokeApiKey(orgId: string, keyId: string): Promise<boolean>;