@openlup/platform 0.0.0 → 0.16.0

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 (395) hide show
  1. package/CHANGELOG.md +59 -0
  2. package/LICENSE +204 -0
  3. package/README.md +471 -1
  4. package/dist/bff/dispatch.d.ts +27 -0
  5. package/dist/bff/dispatch.js +47 -0
  6. package/dist/bff/envelope.d.ts +46 -0
  7. package/dist/bff/envelope.js +80 -0
  8. package/dist/bff/health.d.ts +24 -0
  9. package/dist/bff/health.js +19 -0
  10. package/dist/bff/index.d.ts +6 -0
  11. package/dist/bff/index.js +6 -0
  12. package/dist/bff/lazyRoute.d.ts +4 -0
  13. package/dist/bff/lazyRoute.js +13 -0
  14. package/dist/bff/requestContext.d.ts +17 -0
  15. package/dist/bff/requestContext.js +26 -0
  16. package/dist/bff/routerRewrite.d.ts +2 -0
  17. package/dist/bff/routerRewrite.js +31 -0
  18. package/dist/blob/binding.d.ts +16 -0
  19. package/dist/blob/binding.js +37 -0
  20. package/dist/blob/filesystem.d.ts +8 -0
  21. package/dist/blob/filesystem.js +71 -0
  22. package/dist/blob/index.d.ts +6 -0
  23. package/dist/blob/index.js +6 -0
  24. package/dist/blob/s3.d.ts +32 -0
  25. package/dist/blob/s3.js +56 -0
  26. package/dist/blob/s3Client.d.ts +25 -0
  27. package/dist/blob/s3Client.js +76 -0
  28. package/dist/blob/supabase.d.ts +40 -0
  29. package/dist/blob/supabase.js +59 -0
  30. package/dist/blob/vercel.d.ts +63 -0
  31. package/dist/blob/vercel.js +69 -0
  32. package/dist/composition/composeBundle.d.ts +47 -0
  33. package/dist/composition/composeBundle.js +45 -0
  34. package/dist/composition/httpBinding.d.ts +8 -0
  35. package/dist/composition/httpBinding.js +18 -0
  36. package/dist/composition/index.d.ts +3 -0
  37. package/dist/composition/index.js +3 -0
  38. package/dist/composition/nodeHost.d.ts +103 -0
  39. package/dist/composition/nodeHost.js +145 -0
  40. package/dist/data/binding.d.ts +34 -0
  41. package/dist/data/binding.js +92 -0
  42. package/dist/data/index.d.ts +5 -0
  43. package/dist/data/index.js +5 -0
  44. package/dist/data/postgresGateway.d.ts +42 -0
  45. package/dist/data/postgresGateway.js +135 -0
  46. package/dist/data/queryBuilder.d.ts +54 -0
  47. package/dist/data/queryBuilder.js +240 -0
  48. package/dist/data/queryBuilderSql.d.ts +76 -0
  49. package/dist/data/queryBuilderSql.js +145 -0
  50. package/dist/data/supabaseClientFactory.d.ts +34 -0
  51. package/dist/data/supabaseClientFactory.js +56 -0
  52. package/dist/data/supabaseGateway.d.ts +22 -0
  53. package/dist/data/supabaseGateway.js +49 -0
  54. package/dist/http/deadline.d.ts +20 -0
  55. package/dist/http/deadline.js +93 -0
  56. package/dist/http/index.d.ts +6 -0
  57. package/dist/http/index.js +5 -0
  58. package/dist/http/requestAdapter.d.ts +38 -0
  59. package/dist/http/requestAdapter.js +203 -0
  60. package/dist/http/runtime.d.ts +83 -0
  61. package/dist/http/runtime.js +199 -0
  62. package/dist/http/security.d.ts +44 -0
  63. package/dist/http/security.js +114 -0
  64. package/dist/http/staticAssets.d.ts +3 -0
  65. package/dist/http/staticAssets.js +42 -0
  66. package/dist/http/types.d.ts +23 -0
  67. package/dist/http/types.js +6 -0
  68. package/dist/identity/adminAuthBinding.d.ts +28 -0
  69. package/dist/identity/adminAuthBinding.js +66 -0
  70. package/dist/identity/adminAuthorization.d.ts +34 -0
  71. package/dist/identity/adminAuthorization.js +72 -0
  72. package/dist/identity/adminContracts.d.ts +70 -0
  73. package/dist/identity/adminContracts.js +43 -0
  74. package/dist/identity/adminMagicLinkHandler.d.ts +39 -0
  75. package/dist/identity/adminMagicLinkHandler.js +94 -0
  76. package/dist/identity/adminMeHandler.d.ts +15 -0
  77. package/dist/identity/adminMeHandler.js +49 -0
  78. package/dist/identity/adminPreflight.d.ts +16 -0
  79. package/dist/identity/adminPreflight.js +38 -0
  80. package/dist/identity/adminUserActionPorts.d.ts +31 -0
  81. package/dist/identity/adminUserActionPorts.js +51 -0
  82. package/dist/identity/adminUserHandlers.d.ts +38 -0
  83. package/dist/identity/adminUserHandlers.js +140 -0
  84. package/dist/identity/authVerifyUrl.d.ts +6 -0
  85. package/dist/identity/authVerifyUrl.js +54 -0
  86. package/dist/identity/contracts/contracts.d.ts +8 -0
  87. package/dist/identity/contracts/contracts.js +2 -0
  88. package/dist/identity/contracts/emailTrust.d.ts +3 -0
  89. package/dist/identity/contracts/emailTrust.js +4 -0
  90. package/dist/identity/contracts/index.d.ts +8 -0
  91. package/dist/identity/contracts/index.js +8 -0
  92. package/dist/identity/contracts/oidc.d.ts +3 -0
  93. package/dist/identity/contracts/oidc.js +9 -0
  94. package/dist/identity/contracts/operatorAuth.d.ts +35 -0
  95. package/dist/identity/contracts/operatorAuth.js +53 -0
  96. package/dist/identity/contracts/operatorInvitations.d.ts +37 -0
  97. package/dist/identity/contracts/operatorInvitations.js +1 -0
  98. package/dist/identity/contracts/ports.d.ts +22 -0
  99. package/dist/identity/contracts/ports.js +1 -0
  100. package/dist/identity/contracts/privacy.d.ts +62 -0
  101. package/dist/identity/contracts/privacy.js +99 -0
  102. package/dist/identity/contracts/providerList.d.ts +3 -0
  103. package/dist/identity/contracts/providerList.js +11 -0
  104. package/dist/identity/contracts/types.d.ts +16 -0
  105. package/dist/identity/contracts/types.js +4 -0
  106. package/dist/identity/contribution.d.ts +8 -0
  107. package/dist/identity/contribution.js +22 -0
  108. package/dist/identity/customerAuth.d.ts +8 -0
  109. package/dist/identity/customerAuth.js +1 -0
  110. package/dist/identity/customerAuthorization.d.ts +5 -0
  111. package/dist/identity/customerAuthorization.js +22 -0
  112. package/dist/identity/customerContracts.d.ts +42 -0
  113. package/dist/identity/customerContracts.js +43 -0
  114. package/dist/identity/customerIdentityBinding.d.ts +44 -0
  115. package/dist/identity/customerIdentityBinding.js +60 -0
  116. package/dist/identity/customerIdentityVerifier.d.ts +8 -0
  117. package/dist/identity/customerIdentityVerifier.js +101 -0
  118. package/dist/identity/customerMagicLinkHandler.d.ts +38 -0
  119. package/dist/identity/customerMagicLinkHandler.js +115 -0
  120. package/dist/identity/customerMeHandler.d.ts +11 -0
  121. package/dist/identity/customerMeHandler.js +46 -0
  122. package/dist/identity/customerPorts.d.ts +4 -0
  123. package/dist/identity/customerPorts.js +1 -0
  124. package/dist/identity/customerSessionBinding.d.ts +38 -0
  125. package/dist/identity/customerSessionBinding.js +80 -0
  126. package/dist/identity/customerSessionIssuer.d.ts +10 -0
  127. package/dist/identity/customerSessionIssuer.js +57 -0
  128. package/dist/identity/customerVerifyOtpHandler.d.ts +8 -0
  129. package/dist/identity/customerVerifyOtpHandler.js +41 -0
  130. package/dist/identity/directOperatorInvitationStore.d.ts +19 -0
  131. package/dist/identity/directOperatorInvitationStore.js +193 -0
  132. package/dist/identity/emailTrustPolicy.d.ts +14 -0
  133. package/dist/identity/emailTrustPolicy.js +16 -0
  134. package/dist/identity/identityPersonalDataContributor.d.ts +7 -0
  135. package/dist/identity/identityPersonalDataContributor.js +46 -0
  136. package/dist/identity/identityReadiness.d.ts +10 -0
  137. package/dist/identity/identityReadiness.js +25 -0
  138. package/dist/identity/index.d.ts +67 -0
  139. package/dist/identity/index.js +66 -0
  140. package/dist/identity/inviteAdminUserUseCase.d.ts +89 -0
  141. package/dist/identity/inviteAdminUserUseCase.js +75 -0
  142. package/dist/identity/jwtLibrary.d.ts +54 -0
  143. package/dist/identity/jwtLibrary.js +1 -0
  144. package/dist/identity/machineActorWriteFence.d.ts +4 -0
  145. package/dist/identity/machineActorWriteFence.js +13 -0
  146. package/dist/identity/magicLinkPorts.d.ts +28 -0
  147. package/dist/identity/magicLinkPorts.js +1 -0
  148. package/dist/identity/managedAccountLinkStore.d.ts +2 -0
  149. package/dist/identity/managedAccountLinkStore.js +93 -0
  150. package/dist/identity/managedAdminAuthVerifier.d.ts +7 -0
  151. package/dist/identity/managedAdminAuthVerifier.js +27 -0
  152. package/dist/identity/managedAdminUserGateways.d.ts +123 -0
  153. package/dist/identity/managedAdminUserGateways.js +127 -0
  154. package/dist/identity/managedClient.d.ts +90 -0
  155. package/dist/identity/managedClient.js +10 -0
  156. package/dist/identity/managedClientAuthLink.d.ts +3 -0
  157. package/dist/identity/managedClientAuthLink.js +115 -0
  158. package/dist/identity/managedCustomerMe.d.ts +13 -0
  159. package/dist/identity/managedCustomerMe.js +30 -0
  160. package/dist/identity/managedEmailTrustPolicy.d.ts +11 -0
  161. package/dist/identity/managedEmailTrustPolicy.js +105 -0
  162. package/dist/identity/managedIdentityVerifier.d.ts +2 -0
  163. package/dist/identity/managedIdentityVerifier.js +16 -0
  164. package/dist/identity/managedPrivacy.d.ts +4 -0
  165. package/dist/identity/managedPrivacy.js +39 -0
  166. package/dist/identity/messages.d.ts +29 -0
  167. package/dist/identity/messages.js +39 -0
  168. package/dist/identity/oidcIdentityVerifier.d.ts +45 -0
  169. package/dist/identity/oidcIdentityVerifier.js +309 -0
  170. package/dist/identity/oidcPrincipal.d.ts +2 -0
  171. package/dist/identity/oidcPrincipal.js +10 -0
  172. package/dist/identity/operatorAdminAuth.d.ts +6 -0
  173. package/dist/identity/operatorAdminAuth.js +13 -0
  174. package/dist/identity/operatorAuthHandlers.d.ts +18 -0
  175. package/dist/identity/operatorAuthHandlers.js +71 -0
  176. package/dist/identity/operatorIdentityVerifier.d.ts +8 -0
  177. package/dist/identity/operatorIdentityVerifier.js +19 -0
  178. package/dist/identity/operatorInvitationLifecycle.d.ts +7 -0
  179. package/dist/identity/operatorInvitationLifecycle.js +15 -0
  180. package/dist/identity/operatorInvitationPorts.d.ts +31 -0
  181. package/dist/identity/operatorInvitationPorts.js +1 -0
  182. package/dist/identity/operatorPorts.d.ts +37 -0
  183. package/dist/identity/operatorPorts.js +1 -0
  184. package/dist/identity/operatorSession.d.ts +13 -0
  185. package/dist/identity/operatorSession.js +31 -0
  186. package/dist/identity/operatorTokenAdminAuth.d.ts +15 -0
  187. package/dist/identity/operatorTokenAdminAuth.js +50 -0
  188. package/dist/identity/personalDataRegistry.d.ts +4 -0
  189. package/dist/identity/personalDataRegistry.js +11 -0
  190. package/dist/identity/platformAdminAuth.d.ts +15 -0
  191. package/dist/identity/platformAdminAuth.js +1 -0
  192. package/dist/identity/ports.d.ts +127 -0
  193. package/dist/identity/ports.js +38 -0
  194. package/dist/identity/postgresAccountLinkStore.d.ts +7 -0
  195. package/dist/identity/postgresAccountLinkStore.js +52 -0
  196. package/dist/identity/postgresCustomerMe.d.ts +3 -0
  197. package/dist/identity/postgresCustomerMe.js +21 -0
  198. package/dist/identity/postgresCustomerRecovery.d.ts +11 -0
  199. package/dist/identity/postgresCustomerRecovery.js +57 -0
  200. package/dist/identity/postgresOperatorIdentity.d.ts +5 -0
  201. package/dist/identity/postgresOperatorIdentity.js +137 -0
  202. package/dist/identity/postgresPrivacy.d.ts +7 -0
  203. package/dist/identity/postgresPrivacy.js +126 -0
  204. package/dist/identity/privacyHandlers.d.ts +10 -0
  205. package/dist/identity/privacyHandlers.js +35 -0
  206. package/dist/identity/privacyPorts.d.ts +72 -0
  207. package/dist/identity/privacyPorts.js +8 -0
  208. package/dist/identity/privacyUseCases.d.ts +5 -0
  209. package/dist/identity/privacyUseCases.js +55 -0
  210. package/dist/identity/reconcileAccountForPrincipal.d.ts +20 -0
  211. package/dist/identity/reconcileAccountForPrincipal.js +66 -0
  212. package/dist/identity/reconcileAccountHandler.d.ts +11 -0
  213. package/dist/identity/reconcileAccountHandler.js +30 -0
  214. package/dist/identity/removeAdminUserUseCase.d.ts +31 -0
  215. package/dist/identity/removeAdminUserUseCase.js +60 -0
  216. package/dist/identity/response.d.ts +3 -0
  217. package/dist/identity/response.js +2 -0
  218. package/dist/identity/sql.d.ts +44 -0
  219. package/dist/identity/sql.js +10 -0
  220. package/dist/job-ledger/index.d.ts +5 -0
  221. package/dist/job-ledger/index.js +3 -0
  222. package/dist/job-ledger/ports.d.ts +10 -0
  223. package/dist/job-ledger/ports.js +7 -0
  224. package/dist/job-ledger/postgres.d.ts +10 -0
  225. package/dist/job-ledger/postgres.js +101 -0
  226. package/dist/job-ledger/sql.d.ts +7 -0
  227. package/dist/job-ledger/sql.js +10 -0
  228. package/dist/job-ledger/supabase.d.ts +77 -0
  229. package/dist/job-ledger/supabase.js +202 -0
  230. package/dist/kernel/index.d.ts +1 -0
  231. package/dist/kernel/index.js +1 -0
  232. package/dist/kernel/platformKernel.d.ts +34 -0
  233. package/dist/kernel/platformKernel.js +115 -0
  234. package/dist/migrations/index.d.ts +1 -0
  235. package/dist/migrations/index.js +1 -0
  236. package/dist/migrations/postgresRunner.d.ts +50 -0
  237. package/dist/migrations/postgresRunner.js +176 -0
  238. package/dist/scheduler/advisoryLock.d.ts +44 -0
  239. package/dist/scheduler/advisoryLock.js +114 -0
  240. package/dist/scheduler/cronGuard.d.ts +10 -0
  241. package/dist/scheduler/cronGuard.js +19 -0
  242. package/dist/scheduler/cronSecret.d.ts +5 -0
  243. package/dist/scheduler/cronSecret.js +23 -0
  244. package/dist/scheduler/handlers.d.ts +9 -0
  245. package/dist/scheduler/handlers.js +10 -0
  246. package/dist/scheduler/httpEntry.d.ts +16 -0
  247. package/dist/scheduler/httpEntry.js +31 -0
  248. package/dist/scheduler/index.d.ts +9 -0
  249. package/dist/scheduler/index.js +9 -0
  250. package/dist/scheduler/jobRegistry.d.ts +21 -0
  251. package/dist/scheduler/jobRegistry.js +39 -0
  252. package/dist/scheduler/jobs.d.ts +48 -0
  253. package/dist/scheduler/jobs.js +92 -0
  254. package/dist/scheduler/nodeScheduler.d.ts +42 -0
  255. package/dist/scheduler/nodeScheduler.js +73 -0
  256. package/dist/scheduler/vercelScheduler.d.ts +29 -0
  257. package/dist/scheduler/vercelScheduler.js +51 -0
  258. package/package.json +116 -3
  259. package/release-gates.json +143 -0
  260. package/sql/20260809150000_job_run_ledger.sql +301 -0
  261. package/sql/20260809150001_job_run_ledger_v3.sql +70 -0
  262. package/sql/20260810180001_self_host_actor_auth.sql +40 -0
  263. package/sql/20260810204057_customer_identity_read.sql +31 -0
  264. package/sql/20260814090000_customer_selfservice_recovery.sql +477 -0
  265. package/sql/20260901143000_admin_membership_authority_foundation.sql +234 -0
  266. package/sql/20261018120000_identity_operator_invitations.sql +42 -0
  267. package/sql/20261018180000_identity_operator_challenges.sql +56 -0
  268. package/sql/20261018190000_identity_privacy_erasures.sql +15 -0
  269. package/src/bff/dispatch.ts +81 -0
  270. package/src/bff/envelope.ts +142 -0
  271. package/src/bff/health.ts +37 -0
  272. package/src/bff/index.ts +30 -0
  273. package/src/bff/lazyRoute.ts +28 -0
  274. package/src/bff/requestContext.ts +48 -0
  275. package/src/bff/routerRewrite.ts +38 -0
  276. package/src/blob/binding.ts +55 -0
  277. package/src/blob/filesystem.ts +86 -0
  278. package/src/blob/index.ts +17 -0
  279. package/src/blob/s3.ts +87 -0
  280. package/src/blob/s3Client.ts +91 -0
  281. package/src/blob/supabase.ts +88 -0
  282. package/src/blob/vercel.ts +127 -0
  283. package/src/composition/composeBundle.ts +109 -0
  284. package/src/composition/httpBinding.ts +30 -0
  285. package/src/composition/index.ts +17 -0
  286. package/src/composition/nodeHost.ts +289 -0
  287. package/src/data/binding.ts +157 -0
  288. package/src/data/index.ts +33 -0
  289. package/src/data/postgresGateway.ts +186 -0
  290. package/src/data/queryBuilder.ts +300 -0
  291. package/src/data/queryBuilderSql.ts +183 -0
  292. package/src/data/supabaseClientFactory.ts +92 -0
  293. package/src/data/supabaseGateway.ts +76 -0
  294. package/src/http/deadline.ts +117 -0
  295. package/src/http/index.ts +36 -0
  296. package/src/http/requestAdapter.ts +215 -0
  297. package/src/http/runtime.ts +319 -0
  298. package/src/http/security.ts +155 -0
  299. package/src/http/staticAssets.ts +42 -0
  300. package/src/http/types.ts +31 -0
  301. package/src/identity/adminAuthBinding.ts +76 -0
  302. package/src/identity/adminAuthorization.ts +106 -0
  303. package/src/identity/adminContracts.ts +52 -0
  304. package/src/identity/adminMagicLinkHandler.ts +132 -0
  305. package/src/identity/adminMeHandler.ts +64 -0
  306. package/src/identity/adminPreflight.ts +46 -0
  307. package/src/identity/adminUserActionPorts.ts +78 -0
  308. package/src/identity/adminUserHandlers.ts +174 -0
  309. package/src/identity/authVerifyUrl.ts +56 -0
  310. package/src/identity/contracts/contracts.ts +15 -0
  311. package/src/identity/contracts/emailTrust.ts +5 -0
  312. package/src/identity/contracts/index.ts +8 -0
  313. package/src/identity/contracts/oidc.ts +10 -0
  314. package/src/identity/contracts/operatorAuth.ts +61 -0
  315. package/src/identity/contracts/operatorInvitations.ts +32 -0
  316. package/src/identity/contracts/ports.ts +25 -0
  317. package/src/identity/contracts/privacy.ts +86 -0
  318. package/src/identity/contracts/providerList.ts +13 -0
  319. package/src/identity/contracts/types.ts +25 -0
  320. package/src/identity/contribution.ts +29 -0
  321. package/src/identity/customerAuth.ts +3 -0
  322. package/src/identity/customerAuthorization.ts +38 -0
  323. package/src/identity/customerContracts.ts +52 -0
  324. package/src/identity/customerIdentityBinding.ts +125 -0
  325. package/src/identity/customerIdentityVerifier.ts +141 -0
  326. package/src/identity/customerMagicLinkHandler.ts +171 -0
  327. package/src/identity/customerMeHandler.ts +64 -0
  328. package/src/identity/customerPorts.ts +2 -0
  329. package/src/identity/customerSessionBinding.ts +114 -0
  330. package/src/identity/customerSessionIssuer.ts +71 -0
  331. package/src/identity/customerVerifyOtpHandler.ts +44 -0
  332. package/src/identity/directOperatorInvitationStore.ts +178 -0
  333. package/src/identity/emailTrustPolicy.ts +16 -0
  334. package/src/identity/identityPersonalDataContributor.ts +48 -0
  335. package/src/identity/identityReadiness.ts +26 -0
  336. package/src/identity/index.ts +69 -0
  337. package/src/identity/inviteAdminUserUseCase.ts +134 -0
  338. package/src/identity/jwtLibrary.ts +24 -0
  339. package/src/identity/machineActorWriteFence.ts +19 -0
  340. package/src/identity/magicLinkPorts.ts +7 -0
  341. package/src/identity/managedAccountLinkStore.ts +105 -0
  342. package/src/identity/managedAdminAuthVerifier.ts +40 -0
  343. package/src/identity/managedAdminUserGateways.ts +164 -0
  344. package/src/identity/managedClient.ts +58 -0
  345. package/src/identity/managedClientAuthLink.ts +120 -0
  346. package/src/identity/managedCustomerMe.ts +52 -0
  347. package/src/identity/managedEmailTrustPolicy.ts +90 -0
  348. package/src/identity/managedIdentityVerifier.ts +20 -0
  349. package/src/identity/managedPrivacy.ts +37 -0
  350. package/src/identity/messages.ts +41 -0
  351. package/src/identity/oidcIdentityVerifier.ts +229 -0
  352. package/src/identity/oidcPrincipal.ts +10 -0
  353. package/src/identity/operatorAdminAuth.ts +14 -0
  354. package/src/identity/operatorAuthHandlers.ts +52 -0
  355. package/src/identity/operatorIdentityVerifier.ts +19 -0
  356. package/src/identity/operatorInvitationLifecycle.ts +14 -0
  357. package/src/identity/operatorInvitationPorts.ts +11 -0
  358. package/src/identity/operatorPorts.ts +11 -0
  359. package/src/identity/operatorSession.ts +30 -0
  360. package/src/identity/operatorTokenAdminAuth.ts +79 -0
  361. package/src/identity/personalDataRegistry.ts +8 -0
  362. package/src/identity/platformAdminAuth.ts +7 -0
  363. package/src/identity/ports.ts +158 -0
  364. package/src/identity/postgresAccountLinkStore.ts +49 -0
  365. package/src/identity/postgresCustomerMe.ts +33 -0
  366. package/src/identity/postgresCustomerRecovery.ts +72 -0
  367. package/src/identity/postgresOperatorIdentity.ts +123 -0
  368. package/src/identity/postgresPrivacy.ts +117 -0
  369. package/src/identity/privacyHandlers.ts +33 -0
  370. package/src/identity/privacyPorts.ts +52 -0
  371. package/src/identity/privacyUseCases.ts +48 -0
  372. package/src/identity/reconcileAccountForPrincipal.ts +99 -0
  373. package/src/identity/reconcileAccountHandler.ts +36 -0
  374. package/src/identity/removeAdminUserUseCase.ts +85 -0
  375. package/src/identity/response.ts +3 -0
  376. package/src/identity/sql.ts +10 -0
  377. package/src/job-ledger/index.ts +25 -0
  378. package/src/job-ledger/ports.ts +27 -0
  379. package/src/job-ledger/postgres.ts +121 -0
  380. package/src/job-ledger/sql.ts +12 -0
  381. package/src/job-ledger/supabase.ts +294 -0
  382. package/src/kernel/index.ts +17 -0
  383. package/src/kernel/platformKernel.ts +153 -0
  384. package/src/migrations/index.ts +10 -0
  385. package/src/migrations/postgresRunner.ts +255 -0
  386. package/src/scheduler/advisoryLock.ts +145 -0
  387. package/src/scheduler/cronGuard.ts +30 -0
  388. package/src/scheduler/cronSecret.ts +26 -0
  389. package/src/scheduler/handlers.ts +24 -0
  390. package/src/scheduler/httpEntry.ts +49 -0
  391. package/src/scheduler/index.ts +47 -0
  392. package/src/scheduler/jobRegistry.ts +57 -0
  393. package/src/scheduler/jobs.ts +119 -0
  394. package/src/scheduler/nodeScheduler.ts +123 -0
  395. package/src/scheduler/vercelScheduler.ts +71 -0
