@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,80 @@
1
+ // BFF response envelope.
2
+ //
3
+ // Every BFF response is JSON `{ ok: true, data, meta? }` or `{ ok: false, error: { code, message,
4
+ // details? }, meta? }`. A private response carries the request reference in the `x-request-id`
5
+ // header and, for errors or responses with meta, in `meta.requestId`; a shared-cache response
6
+ // (public or s-maxage) carries neither. `undefined` members are stripped before serialization.
7
+ export const BFF_REQUEST_REFERENCE_HEADER = "x-request-id";
8
+ export const BFF_ERROR_CODES = [
9
+ "BAD_REQUEST",
10
+ "UNAUTHORIZED",
11
+ "FORBIDDEN",
12
+ "NOT_FOUND",
13
+ "METHOD_NOT_ALLOWED",
14
+ "CONFLICT",
15
+ "RATE_LIMITED",
16
+ "UPSTREAM_UNAVAILABLE",
17
+ "INTERNAL",
18
+ "INVALID_RESPONSE",
19
+ ];
20
+ /** The HTTP status each error code answers with unless the caller names another. */
21
+ export const BFF_ERROR_STATUS = {
22
+ BAD_REQUEST: 400,
23
+ UNAUTHORIZED: 401,
24
+ FORBIDDEN: 403,
25
+ NOT_FOUND: 404,
26
+ METHOD_NOT_ALLOWED: 405,
27
+ CONFLICT: 409,
28
+ RATE_LIMITED: 429,
29
+ UPSTREAM_UNAVAILABLE: 503,
30
+ INTERNAL: 500,
31
+ INVALID_RESPONSE: 502,
32
+ };
33
+ /** Build the envelope writers a deployment's handlers share. */
34
+ export function createBffResponder(options = {}) {
35
+ function sendBffSuccess(res, data, meta, successOptions = {}) {
36
+ sendJson(res, 200, { ok: true, data, meta }, successOptions.cacheControl);
37
+ }
38
+ function sendBffError(res, code, message, errorOptions = {}) {
39
+ sendJson(res, errorOptions.status ?? BFF_ERROR_STATUS[code], {
40
+ ok: false,
41
+ error: { code, message, details: errorOptions.details },
42
+ meta: errorOptions.meta,
43
+ });
44
+ }
45
+ function sendMethodNotAllowed(res, allowedMethods) {
46
+ res.setHeader("Allow", allowedMethods.join(", "));
47
+ sendBffError(res, "METHOD_NOT_ALLOWED", "Method not allowed", { status: 405 });
48
+ }
49
+ function sendJson(res, status, body, cacheControl = "no-store") {
50
+ const shared = /(?:^|,)\s*(?:public\b|s-maxage\s*=)/i.test(cacheControl);
51
+ const requestId = shared ? undefined : options.responseRequestId?.(res);
52
+ const responseBody = withRequestReference(body, requestId, shared);
53
+ res.setHeader("Content-Type", "application/json");
54
+ res.setHeader("Cache-Control", cacheControl);
55
+ if (shared && typeof res.removeHeader === "function")
56
+ res.removeHeader(BFF_REQUEST_REFERENCE_HEADER);
57
+ else if (requestId)
58
+ res.setHeader(BFF_REQUEST_REFERENCE_HEADER, requestId);
59
+ res.status(status).json(stripUndefined(responseBody));
60
+ }
61
+ return { sendBffSuccess, sendBffError, sendMethodNotAllowed };
62
+ }
63
+ function withRequestReference(body, requestId, omitRequestId) {
64
+ if (requestId && (body.ok === false || body.meta)) {
65
+ return { ...body, meta: { ...body.meta, requestId } };
66
+ }
67
+ if (!omitRequestId || !body.meta || !("requestId" in body.meta))
68
+ return body;
69
+ const { requestId: _omitted, ...meta } = body.meta;
70
+ return { ...body, meta: Object.keys(meta).length > 0 ? meta : undefined };
71
+ }
72
+ function stripUndefined(value) {
73
+ if (Array.isArray(value))
74
+ return value.map(stripUndefined);
75
+ if (!value || typeof value !== "object")
76
+ return value;
77
+ return Object.fromEntries(Object.entries(value)
78
+ .filter(([, entryValue]) => entryValue !== undefined)
79
+ .map(([key, entryValue]) => [key, stripUndefined(entryValue)]));
80
+ }
@@ -0,0 +1,24 @@
1
+ import type { HttpRequest, HttpResponse } from "../http/index.js";
2
+ import type { BffResponder } from "./envelope.js";
3
+ export interface HealthContract<T extends {
4
+ contractVersion: string;
5
+ }> {
6
+ safeParse(value: unknown): {
7
+ success: true;
8
+ data: T;
9
+ } | {
10
+ success: false;
11
+ };
12
+ }
13
+ export interface HealthHandlerOptions<T extends {
14
+ contractVersion: string;
15
+ }> {
16
+ responder: BffResponder;
17
+ /** The deployment's strict health contract. */
18
+ contract: HealthContract<T>;
19
+ /** Builds this request's health payload: service identity, release provenance and the rest. */
20
+ read: () => unknown;
21
+ }
22
+ export declare function createHealthHandler<T extends {
23
+ contractVersion: string;
24
+ }>(options: HealthHandlerOptions<T>): (req: HttpRequest, res: HttpResponse) => void;
@@ -0,0 +1,19 @@
1
+ // BFF health route.
2
+ //
3
+ // GET answers the deployment's health payload, validated by the deployment's strict contract, with
4
+ // the payload's contract version in `meta`; any other method answers 405 with `Allow: GET`. A
5
+ // payload that fails its own contract answers INTERNAL rather than an unchecked body.
6
+ export function createHealthHandler(options) {
7
+ return function health(req, res) {
8
+ if (req.method !== "GET") {
9
+ options.responder.sendMethodNotAllowed(res, ["GET"]);
10
+ return;
11
+ }
12
+ const result = options.contract.safeParse(options.read());
13
+ if (!result.success) {
14
+ options.responder.sendBffError(res, "INTERNAL", "Health contract is invalid");
15
+ return;
16
+ }
17
+ options.responder.sendBffSuccess(res, result.data, { contractVersion: result.data.contractVersion });
18
+ };
19
+ }
@@ -0,0 +1,6 @@
1
+ export { createBffDispatcher, type BffDispatch, type BffDispatcherOptions, type RouteEntry } from "./dispatch.js";
2
+ export { lazyBffRoute } from "./lazyRoute.js";
3
+ export { rewriteBffRouterRequestUrl } from "./routerRewrite.js";
4
+ export { BFF_ERROR_CODES, BFF_ERROR_STATUS, BFF_REQUEST_REFERENCE_HEADER, createBffResponder, type BffError, type BffErrorCode, type BffErrorOptions, type BffErrorResponse, type BffMeta, type BffResponder, type BffResponderOptions, type BffResponse, type BffSuccessOptions, type BffSuccessResponse, } from "./envelope.js";
5
+ export { createHealthHandler, type HealthContract, type HealthHandlerOptions, } from "./health.js";
6
+ export { createRequestContextStore, type ObservedRequestContext, type RequestContextOptions, type RequestContextStore, } from "./requestContext.js";
@@ -0,0 +1,6 @@
1
+ export { createBffDispatcher } from "./dispatch.js";
2
+ export { lazyBffRoute } from "./lazyRoute.js";
3
+ export { rewriteBffRouterRequestUrl } from "./routerRewrite.js";
4
+ export { BFF_ERROR_CODES, BFF_ERROR_STATUS, BFF_REQUEST_REFERENCE_HEADER, createBffResponder, } from "./envelope.js";
5
+ export { createHealthHandler, } from "./health.js";
6
+ export { createRequestContextStore, } from "./requestContext.js";
@@ -0,0 +1,4 @@
1
+ import type { RouteEntry } from "./dispatch.js";
2
+ export declare function lazyBffRoute(load: () => Promise<{
3
+ default: RouteEntry["handler"];
4
+ }>): RouteEntry["handler"];
@@ -0,0 +1,13 @@
1
+ export function lazyBffRoute(load) {
2
+ let handler;
3
+ let loading;
4
+ return (req, res) => {
5
+ if (handler)
6
+ return handler(req, res);
7
+ loading ??= load().then((module) => (handler = module.default), (error) => {
8
+ loading = undefined;
9
+ throw error;
10
+ });
11
+ return loading.then((loaded) => loaded(req, res));
12
+ };
13
+ }
@@ -0,0 +1,17 @@
1
+ import type { HttpRequest, HttpResponse } from "../http/index.js";
2
+ export interface ObservedRequestContext {
3
+ readonly requestId: string;
4
+ }
5
+ export interface RequestContextOptions {
6
+ /** Chooses this request's reference. Called once per request. */
7
+ readRequestId: (req: HttpRequest) => string;
8
+ }
9
+ export interface RequestContextStore {
10
+ /** Create (or reuse) the request's context and attach it to the response. */
11
+ install(req: HttpRequest, res: HttpResponse): ObservedRequestContext;
12
+ /** The request's context, created on first use. */
13
+ readOrCreate(req: HttpRequest): ObservedRequestContext;
14
+ /** The context attached to a response by `install`, if any. */
15
+ readResponse(res: HttpResponse): ObservedRequestContext | undefined;
16
+ }
17
+ export declare function createRequestContextStore(options: RequestContextOptions): RequestContextStore;
@@ -0,0 +1,26 @@
1
+ // Observed request context.
2
+ //
3
+ // One context per request, created on first use and shared by everything that observes the same
4
+ // request or its response. The deployment decides how a request reference is chosen (for example,
5
+ // a host-assigned request ID or a generated one); the context only keeps the answer stable.
6
+ export function createRequestContextStore(options) {
7
+ const requestContexts = new WeakMap();
8
+ const responseContexts = new WeakMap();
9
+ function readOrCreate(req) {
10
+ const existing = requestContexts.get(req);
11
+ if (existing)
12
+ return existing;
13
+ const context = { requestId: options.readRequestId(req) };
14
+ requestContexts.set(req, context);
15
+ return context;
16
+ }
17
+ return {
18
+ install(req, res) {
19
+ const context = readOrCreate(req);
20
+ responseContexts.set(res, context);
21
+ return context;
22
+ },
23
+ readOrCreate,
24
+ readResponse: (res) => responseContexts.get(res),
25
+ };
26
+ }
@@ -0,0 +1,2 @@
1
+ import type { HttpRequest } from "../http/index.js";
2
+ export declare function rewriteBffRouterRequestUrl(req: HttpRequest): void;
@@ -0,0 +1,31 @@
1
+ // URL rewrite for a hosted BFF router entrypoint.
2
+ //
3
+ // A host rewrite can deliver every `/api/bff/*` request to one function, passing the original
4
+ // path in the internal `__bffPath` query value. This restores the request URL and query the
5
+ // dispatcher expects before it matches a route.
6
+ const INTERNAL_BFF_PATH_QUERY = "__bffPath";
7
+ function firstQueryValue(value) {
8
+ if (Array.isArray(value))
9
+ return value[0] ?? null;
10
+ return value ?? null;
11
+ }
12
+ // The rewrite keeps only the path and query, which do not depend on the URL base; this host only
13
+ // completes a request that carries no Host header.
14
+ const FALLBACK_HOST = "bff.invalid";
15
+ export function rewriteBffRouterRequestUrl(req) {
16
+ const routedPath = firstQueryValue(req.query?.[INTERNAL_BFF_PATH_QUERY]);
17
+ if (!routedPath)
18
+ return;
19
+ const host = typeof req.headers?.host === "string" ? req.headers.host : FALLBACK_HOST;
20
+ const currentUrl = new URL(typeof req.url === "string" ? req.url : "/", `https://${host}`);
21
+ currentUrl.searchParams.delete(INTERNAL_BFF_PATH_QUERY);
22
+ currentUrl.searchParams.delete("path");
23
+ const normalizedPath = routedPath
24
+ .split("/")
25
+ .filter(Boolean)
26
+ .map((segment) => encodeURIComponent(decodeURIComponent(segment)))
27
+ .join("/");
28
+ req.url = `/api/bff/${normalizedPath}${currentUrl.search}`;
29
+ const { [INTERNAL_BFF_PATH_QUERY]: _internal, path: _hostPath, ...query } = req.query ?? {};
30
+ req.query = query;
31
+ }
@@ -0,0 +1,16 @@
1
+ import type { BlobStoragePort } from "@openlup/core/platform-runtime";
2
+ import { type S3ObjectClient } from "./s3.js";
3
+ import { type SupabaseStorageClient } from "./supabase.js";
4
+ import { type VercelBlobSdk } from "./vercel.js";
5
+ type Env = Record<string, string | undefined>;
6
+ export interface BlobBindingDeps {
7
+ /** The `@vercel/blob` module; without it the vercel-blob capability stays unbound. */
8
+ vercelBlob?: VercelBlobSdk;
9
+ /** S3 client factory; defaults to `createS3ClientFromEnv`. */
10
+ s3ClientFactory?: (env: Env) => S3ObjectClient;
11
+ /** Ready Supabase Storage client factory; without it the supabase capability stays unbound. */
12
+ supabaseStorageClient?: () => SupabaseStorageClient;
13
+ }
14
+ /** Resolve the BlobStoragePort for a blob capability, or null when it cannot be bound. */
15
+ export declare function bindBlobPort(blobKind: string, env: Env, deps?: BlobBindingDeps): BlobStoragePort | null;
16
+ export {};
@@ -0,0 +1,37 @@
1
+ // Blob port binding for bundle composition.
2
+ //
3
+ // Maps the selected blob capability to a concrete BlobStoragePort built from env: filesystem
4
+ // (the local development default), S3-compatible storage (the default client unless one is
5
+ // injected), Vercel Blob (with the injected `@vercel/blob` module) and Supabase Storage (a
6
+ // ready storage client supplied by the injected factory).
7
+ // A capability whose settings or client are missing stays unbound (null); readiness reports it.
8
+ import { createFilesystemBlobStorage } from "./filesystem.js";
9
+ import { createS3BlobStorage } from "./s3.js";
10
+ import { createS3ClientFromEnv } from "./s3Client.js";
11
+ import { createSupabaseBlobStorage } from "./supabase.js";
12
+ import { createVercelBlobStorage } from "./vercel.js";
13
+ /** Resolve the BlobStoragePort for a blob capability, or null when it cannot be bound. */
14
+ export function bindBlobPort(blobKind, env, deps = {}) {
15
+ switch (blobKind) {
16
+ case "vercel-blob":
17
+ return deps.vercelBlob ? createVercelBlobStorage({ env, sdk: deps.vercelBlob }) : null;
18
+ case "filesystem":
19
+ return env.BLOB_FS_ROOT && env.BLOB_FS_BASE_URL
20
+ ? createFilesystemBlobStorage({ root: env.BLOB_FS_ROOT, baseUrl: env.BLOB_FS_BASE_URL })
21
+ : null;
22
+ case "s3":
23
+ return env.BLOB_S3_BUCKET && env.BLOB_S3_BASE_URL
24
+ ? createS3BlobStorage((deps.s3ClientFactory ?? createS3ClientFromEnv)(env), {
25
+ bucket: env.BLOB_S3_BUCKET,
26
+ baseUrl: env.BLOB_S3_BASE_URL,
27
+ })
28
+ : null;
29
+ case "supabase": {
30
+ if (!deps.supabaseStorageClient || !env.BLOB_SUPABASE_BUCKET)
31
+ return null;
32
+ return createSupabaseBlobStorage(deps.supabaseStorageClient(), env.BLOB_SUPABASE_BUCKET);
33
+ }
34
+ default:
35
+ return null;
36
+ }
37
+ }
@@ -0,0 +1,8 @@
1
+ import type { BlobStoragePort } from "@openlup/core/platform-runtime";
2
+ export interface FilesystemBlobOptions {
3
+ /** Absolute root directory blobs are written under. */
4
+ root: string;
5
+ /** Base URL prefix for urlFor()/upload() result URLs. No trailing slash required. */
6
+ baseUrl: string;
7
+ }
8
+ export declare function createFilesystemBlobStorage(options: FilesystemBlobOptions): BlobStoragePort;
@@ -0,0 +1,71 @@
1
+ // Filesystem adapter for the platform-runtime BlobStoragePort.
2
+ //
3
+ // Stores blobs as plain files under a root directory and serves them through a configurable
4
+ // public base URL (e.g. a static file server / nginx in a self-hosted node-postgres bundle).
5
+ // This is the credential-free adapter the per-adapter contract test runs against in CI.
6
+ import { promises as fs } from "node:fs";
7
+ import { dirname, join, relative, resolve, sep } from "node:path";
8
+ export function createFilesystemBlobStorage(options) {
9
+ const root = resolve(options.root);
10
+ const baseUrl = options.baseUrl.replace(/\/$/, "");
11
+ const pathFor = (key) => {
12
+ const target = resolve(root, normalizeKey(key));
13
+ if (target !== root && !target.startsWith(root + sep)) {
14
+ throw new Error(`filesystem_blob_key_escapes_root:${key}`);
15
+ }
16
+ return target;
17
+ };
18
+ return {
19
+ urlFor(key) {
20
+ return `${baseUrl}/${normalizeKey(key)}`;
21
+ },
22
+ async upload(key, data, _opts) {
23
+ const filePath = pathFor(key);
24
+ await fs.mkdir(dirname(filePath), { recursive: true });
25
+ await fs.writeFile(filePath, data);
26
+ return { url: this.urlFor(key), pathname: normalizeKey(key) };
27
+ },
28
+ async exists(key) {
29
+ try {
30
+ await fs.access(pathFor(key));
31
+ return true;
32
+ }
33
+ catch {
34
+ return false;
35
+ }
36
+ },
37
+ async delete(keyOrUrl) {
38
+ const key = stripBaseUrl(keyOrUrl, baseUrl);
39
+ await fs.rm(pathFor(key), { force: true });
40
+ },
41
+ async list(prefix) {
42
+ const dir = prefix ? pathFor(prefix) : root;
43
+ const keys = await walk(dir).catch((error) => {
44
+ if (error.code === "ENOENT")
45
+ return [];
46
+ throw error;
47
+ });
48
+ return {
49
+ keys: keys.map((absolute) => relative(root, absolute).split(sep).join("/")).sort(),
50
+ };
51
+ },
52
+ };
53
+ }
54
+ function normalizeKey(key) {
55
+ return key.replace(/^\/+/, "");
56
+ }
57
+ function stripBaseUrl(keyOrUrl, baseUrl) {
58
+ return keyOrUrl.startsWith(`${baseUrl}/`) ? keyOrUrl.slice(baseUrl.length + 1) : keyOrUrl;
59
+ }
60
+ async function walk(dir) {
61
+ const entries = await fs.readdir(dir, { withFileTypes: true });
62
+ const out = [];
63
+ for (const entry of entries) {
64
+ const full = join(dir, entry.name);
65
+ if (entry.isDirectory())
66
+ out.push(...(await walk(full)));
67
+ else
68
+ out.push(full);
69
+ }
70
+ return out;
71
+ }
@@ -0,0 +1,6 @@
1
+ export { createFilesystemBlobStorage, type FilesystemBlobOptions } from "./filesystem.js";
2
+ export { createS3BlobStorage, type S3BlobOptions, type S3ObjectClient } from "./s3.js";
3
+ export { createS3ClientFromEnv } from "./s3Client.js";
4
+ export { createSupabaseBlobStorage, type SupabaseStorageBucket, type SupabaseStorageClient, } from "./supabase.js";
5
+ export { createVercelBlobStorage, parseVercelStoreId, type VercelBlobAdapterOptions, type VercelBlobEntry, type VercelBlobSdk, type VercelBlobStorage, } from "./vercel.js";
6
+ export { bindBlobPort, type BlobBindingDeps } from "./binding.js";
@@ -0,0 +1,6 @@
1
+ export { createFilesystemBlobStorage } from "./filesystem.js";
2
+ export { createS3BlobStorage } from "./s3.js";
3
+ export { createS3ClientFromEnv } from "./s3Client.js";
4
+ export { createSupabaseBlobStorage, } from "./supabase.js";
5
+ export { createVercelBlobStorage, parseVercelStoreId, } from "./vercel.js";
6
+ export { bindBlobPort } from "./binding.js";
@@ -0,0 +1,32 @@
1
+ import type { BlobStoragePort } from "@openlup/core/platform-runtime";
2
+ export interface S3ObjectClient {
3
+ putObject(input: {
4
+ Bucket: string;
5
+ Key: string;
6
+ Body: Uint8Array | Buffer;
7
+ ContentType?: string;
8
+ }): Promise<unknown>;
9
+ headObject(input: {
10
+ Bucket: string;
11
+ Key: string;
12
+ }): Promise<unknown>;
13
+ deleteObject(input: {
14
+ Bucket: string;
15
+ Key: string;
16
+ }): Promise<unknown>;
17
+ listObjectsV2(input: {
18
+ Bucket: string;
19
+ Prefix?: string;
20
+ }): Promise<{
21
+ Contents?: Array<{
22
+ Key?: string;
23
+ }>;
24
+ }>;
25
+ }
26
+ export interface S3BlobOptions {
27
+ bucket: string;
28
+ /** Public base URL for objects, e.g. https://cdn.example.com or https://s3.region.amazonaws.com/bucket. */
29
+ baseUrl: string;
30
+ defaultContentType?: string;
31
+ }
32
+ export declare function createS3BlobStorage(client: S3ObjectClient, options: S3BlobOptions): BlobStoragePort;
@@ -0,0 +1,56 @@
1
+ // S3-compatible adapter for the platform-runtime BlobStoragePort.
2
+ //
3
+ // Targets any S3-compatible object store (AWS S3, MinIO, R2, Spaces) used by a self-hosted
4
+ // node-postgres bundle. To stay credential-free in CI and avoid pulling a new SDK dependency
5
+ // into this wave, it depends on a minimal STRUCTURAL client interface (PutObject/HeadObject/
6
+ // DeleteObject/ListObjectsV2). A thin factory over `@aws-sdk/client-s3` satisfies this shape;
7
+ // tests inject a fake. URLs are derived from a configurable public base (CDN or path-style).
8
+ export function createS3BlobStorage(client, options) {
9
+ const bucket = options.bucket;
10
+ const baseUrl = options.baseUrl.replace(/\/$/, "");
11
+ const defaultContentType = options.defaultContentType ?? "application/octet-stream";
12
+ return {
13
+ urlFor(key) {
14
+ return `${baseUrl}/${normalizeKey(key)}`;
15
+ },
16
+ async upload(key, data, opts) {
17
+ const objectKey = normalizeKey(key);
18
+ await client.putObject({
19
+ Bucket: bucket,
20
+ Key: objectKey,
21
+ Body: data,
22
+ ContentType: opts?.contentType ?? defaultContentType,
23
+ });
24
+ return { url: this.urlFor(objectKey), pathname: objectKey };
25
+ },
26
+ async exists(key) {
27
+ try {
28
+ await client.headObject({ Bucket: bucket, Key: normalizeKey(key) });
29
+ return true;
30
+ }
31
+ catch {
32
+ return false;
33
+ }
34
+ },
35
+ async delete(keyOrUrl) {
36
+ await client.deleteObject({ Bucket: bucket, Key: stripBaseUrl(keyOrUrl, baseUrl) });
37
+ },
38
+ async list(prefix) {
39
+ const result = await client.listObjectsV2({
40
+ Bucket: bucket,
41
+ Prefix: prefix ? normalizeKey(prefix) : undefined,
42
+ });
43
+ const keys = (result.Contents ?? [])
44
+ .map((entry) => entry.Key)
45
+ .filter((key) => typeof key === "string")
46
+ .sort();
47
+ return { keys };
48
+ },
49
+ };
50
+ }
51
+ function normalizeKey(key) {
52
+ return key.replace(/^\/+/, "");
53
+ }
54
+ function stripBaseUrl(keyOrUrl, baseUrl) {
55
+ return keyOrUrl.startsWith(`${baseUrl}/`) ? normalizeKey(keyOrUrl.slice(baseUrl.length + 1)) : normalizeKey(keyOrUrl);
56
+ }
@@ -0,0 +1,25 @@
1
+ import type { S3ObjectClient } from "./s3.js";
2
+ type Env = Record<string, string | undefined>;
3
+ export interface S3Sdk {
4
+ S3Client: new (config: {
5
+ region: string;
6
+ endpoint?: string;
7
+ forcePathStyle: boolean;
8
+ credentials?: {
9
+ accessKeyId: string;
10
+ secretAccessKey: string;
11
+ };
12
+ }) => {
13
+ send(command: unknown): ReturnType<S3ObjectClient["listObjectsV2"]>;
14
+ };
15
+ PutObjectCommand: new (input: Parameters<S3ObjectClient["putObject"]>[0]) => unknown;
16
+ HeadObjectCommand: new (input: Parameters<S3ObjectClient["headObject"]>[0]) => unknown;
17
+ DeleteObjectCommand: new (input: Parameters<S3ObjectClient["deleteObject"]>[0]) => unknown;
18
+ ListObjectsV2Command: new (input: Parameters<S3ObjectClient["listObjectsV2"]>[0]) => unknown;
19
+ }
20
+ /**
21
+ * Build an `S3ObjectClient` whose SDK client is created lazily from the given environment.
22
+ * `loadSdk` replaces the SDK loader (tests).
23
+ */
24
+ export declare function createS3ClientFromEnv(env: Env, loadSdk?: () => S3Sdk): S3ObjectClient;
25
+ export {};
@@ -0,0 +1,76 @@
1
+ // Default S3 client for the S3-compatible blob capability, built from the environment.
2
+ //
3
+ // `@aws-sdk/client-s3` is an optional peer: it is loaded on the first storage call, through Node's
4
+ // own module resolution, so an application that never selects S3 needs neither the SDK nor its
5
+ // settings. Settings:
6
+ // BLOB_S3_REGION (default us-east-1), BLOB_S3_ENDPOINT (an S3-compatible service; path-style
7
+ // addressing follows from BLOB_S3_FORCE_PATH_STYLE=true), and BLOB_S3_ACCESS_KEY_ID with
8
+ // BLOB_S3_SECRET_ACCESS_KEY (both, or neither to use the SDK's default credential chain).
9
+ import { createRequire } from "node:module";
10
+ function setting(env, name) {
11
+ const value = env[name]?.trim();
12
+ return value ? value : undefined;
13
+ }
14
+ function loadS3Sdk() {
15
+ return createRequire(import.meta.url)("@aws-sdk/client-s3");
16
+ }
17
+ /**
18
+ * Build an `S3ObjectClient` whose SDK client is created lazily from the given environment.
19
+ * `loadSdk` replaces the SDK loader (tests).
20
+ */
21
+ export function createS3ClientFromEnv(env, loadSdk = loadS3Sdk) {
22
+ const accessKeyId = setting(env, "BLOB_S3_ACCESS_KEY_ID");
23
+ const secretAccessKey = setting(env, "BLOB_S3_SECRET_ACCESS_KEY");
24
+ if ((accessKeyId === undefined) !== (secretAccessKey === undefined)) {
25
+ throw new Error("blob_s3_credentials_incomplete");
26
+ }
27
+ let loaded = null;
28
+ const connect = async () => {
29
+ if (loaded)
30
+ return loaded;
31
+ let sdk;
32
+ try {
33
+ sdk = loadSdk();
34
+ }
35
+ catch (cause) {
36
+ peerMissing(cause);
37
+ }
38
+ loaded = {
39
+ sdk,
40
+ client: new sdk.S3Client({
41
+ region: setting(env, "BLOB_S3_REGION") ?? "us-east-1",
42
+ endpoint: setting(env, "BLOB_S3_ENDPOINT"),
43
+ forcePathStyle: setting(env, "BLOB_S3_FORCE_PATH_STYLE") === "true",
44
+ credentials: accessKeyId && secretAccessKey ? { accessKeyId, secretAccessKey } : undefined,
45
+ }),
46
+ };
47
+ return loaded;
48
+ };
49
+ return {
50
+ async putObject(input) {
51
+ const { sdk, client } = await connect();
52
+ return client.send(new sdk.PutObjectCommand(input));
53
+ },
54
+ async headObject(input) {
55
+ const { sdk, client } = await connect();
56
+ return client.send(new sdk.HeadObjectCommand(input));
57
+ },
58
+ async deleteObject(input) {
59
+ const { sdk, client } = await connect();
60
+ return client.send(new sdk.DeleteObjectCommand(input));
61
+ },
62
+ async listObjectsV2(input) {
63
+ const { sdk, client } = await connect();
64
+ return client.send(new sdk.ListObjectsV2Command(input));
65
+ },
66
+ };
67
+ }
68
+ /** Translate only absence of this optional peer; preserve broken SDKs and other failures. */
69
+ function peerMissing(cause) {
70
+ if (cause instanceof Error && "code" in cause
71
+ && (cause.code === "ERR_MODULE_NOT_FOUND" || cause.code === "MODULE_NOT_FOUND")
72
+ && /^Cannot find (?:package|module) ['"]@aws-sdk\/client-s3['"]/.test(cause.message)) {
73
+ throw new Error("OPENLUP_E_PEER_MISSING: @aws-sdk/client-s3 is required by the S3 blob capability; install it alongside @openlup/platform", { cause });
74
+ }
75
+ throw cause;
76
+ }
@@ -0,0 +1,40 @@
1
+ import type { BlobStoragePort } from "@openlup/core/platform-runtime";
2
+ /** Minimal structural shape of the Supabase Storage bucket API we depend on. */
3
+ export interface SupabaseStorageBucket {
4
+ upload(path: string, bytes: Uint8Array | Buffer, options?: {
5
+ contentType?: string;
6
+ upsert?: boolean;
7
+ }): PromiseLike<{
8
+ data: unknown;
9
+ error: {
10
+ message?: string;
11
+ } | null;
12
+ }>;
13
+ remove(paths: string[]): PromiseLike<{
14
+ data: unknown;
15
+ error: {
16
+ message?: string;
17
+ } | null;
18
+ }>;
19
+ list(prefix?: string): PromiseLike<{
20
+ data: Array<{
21
+ name: string;
22
+ }> | null;
23
+ error: {
24
+ message?: string;
25
+ } | null;
26
+ }>;
27
+ getPublicUrl(path: string): {
28
+ data: {
29
+ publicUrl: string;
30
+ };
31
+ };
32
+ }
33
+ export interface SupabaseStorageClient {
34
+ storage: {
35
+ from(bucket: string): SupabaseStorageBucket;
36
+ };
37
+ }
38
+ export declare function createSupabaseBlobStorage(client: SupabaseStorageClient, bucket: string, options?: {
39
+ defaultContentType?: string;
40
+ }): BlobStoragePort;