@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,298 @@
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
+ */ /**
18
+ * The Vercel driver — how Aglyn's own hosting registers a name.
19
+ *
20
+ * One implementation of the call, one deadline, one never-throws contract. It
21
+ * was five copies before this file existed: `workspace-domains.ts`, three
22
+ * console routes and `subdomain-redirect.ts` each held their own `fetch` to
23
+ * `api.vercel.com`, which is how two copies of a constant drift apart — the
24
+ * bug AGL-1135 was, one layer down.
25
+ *
26
+ * ## The two projects
27
+ *
28
+ * The console and the tenant runtime are separate Vercel projects, so the
29
+ * scope picks the id. One token and one team serve both: a second variable for
30
+ * the same token is the drift this file exists to prevent.
31
+ */ import { abortedDetail, domainDeadline, domainStatus, normalizeHost, redirectHostname } from "./domain-provider.js";
32
+ /** Mirrors the app-level canonical redirect: revocable, method-preserving. */ const REDIRECT_STATUS_CODE = 307;
33
+ function settingsFor(scope) {
34
+ const token = process.env.VERCEL_TOKEN;
35
+ const projectId = scope === 'tenant' ? process.env.VERCEL_TENANT_PROJECT_ID : process.env.VERCEL_CONSOLE_PROJECT_ID;
36
+ if (!token || !projectId) return null;
37
+ return {
38
+ token,
39
+ projectId,
40
+ teamId: process.env.VERCEL_TEAM_ID
41
+ };
42
+ }
43
+ function query(teamId) {
44
+ return teamId ? `?teamId=${encodeURIComponent(teamId)}` : '';
45
+ }
46
+ function authHeaders(settings) {
47
+ return {
48
+ Authorization: `Bearer ${settings.token}`
49
+ };
50
+ }
51
+ /**
52
+ * Attach one fully-qualified name to the scope's project.
53
+ *
54
+ * Idempotent: Vercel answers `domain_already_in_use` when the name is already
55
+ * on the project, which is success for our purposes — the reconcile script and
56
+ * the create path can both run without coordinating.
57
+ *
58
+ * ⚠️ Tolerating `domain_already_in_use` is only safe while **every** name this
59
+ * is asked to attach is also claimed in Firestore. Without that, a second org
60
+ * attaches a name someone else already holds, reads `already-exists` as
61
+ * success, and gets a console that looks healthy while its visitors are served
62
+ * — or redirected — somewhere else. That is the AGL-743 shape, and
63
+ * `console-domains.ts` is where the claim is kept in step.
64
+ */ async function attach(scope, name, options = {}) {
65
+ const domain = normalizeHost(name);
66
+ const settings = settingsFor(scope);
67
+ if (!settings || !domain) return {
68
+ outcome: 'skipped',
69
+ domain
70
+ };
71
+ const redirect = options.redirectTo ? redirectHostname(options.redirectTo) : null;
72
+ if (options.redirectTo && !redirect) {
73
+ console.error('[domain-provider:vercel] refusing a non-hostname redirect', options.redirectTo);
74
+ return {
75
+ outcome: 'failed',
76
+ domain,
77
+ detail: 'invalid-redirect'
78
+ };
79
+ }
80
+ const body = redirect ? {
81
+ name: domain,
82
+ redirect,
83
+ redirectStatusCode: REDIRECT_STATUS_CODE
84
+ } : {
85
+ name: domain
86
+ };
87
+ try {
88
+ var _ref;
89
+ var _payload_error;
90
+ const response = await fetch(`https://api.vercel.com/v10/projects/${settings.projectId}/domains${query(settings.teamId)}`, {
91
+ method: 'POST',
92
+ headers: _extends({}, authHeaders(settings), {
93
+ 'Content-Type': 'application/json'
94
+ }),
95
+ body: JSON.stringify(body),
96
+ signal: domainDeadline()
97
+ });
98
+ if (response.ok) return {
99
+ outcome: 'attached',
100
+ domain
101
+ };
102
+ const payload = await response.json().catch(()=>null);
103
+ const code = String((_ref = payload == null ? void 0 : (_payload_error = payload.error) == null ? void 0 : _payload_error.code) != null ? _ref : '');
104
+ if (code === 'domain_already_in_use' || response.status === 409) {
105
+ // An entry that already exists still has to CARRY the redirect, or the
106
+ // twin quietly serves the console instead of forwarding to the primary.
107
+ if (redirect) return patchRedirect(settings, domain, redirect);
108
+ return {
109
+ outcome: 'already-exists',
110
+ domain
111
+ };
112
+ }
113
+ console.error('[domain-provider:vercel] attach failed', domain, code);
114
+ return {
115
+ outcome: 'failed',
116
+ domain,
117
+ detail: code || String(response.status)
118
+ };
119
+ } catch (error) {
120
+ console.error('[domain-provider:vercel] attach threw', domain, error);
121
+ return {
122
+ outcome: 'failed',
123
+ domain,
124
+ detail: abortedDetail(error)
125
+ };
126
+ }
127
+ }
128
+ /** Point an already-attached name at `redirect`. Same never-throws contract. */ async function patchRedirect(settings, domain, redirect) {
129
+ try {
130
+ const response = await fetch(`https://api.vercel.com/v9/projects/${settings.projectId}/domains/${encodeURIComponent(domain)}${query(settings.teamId)}`, {
131
+ method: 'PATCH',
132
+ headers: _extends({}, authHeaders(settings), {
133
+ 'Content-Type': 'application/json'
134
+ }),
135
+ body: JSON.stringify({
136
+ redirect,
137
+ redirectStatusCode: REDIRECT_STATUS_CODE
138
+ }),
139
+ signal: domainDeadline()
140
+ });
141
+ if (response.ok) return {
142
+ outcome: 'already-exists',
143
+ domain
144
+ };
145
+ console.error('[domain-provider:vercel] redirect patch failed', domain, response.status);
146
+ return {
147
+ outcome: 'failed',
148
+ domain,
149
+ detail: String(response.status)
150
+ };
151
+ } catch (error) {
152
+ console.error('[domain-provider:vercel] redirect patch threw', domain, error);
153
+ return {
154
+ outcome: 'failed',
155
+ domain,
156
+ detail: abortedDetail(error)
157
+ };
158
+ }
159
+ }
160
+ async function detach(scope, name) {
161
+ const domain = normalizeHost(name);
162
+ const settings = settingsFor(scope);
163
+ if (!settings || !domain) return {
164
+ outcome: 'skipped',
165
+ domain
166
+ };
167
+ try {
168
+ const response = await fetch(`https://api.vercel.com/v9/projects/${settings.projectId}/domains/${encodeURIComponent(domain)}${query(settings.teamId)}`, {
169
+ method: 'DELETE',
170
+ headers: authHeaders(settings),
171
+ signal: domainDeadline()
172
+ });
173
+ if (response.ok) return {
174
+ outcome: 'detached',
175
+ domain
176
+ };
177
+ if (response.status === 404) return {
178
+ outcome: 'not-found',
179
+ domain
180
+ };
181
+ console.error('[domain-provider:vercel] detach failed', domain, response.status);
182
+ return {
183
+ outcome: 'failed',
184
+ domain,
185
+ detail: String(response.status)
186
+ };
187
+ } catch (error) {
188
+ console.error('[domain-provider:vercel] detach threw', domain, error);
189
+ return {
190
+ outcome: 'failed',
191
+ domain,
192
+ detail: abortedDetail(error)
193
+ };
194
+ }
195
+ }
196
+ /** Whether any issued certificate's names cover `domain`, wildcards included. */ function certificateCovers(cns, domain) {
197
+ if (!Array.isArray(cns)) return false;
198
+ const parent = domain.split('.').slice(1).join('.');
199
+ return cns.some((name)=>{
200
+ const cn = normalizeHost(String(name != null ? name : ''));
201
+ return cn === domain || cn.startsWith('*.') && cn.slice(2) === parent;
202
+ });
203
+ }
204
+ /**
205
+ * What a name is ACTUALLY doing on the project, right now.
206
+ *
207
+ * `attach` answers "did the POST succeed", which is a different question and a
208
+ * much weaker one. A domain can be on the project and still serve nothing:
209
+ * Vercel accepts the name, then withholds routing and the certificate until
210
+ * ownership is proven (when the apex is registered to another Vercel account)
211
+ * or until DNS actually points here. Both states return a successful POST.
212
+ *
213
+ * That is the gap this closes (AGL-1913): the site custom-domain wizard
214
+ * reported `attached: true` off the POST alone, so "certificate is still
215
+ * issuing" and "this will never work" rendered as the same green chip.
216
+ *
217
+ * Three reads, cheapest first, each of which can end the walk:
218
+ *
219
+ * 1. `GET /v9/projects/{project}/domains/{name}` — 404 means the name is not
220
+ * on this project at all, and `verified: false` means Vercel is holding it
221
+ * pending the `verification` challenge it returns alongside.
222
+ * 2. `GET /v6/domains/{name}/config` — the platform's own DNS check.
223
+ * `misconfigured` is authoritative in a way our resolver check is not, and
224
+ * `conflicts` names the records answering for the domain that are NOT ours
225
+ * — the stale-A-record-shadowing-an-ALIAS case, reported rather than
226
+ * inferred.
227
+ * 3. `GET /v5/certs?domain={name}` — whether a certificate covers the name
228
+ * yet. Only consulted once the first two are clean, and a FAILED cert read
229
+ * reports `serving` rather than inventing a problem: an unreachable certs
230
+ * API must never turn a healthy domain amber.
231
+ */ async function status(scope, name) {
232
+ const domain = normalizeHost(name);
233
+ const settings = settingsFor(scope);
234
+ if (!settings || !domain) return domainStatus(domain, 'skipped');
235
+ const headers = authHeaders(settings);
236
+ const team = query(settings.teamId);
237
+ try {
238
+ const attached = await fetch(`https://api.vercel.com/v9/projects/${settings.projectId}/domains/${encodeURIComponent(domain)}${team}`, {
239
+ headers,
240
+ signal: domainDeadline()
241
+ });
242
+ if (attached.status === 404) return domainStatus(domain, 'not-attached');
243
+ if (!attached.ok) {
244
+ return domainStatus(domain, 'unknown', {
245
+ detail: String(attached.status)
246
+ });
247
+ }
248
+ const payload = await attached.json().catch(()=>null);
249
+ if ((payload == null ? void 0 : payload.verified) === false) {
250
+ return domainStatus(domain, 'ownership-pending', {
251
+ verification: Array.isArray(payload == null ? void 0 : payload.verification) ? payload.verification : []
252
+ });
253
+ }
254
+ const configured = await fetch(`https://api.vercel.com/v6/domains/${encodeURIComponent(domain)}/config${team}`, {
255
+ headers,
256
+ signal: domainDeadline()
257
+ });
258
+ if (!configured.ok) {
259
+ return domainStatus(domain, 'unknown', {
260
+ detail: String(configured.status)
261
+ });
262
+ }
263
+ const dns = await configured.json().catch(()=>null);
264
+ const conflicts = Array.isArray(dns == null ? void 0 : dns.conflicts) ? dns.conflicts : [];
265
+ if ((dns == null ? void 0 : dns.misconfigured) === true) {
266
+ return domainStatus(domain, 'dns-misconfigured', {
267
+ conflicts
268
+ });
269
+ }
270
+ const certs = await fetch(`https://api.vercel.com/v5/certs?domain=${encodeURIComponent(domain)}${settings.teamId ? `&teamId=${encodeURIComponent(settings.teamId)}` : ''}`, {
271
+ headers,
272
+ signal: domainDeadline()
273
+ });
274
+ // A certs read that did not answer must not turn a healthy domain amber.
275
+ if (!certs.ok) return domainStatus(domain, 'serving', {
276
+ conflicts
277
+ });
278
+ const issued = await certs.json().catch(()=>null);
279
+ const covered = Array.isArray(issued == null ? void 0 : issued.certs) && issued.certs.some((cert)=>certificateCovers(cert == null ? void 0 : cert.cns, domain));
280
+ return domainStatus(domain, covered ? 'serving' : 'certificate-pending', {
281
+ conflicts
282
+ });
283
+ } catch (error) {
284
+ console.error('[domain-provider:vercel] status threw', domain, error);
285
+ return domainStatus(domain, 'unknown', {
286
+ detail: abortedDetail(error)
287
+ });
288
+ }
289
+ }
290
+ export const VERCEL_DOMAIN_PROVIDER = {
291
+ id: 'vercel',
292
+ configured: (scope)=>settingsFor(scope) !== null,
293
+ attach,
294
+ detach,
295
+ status
296
+ };
297
+
298
+ //# sourceMappingURL=domain-provider-vercel.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../../../../../libs/tenant/data/admin/src/lib/server/domain-provider-vercel.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 * The Vercel driver — how Aglyn's own hosting registers a name.\n *\n * One implementation of the call, one deadline, one never-throws contract. It\n * was five copies before this file existed: `workspace-domains.ts`, three\n * console routes and `subdomain-redirect.ts` each held their own `fetch` to\n * `api.vercel.com`, which is how two copies of a constant drift apart — the\n * bug AGL-1135 was, one layer down.\n *\n * ## The two projects\n *\n * The console and the tenant runtime are separate Vercel projects, so the\n * scope picks the id. One token and one team serve both: a second variable for\n * the same token is the drift this file exists to prevent.\n */\n\nimport {\n abortedDetail,\n domainDeadline,\n domainStatus,\n normalizeHost,\n redirectHostname,\n type DomainAttachOptions,\n type DomainProvider,\n type DomainResult,\n type DomainScope,\n type ProjectDomainStatus,\n} from './domain-provider'\n\n/** Mirrors the app-level canonical redirect: revocable, method-preserving. */\nconst REDIRECT_STATUS_CODE = 307\n\ninterface VercelSettings {\n token: string\n projectId: string\n teamId?: string\n}\n\nfunction settingsFor(scope: DomainScope): VercelSettings | null {\n const token = process.env.VERCEL_TOKEN\n const projectId =\n scope === 'tenant'\n ? process.env.VERCEL_TENANT_PROJECT_ID\n : process.env.VERCEL_CONSOLE_PROJECT_ID\n if (!token || !projectId) return null\n return { token, projectId, teamId: process.env.VERCEL_TEAM_ID }\n}\n\nfunction query(teamId?: string): string {\n return teamId ? `?teamId=${encodeURIComponent(teamId)}` : ''\n}\n\nfunction authHeaders(settings: VercelSettings): Record<string, string> {\n return { Authorization: `Bearer ${settings.token}` }\n}\n\n/**\n * Attach one fully-qualified name to the scope's project.\n *\n * Idempotent: Vercel answers `domain_already_in_use` when the name is already\n * on the project, which is success for our purposes — the reconcile script and\n * the create path can both run without coordinating.\n *\n * ⚠️ Tolerating `domain_already_in_use` is only safe while **every** name this\n * is asked to attach is also claimed in Firestore. Without that, a second org\n * attaches a name someone else already holds, reads `already-exists` as\n * success, and gets a console that looks healthy while its visitors are served\n * — or redirected — somewhere else. That is the AGL-743 shape, and\n * `console-domains.ts` is where the claim is kept in step.\n */\nasync function attach(\n scope: DomainScope,\n name: string,\n options: DomainAttachOptions = {},\n): Promise<DomainResult> {\n const domain = normalizeHost(name)\n const settings = settingsFor(scope)\n if (!settings || !domain) return { outcome: 'skipped', domain }\n\n const redirect = options.redirectTo\n ? redirectHostname(options.redirectTo)\n : null\n if (options.redirectTo && !redirect) {\n console.error('[domain-provider:vercel] refusing a non-hostname redirect', options.redirectTo)\n return { outcome: 'failed', domain, detail: 'invalid-redirect' }\n }\n const body = redirect\n ? { name: domain, redirect, redirectStatusCode: REDIRECT_STATUS_CODE }\n : { name: domain }\n\n try {\n const response = await fetch(\n `https://api.vercel.com/v10/projects/${settings.projectId}/domains${query(settings.teamId)}`,\n {\n method: 'POST',\n headers: { ...authHeaders(settings), 'Content-Type': 'application/json' },\n body: JSON.stringify(body),\n signal: domainDeadline(),\n },\n )\n if (response.ok) return { outcome: 'attached', domain }\n const payload = await response.json().catch(() => null)\n const code = String(payload?.error?.code ?? '')\n if (code === 'domain_already_in_use' || response.status === 409) {\n // An entry that already exists still has to CARRY the redirect, or the\n // twin quietly serves the console instead of forwarding to the primary.\n if (redirect) return patchRedirect(settings, domain, redirect)\n return { outcome: 'already-exists', domain }\n }\n console.error('[domain-provider:vercel] attach failed', domain, code)\n return { outcome: 'failed', domain, detail: code || String(response.status) }\n } catch (error) {\n console.error('[domain-provider:vercel] attach threw', domain, error)\n return { outcome: 'failed', domain, detail: abortedDetail(error) }\n }\n}\n\n/** Point an already-attached name at `redirect`. Same never-throws contract. */\nasync function patchRedirect(\n settings: VercelSettings,\n domain: string,\n redirect: string,\n): Promise<DomainResult> {\n try {\n const response = await fetch(\n `https://api.vercel.com/v9/projects/${settings.projectId}/domains/${encodeURIComponent(domain)}${query(settings.teamId)}`,\n {\n method: 'PATCH',\n headers: { ...authHeaders(settings), 'Content-Type': 'application/json' },\n body: JSON.stringify({\n redirect,\n redirectStatusCode: REDIRECT_STATUS_CODE,\n }),\n signal: domainDeadline(),\n },\n )\n if (response.ok) return { outcome: 'already-exists', domain }\n console.error('[domain-provider:vercel] redirect patch failed', domain, response.status)\n return { outcome: 'failed', domain, detail: String(response.status) }\n } catch (error) {\n console.error('[domain-provider:vercel] redirect patch threw', domain, error)\n return { outcome: 'failed', domain, detail: abortedDetail(error) }\n }\n}\n\nasync function detach(\n scope: DomainScope,\n name: string,\n): Promise<DomainResult> {\n const domain = normalizeHost(name)\n const settings = settingsFor(scope)\n if (!settings || !domain) return { outcome: 'skipped', domain }\n try {\n const response = await fetch(\n `https://api.vercel.com/v9/projects/${settings.projectId}/domains/${encodeURIComponent(domain)}${query(settings.teamId)}`,\n {\n method: 'DELETE',\n headers: authHeaders(settings),\n signal: domainDeadline(),\n },\n )\n if (response.ok) return { outcome: 'detached', domain }\n if (response.status === 404) return { outcome: 'not-found', domain }\n console.error('[domain-provider:vercel] detach failed', domain, response.status)\n return { outcome: 'failed', domain, detail: String(response.status) }\n } catch (error) {\n console.error('[domain-provider:vercel] detach threw', domain, error)\n return { outcome: 'failed', domain, detail: abortedDetail(error) }\n }\n}\n\n/** Whether any issued certificate's names cover `domain`, wildcards included. */\nfunction certificateCovers(cns: unknown, domain: string): boolean {\n if (!Array.isArray(cns)) return false\n const parent = domain.split('.').slice(1).join('.')\n return cns.some((name) => {\n const cn = normalizeHost(String(name ?? ''))\n return cn === domain || (cn.startsWith('*.') && cn.slice(2) === parent)\n })\n}\n\n/**\n * What a name is ACTUALLY doing on the project, right now.\n *\n * `attach` answers \"did the POST succeed\", which is a different question and a\n * much weaker one. A domain can be on the project and still serve nothing:\n * Vercel accepts the name, then withholds routing and the certificate until\n * ownership is proven (when the apex is registered to another Vercel account)\n * or until DNS actually points here. Both states return a successful POST.\n *\n * That is the gap this closes (AGL-1913): the site custom-domain wizard\n * reported `attached: true` off the POST alone, so \"certificate is still\n * issuing\" and \"this will never work\" rendered as the same green chip.\n *\n * Three reads, cheapest first, each of which can end the walk:\n *\n * 1. `GET /v9/projects/{project}/domains/{name}` — 404 means the name is not\n * on this project at all, and `verified: false` means Vercel is holding it\n * pending the `verification` challenge it returns alongside.\n * 2. `GET /v6/domains/{name}/config` — the platform's own DNS check.\n * `misconfigured` is authoritative in a way our resolver check is not, and\n * `conflicts` names the records answering for the domain that are NOT ours\n * — the stale-A-record-shadowing-an-ALIAS case, reported rather than\n * inferred.\n * 3. `GET /v5/certs?domain={name}` — whether a certificate covers the name\n * yet. Only consulted once the first two are clean, and a FAILED cert read\n * reports `serving` rather than inventing a problem: an unreachable certs\n * API must never turn a healthy domain amber.\n */\nasync function status(\n scope: DomainScope,\n name: string,\n): Promise<ProjectDomainStatus> {\n const domain = normalizeHost(name)\n const settings = settingsFor(scope)\n if (!settings || !domain) return domainStatus(domain, 'skipped')\n const headers = authHeaders(settings)\n const team = query(settings.teamId)\n\n try {\n const attached = await fetch(\n `https://api.vercel.com/v9/projects/${settings.projectId}/domains/${encodeURIComponent(domain)}${team}`,\n { headers, signal: domainDeadline() },\n )\n if (attached.status === 404) return domainStatus(domain, 'not-attached')\n if (!attached.ok) {\n return domainStatus(domain, 'unknown', { detail: String(attached.status) })\n }\n const payload = await attached.json().catch(() => null)\n if (payload?.verified === false) {\n return domainStatus(domain, 'ownership-pending', {\n verification: Array.isArray(payload?.verification)\n ? payload.verification\n : [],\n })\n }\n\n const configured = await fetch(\n `https://api.vercel.com/v6/domains/${encodeURIComponent(domain)}/config${team}`,\n { headers, signal: domainDeadline() },\n )\n if (!configured.ok) {\n return domainStatus(domain, 'unknown', { detail: String(configured.status) })\n }\n const dns = await configured.json().catch(() => null)\n const conflicts = Array.isArray(dns?.conflicts) ? dns.conflicts : []\n if (dns?.misconfigured === true) {\n return domainStatus(domain, 'dns-misconfigured', { conflicts })\n }\n\n const certs = await fetch(\n `https://api.vercel.com/v5/certs?domain=${encodeURIComponent(domain)}${\n settings.teamId ? `&teamId=${encodeURIComponent(settings.teamId)}` : ''\n }`,\n { headers, signal: domainDeadline() },\n )\n // A certs read that did not answer must not turn a healthy domain amber.\n if (!certs.ok) return domainStatus(domain, 'serving', { conflicts })\n const issued = await certs.json().catch(() => null)\n const covered =\n Array.isArray(issued?.certs) &&\n issued.certs.some((cert: { cns?: unknown }) =>\n certificateCovers(cert?.cns, domain),\n )\n return domainStatus(domain, covered ? 'serving' : 'certificate-pending', {\n conflicts,\n })\n } catch (error) {\n console.error('[domain-provider:vercel] status threw', domain, error)\n return domainStatus(domain, 'unknown', { detail: abortedDetail(error) })\n }\n}\n\nexport const VERCEL_DOMAIN_PROVIDER: DomainProvider = {\n id: 'vercel',\n configured: (scope) => settingsFor(scope) !== null,\n attach,\n detach,\n status,\n}\n"],"names":["abortedDetail","domainDeadline","domainStatus","normalizeHost","redirectHostname","REDIRECT_STATUS_CODE","settingsFor","scope","token","process","env","VERCEL_TOKEN","projectId","VERCEL_TENANT_PROJECT_ID","VERCEL_CONSOLE_PROJECT_ID","teamId","VERCEL_TEAM_ID","query","encodeURIComponent","authHeaders","settings","Authorization","attach","name","options","domain","outcome","redirect","redirectTo","console","error","detail","body","redirectStatusCode","payload","response","fetch","method","headers","JSON","stringify","signal","ok","json","catch","code","String","status","patchRedirect","detach","certificateCovers","cns","Array","isArray","parent","split","slice","join","some","cn","startsWith","team","attached","verified","verification","configured","dns","conflicts","misconfigured","certs","issued","covered","cert","VERCEL_DOMAIN_PROVIDER","id"],"mappings":"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"}
@@ -0,0 +1,73 @@
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
+ * The webhook driver — the layer an operator supplies.
19
+ *
20
+ * Aglyn asks one endpoint the three questions in the provider contract and
21
+ * takes its word for the answer. What is on the other side is entirely the
22
+ * operator's business: Caddy's admin API, a Traefik dynamic-config file, a
23
+ * cert-manager `Certificate`, a registrar's API, or twenty lines of shell.
24
+ *
25
+ * This is the extension point rather than a module path the app imports,
26
+ * because a module path does not survive the product. Both apps are bundled by
27
+ * Next, so a path resolved at runtime is not in the bundle, and an operator
28
+ * running the published image has no build step in which to add one. A webhook
29
+ * needs no rebuild and works from the shipped image unchanged.
30
+ *
31
+ * ## The wire format
32
+ *
33
+ * `POST $AGLYN_DOMAIN_WEBHOOK_URL`, `Content-Type: application/json`, and
34
+ * `Authorization: Bearer $AGLYN_DOMAIN_WEBHOOK_TOKEN` when that is set:
35
+ *
36
+ * ```json
37
+ * { "action": "attach", "scope": "tenant", "domain": "shop.acme.com",
38
+ * "redirectTo": "acme.com" }
39
+ * ```
40
+ *
41
+ * `action` is `attach`, `detach` or `status`. `scope` says which app should
42
+ * answer for the name — `console` for the admin app and its workspace
43
+ * subdomains, `tenant` for published sites. `redirectTo` appears only when the
44
+ * name should forward rather than serve, and is always a bare hostname.
45
+ *
46
+ * Answer `200` with either an outcome:
47
+ *
48
+ * ```json
49
+ * { "outcome": "attached", "detail": "added to caddy" }
50
+ * ```
51
+ *
52
+ * or, for `status`, a state:
53
+ *
54
+ * ```json
55
+ * { "state": "ownership-pending",
56
+ * "verification": [{ "type": "TXT", "domain": "_acme-challenge.shop.acme.com",
57
+ * "value": "…" }] }
58
+ * ```
59
+ *
60
+ * ## What Aglyn does with a bad answer
61
+ *
62
+ * An unrecognized `outcome` is `failed` and an unrecognized `state` is
63
+ * `unknown`, both logged with what arrived. A typo must not read as success —
64
+ * an endpoint that answers `{"outcome":"attach"}` has registered nothing, and
65
+ * accepting it would leave a workspace advertising a URL that resolves
66
+ * nowhere.
67
+ *
68
+ * ⚠️ Send this to a service on your own network. The request carries the
69
+ * bearer token and the names of your customers' domains, and the reply decides
70
+ * whether the console tells someone their site is live.
71
+ */
72
+ import { type DomainProvider } from './domain-provider';
73
+ export declare function webhookDomainProvider(): DomainProvider;
@@ -0,0 +1,223 @@
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
+ */ /**
18
+ * The webhook driver — the layer an operator supplies.
19
+ *
20
+ * Aglyn asks one endpoint the three questions in the provider contract and
21
+ * takes its word for the answer. What is on the other side is entirely the
22
+ * operator's business: Caddy's admin API, a Traefik dynamic-config file, a
23
+ * cert-manager `Certificate`, a registrar's API, or twenty lines of shell.
24
+ *
25
+ * This is the extension point rather than a module path the app imports,
26
+ * because a module path does not survive the product. Both apps are bundled by
27
+ * Next, so a path resolved at runtime is not in the bundle, and an operator
28
+ * running the published image has no build step in which to add one. A webhook
29
+ * needs no rebuild and works from the shipped image unchanged.
30
+ *
31
+ * ## The wire format
32
+ *
33
+ * `POST $AGLYN_DOMAIN_WEBHOOK_URL`, `Content-Type: application/json`, and
34
+ * `Authorization: Bearer $AGLYN_DOMAIN_WEBHOOK_TOKEN` when that is set:
35
+ *
36
+ * ```json
37
+ * { "action": "attach", "scope": "tenant", "domain": "shop.acme.com",
38
+ * "redirectTo": "acme.com" }
39
+ * ```
40
+ *
41
+ * `action` is `attach`, `detach` or `status`. `scope` says which app should
42
+ * answer for the name — `console` for the admin app and its workspace
43
+ * subdomains, `tenant` for published sites. `redirectTo` appears only when the
44
+ * name should forward rather than serve, and is always a bare hostname.
45
+ *
46
+ * Answer `200` with either an outcome:
47
+ *
48
+ * ```json
49
+ * { "outcome": "attached", "detail": "added to caddy" }
50
+ * ```
51
+ *
52
+ * or, for `status`, a state:
53
+ *
54
+ * ```json
55
+ * { "state": "ownership-pending",
56
+ * "verification": [{ "type": "TXT", "domain": "_acme-challenge.shop.acme.com",
57
+ * "value": "…" }] }
58
+ * ```
59
+ *
60
+ * ## What Aglyn does with a bad answer
61
+ *
62
+ * An unrecognized `outcome` is `failed` and an unrecognized `state` is
63
+ * `unknown`, both logged with what arrived. A typo must not read as success —
64
+ * an endpoint that answers `{"outcome":"attach"}` has registered nothing, and
65
+ * accepting it would leave a workspace advertising a URL that resolves
66
+ * nowhere.
67
+ *
68
+ * ⚠️ Send this to a service on your own network. The request carries the
69
+ * bearer token and the names of your customers' domains, and the reply decides
70
+ * whether the console tells someone their site is live.
71
+ */ import { abortedDetail, domainDeadline, domainStatus, normalizeHost, redirectHostname } from "./domain-provider.js";
72
+ const OUTCOMES = [
73
+ 'attached',
74
+ 'detached',
75
+ 'already-exists',
76
+ 'not-found',
77
+ 'skipped',
78
+ 'failed'
79
+ ];
80
+ const STATES = [
81
+ 'serving',
82
+ 'certificate-pending',
83
+ 'ownership-pending',
84
+ 'dns-misconfigured',
85
+ 'not-attached',
86
+ 'skipped',
87
+ 'unknown'
88
+ ];
89
+ function settings() {
90
+ var _process_env_AGLYN_DOMAIN_WEBHOOK_URL;
91
+ var _process_env_AGLYN_DOMAIN_WEBHOOK_TOKEN;
92
+ const url = String((_process_env_AGLYN_DOMAIN_WEBHOOK_URL = process.env.AGLYN_DOMAIN_WEBHOOK_URL) != null ? _process_env_AGLYN_DOMAIN_WEBHOOK_URL : '').trim();
93
+ if (!url) return null;
94
+ return {
95
+ url,
96
+ token: ((_process_env_AGLYN_DOMAIN_WEBHOOK_TOKEN = process.env.AGLYN_DOMAIN_WEBHOOK_TOKEN) == null ? void 0 : _process_env_AGLYN_DOMAIN_WEBHOOK_TOKEN.trim()) || undefined
97
+ };
98
+ }
99
+ async function call(config, action, scope, domain, redirectTo) {
100
+ try {
101
+ const response = await fetch(config.url, {
102
+ method: 'POST',
103
+ headers: _extends({
104
+ 'Content-Type': 'application/json'
105
+ }, config.token ? {
106
+ Authorization: `Bearer ${config.token}`
107
+ } : {}),
108
+ body: JSON.stringify(_extends({
109
+ action,
110
+ scope,
111
+ domain
112
+ }, redirectTo ? {
113
+ redirectTo
114
+ } : {})),
115
+ signal: domainDeadline()
116
+ });
117
+ const body = await response.json().catch(()=>null);
118
+ if (!response.ok) {
119
+ console.error(`[domain-provider:webhook] ${action} answered ${response.status}`, domain);
120
+ return {
121
+ ok: false,
122
+ body,
123
+ detail: String(response.status)
124
+ };
125
+ }
126
+ return {
127
+ ok: true,
128
+ body,
129
+ detail: ''
130
+ };
131
+ } catch (error) {
132
+ console.error(`[domain-provider:webhook] ${action} threw`, domain, error);
133
+ return {
134
+ ok: false,
135
+ body: null,
136
+ detail: abortedDetail(error)
137
+ };
138
+ }
139
+ }
140
+ /** The endpoint's `detail`, kept short — it reaches operator-facing logs. */ function detailFrom(body) {
141
+ const raw = body == null ? void 0 : body['detail'];
142
+ if (typeof raw !== 'string') return undefined;
143
+ const trimmed = raw.trim().slice(0, 200);
144
+ return trimmed || undefined;
145
+ }
146
+ function recordList(value) {
147
+ return Array.isArray(value) ? value : [];
148
+ }
149
+ export function webhookDomainProvider() {
150
+ const act = async (action, scope, name, options = {})=>{
151
+ const domain = normalizeHost(name);
152
+ const config = settings();
153
+ if (!config || !domain) return {
154
+ outcome: 'skipped',
155
+ domain
156
+ };
157
+ // Normalized before the wire, so an operator's endpoint receives the same
158
+ // bare hostname Vercel demands and no two drivers disagree on the shape.
159
+ const redirect = options.redirectTo ? redirectHostname(options.redirectTo) : null;
160
+ if (options.redirectTo && !redirect) {
161
+ console.error('[domain-provider:webhook] refusing a non-hostname redirect', options.redirectTo);
162
+ return {
163
+ outcome: 'failed',
164
+ domain,
165
+ detail: 'invalid-redirect'
166
+ };
167
+ }
168
+ const { ok, body, detail } = await call(config, action, scope, domain, redirect != null ? redirect : undefined);
169
+ if (!ok) return {
170
+ outcome: 'failed',
171
+ domain,
172
+ detail: detail || 'webhook'
173
+ };
174
+ const outcome = body == null ? void 0 : body['outcome'];
175
+ if (typeof outcome !== 'string' || !OUTCOMES.includes(outcome)) {
176
+ console.error(`[domain-provider:webhook] ${action} returned an unknown outcome`, domain, outcome);
177
+ return {
178
+ outcome: 'failed',
179
+ domain,
180
+ detail: 'bad-outcome'
181
+ };
182
+ }
183
+ return _extends({
184
+ outcome: outcome,
185
+ domain
186
+ }, detailFrom(body) ? {
187
+ detail: detailFrom(body)
188
+ } : {});
189
+ };
190
+ return {
191
+ id: 'webhook',
192
+ configured: ()=>settings() !== null,
193
+ attach: (scope, name, options)=>act('attach', scope, name, options),
194
+ detach: (scope, name)=>act('detach', scope, name),
195
+ status: async (scope, name)=>{
196
+ const domain = normalizeHost(name);
197
+ const config = settings();
198
+ if (!config || !domain) return domainStatus(domain, 'skipped');
199
+ const { ok, body, detail } = await call(config, 'status', scope, domain);
200
+ // Not `not-attached`: an endpoint that did not answer is not evidence
201
+ // that the name is missing, and reporting one as the other would strand
202
+ // every live domain the moment the operator's service restarted.
203
+ if (!ok) return domainStatus(domain, 'unknown', {
204
+ detail: detail || 'webhook'
205
+ });
206
+ const state = body == null ? void 0 : body['state'];
207
+ if (typeof state !== 'string' || !STATES.includes(state)) {
208
+ console.error('[domain-provider:webhook] status returned an unknown state', domain, state);
209
+ return domainStatus(domain, 'unknown', {
210
+ detail: 'bad-state'
211
+ });
212
+ }
213
+ return domainStatus(domain, state, _extends({
214
+ verification: recordList(body == null ? void 0 : body['verification']),
215
+ conflicts: recordList(body == null ? void 0 : body['conflicts'])
216
+ }, detailFrom(body) ? {
217
+ detail: detailFrom(body)
218
+ } : {}));
219
+ }
220
+ };
221
+ }
222
+
223
+ //# sourceMappingURL=domain-provider-webhook.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../../../../../libs/tenant/data/admin/src/lib/server/domain-provider-webhook.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 * The webhook driver — the layer an operator supplies.\n *\n * Aglyn asks one endpoint the three questions in the provider contract and\n * takes its word for the answer. What is on the other side is entirely the\n * operator's business: Caddy's admin API, a Traefik dynamic-config file, a\n * cert-manager `Certificate`, a registrar's API, or twenty lines of shell.\n *\n * This is the extension point rather than a module path the app imports,\n * because a module path does not survive the product. Both apps are bundled by\n * Next, so a path resolved at runtime is not in the bundle, and an operator\n * running the published image has no build step in which to add one. A webhook\n * needs no rebuild and works from the shipped image unchanged.\n *\n * ## The wire format\n *\n * `POST $AGLYN_DOMAIN_WEBHOOK_URL`, `Content-Type: application/json`, and\n * `Authorization: Bearer $AGLYN_DOMAIN_WEBHOOK_TOKEN` when that is set:\n *\n * ```json\n * { \"action\": \"attach\", \"scope\": \"tenant\", \"domain\": \"shop.acme.com\",\n * \"redirectTo\": \"acme.com\" }\n * ```\n *\n * `action` is `attach`, `detach` or `status`. `scope` says which app should\n * answer for the name — `console` for the admin app and its workspace\n * subdomains, `tenant` for published sites. `redirectTo` appears only when the\n * name should forward rather than serve, and is always a bare hostname.\n *\n * Answer `200` with either an outcome:\n *\n * ```json\n * { \"outcome\": \"attached\", \"detail\": \"added to caddy\" }\n * ```\n *\n * or, for `status`, a state:\n *\n * ```json\n * { \"state\": \"ownership-pending\",\n * \"verification\": [{ \"type\": \"TXT\", \"domain\": \"_acme-challenge.shop.acme.com\",\n * \"value\": \"…\" }] }\n * ```\n *\n * ## What Aglyn does with a bad answer\n *\n * An unrecognized `outcome` is `failed` and an unrecognized `state` is\n * `unknown`, both logged with what arrived. A typo must not read as success —\n * an endpoint that answers `{\"outcome\":\"attach\"}` has registered nothing, and\n * accepting it would leave a workspace advertising a URL that resolves\n * nowhere.\n *\n * ⚠️ Send this to a service on your own network. The request carries the\n * bearer token and the names of your customers' domains, and the reply decides\n * whether the console tells someone their site is live.\n */\n\nimport {\n abortedDetail,\n domainDeadline,\n domainStatus,\n normalizeHost,\n redirectHostname,\n type DomainAttachOptions,\n type DomainOutcome,\n type DomainProvider,\n type DomainResult,\n type DomainScope,\n type ProjectDomainState,\n type ProjectDomainStatus,\n} from './domain-provider'\n\nconst OUTCOMES: readonly DomainOutcome[] = [\n 'attached',\n 'detached',\n 'already-exists',\n 'not-found',\n 'skipped',\n 'failed',\n]\n\nconst STATES: readonly ProjectDomainState[] = [\n 'serving',\n 'certificate-pending',\n 'ownership-pending',\n 'dns-misconfigured',\n 'not-attached',\n 'skipped',\n 'unknown',\n]\n\ninterface WebhookSettings {\n url: string\n token?: string\n}\n\nfunction settings(): WebhookSettings | null {\n const url = String(process.env.AGLYN_DOMAIN_WEBHOOK_URL ?? '').trim()\n if (!url) return null\n return { url, token: process.env.AGLYN_DOMAIN_WEBHOOK_TOKEN?.trim() || undefined }\n}\n\nasync function call(\n config: WebhookSettings,\n action: 'attach' | 'detach' | 'status',\n scope: DomainScope,\n domain: string,\n redirectTo?: string,\n): Promise<{ ok: boolean; body: Record<string, unknown> | null; detail: string }> {\n try {\n const response = await fetch(config.url, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n ...(config.token ? { Authorization: `Bearer ${config.token}` } : {}),\n },\n body: JSON.stringify({\n action,\n scope,\n domain,\n ...(redirectTo ? { redirectTo } : {}),\n }),\n signal: domainDeadline(),\n })\n const body = (await response.json().catch(() => null)) as\n | Record<string, unknown>\n | null\n if (!response.ok) {\n console.error(\n `[domain-provider:webhook] ${action} answered ${response.status}`,\n domain,\n )\n return { ok: false, body, detail: String(response.status) }\n }\n return { ok: true, body, detail: '' }\n } catch (error) {\n console.error(`[domain-provider:webhook] ${action} threw`, domain, error)\n return { ok: false, body: null, detail: abortedDetail(error) }\n }\n}\n\n/** The endpoint's `detail`, kept short — it reaches operator-facing logs. */\nfunction detailFrom(body: Record<string, unknown> | null): string | undefined {\n const raw = body?.['detail']\n if (typeof raw !== 'string') return undefined\n const trimmed = raw.trim().slice(0, 200)\n return trimmed || undefined\n}\n\nfunction recordList(\n value: unknown,\n): { type?: string; name?: string; value?: string }[] {\n return Array.isArray(value) ? (value as { type?: string }[]) : []\n}\n\nexport function webhookDomainProvider(): DomainProvider {\n const act = async (\n action: 'attach' | 'detach',\n scope: DomainScope,\n name: string,\n options: DomainAttachOptions = {},\n ): Promise<DomainResult> => {\n const domain = normalizeHost(name)\n const config = settings()\n if (!config || !domain) return { outcome: 'skipped', domain }\n\n // Normalized before the wire, so an operator's endpoint receives the same\n // bare hostname Vercel demands and no two drivers disagree on the shape.\n const redirect = options.redirectTo\n ? redirectHostname(options.redirectTo)\n : null\n if (options.redirectTo && !redirect) {\n console.error(\n '[domain-provider:webhook] refusing a non-hostname redirect',\n options.redirectTo,\n )\n return { outcome: 'failed', domain, detail: 'invalid-redirect' }\n }\n\n const { ok, body, detail } = await call(\n config,\n action,\n scope,\n domain,\n redirect ?? undefined,\n )\n if (!ok) return { outcome: 'failed', domain, detail: detail || 'webhook' }\n\n const outcome = body?.['outcome']\n if (typeof outcome !== 'string' || !OUTCOMES.includes(outcome as DomainOutcome)) {\n console.error(\n `[domain-provider:webhook] ${action} returned an unknown outcome`,\n domain,\n outcome,\n )\n return { outcome: 'failed', domain, detail: 'bad-outcome' }\n }\n return {\n outcome: outcome as DomainOutcome,\n domain,\n ...(detailFrom(body) ? { detail: detailFrom(body) } : {}),\n }\n }\n\n return {\n id: 'webhook',\n configured: () => settings() !== null,\n attach: (scope, name, options) => act('attach', scope, name, options),\n detach: (scope, name) => act('detach', scope, name),\n status: async (scope, name): Promise<ProjectDomainStatus> => {\n const domain = normalizeHost(name)\n const config = settings()\n if (!config || !domain) return domainStatus(domain, 'skipped')\n\n const { ok, body, detail } = await call(config, 'status', scope, domain)\n // Not `not-attached`: an endpoint that did not answer is not evidence\n // that the name is missing, and reporting one as the other would strand\n // every live domain the moment the operator's service restarted.\n if (!ok) return domainStatus(domain, 'unknown', { detail: detail || 'webhook' })\n\n const state = body?.['state']\n if (\n typeof state !== 'string' ||\n !STATES.includes(state as ProjectDomainState)\n ) {\n console.error(\n '[domain-provider:webhook] status returned an unknown state',\n domain,\n state,\n )\n return domainStatus(domain, 'unknown', { detail: 'bad-state' })\n }\n return domainStatus(domain, state as ProjectDomainState, {\n verification: recordList(body?.['verification']) as never,\n conflicts: recordList(body?.['conflicts']),\n ...(detailFrom(body) ? { detail: detailFrom(body) } : {}),\n })\n },\n }\n}\n"],"names":["abortedDetail","domainDeadline","domainStatus","normalizeHost","redirectHostname","OUTCOMES","STATES","settings","process","url","String","env","AGLYN_DOMAIN_WEBHOOK_URL","trim","token","AGLYN_DOMAIN_WEBHOOK_TOKEN","undefined","call","config","action","scope","domain","redirectTo","response","fetch","method","headers","Authorization","body","JSON","stringify","signal","json","catch","ok","console","error","status","detail","detailFrom","raw","trimmed","slice","recordList","value","Array","isArray","webhookDomainProvider","act","name","options","outcome","redirect","includes","id","configured","attach","detach","state","verification","conflicts"],"mappings":"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"}