@@ -0,0 +1,92 @@
1
+ // Supabase client factory for the DataGatewayPort.
2
+ //
3
+ // ONE canonical definition of the two supabase-js client shapes BFF handlers need:
4
+ // - SERVICE-ROLE client (elevated, RLS-bypassing) — `createClient(url, serviceRoleKey, {auth:{...}})`
5
+ // - ACTOR client (RLS-bound via a Bearer access token) — anon key + `Authorization: Bearer <jwt>`
6
+ // Both use one exact option shape
7
+ // (`auth: { persistSession: false, autoRefreshToken: false }`, plus the actor's global Authorization
8
+ // header). The supabase-js `createClient`
9
+ // is always supplied by the application's composition root, so this package carries no SDK import
10
+ // and the adapter stays unit-testable without network.
11
+
12
+ /** Structural shape of the supabase-js `createClient` we depend on (injection seam for tests). */
13
+ export type SupabaseCreateClient = (
14
+ url: string,
15
+ key: string,
16
+ options?: {
17
+ auth?: { persistSession?: boolean; autoRefreshToken?: boolean };
18
+ global?: { headers?: Record<string, string> };
19
+ },
20
+ ) => unknown;
21
+
22
+ export interface SupabaseDataGatewayEnv {
23
+ /** Project URL (SUPABASE_URL / VITE_SUPABASE_URL). */
24
+ url: string;
25
+ /** Anon/publishable key — used for actor (RLS-bound) clients. */
26
+ anonKey: string;
27
+ /** Service-role key — used for elevated (RLS-bypassing) clients. */
28
+ serviceRoleKey: string;
29
+ }
30
+
31
+ /** Read the gateway env using the same precedence the inline sites use today. */
32
+ export function readSupabaseDataGatewayEnv(
33
+ env: Record<string, string | undefined>,
34
+ ): SupabaseDataGatewayEnv | null {
35
+ const url = env.SUPABASE_URL ?? env.VITE_SUPABASE_URL;
36
+ const anonKey =
37
+ env.VITE_SUPABASE_PUBLISHABLE_KEY ??
38
+ env.VITE_SUPABASE_ANON_KEY ??
39
+ env.SUPABASE_ANON_KEY;
40
+ const serviceRoleKey = env.SUPABASE_SERVICE_ROLE_KEY;
41
+ // anonKey is only needed for actor clients; service-role work needs only url + serviceRoleKey.
42
+ // We require url + serviceRoleKey (the elevated path the default bundle uses); anonKey may be
43
+ // empty for service-only deployments and is validated lazily when an actor client is requested.
44
+ return url && serviceRoleKey ? { url, anonKey: anonKey ?? "", serviceRoleKey } : null;
45
+ }
46
+
47
+ /** Read env for actor-only gateway consumers. Does not require the elevated service-role key. */
48
+ export function readSupabaseActorDataGatewayEnv(
49
+ env: Record<string, string | undefined>,
50
+ ): SupabaseDataGatewayEnv | null {
51
+ const url = env.SUPABASE_URL ?? env.VITE_SUPABASE_URL;
52
+ const anonKey =
53
+ env.VITE_SUPABASE_PUBLISHABLE_KEY ??
54
+ env.VITE_SUPABASE_ANON_KEY ??
55
+ env.SUPABASE_ANON_KEY;
56
+ return url && anonKey ? { url, anonKey, serviceRoleKey: env.SUPABASE_SERVICE_ROLE_KEY ?? "" } : null;
57
+ }
58
+
59
+ /**
60
+ * Build an elevated (service-role) supabase client — byte-identical to the inline
61
+ * `createClient(env.url, env.serviceRoleKey, { auth: { persistSession: false, autoRefreshToken: false } })`
62
+ * shape the BFF handlers use today.
63
+ */
64
+ export function createServiceClient(
65
+ env: SupabaseDataGatewayEnv,
66
+ factory: SupabaseCreateClient,
67
+ ): unknown {
68
+ return factory(env.url, env.serviceRoleKey, {
69
+ auth: { persistSession: false, autoRefreshToken: false },
70
+ });
71
+ }
72
+
73
+ /**
74
+ * Build an actor (RLS-bound) supabase client — anon key + the actor's Bearer access token, matching
75
+ * the inline `createAdminAuthClient` / `createCustomerClient` shape. When no token is supplied the
76
+ * global Authorization header is omitted (identical to the inline `accessToken ? {...} : {}`).
77
+ */
78
+ export function createActorClient(
79
+ env: SupabaseDataGatewayEnv,
80
+ accessToken: string | null,
81
+ factory: SupabaseCreateClient,
82
+ ): unknown {
83
+ if (!env.anonKey) {
84
+ throw new Error("supabase_data_gateway_actor_requires_anon_key");
85
+ }
86
+ return factory(env.url, env.anonKey, {
87
+ auth: { persistSession: false, autoRefreshToken: false },
88
+ global: {
89
+ headers: accessToken ? { Authorization: `Bearer ${accessToken}` } : {},
90
+ },
91
+ });
92
+ }
@@ -0,0 +1,76 @@
1
+ // Supabase adapter for the platform-runtime DataGatewayPort.
2
+ //
3
+ // Centralizes the two persistence access modes the BFF handlers build inline today:
4
+ // - asService(work): elevated, RLS-bypassing — a service-role client (the default bundle's path).
5
+ // - asActor(claims, work): RLS-bound — an anon client carrying the actor's Bearer access token, so
6
+ // RLS evaluates the request as that principal (UUID-keep: claims.sub IS the supabase auth uuid).
7
+ // The work callback receives the concrete supabase client (typed `unknown` per the port contract;
8
+ // consumers cast to their narrow read/write surface). Behavior is byte-identical to the inline
9
+ // `createClient(...)` sites this replaces — same option shape, same Authorization header — so the
10
+ // default vercel-supabase bundle stays unchanged (golden-master byte-identical).
11
+ //
12
+ // POOLING SEMANTICS (documented, no behavior change):
13
+ // - Serverless (vercel-supabase): each invocation is short-lived; supabase-js talks to PostgREST
14
+ // over HTTP (PgBouncer in transaction-mode behind the scenes), so there is no long-lived pool to
15
+ // manage — a fresh client per asActor/asService call matches the current per-request creation and
16
+ // is correct for transaction-pooled connections (no session-level state assumed).
17
+ // - Long-running Node (node-supabase): the same supabase-js HTTP path applies; a direct-PG gateway
18
+ // (node-postgres) would instead hold a standard `pg.Pool` and run asActor work inside a
19
+ // `BEGIN; SET LOCAL request.jwt.claims = ...; ... COMMIT` tx-per-request for RLS. That is the
20
+ // MigrationRunner/postgres band's concern; this supabase adapter stays HTTP-pooled either way.
21
+ //
22
+ // asActor honors the access token supplied via `options.resolveAccessToken(claims)` (defaults to the
23
+ // claims' own bearer if present) so RLS is enforced; when no token resolves it falls back to an anon
24
+ // client (RLS still applies, just unauthenticated) rather than silently elevating — fail-safe.
25
+
26
+ import type { DataGatewayPort } from "@openlup/core/platform-runtime";
27
+ import {
28
+ createActorClient,
29
+ createServiceClient,
30
+ type SupabaseCreateClient,
31
+ type SupabaseDataGatewayEnv,
32
+ } from "./supabaseClientFactory.js";
33
+
34
+ type ActorClaims = { sub?: string; role?: string } | null;
35
+
36
+ export interface SupabaseDataGatewayOptions {
37
+ /** The supabase-js `createClient` (tests pass a stub). */
38
+ createClientImpl: SupabaseCreateClient;
39
+ /**
40
+ * Resolve an actor's Bearer access token from their claims. The port contract carries only
41
+ * `{ sub, role }`, but RLS needs the raw JWT; call sites that have the request token bind it here.
42
+ * Returns null → anon (RLS-bound but unauthenticated) client.
43
+ */
44
+ resolveAccessToken?: (claims: ActorClaims) => string | null;
45
+ }
46
+
47
+ /**
48
+ * Build a supabase-backed DataGatewayPort from resolved env. Pure: no client is created until a
49
+ * port method runs work (matches the per-request creation the inline sites do today).
50
+ */
51
+ export function createSupabaseDataGateway(
52
+ env: SupabaseDataGatewayEnv,
53
+ options: SupabaseDataGatewayOptions,
54
+ ): DataGatewayPort {
55
+ const resolveAccessToken = options.resolveAccessToken ?? defaultResolveAccessToken;
56
+
57
+ return {
58
+ async asService<T>(work: (gateway: unknown) => Promise<T>): Promise<T> {
59
+ const client = createServiceClient(env, options.createClientImpl);
60
+ return work(client);
61
+ },
62
+
63
+ async asActor<T>(claims: ActorClaims, work: (gateway: unknown) => Promise<T>): Promise<T> {
64
+ const accessToken = resolveAccessToken(claims);
65
+ const client = createActorClient(env, accessToken, options.createClientImpl);
66
+ return work(client);
67
+ },
68
+ };
69
+ }
70
+
71
+ // Default token resolver: the port's claims shape doesn't carry a raw JWT, so without an injected
72
+ // resolver an actor request maps to an anon (unauthenticated, still RLS-bound) client. Call sites
73
+ // that hold the request bearer inject `resolveAccessToken` to make RLS see the real principal.
74
+ function defaultResolveAccessToken(_claims: ActorClaims): string | null {
75
+ return null;
76
+ }
@@ -0,0 +1,117 @@
1
+ // Bounded request/response lifecycle for the Node HTTP adapter.
2
+ //
3
+ // This owns transport cancellation only. It gives handlers a cooperative
4
+ // AbortSignal, but cannot roll back downstream work that ignores it.
5
+
6
+ import type { HttpRequest, HttpResponse } from "./types.js";
7
+
8
+ export const NODE_HTTP_TIMEOUT_CODE = "NODE_HTTP_TIMEOUT";
9
+
10
+ type Destroyable = { destroy?: (error?: Error) => void };
11
+ type Evented = { once?: (event: string, listener: () => void) => unknown; removeListener?: (event: string, listener: () => void) => unknown };
12
+ type WritableResponse = HttpResponse & {
13
+ write?: (...args: unknown[]) => boolean;
14
+ writeHead?: (...args: unknown[]) => unknown;
15
+ end?: (...args: unknown[]) => unknown;
16
+ setHeader?: (...args: unknown[]) => unknown;
17
+ };
18
+
19
+ export interface HttpDeadlineOptions {
20
+ req: HttpRequest;
21
+ res: HttpResponse;
22
+ durationMs: number;
23
+ run: () => Promise<unknown> | unknown;
24
+ onLateError?: (error: unknown) => void;
25
+ }
26
+
27
+ /**
28
+ * Run one Node HTTP admission from the instant before lazy loading/adaptation.
29
+ *
30
+ * A timeout before headers commit gets one minimal JSON 504. After headers are
31
+ * committed we cannot change the status safely, so the response is destroyed.
32
+ * The invocation promise remains observed, preventing a late rejection from
33
+ * becoming an unhandled rejection or a second response.
34
+ */
35
+ export async function runWithHttpDeadline(options: HttpDeadlineOptions): Promise<void> {
36
+ const { req, res, durationMs, run, onLateError } = options;
37
+ if (!Number.isSafeInteger(durationMs) || durationMs <= 0 || durationMs > 2_147_483_647) {
38
+ throw new Error("invalid_node_http_deadline");
39
+ }
40
+
41
+ const controller = new AbortController();
42
+ // Node 24 exposes IncomingMessage.signal as a getter without a setter. Keep
43
+ // that host-native property untouched and use the portable adapter seam.
44
+ req.abortSignal = controller.signal;
45
+ const eventedReq = req as HttpRequest & Evented;
46
+ const eventedRes = res as HttpResponse & Evented;
47
+ const abortForClose = () => controller.abort();
48
+ eventedReq.once?.("aborted", abortForClose);
49
+ eventedRes.once?.("close", abortForClose);
50
+
51
+ let expired = false;
52
+ let finishTimeout: (() => void) | undefined;
53
+ const timeout = new Promise<void>((resolve) => { finishTimeout = resolve; });
54
+ const timer = setTimeout(() => {
55
+ expired = true;
56
+ controller.abort();
57
+ if (res.destroyed || res.writableEnded) {
58
+ destroyInput(req);
59
+ return finishTimeout?.();
60
+ }
61
+ if (!res.headersSent && !res.writableEnded) {
62
+ closeInputAfterResponse(req, res);
63
+ res.statusCode = 504;
64
+ res.setHeader("content-type", "application/json; charset=utf-8");
65
+ res.end(JSON.stringify({ ok: false, error: { code: NODE_HTTP_TIMEOUT_CODE, message: "Request timed out" } }));
66
+ sealLateWrites(res);
67
+ } else if (!res.writableEnded) {
68
+ sealLateWrites(res);
69
+ (res as HttpResponse & Destroyable).destroy?.();
70
+ destroyInput(req);
71
+ }
72
+ finishTimeout?.();
73
+ }, durationMs);
74
+
75
+ const invocation = Promise.resolve().then(run);
76
+ // Keep a late completion/rejection observed after timeout. It must never get a
77
+ // chance to write a second envelope, and its raw value is not client output.
78
+ void invocation.catch((error) => {
79
+ if (expired) onLateError?.(error);
80
+ });
81
+
82
+ try {
83
+ await Promise.race([invocation.then(() => undefined), timeout]);
84
+ } finally {
85
+ clearTimeout(timer);
86
+ eventedReq.removeListener?.("aborted", abortForClose);
87
+ eventedRes.removeListener?.("close", abortForClose);
88
+ }
89
+ }
90
+
91
+ /** Send a response before closing its request input, so the status is deliverable. */
92
+ export function closeInputAfterResponse(req: HttpRequest, res: HttpResponse): void {
93
+ const evented = res as HttpResponse & Evented;
94
+ let closed = false;
95
+ const destroy = () => {
96
+ if (closed) return;
97
+ closed = true;
98
+ evented.removeListener?.("finish", destroy);
99
+ evented.removeListener?.("close", destroy);
100
+ destroyInput(req);
101
+ };
102
+ evented.once?.("finish", destroy);
103
+ evented.once?.("close", destroy);
104
+ }
105
+
106
+ function destroyInput(req: HttpRequest): void {
107
+ (req as HttpRequest & Destroyable).destroy?.();
108
+ }
109
+
110
+ /** Refuse late handler writes after a deadline response has committed. */
111
+ function sealLateWrites(res: HttpResponse): void {
112
+ const mutable = res as WritableResponse;
113
+ mutable.write = () => false;
114
+ mutable.writeHead = () => mutable;
115
+ mutable.end = () => mutable;
116
+ mutable.setHeader = () => mutable;
117
+ }
@@ -0,0 +1,36 @@
1
+ export type { EntrypointLoader, HttpHandler, HttpRequest, HttpResponse } from "./types.js";
2
+ export {
3
+ DEFAULT_NODE_BUFFERED_BODY_LIMIT_BYTES,
4
+ NodeRequestAbortedError,
5
+ NodeRequestBodyTooLargeError,
6
+ adaptRequest,
7
+ decorateRequest,
8
+ decorateResponse,
9
+ isRawStreamEntrypoint,
10
+ isWebhookPath,
11
+ pathnameOf,
12
+ } from "./requestAdapter.js";
13
+ export {
14
+ NODE_HTTP_TIMEOUT_CODE,
15
+ closeInputAfterResponse,
16
+ runWithHttpDeadline,
17
+ type HttpDeadlineOptions,
18
+ } from "./deadline.js";
19
+ export {
20
+ applySecurityHeaders,
21
+ contentTypeFor,
22
+ looksLikeAsset,
23
+ serveSpaStatic,
24
+ type NodeSecurityHeaderPolicy,
25
+ type StaticAssetReader,
26
+ } from "./security.js";
27
+ export { createDistStaticReader } from "./staticAssets.js";
28
+ export {
29
+ createNodeHttpRuntime,
30
+ createRequestHandler,
31
+ type HttpEntrypointRegistration,
32
+ type HttpPathEntrypoint,
33
+ type NodeCsrFallback,
34
+ type NodeHttpRuntimeOptions,
35
+ type NodeLegacyRedirect,
36
+ } from "./runtime.js";
@@ -0,0 +1,215 @@
1
+ // Node request/response adaptation to the hosted-function request shape for the HttpRuntimePort.
2
+ //
3
+ // Owns the RAW-BODY INVARIANT: every buffered request gets the exact bytes re-exposed as an
4
+ // async-iterable (so raw-body signature verification works for ANY route, including ones outside a
5
+ // `/webhooks/` segment) while still setting a parsed JSON req.body; stream-first standalone
6
+ // entrypoints keep their live stream and NO req.body.
7
+
8
+ import type { IncomingMessage, ServerResponse } from "node:http";
9
+
10
+ import type { HttpRequest, HttpResponse } from "./types.js";
11
+
12
+ export const DEFAULT_NODE_BUFFERED_BODY_LIMIT_BYTES = 1_048_576;
13
+
14
+ export class NodeRequestBodyTooLargeError extends Error {
15
+ constructor() {
16
+ super("node_request_body_too_large");
17
+ }
18
+ }
19
+
20
+ export class NodeRequestAbortedError extends Error {
21
+ constructor() {
22
+ super("node_request_aborted");
23
+ }
24
+ }
25
+
26
+ /** A path is a webhook iff it contains a `/webhooks/` segment. */
27
+ export function isWebhookPath(pathname: string): boolean {
28
+ return pathname.includes("/webhooks/");
29
+ }
30
+
31
+ /**
32
+ * The PLATFORM's stream-first standalone entrypoints: they read the raw stream
33
+ * themselves, so the runtime must never pre-parse their body.
34
+ *
35
+ * A deployment's own stream-first endpoint is NOT listed here. It declares
36
+ * `rawStream: true` on its `HttpEntrypointRegistration` and the runtime passes that
37
+ * decision to `adaptRequest` directly — which is what stops this publishable list
38
+ * from having to name a path only that deployment owns.
39
+ */
40
+ export function isRawStreamEntrypoint(pathname: string): boolean {
41
+ return pathname === "/api/csp-report";
42
+ }
43
+
44
+ export function pathnameOf(req: IncomingMessage): string {
45
+ return new URL(req.url ?? "/", "http://node.local").pathname;
46
+ }
47
+
48
+ function parseQuery(req: IncomingMessage): Record<string, string | string[] | undefined> {
49
+ const url = new URL(req.url ?? "/", "http://node.local");
50
+ const query: Record<string, string | string[] | undefined> = {};
51
+ for (const key of url.searchParams.keys()) {
52
+ if (key in query) continue;
53
+ const all = url.searchParams.getAll(key);
54
+ query[key] = all.length > 1 ? all : all[0];
55
+ }
56
+ return query;
57
+ }
58
+
59
+ function parseCookies(req: IncomingMessage): Record<string, string> {
60
+ const header = req.headers.cookie;
61
+ if (!header) return {};
62
+ const out: Record<string, string> = {};
63
+ for (const part of header.split(";")) {
64
+ const idx = part.indexOf("=");
65
+ if (idx < 0) continue;
66
+ const key = part.slice(0, idx).trim();
67
+ const value = part.slice(idx + 1).trim();
68
+ if (key) out[key] = decodeURIComponent(value);
69
+ }
70
+ return out;
71
+ }
72
+
73
+ /** Buffer the request stream once into a single Buffer (GET/HEAD => empty). */
74
+ async function bufferBody(req: HttpRequest, maxBytes: number): Promise<Buffer> {
75
+ const method = (req.method ?? "GET").toUpperCase();
76
+ if (method === "GET" || method === "HEAD") return Buffer.alloc(0);
77
+ return new Promise((resolve, reject) => {
78
+ const chunks: Buffer[] = [];
79
+ let bytes = 0;
80
+ let settled = false;
81
+ const finish = (result: Buffer | Error) => {
82
+ if (settled) return;
83
+ settled = true;
84
+ req.removeListener("data", onData);
85
+ req.removeListener("end", onEnd);
86
+ req.removeListener("error", onError);
87
+ req.abortSignal?.removeEventListener("abort", onAbort);
88
+ if (result instanceof Error) reject(result);
89
+ else resolve(result);
90
+ };
91
+ const onData = (chunk: Buffer | string) => {
92
+ const bytesChunk = typeof chunk === "string" ? Buffer.from(chunk) : chunk;
93
+ bytes += bytesChunk.length;
94
+ if (bytes > maxBytes) {
95
+ finish(new NodeRequestBodyTooLargeError());
96
+ return;
97
+ }
98
+ chunks.push(bytesChunk);
99
+ };
100
+ const onEnd = () => finish(Buffer.concat(chunks));
101
+ const onError = (error: Error) => finish(error);
102
+ const onAbort = () => finish(new NodeRequestAbortedError());
103
+ if (req.abortSignal?.aborted) return onAbort();
104
+ req.on("data", onData);
105
+ req.once("end", onEnd);
106
+ req.once("error", onError);
107
+ req.abortSignal?.addEventListener("abort", onAbort, { once: true });
108
+ });
109
+ }
110
+
111
+ /** Re-expose a fixed Buffer as the request's async-iterable so readRawBody sees the EXACT bytes. */
112
+ function reexposeRawStream(req: IncomingMessage, raw: Buffer): void {
113
+ (req as unknown as { [Symbol.asyncIterator]: () => AsyncIterator<Buffer> })[Symbol.asyncIterator] =
114
+ async function* () {
115
+ yield raw;
116
+ };
117
+ }
118
+
119
+ /** Best-effort JSON parse of a buffered body for handlers that read `req.body`. */
120
+ function parseJsonBody(raw: Buffer, contentType: string): unknown {
121
+ if (raw.length === 0) return undefined;
122
+ const text = raw.toString("utf8").trim();
123
+ if (!text) return undefined;
124
+ if (contentType.includes("application/json") || text.startsWith("{") || text.startsWith("[")) {
125
+ try {
126
+ return JSON.parse(text);
127
+ } catch {
128
+ return text;
129
+ }
130
+ }
131
+ return text;
132
+ }
133
+
134
+ /**
135
+ * Decorate a Node response with Vercel's status/json/send/redirect sugar (productionized from the
136
+ * dev shim's decorateResponse — identical semantics).
137
+ */
138
+ export function decorateResponse(res: ServerResponse): HttpResponse {
139
+ const vres = res as HttpResponse;
140
+ vres.status = function status(code: number): HttpResponse {
141
+ res.statusCode = code;
142
+ return vres;
143
+ };
144
+ vres.json = function json(body: unknown): HttpResponse {
145
+ if (!res.hasHeader("content-type")) {
146
+ res.setHeader("content-type", "application/json; charset=utf-8");
147
+ }
148
+ res.end(JSON.stringify(body));
149
+ return vres;
150
+ };
151
+ vres.send = function send(body: string | Buffer | object): HttpResponse {
152
+ if (Buffer.isBuffer(body) || typeof body === "string") {
153
+ res.end(body);
154
+ return vres;
155
+ }
156
+ if (!res.hasHeader("content-type")) {
157
+ res.setHeader("content-type", "application/json; charset=utf-8");
158
+ }
159
+ res.end(JSON.stringify(body));
160
+ return vres;
161
+ };
162
+ vres.redirect = function redirect(statusOrUrl: number | string, maybeUrl?: string): HttpResponse {
163
+ const code = typeof statusOrUrl === "number" ? statusOrUrl : 302;
164
+ const location = typeof statusOrUrl === "number" ? maybeUrl ?? "/" : statusOrUrl;
165
+ res.statusCode = code;
166
+ res.setHeader("location", location);
167
+ res.end();
168
+ return vres;
169
+ };
170
+ return vres;
171
+ }
172
+
173
+ /**
174
+ * Adapt a Node IncomingMessage into the Vercel request shape, honoring the raw-body invariant:
175
+ * - raw-stream entrypoints (process/csp/feedback): stream left intact, NO req.body;
176
+ * - everything else: raw bytes buffered + re-exposed as async-iterable (so readRawBody sees the
177
+ * EXACT bytes for any signature-verifying route, webhook-segment or not) AND req.body = parsed JSON.
178
+ */
179
+ export async function adaptRequest(
180
+ req: IncomingMessage,
181
+ pathname: string,
182
+ /** Explicit override for a caller that knows the entrypoint's body contract (see above). */
183
+ rawStream: boolean = isRawStreamEntrypoint(pathname),
184
+ bufferedBodyLimitBytes: number = DEFAULT_NODE_BUFFERED_BODY_LIMIT_BYTES,
185
+ ): Promise<HttpRequest> {
186
+ const vreq = req as HttpRequest;
187
+ vreq.query = parseQuery(req);
188
+ vreq.cookies = parseCookies(req);
189
+
190
+ if (rawStream) {
191
+ // Leave the live stream untouched; the handler reads it itself (formidable / custom readBody).
192
+ return vreq;
193
+ }
194
+
195
+ const raw = await bufferBody(vreq, bufferedBodyLimitBytes);
196
+ const contentType = String(req.headers["content-type"] ?? "");
197
+
198
+ // Re-expose the EXACT bytes as an async-iterable for EVERY buffered request, not only
199
+ // `/webhooks/` paths. Some signature-verifying routes live outside that segment — e.g.
200
+ // `/api/bff/communications/integrations/events` calls readRawBody for HMAC verification.
201
+ // readRawBody is stream-first (isAsyncIterable), so any handler that needs the raw bytes
202
+ // works; handlers that read req.body still get parsed JSON. The re-exposed iterator is
203
+ // simply never consumed for routes that don't call readRawBody, so this is a safe superset
204
+ // of the old `/webhooks/`-only behavior (and not dependent on a fragile path convention).
205
+ reexposeRawStream(req, raw);
206
+ vreq.body = parseJsonBody(raw, contentType);
207
+ return vreq;
208
+ }
209
+
210
+ /** Narrow a raw IncomingMessage to HttpRequest at the server boundary (query/cookies set in adapt). */
211
+ export function decorateRequest(req: IncomingMessage): HttpRequest {
212
+ const vreq = req as HttpRequest;
213
+ if (!vreq.query) vreq.query = {};
214
+ return vreq;
215
+ }