@smthrs/gateway 0.34.0 → 1.0.0-rc.3

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 (605) hide show
  1. package/CHANGELOG.md +198 -0
  2. package/LICENSE +1 -1
  3. package/README.md +92 -0
  4. package/dist/cjs/Diagnosis.d.ts +275 -0
  5. package/dist/cjs/Diagnosis.d.ts.map +1 -0
  6. package/dist/cjs/Diagnosis.js +476 -0
  7. package/dist/cjs/Diagnosis.js.map +7 -0
  8. package/dist/cjs/EngineTrace.d.ts +76 -0
  9. package/dist/cjs/EngineTrace.d.ts.map +1 -0
  10. package/dist/cjs/EngineTrace.js +501 -0
  11. package/dist/cjs/EngineTrace.js.map +7 -0
  12. package/dist/cjs/GatewayError.d.ts +54 -0
  13. package/dist/cjs/GatewayError.d.ts.map +1 -0
  14. package/dist/cjs/GatewayError.js +51 -0
  15. package/dist/cjs/GatewayError.js.map +7 -0
  16. package/dist/cjs/GatewayProjection.d.ts +480 -0
  17. package/dist/cjs/GatewayProjection.d.ts.map +1 -0
  18. package/dist/cjs/GatewayProjection.js +462 -0
  19. package/dist/cjs/GatewayProjection.js.map +7 -0
  20. package/dist/cjs/GatewayRpcs.d.ts +1886 -0
  21. package/dist/cjs/GatewayRpcs.d.ts.map +1 -0
  22. package/dist/cjs/GatewayRpcs.js +121 -0
  23. package/dist/cjs/GatewayRpcs.js.map +7 -0
  24. package/dist/cjs/GatewaySchema.d.ts +3274 -0
  25. package/dist/cjs/GatewaySchema.d.ts.map +1 -0
  26. package/dist/cjs/GatewaySchema.js +141 -0
  27. package/dist/cjs/GatewaySchema.js.map +7 -0
  28. package/dist/cjs/GatewayServer.d.ts +427 -0
  29. package/dist/cjs/GatewayServer.d.ts.map +1 -0
  30. package/dist/cjs/GatewayServer.js +428 -0
  31. package/dist/cjs/GatewayServer.js.map +7 -0
  32. package/dist/cjs/HostBranchPresence.d.ts +12 -0
  33. package/dist/cjs/HostBranchPresence.d.ts.map +1 -0
  34. package/dist/cjs/HostBranchPresence.js +82 -0
  35. package/dist/cjs/HostBranchPresence.js.map +7 -0
  36. package/dist/cjs/Projections.d.ts +209 -0
  37. package/dist/cjs/Projections.d.ts.map +1 -0
  38. package/dist/cjs/Projections.js +906 -0
  39. package/dist/cjs/Projections.js.map +7 -0
  40. package/dist/cjs/RunDevTools.d.ts +181 -0
  41. package/dist/cjs/RunDevTools.d.ts.map +1 -0
  42. package/dist/cjs/RunDevTools.js +195 -0
  43. package/dist/cjs/RunDevTools.js.map +7 -0
  44. package/dist/cjs/RunHistory.d.ts +185 -0
  45. package/dist/cjs/RunHistory.d.ts.map +1 -0
  46. package/dist/cjs/RunHistory.js +68 -0
  47. package/dist/cjs/RunHistory.js.map +7 -0
  48. package/dist/cjs/RunTrace.d.ts +753 -0
  49. package/dist/cjs/RunTrace.d.ts.map +1 -0
  50. package/dist/cjs/RunTrace.js +1593 -0
  51. package/dist/cjs/RunTrace.js.map +7 -0
  52. package/dist/cjs/RuntimeBridge.d.ts +1386 -0
  53. package/dist/cjs/RuntimeBridge.d.ts.map +1 -0
  54. package/dist/cjs/RuntimeBridge.js +598 -0
  55. package/dist/cjs/RuntimeBridge.js.map +7 -0
  56. package/dist/cjs/bun/BunGateway.d.ts +20 -0
  57. package/dist/cjs/bun/BunGateway.d.ts.map +1 -0
  58. package/dist/cjs/bun/BunGateway.js +62 -0
  59. package/dist/cjs/bun/BunGateway.js.map +7 -0
  60. package/dist/cjs/index.d.ts +65 -0
  61. package/dist/cjs/index.d.ts.map +1 -0
  62. package/dist/cjs/index.js +59 -0
  63. package/dist/cjs/index.js.map +7 -0
  64. package/dist/cjs/internal/NativeGateway.d.ts +176 -0
  65. package/dist/cjs/internal/NativeGateway.d.ts.map +1 -0
  66. package/dist/cjs/internal/NativeGateway.js +187 -0
  67. package/dist/cjs/internal/NativeGateway.js.map +7 -0
  68. package/dist/cjs/internal/actionRenderings.d.ts +2 -0
  69. package/dist/cjs/internal/actionRenderings.d.ts.map +1 -0
  70. package/dist/cjs/internal/actionRenderings.js +224 -0
  71. package/dist/cjs/internal/actionRenderings.js.map +7 -0
  72. package/dist/cjs/internal/callEvents.d.ts +97 -0
  73. package/dist/cjs/internal/callEvents.d.ts.map +1 -0
  74. package/dist/cjs/internal/callEvents.js +169 -0
  75. package/dist/cjs/internal/callEvents.js.map +7 -0
  76. package/dist/cjs/internal/digestIndex.d.ts +31 -0
  77. package/dist/cjs/internal/digestIndex.d.ts.map +1 -0
  78. package/dist/cjs/internal/digestIndex.js +105 -0
  79. package/dist/cjs/internal/digestIndex.js.map +7 -0
  80. package/dist/cjs/internal/digestMemory.d.ts +19 -0
  81. package/dist/cjs/internal/digestMemory.d.ts.map +1 -0
  82. package/dist/cjs/internal/digestMemory.js +33 -0
  83. package/dist/cjs/internal/digestMemory.js.map +7 -0
  84. package/dist/cjs/internal/inspectLabels.d.ts +8 -0
  85. package/dist/cjs/internal/inspectLabels.d.ts.map +1 -0
  86. package/dist/cjs/internal/inspectLabels.js +29 -0
  87. package/dist/cjs/internal/inspectLabels.js.map +7 -0
  88. package/dist/cjs/internal/nativeResolution.d.ts +41 -0
  89. package/dist/cjs/internal/nativeResolution.d.ts.map +1 -0
  90. package/dist/cjs/internal/nativeResolution.js +62 -0
  91. package/dist/cjs/internal/nativeResolution.js.map +7 -0
  92. package/dist/cjs/internal/nodeEvents.d.ts +47 -0
  93. package/dist/cjs/internal/nodeEvents.d.ts.map +1 -0
  94. package/dist/cjs/internal/nodeEvents.js +60 -0
  95. package/dist/cjs/internal/nodeEvents.js.map +7 -0
  96. package/dist/cjs/node/NodeGateway.d.ts +16 -0
  97. package/dist/cjs/node/NodeGateway.d.ts.map +1 -0
  98. package/dist/cjs/node/NodeGateway.js +48 -0
  99. package/dist/cjs/node/NodeGateway.js.map +7 -0
  100. package/dist/cjs/package.json +1 -0
  101. package/dist/esm/Diagnosis.d.ts +275 -0
  102. package/dist/esm/Diagnosis.d.ts.map +1 -0
  103. package/dist/esm/Diagnosis.js +697 -0
  104. package/dist/esm/Diagnosis.js.map +1 -0
  105. package/dist/esm/EngineTrace.d.ts +76 -0
  106. package/dist/esm/EngineTrace.d.ts.map +1 -0
  107. package/dist/esm/EngineTrace.js +548 -0
  108. package/dist/esm/EngineTrace.js.map +1 -0
  109. package/dist/esm/GatewayError.d.ts +54 -0
  110. package/dist/esm/GatewayError.d.ts.map +1 -0
  111. package/dist/esm/GatewayError.js +60 -0
  112. package/dist/esm/GatewayError.js.map +1 -0
  113. package/dist/esm/GatewayProjection.d.ts +480 -0
  114. package/dist/esm/GatewayProjection.d.ts.map +1 -0
  115. package/dist/esm/GatewayProjection.js +792 -0
  116. package/dist/esm/GatewayProjection.js.map +1 -0
  117. package/dist/esm/GatewayRpcs.d.ts +1886 -0
  118. package/dist/esm/GatewayRpcs.d.ts.map +1 -0
  119. package/dist/esm/GatewayRpcs.js +145 -0
  120. package/dist/esm/GatewayRpcs.js.map +1 -0
  121. package/dist/esm/GatewaySchema.d.ts +3274 -0
  122. package/dist/esm/GatewaySchema.d.ts.map +1 -0
  123. package/dist/esm/GatewaySchema.js +244 -0
  124. package/dist/esm/GatewaySchema.js.map +1 -0
  125. package/dist/esm/GatewayServer.d.ts +427 -0
  126. package/dist/esm/GatewayServer.d.ts.map +1 -0
  127. package/dist/esm/GatewayServer.js +725 -0
  128. package/dist/esm/GatewayServer.js.map +1 -0
  129. package/dist/esm/HostBranchPresence.d.ts +12 -0
  130. package/dist/esm/HostBranchPresence.d.ts.map +1 -0
  131. package/dist/esm/HostBranchPresence.js +54 -0
  132. package/dist/esm/HostBranchPresence.js.map +1 -0
  133. package/dist/esm/Projections.d.ts +209 -0
  134. package/dist/esm/Projections.d.ts.map +1 -0
  135. package/dist/esm/Projections.js +1254 -0
  136. package/dist/esm/Projections.js.map +1 -0
  137. package/dist/esm/RunDevTools.d.ts +181 -0
  138. package/dist/esm/RunDevTools.d.ts.map +1 -0
  139. package/dist/esm/RunDevTools.js +281 -0
  140. package/dist/esm/RunDevTools.js.map +1 -0
  141. package/dist/esm/RunHistory.d.ts +185 -0
  142. package/dist/esm/RunHistory.d.ts.map +1 -0
  143. package/dist/esm/RunHistory.js +109 -0
  144. package/dist/esm/RunHistory.js.map +1 -0
  145. package/dist/esm/RunTrace.d.ts +753 -0
  146. package/dist/esm/RunTrace.d.ts.map +1 -0
  147. package/dist/esm/RunTrace.js +2105 -0
  148. package/dist/esm/RunTrace.js.map +1 -0
  149. package/dist/esm/RuntimeBridge.d.ts +1386 -0
  150. package/dist/esm/RuntimeBridge.d.ts.map +1 -0
  151. package/dist/esm/RuntimeBridge.js +659 -0
  152. package/dist/esm/RuntimeBridge.js.map +1 -0
  153. package/dist/esm/bun/BunGateway.d.ts +20 -0
  154. package/dist/esm/bun/BunGateway.d.ts.map +1 -0
  155. package/dist/esm/bun/BunGateway.js +32 -0
  156. package/dist/esm/bun/BunGateway.js.map +1 -0
  157. package/dist/esm/index.d.ts +65 -0
  158. package/dist/esm/index.d.ts.map +1 -0
  159. package/dist/esm/index.js +65 -0
  160. package/dist/esm/index.js.map +1 -0
  161. package/dist/esm/internal/NativeGateway.d.ts +176 -0
  162. package/dist/esm/internal/NativeGateway.d.ts.map +1 -0
  163. package/dist/esm/internal/NativeGateway.js +268 -0
  164. package/dist/esm/internal/NativeGateway.js.map +1 -0
  165. package/dist/esm/internal/actionRenderings.d.ts +2 -0
  166. package/dist/esm/internal/actionRenderings.d.ts.map +1 -0
  167. package/dist/esm/internal/actionRenderings.js +202 -0
  168. package/dist/esm/internal/actionRenderings.js.map +1 -0
  169. package/dist/esm/internal/callEvents.d.ts +97 -0
  170. package/dist/esm/internal/callEvents.d.ts.map +1 -0
  171. package/dist/esm/internal/callEvents.js +206 -0
  172. package/dist/esm/internal/callEvents.js.map +1 -0
  173. package/dist/esm/internal/digestIndex.d.ts +31 -0
  174. package/dist/esm/internal/digestIndex.d.ts.map +1 -0
  175. package/dist/esm/internal/digestIndex.js +87 -0
  176. package/dist/esm/internal/digestIndex.js.map +1 -0
  177. package/dist/esm/internal/digestMemory.d.ts +19 -0
  178. package/dist/esm/internal/digestMemory.d.ts.map +1 -0
  179. package/dist/esm/internal/digestMemory.js +23 -0
  180. package/dist/esm/internal/digestMemory.js.map +1 -0
  181. package/dist/esm/internal/inspectLabels.d.ts +8 -0
  182. package/dist/esm/internal/inspectLabels.d.ts.map +1 -0
  183. package/dist/esm/internal/inspectLabels.js +12 -0
  184. package/dist/esm/internal/inspectLabels.js.map +1 -0
  185. package/dist/esm/internal/nativeResolution.d.ts +41 -0
  186. package/dist/esm/internal/nativeResolution.d.ts.map +1 -0
  187. package/dist/esm/internal/nativeResolution.js +78 -0
  188. package/dist/esm/internal/nativeResolution.js.map +1 -0
  189. package/dist/esm/internal/nodeEvents.d.ts +47 -0
  190. package/dist/esm/internal/nodeEvents.d.ts.map +1 -0
  191. package/dist/esm/internal/nodeEvents.js +60 -0
  192. package/dist/esm/internal/nodeEvents.js.map +1 -0
  193. package/dist/esm/node/NodeGateway.d.ts +16 -0
  194. package/dist/esm/node/NodeGateway.d.ts.map +1 -0
  195. package/dist/esm/node/NodeGateway.js +13 -0
  196. package/dist/esm/node/NodeGateway.js.map +1 -0
  197. package/dist/vendor/adapters.json +2707 -0
  198. package/dist/vendor/bun/BunChildProcessSpawner.d.ts +7 -0
  199. package/dist/vendor/bun/BunChildProcessSpawner.js +14 -0
  200. package/dist/vendor/bun/BunClusterHttp.d.ts +83 -0
  201. package/dist/vendor/bun/BunClusterHttp.js +39 -0
  202. package/dist/vendor/bun/BunClusterSocket.d.ts +65 -0
  203. package/dist/vendor/bun/BunClusterSocket.js +22 -0
  204. package/dist/vendor/bun/BunCrypto.d.ts +10 -0
  205. package/dist/vendor/bun/BunCrypto.js +8 -0
  206. package/dist/vendor/bun/BunFileSystem.d.ts +10 -0
  207. package/dist/vendor/bun/BunFileSystem.js +9 -0
  208. package/dist/vendor/bun/BunHttpClient.d.ts +9 -0
  209. package/dist/vendor/bun/BunHttpClient.js +1 -0
  210. package/dist/vendor/bun/BunHttpPlatform.d.ts +10 -0
  211. package/dist/vendor/bun/BunHttpPlatform.js +11 -0
  212. package/dist/vendor/bun/BunHttpServer.d.ts +142 -0
  213. package/dist/vendor/bun/BunHttpServer.js +38 -0
  214. package/dist/vendor/bun/BunHttpServerRequest.d.ts +18 -0
  215. package/dist/vendor/bun/BunHttpServerRequest.js +7 -0
  216. package/dist/vendor/bun/BunMultipart.d.ts +31 -0
  217. package/dist/vendor/bun/BunMultipart.js +12 -0
  218. package/dist/vendor/bun/BunPath.d.ts +24 -0
  219. package/dist/vendor/bun/BunPath.js +12 -0
  220. package/dist/vendor/bun/BunRedis.d.ts +49 -0
  221. package/dist/vendor/bun/BunRedis.js +11 -0
  222. package/dist/vendor/bun/BunRuntime.d.ts +85 -0
  223. package/dist/vendor/bun/BunRuntime.js +8 -0
  224. package/dist/vendor/bun/BunServices.d.ts +34 -0
  225. package/dist/vendor/bun/BunServices.js +23 -0
  226. package/dist/vendor/bun/BunSink.d.ts +8 -0
  227. package/dist/vendor/bun/BunSink.js +12 -0
  228. package/dist/vendor/bun/BunSocket.d.ts +40 -0
  229. package/dist/vendor/bun/BunSocket.js +29 -0
  230. package/dist/vendor/bun/BunSocketServer.d.ts +8 -0
  231. package/dist/vendor/bun/BunSocketServer.js +21 -0
  232. package/dist/vendor/bun/BunStdio.d.ts +11 -0
  233. package/dist/vendor/bun/BunStdio.js +10 -0
  234. package/dist/vendor/bun/BunStream.d.ts +19 -0
  235. package/dist/vendor/bun/BunStream.js +30 -0
  236. package/dist/vendor/bun/BunTerminal.d.ts +21 -0
  237. package/dist/vendor/bun/BunTerminal.js +10 -0
  238. package/dist/vendor/bun/BunWorker.d.ts +20 -0
  239. package/dist/vendor/bun/BunWorker.js +9 -0
  240. package/dist/vendor/bun/BunWorkerRunner.d.ts +12 -0
  241. package/dist/vendor/bun/BunWorkerRunner.js +7 -0
  242. package/dist/vendor/bun/LICENSE +21 -0
  243. package/dist/vendor/bun/index.d.ts +92 -0
  244. package/dist/vendor/bun/index.js +107 -0
  245. package/dist/vendor/chunk-2GCOZTTW.js +48 -0
  246. package/dist/vendor/chunk-37CUR3PO.js +81 -0
  247. package/dist/vendor/chunk-3ARKKENZ.js +45 -0
  248. package/dist/vendor/chunk-3NV2UDBI.js +7 -0
  249. package/dist/vendor/chunk-3OSW7CT7.js +143 -0
  250. package/dist/vendor/chunk-62R63L74.js +62 -0
  251. package/dist/vendor/chunk-6AQLBC2I.js +118 -0
  252. package/dist/vendor/chunk-6C5H7S6B.js +18 -0
  253. package/dist/vendor/chunk-6DY5RBZD.js +85 -0
  254. package/dist/vendor/chunk-6UPBD47P.js +53 -0
  255. package/dist/vendor/chunk-AVVVN75Z.js +96 -0
  256. package/dist/vendor/chunk-B4DHEMBB.js +14 -0
  257. package/dist/vendor/chunk-BCK3NJOX.js +15 -0
  258. package/dist/vendor/chunk-BFBIHJ6X.js +0 -0
  259. package/dist/vendor/chunk-CES4OUM4.js +63 -0
  260. package/dist/vendor/chunk-D64L6YND.js +59 -0
  261. package/dist/vendor/chunk-DS2R6WOM.js +18 -0
  262. package/dist/vendor/chunk-DX4Z23DM.js +65 -0
  263. package/dist/vendor/chunk-DZ2VOUDP.js +564 -0
  264. package/dist/vendor/chunk-FOXN6JPJ.js +61 -0
  265. package/dist/vendor/chunk-FPQF7CR5.js +55 -0
  266. package/dist/vendor/chunk-FUTCTCEG.js +20 -0
  267. package/dist/vendor/chunk-GUPAV2YZ.js +18 -0
  268. package/dist/vendor/chunk-H2Z2Y4QH.js +70 -0
  269. package/dist/vendor/chunk-HAEDVSSM.js +129 -0
  270. package/dist/vendor/chunk-HY7BBL3K.js +93 -0
  271. package/dist/vendor/chunk-IFTTT6D5.js +31 -0
  272. package/dist/vendor/chunk-KQDSNB7M.js +26 -0
  273. package/dist/vendor/chunk-LIHLXXGQ.js +26 -0
  274. package/dist/vendor/chunk-LKCGQEE5.js +28 -0
  275. package/dist/vendor/chunk-MH2J3BYB.js +0 -0
  276. package/dist/vendor/chunk-ML2NAFH2.js +131 -0
  277. package/dist/vendor/chunk-MLFIZ5O2.js +0 -0
  278. package/dist/vendor/chunk-MLKGABMK.js +9 -0
  279. package/dist/vendor/chunk-MSCLEMYX.js +71 -0
  280. package/dist/vendor/chunk-OBWXQKVP.js +57 -0
  281. package/dist/vendor/chunk-QR557K66.js +20 -0
  282. package/dist/vendor/chunk-R4QUY7DH.js +18 -0
  283. package/dist/vendor/chunk-RGECXXJS.js +18 -0
  284. package/dist/vendor/chunk-TFY7W6D4.js +36 -0
  285. package/dist/vendor/chunk-TGPIVY4A.js +104 -0
  286. package/dist/vendor/chunk-TGXCX6W3.js +398 -0
  287. package/dist/vendor/chunk-TOHSFXMV.js +437 -0
  288. package/dist/vendor/chunk-UHIDPUFF.js +34 -0
  289. package/dist/vendor/chunk-ULT6CWUI.js +109 -0
  290. package/dist/vendor/chunk-UXBGYQR3.js +18 -0
  291. package/dist/vendor/chunk-V6LUGQ7O.js +21 -0
  292. package/dist/vendor/chunk-W4OASVX6.js +92 -0
  293. package/dist/vendor/chunk-WAII54GA.js +18 -0
  294. package/dist/vendor/chunk-WECQDT36.js +426 -0
  295. package/dist/vendor/chunk-WHKMOC7Z.js +28 -0
  296. package/dist/vendor/chunk-WVFOZ2UL.js +40 -0
  297. package/dist/vendor/chunk-X73E2UPI.js +18 -0
  298. package/dist/vendor/chunk-XCYARUII.js +63 -0
  299. package/dist/vendor/chunk-XFEVTDVD.js +34 -0
  300. package/dist/vendor/chunk-XFZZYSJ4.js +22 -0
  301. package/dist/vendor/chunk-XJWJXLXJ.js +18 -0
  302. package/dist/vendor/chunk-XY3Q62EO.js +18 -0
  303. package/dist/vendor/chunk-YDASSC75.js +394 -0
  304. package/dist/vendor/chunk-Z6C7SO5Z.js +507 -0
  305. package/dist/vendor/chunk-Z7QNPJEB.js +229 -0
  306. package/dist/vendor/chunk-Z7QPP4YH.js +249 -0
  307. package/dist/vendor/chunk-ZCHYLHJH.js +86 -0
  308. package/dist/vendor/chunk-ZIQVYVMA.js +22 -0
  309. package/dist/vendor/chunk-ZPIG2CUA.js +34 -0
  310. package/dist/vendor/cjs/bun/BunChildProcessSpawner.d.ts +7 -0
  311. package/dist/vendor/cjs/bun/BunChildProcessSpawner.js +1 -0
  312. package/dist/vendor/cjs/bun/BunClusterHttp.d.ts +83 -0
  313. package/dist/vendor/cjs/bun/BunClusterHttp.js +1 -0
  314. package/dist/vendor/cjs/bun/BunClusterSocket.d.ts +65 -0
  315. package/dist/vendor/cjs/bun/BunClusterSocket.js +1 -0
  316. package/dist/vendor/cjs/bun/BunCrypto.d.ts +10 -0
  317. package/dist/vendor/cjs/bun/BunCrypto.js +1 -0
  318. package/dist/vendor/cjs/bun/BunFileSystem.d.ts +10 -0
  319. package/dist/vendor/cjs/bun/BunFileSystem.js +1 -0
  320. package/dist/vendor/cjs/bun/BunHttpClient.d.ts +9 -0
  321. package/dist/vendor/cjs/bun/BunHttpClient.js +1 -0
  322. package/dist/vendor/cjs/bun/BunHttpPlatform.d.ts +10 -0
  323. package/dist/vendor/cjs/bun/BunHttpPlatform.js +1 -0
  324. package/dist/vendor/cjs/bun/BunHttpServer.d.ts +142 -0
  325. package/dist/vendor/cjs/bun/BunHttpServer.js +1 -0
  326. package/dist/vendor/cjs/bun/BunHttpServerRequest.d.ts +18 -0
  327. package/dist/vendor/cjs/bun/BunHttpServerRequest.js +1 -0
  328. package/dist/vendor/cjs/bun/BunMultipart.d.ts +31 -0
  329. package/dist/vendor/cjs/bun/BunMultipart.js +1 -0
  330. package/dist/vendor/cjs/bun/BunPath.d.ts +24 -0
  331. package/dist/vendor/cjs/bun/BunPath.js +1 -0
  332. package/dist/vendor/cjs/bun/BunRedis.d.ts +49 -0
  333. package/dist/vendor/cjs/bun/BunRedis.js +1 -0
  334. package/dist/vendor/cjs/bun/BunRuntime.d.ts +85 -0
  335. package/dist/vendor/cjs/bun/BunRuntime.js +1 -0
  336. package/dist/vendor/cjs/bun/BunServices.d.ts +34 -0
  337. package/dist/vendor/cjs/bun/BunServices.js +1 -0
  338. package/dist/vendor/cjs/bun/BunSink.d.ts +8 -0
  339. package/dist/vendor/cjs/bun/BunSink.js +1 -0
  340. package/dist/vendor/cjs/bun/BunSocket.d.ts +40 -0
  341. package/dist/vendor/cjs/bun/BunSocket.js +1 -0
  342. package/dist/vendor/cjs/bun/BunSocketServer.d.ts +8 -0
  343. package/dist/vendor/cjs/bun/BunSocketServer.js +1 -0
  344. package/dist/vendor/cjs/bun/BunStdio.d.ts +11 -0
  345. package/dist/vendor/cjs/bun/BunStdio.js +1 -0
  346. package/dist/vendor/cjs/bun/BunStream.d.ts +19 -0
  347. package/dist/vendor/cjs/bun/BunStream.js +1 -0
  348. package/dist/vendor/cjs/bun/BunTerminal.d.ts +21 -0
  349. package/dist/vendor/cjs/bun/BunTerminal.js +1 -0
  350. package/dist/vendor/cjs/bun/BunWorker.d.ts +20 -0
  351. package/dist/vendor/cjs/bun/BunWorker.js +1 -0
  352. package/dist/vendor/cjs/bun/BunWorkerRunner.d.ts +12 -0
  353. package/dist/vendor/cjs/bun/BunWorkerRunner.js +1 -0
  354. package/dist/vendor/cjs/bun/index.d.ts +92 -0
  355. package/dist/vendor/cjs/bun/index.js +1 -0
  356. package/dist/vendor/cjs/node/NodeChildProcessSpawner.d.ts +7 -0
  357. package/dist/vendor/cjs/node/NodeChildProcessSpawner.js +1 -0
  358. package/dist/vendor/cjs/node/NodeClusterHttp.d.ts +63 -0
  359. package/dist/vendor/cjs/node/NodeClusterHttp.js +1 -0
  360. package/dist/vendor/cjs/node/NodeClusterSocket.d.ts +79 -0
  361. package/dist/vendor/cjs/node/NodeClusterSocket.js +1 -0
  362. package/dist/vendor/cjs/node/NodeCrypto.d.ts +10 -0
  363. package/dist/vendor/cjs/node/NodeCrypto.js +1 -0
  364. package/dist/vendor/cjs/node/NodeFileSystem.d.ts +10 -0
  365. package/dist/vendor/cjs/node/NodeFileSystem.js +1 -0
  366. package/dist/vendor/cjs/node/NodeHttpClient.d.ts +179 -0
  367. package/dist/vendor/cjs/node/NodeHttpClient.js +1 -0
  368. package/dist/vendor/cjs/node/NodeHttpIncomingMessage.d.ts +62 -0
  369. package/dist/vendor/cjs/node/NodeHttpIncomingMessage.js +1 -0
  370. package/dist/vendor/cjs/node/NodeHttpPlatform.d.ts +36 -0
  371. package/dist/vendor/cjs/node/NodeHttpPlatform.js +1 -0
  372. package/dist/vendor/cjs/node/NodeHttpServer.d.ts +120 -0
  373. package/dist/vendor/cjs/node/NodeHttpServer.js +1 -0
  374. package/dist/vendor/cjs/node/NodeHttpServerRequest.d.ts +29 -0
  375. package/dist/vendor/cjs/node/NodeHttpServerRequest.js +1 -0
  376. package/dist/vendor/cjs/node/NodeMultipart.d.ts +46 -0
  377. package/dist/vendor/cjs/node/NodeMultipart.js +1 -0
  378. package/dist/vendor/cjs/node/NodeMultipartParser.d.ts +97 -0
  379. package/dist/vendor/cjs/node/NodeMultipartParser.js +1 -0
  380. package/dist/vendor/cjs/node/NodePath.d.ts +27 -0
  381. package/dist/vendor/cjs/node/NodePath.js +1 -0
  382. package/dist/vendor/cjs/node/NodeRedis.d.ts +74 -0
  383. package/dist/vendor/cjs/node/NodeRedis.js +1 -0
  384. package/dist/vendor/cjs/node/NodeRuntime.d.ts +85 -0
  385. package/dist/vendor/cjs/node/NodeRuntime.js +1 -0
  386. package/dist/vendor/cjs/node/NodeServices.d.ts +34 -0
  387. package/dist/vendor/cjs/node/NodeServices.js +1 -0
  388. package/dist/vendor/cjs/node/NodeSink.d.ts +8 -0
  389. package/dist/vendor/cjs/node/NodeSink.js +1 -0
  390. package/dist/vendor/cjs/node/NodeSocket.d.ts +38 -0
  391. package/dist/vendor/cjs/node/NodeSocket.js +1 -0
  392. package/dist/vendor/cjs/node/NodeSocketServer.d.ts +8 -0
  393. package/dist/vendor/cjs/node/NodeSocketServer.js +1 -0
  394. package/dist/vendor/cjs/node/NodeStdio.d.ts +11 -0
  395. package/dist/vendor/cjs/node/NodeStdio.js +1 -0
  396. package/dist/vendor/cjs/node/NodeStream.d.ts +8 -0
  397. package/dist/vendor/cjs/node/NodeStream.js +1 -0
  398. package/dist/vendor/cjs/node/NodeTerminal.d.ts +21 -0
  399. package/dist/vendor/cjs/node/NodeTerminal.js +1 -0
  400. package/dist/vendor/cjs/node/NodeWorker.d.ts +22 -0
  401. package/dist/vendor/cjs/node/NodeWorker.js +1 -0
  402. package/dist/vendor/cjs/node/NodeWorkerRunner.d.ts +12 -0
  403. package/dist/vendor/cjs/node/NodeWorkerRunner.js +1 -0
  404. package/dist/vendor/cjs/node/Undici.d.ts +28 -0
  405. package/dist/vendor/cjs/node/Undici.js +1 -0
  406. package/dist/vendor/cjs/node/index.d.ts +100 -0
  407. package/dist/vendor/cjs/node/index.js +1 -0
  408. package/dist/vendor/cjs/node-shared/NodeChildProcessSpawner.d.ts +57 -0
  409. package/dist/vendor/cjs/node-shared/NodeChildProcessSpawner.js +1 -0
  410. package/dist/vendor/cjs/node-shared/NodeClusterSocket.d.ts +22 -0
  411. package/dist/vendor/cjs/node-shared/NodeClusterSocket.js +1 -0
  412. package/dist/vendor/cjs/node-shared/NodeCrypto.d.ts +27 -0
  413. package/dist/vendor/cjs/node-shared/NodeCrypto.js +1 -0
  414. package/dist/vendor/cjs/node-shared/NodeFileSystem.d.ts +11 -0
  415. package/dist/vendor/cjs/node-shared/NodeFileSystem.js +1 -0
  416. package/dist/vendor/cjs/node-shared/NodeHttpCompression.d.ts +36 -0
  417. package/dist/vendor/cjs/node-shared/NodeHttpCompression.js +1 -0
  418. package/dist/vendor/cjs/node-shared/NodePath.d.ts +27 -0
  419. package/dist/vendor/cjs/node-shared/NodePath.js +1 -0
  420. package/dist/vendor/cjs/node-shared/NodeRuntime.d.ts +47 -0
  421. package/dist/vendor/cjs/node-shared/NodeRuntime.js +1 -0
  422. package/dist/vendor/cjs/node-shared/NodeSink.d.ts +69 -0
  423. package/dist/vendor/cjs/node-shared/NodeSink.js +1 -0
  424. package/dist/vendor/cjs/node-shared/NodeSocket.d.ts +134 -0
  425. package/dist/vendor/cjs/node-shared/NodeSocket.js +1 -0
  426. package/dist/vendor/cjs/node-shared/NodeSocketServer.d.ts +80 -0
  427. package/dist/vendor/cjs/node-shared/NodeSocketServer.js +1 -0
  428. package/dist/vendor/cjs/node-shared/NodeStdio.d.ts +12 -0
  429. package/dist/vendor/cjs/node-shared/NodeStdio.js +1 -0
  430. package/dist/vendor/cjs/node-shared/NodeStream.d.ts +179 -0
  431. package/dist/vendor/cjs/node-shared/NodeStream.js +1 -0
  432. package/dist/vendor/cjs/node-shared/NodeTerminal.d.ts +22 -0
  433. package/dist/vendor/cjs/node-shared/NodeTerminal.js +1 -0
  434. package/dist/vendor/cjs/node-shared/index.d.ts +56 -0
  435. package/dist/vendor/cjs/node-shared/index.js +1 -0
  436. package/dist/vendor/cjs/node-shared/internal/nodeChildProcessSpawner.d.ts +4 -0
  437. package/dist/vendor/cjs/node-shared/internal/nodeChildProcessSpawner.js +1 -0
  438. package/dist/vendor/cjs/node-shared/internal/utils.d.ts +2 -0
  439. package/dist/vendor/cjs/node-shared/internal/utils.js +1 -0
  440. package/dist/vendor/cjs/package.json +1 -0
  441. package/dist/vendor/cjs/platform-bun.d.ts +92 -0
  442. package/dist/vendor/cjs/platform-bun.js +1 -0
  443. package/dist/vendor/cjs/platform-node-shared.d.ts +56 -0
  444. package/dist/vendor/cjs/platform-node-shared.js +1 -0
  445. package/dist/vendor/cjs/platform-node.d.ts +100 -0
  446. package/dist/vendor/cjs/platform-node.js +1 -0
  447. package/dist/vendor/node/LICENSE +21 -0
  448. package/dist/vendor/node/NodeChildProcessSpawner.d.ts +7 -0
  449. package/dist/vendor/node/NodeChildProcessSpawner.js +14 -0
  450. package/dist/vendor/node/NodeClusterHttp.d.ts +63 -0
  451. package/dist/vendor/node/NodeClusterHttp.js +43 -0
  452. package/dist/vendor/node/NodeClusterSocket.d.ts +79 -0
  453. package/dist/vendor/node/NodeClusterSocket.js +30 -0
  454. package/dist/vendor/node/NodeCrypto.d.ts +10 -0
  455. package/dist/vendor/node/NodeCrypto.js +8 -0
  456. package/dist/vendor/node/NodeFileSystem.d.ts +10 -0
  457. package/dist/vendor/node/NodeFileSystem.js +9 -0
  458. package/dist/vendor/node/NodeHttpClient.d.ts +179 -0
  459. package/dist/vendor/node/NodeHttpClient.js +46 -0
  460. package/dist/vendor/node/NodeHttpIncomingMessage.d.ts +62 -0
  461. package/dist/vendor/node/NodeHttpIncomingMessage.js +10 -0
  462. package/dist/vendor/node/NodeHttpPlatform.d.ts +36 -0
  463. package/dist/vendor/node/NodeHttpPlatform.js +16 -0
  464. package/dist/vendor/node/NodeHttpServer.d.ts +120 -0
  465. package/dist/vendor/node/NodeHttpServer.js +46 -0
  466. package/dist/vendor/node/NodeHttpServerRequest.d.ts +29 -0
  467. package/dist/vendor/node/NodeHttpServerRequest.js +9 -0
  468. package/dist/vendor/node/NodeMultipart.d.ts +46 -0
  469. package/dist/vendor/node/NodeMultipart.js +15 -0
  470. package/dist/vendor/node/NodeMultipartParser.d.ts +97 -0
  471. package/dist/vendor/node/NodeMultipartParser.js +11 -0
  472. package/dist/vendor/node/NodePath.d.ts +27 -0
  473. package/dist/vendor/node/NodePath.js +12 -0
  474. package/dist/vendor/node/NodeRedis.d.ts +74 -0
  475. package/dist/vendor/node/NodeRedis.js +11 -0
  476. package/dist/vendor/node/NodeRuntime.d.ts +85 -0
  477. package/dist/vendor/node/NodeRuntime.js +8 -0
  478. package/dist/vendor/node/NodeServices.d.ts +34 -0
  479. package/dist/vendor/node/NodeServices.js +23 -0
  480. package/dist/vendor/node/NodeSink.d.ts +8 -0
  481. package/dist/vendor/node/NodeSink.js +12 -0
  482. package/dist/vendor/node/NodeSocket.d.ts +38 -0
  483. package/dist/vendor/node/NodeSocket.js +31 -0
  484. package/dist/vendor/node/NodeSocketServer.d.ts +8 -0
  485. package/dist/vendor/node/NodeSocketServer.js +21 -0
  486. package/dist/vendor/node/NodeStdio.d.ts +11 -0
  487. package/dist/vendor/node/NodeStdio.js +10 -0
  488. package/dist/vendor/node/NodeStream.d.ts +8 -0
  489. package/dist/vendor/node/NodeStream.js +27 -0
  490. package/dist/vendor/node/NodeTerminal.d.ts +21 -0
  491. package/dist/vendor/node/NodeTerminal.js +10 -0
  492. package/dist/vendor/node/NodeWorker.d.ts +22 -0
  493. package/dist/vendor/node/NodeWorker.js +9 -0
  494. package/dist/vendor/node/NodeWorkerRunner.d.ts +12 -0
  495. package/dist/vendor/node/NodeWorkerRunner.js +7 -0
  496. package/dist/vendor/node/Undici.d.ts +28 -0
  497. package/dist/vendor/node/Undici.js +9 -0
  498. package/dist/vendor/node/index.d.ts +100 -0
  499. package/dist/vendor/node/index.js +115 -0
  500. package/dist/vendor/node-shared/LICENSE +21 -0
  501. package/dist/vendor/node-shared/NodeChildProcessSpawner.d.ts +57 -0
  502. package/dist/vendor/node-shared/NodeChildProcessSpawner.js +13 -0
  503. package/dist/vendor/node-shared/NodeClusterSocket.d.ts +22 -0
  504. package/dist/vendor/node-shared/NodeClusterSocket.js +11 -0
  505. package/dist/vendor/node-shared/NodeCrypto.d.ts +27 -0
  506. package/dist/vendor/node-shared/NodeCrypto.js +9 -0
  507. package/dist/vendor/node-shared/NodeFileSystem.d.ts +11 -0
  508. package/dist/vendor/node-shared/NodeFileSystem.js +8 -0
  509. package/dist/vendor/node-shared/NodeHttpCompression.d.ts +36 -0
  510. package/dist/vendor/node-shared/NodeHttpCompression.js +13 -0
  511. package/dist/vendor/node-shared/NodePath.d.ts +27 -0
  512. package/dist/vendor/node-shared/NodePath.js +11 -0
  513. package/dist/vendor/node-shared/NodeRuntime.d.ts +47 -0
  514. package/dist/vendor/node-shared/NodeRuntime.js +7 -0
  515. package/dist/vendor/node-shared/NodeSink.d.ts +69 -0
  516. package/dist/vendor/node-shared/NodeSink.js +11 -0
  517. package/dist/vendor/node-shared/NodeSocket.d.ts +134 -0
  518. package/dist/vendor/node-shared/NodeSocket.js +23 -0
  519. package/dist/vendor/node-shared/NodeSocketServer.d.ts +80 -0
  520. package/dist/vendor/node-shared/NodeSocketServer.js +20 -0
  521. package/dist/vendor/node-shared/NodeStdio.d.ts +12 -0
  522. package/dist/vendor/node-shared/NodeStdio.js +9 -0
  523. package/dist/vendor/node-shared/NodeStream.d.ts +179 -0
  524. package/dist/vendor/node-shared/NodeStream.js +26 -0
  525. package/dist/vendor/node-shared/NodeTerminal.d.ts +22 -0
  526. package/dist/vendor/node-shared/NodeTerminal.js +9 -0
  527. package/dist/vendor/node-shared/index.d.ts +56 -0
  528. package/dist/vendor/node-shared/index.js +58 -0
  529. package/dist/vendor/node-shared/internal/nodeChildProcessSpawner.d.ts +4 -0
  530. package/dist/vendor/node-shared/internal/nodeChildProcessSpawner.js +7 -0
  531. package/dist/vendor/node-shared/internal/utils.d.ts +2 -0
  532. package/dist/vendor/node-shared/internal/utils.js +7 -0
  533. package/dist/vendor/platform-bun.d.ts +92 -0
  534. package/dist/vendor/platform-bun.js +107 -0
  535. package/dist/vendor/platform-node-shared.d.ts +56 -0
  536. package/dist/vendor/platform-node-shared.js +58 -0
  537. package/dist/vendor/platform-node.d.ts +100 -0
  538. package/dist/vendor/platform-node.js +115 -0
  539. package/docs/README.md +158 -0
  540. package/docs/api.md +601 -0
  541. package/docs/concepts/projections.md +250 -0
  542. package/docs/concepts/subscriptions.md +204 -0
  543. package/docs/concepts/trust-boundary.md +256 -0
  544. package/docs/guides/diagnose-a-run.md +115 -0
  545. package/docs/guides/follow-a-run.md +150 -0
  546. package/docs/guides/host-the-gateway.md +153 -0
  547. package/docs/guides/read-a-projection.md +138 -0
  548. package/docs/guides/serve-beyond-loopback.md +166 -0
  549. package/docs/guides/submit-an-approval.md +187 -0
  550. package/docs/guides/testing.md +148 -0
  551. package/docs/installation.md +89 -0
  552. package/docs/quickstart.md +200 -0
  553. package/docs/troubleshooting.md +355 -0
  554. package/package.json +264 -32
  555. package/src/Diagnosis.ts +892 -0
  556. package/src/EngineTrace.ts +605 -0
  557. package/src/GatewayError.ts +71 -0
  558. package/src/GatewayProjection.ts +948 -0
  559. package/src/GatewayRpcs.ts +179 -0
  560. package/src/GatewaySchema.ts +516 -0
  561. package/src/GatewayServer.ts +932 -0
  562. package/src/HostBranchPresence.ts +72 -0
  563. package/src/Projections.ts +1735 -0
  564. package/src/RunDevTools.ts +394 -0
  565. package/src/RunHistory.ts +174 -0
  566. package/src/RunTrace.ts +2793 -0
  567. package/src/RuntimeBridge.ts +835 -0
  568. package/src/bun/BunGateway.ts +52 -0
  569. package/src/index.ts +77 -0
  570. package/src/internal/NativeGateway.ts +360 -0
  571. package/src/internal/actionRenderings.ts +201 -0
  572. package/src/internal/callEvents.ts +257 -0
  573. package/src/internal/digestIndex.ts +134 -0
  574. package/src/internal/digestMemory.ts +27 -0
  575. package/src/internal/inspectLabels.ts +13 -0
  576. package/src/internal/nativeResolution.ts +103 -0
  577. package/src/internal/nodeEvents.ts +100 -0
  578. package/src/node/NodeGateway.ts +30 -0
  579. package/openapi.yaml +0 -29182
  580. package/src/README.md +0 -39
  581. package/src/api/README.md +0 -23
  582. package/src/api/apiCollectionNames.js +0 -10
  583. package/src/api/index.d.ts +0 -126
  584. package/src/api/index.js +0 -14
  585. package/src/api/normalizeApiRow.js +0 -36
  586. package/src/api/serializeAccountRow.js +0 -10
  587. package/src/api/serializeApprovalRow.js +0 -53
  588. package/src/api/serializeComparisonScoreRow.js +0 -18
  589. package/src/api/serializeCronRow.js +0 -30
  590. package/src/api/serializeDocRow.js +0 -20
  591. package/src/api/serializeMemoryFactRow.js +0 -21
  592. package/src/api/serializePromptRow.js +0 -10
  593. package/src/api/serializeRunEventRow.js +0 -28
  594. package/src/api/serializeRunRow.js +0 -61
  595. package/src/api/serializeScoreDetailRow.js +0 -25
  596. package/src/api/serializeScoreRow.js +0 -26
  597. package/src/api/serializeTicketRow.js +0 -20
  598. package/src/api/serializeWorkflowRow.js +0 -10
  599. package/src/auth/scopes.d.ts +0 -19
  600. package/src/auth/scopes.js +0 -164
  601. package/src/index.d.ts +0 -316
  602. package/src/index.js +0 -108
  603. package/src/rpc/gatewayRpcTypes.ts +0 -183
  604. package/src/rpc/index.d.ts +0 -221
  605. package/src/rpc/index.js +0 -2431
@@ -0,0 +1,932 @@
1
+ /**
2
+ * The assembled workspace gateway: one HTTP surface carrying the control
3
+ * plane, the sync read path, the served projections, and a health probe.
4
+ *
5
+ * Mounts:
6
+ *
7
+ * | Path | Protocol | Serves |
8
+ * | --- | --- | --- |
9
+ * | `POST /rpc` | RPC over HTTP | `@smthrs/control` `ControlRpcs` |
10
+ * | `/rpc/ws` | RPC over WebSocket | `ControlRpcs`, including a kept-alive `Watch` |
11
+ * | `POST /projections` | RPC over HTTP | `GatewayRpcs` |
12
+ * | `/projections/ws` | RPC over WebSocket | `GatewayRpcs`, including `Projection.Subscribe` |
13
+ * | `POST /sync` | RPC over HTTP | `@smthrs/sync` `SyncRpcs` |
14
+ * | `/sync/ws` | RPC over WebSocket | `SyncRpcs` |
15
+ * | `GET /health` | JSON | `GatewaySchema.GatewayHealth` plus the package version |
16
+ * | `POST /auth/ticket` | JSON | A single-use {@link Ticket} that opens one protected socket |
17
+ *
18
+ * `/health` is deliberately unauthenticated: a supervisor decides whether to
19
+ * keep or replace a gateway process by asking which workspace it belongs to,
20
+ * and a probe that needed a credential could not answer that question about a
21
+ * gateway it did not start. The response carries identity only: the
22
+ * workspace hash, the gateway id, the protocol version, and the package
23
+ * version. It never carries a token, a run, or a path.
24
+ *
25
+ * @since 1.0.0
26
+ */
27
+
28
+ import { Control } from "@smthrs/control/Control"
29
+ import * as ControlError from "@smthrs/control/ControlError"
30
+ import * as ControlExecutor from "@smthrs/control/ControlExecutor"
31
+ import { ControlPrincipal } from "@smthrs/control/ControlRpcs"
32
+ import type * as ControlSchema from "@smthrs/control/ControlSchema"
33
+ import * as ControlServer from "@smthrs/control/ControlServer"
34
+ import { SyncRpcs } from "@smthrs/sync/SyncRpcs"
35
+ import * as SyncServer from "@smthrs/sync/SyncServer"
36
+ import { Effect, Layer, Option, Schema, Stream, type Types } from "effect"
37
+ import * as ByteSize from "effect/ByteSize"
38
+ import { Headers, HttpRouter, HttpServerRequest, HttpServerResponse } from "effect/unstable/http"
39
+ import { RpcSerialization, RpcServer } from "effect/unstable/rpc"
40
+ import { GatewayError, settingRefusal } from "./GatewayError.ts"
41
+ import { GatewayRpcs } from "./GatewayRpcs.ts"
42
+ import * as GatewaySchema from "./GatewaySchema.ts"
43
+ import * as HostBranchPresence from "./HostBranchPresence.ts"
44
+ import { heartbeatIntervalMillis, Projections } from "./Projections.ts"
45
+ import * as RunHistory from "./RunHistory.ts"
46
+ import * as RuntimeBridge from "./RuntimeBridge.ts"
47
+
48
+ /**
49
+ * What `GET /health` answers: the `GatewaySchema.GatewayHealth` identity plus
50
+ * the version of the package serving it.
51
+ *
52
+ * @since 1.0.0
53
+ * @category models
54
+ */
55
+ export const Health = Schema.Struct({
56
+ ...GatewaySchema.GatewayHealth.fields,
57
+ version: Schema.String,
58
+ runtimeBridge: Schema.optional(RuntimeBridge.Identity)
59
+ })
60
+
61
+ /**
62
+ * What `GET /health` answers.
63
+ *
64
+ * @since 1.0.0
65
+ * @category models
66
+ */
67
+ export type Health = typeof Health.Type
68
+
69
+ const encodeHealth = Schema.encodeUnknownSync(Health)
70
+
71
+ /**
72
+ * Mounts the unauthenticated health probe.
73
+ *
74
+ * @param health the identity this process serves under
75
+ * @since 1.0.0
76
+ * @category layers
77
+ */
78
+ export const layerHealth = (health: Health): Layer.Layer<never, never, HttpRouter.HttpRouter> =>
79
+ HttpRouter.add("GET", "/health", HttpServerResponse.jsonUnsafe(encodeHealth(health)))
80
+
81
+ /**
82
+ * The gateway's own RPC handlers over the read path and the control plane.
83
+ *
84
+ * @since 1.0.0
85
+ * @category layers
86
+ */
87
+ export const layerHandlers = GatewayRpcs.toLayer(
88
+ Effect.gen(function*() {
89
+ const projections = yield* Projections
90
+ const control = yield* Control
91
+ // Only a host that owns the project's stores can fork or verify its runs.
92
+ const history = yield* Effect.serviceOption(RunHistory.RunHistory)
93
+ const historyHost = Option.match(history, {
94
+ onNone: () => Effect.fail(new ControlError.Unavailable({ feature: "run history", ticket: "smithers#3242" })),
95
+ onSome: Effect.succeed
96
+ })
97
+ // Forking writes a durable run and verifying drives the project's flow
98
+ // code, so neither is an anonymous loopback read.
99
+ const operator = Effect.gen(function*() {
100
+ const principal = yield* ControlPrincipal
101
+ if (principal.id === "loopback" && principal.kind === "anonymous") {
102
+ return yield* new ControlError.Unauthorized({ message: "An operator credential is required" })
103
+ }
104
+ })
105
+ return GatewayRpcs.of({
106
+ "Projection.Snapshot": Effect.fn("Gateway.snapshot")(
107
+ ({ selector, after }) => projections.snapshot(selector, after),
108
+ Effect.tapCause(ControlServer.logDefect)
109
+ ),
110
+ "Projection.Subscribe": ({ after, selector }) =>
111
+ Stream.tapCause(projections.subscribe(selector, after), ControlServer.logDefect),
112
+ /* One operator command. Control owns the atomic decision plus durable
113
+ * resume delegation; the gateway is only a transport adapter. */
114
+ "Approval.Submit": Effect.fn("Gateway.submitApproval")((input) =>
115
+ Effect.gen(function*() {
116
+ // The identity the shared `ControlAuth` middleware authenticated, as
117
+ // `ControlServer` stamps it on `Approve` and `Deny`. This mount is
118
+ // the same decision under a different payload, so it is answerable
119
+ // to the same operator, and a decision journaled under the
120
+ // composition's default operator names the wrong one.
121
+ const principal = yield* ControlPrincipal
122
+ // Answering a human wait uses signal rather than the approval policy.
123
+ // Do not let that alternate mutation path bypass loopback authentication.
124
+ if (principal.id === "loopback" && principal.kind === "anonymous") {
125
+ return yield* new ControlError.Unauthorized({ message: "An operator credential is required" })
126
+ }
127
+ const payload = {
128
+ target: input.target,
129
+ scope: input.scope,
130
+ idempotencyKey: input.idempotencyKey,
131
+ principal
132
+ }
133
+ // A gate that asked a question is answered, not granted, and the
134
+ // PAYLOAD says which kind it is: a human wait carries its durable
135
+ // wait token as the target's digest, and nothing else does. Reading
136
+ // it here rather than trusting the client to have sent `answer` is
137
+ // the difference between a clear refusal and the one an operator
138
+ // actually saw — a client that forgot the answer fell through to
139
+ // `Control.approve`, which looked for a registered approval token,
140
+ // found none, and reported `/control/RunNotFound` for a run that was
141
+ // listed, rendered, and waiting (workspace 4bb93306, run-1).
142
+ //
143
+ // `target.runId` is the run the person opened and `wait.name` the
144
+ // wait point's own name; `Control.signal` routes the pair into the
145
+ // run tree, to whichever execution is holding it.
146
+ const wait = ControlExecutor.answerableWait(input.target)
147
+ if (wait !== undefined && input.target._tag === "Node") {
148
+ // Neither half of a grant answers a question. A denial has nowhere
149
+ // to go — the run is parked on a value, and refusing to supply one
150
+ // leaves it parked until its own attempt budget or deadline ends
151
+ // it — and an approval with no value supplies none.
152
+ if (input.decision === "deny") {
153
+ return yield* new ControlError.InvalidInput({
154
+ issue: `decision: "${wait.name}" asks a question, which is answered rather than denied`
155
+ })
156
+ }
157
+ if (input.answer === undefined) {
158
+ return yield* new ControlError.InvalidInput({
159
+ issue: `answer: "${wait.name}" asks a question, so a decision alone does not answer it`
160
+ })
161
+ }
162
+ const decision = yield* control.signal({
163
+ runId: input.target.runId,
164
+ signal: { name: wait.name, payload: input.answer },
165
+ idempotencyKey: input.idempotencyKey,
166
+ principal
167
+ })
168
+ return { decision }
169
+ }
170
+ const decision = input.decision === "approve"
171
+ ? yield* control.approve(payload)
172
+ : yield* control.deny(payload)
173
+ return { decision }
174
+ }), Effect.tapCause(ControlServer.logDefect)),
175
+ "Run.Fork": Effect.fn("Gateway.fork")(
176
+ (input) => Effect.andThen(operator, Effect.flatMap(historyHost, (host) => host.fork(input))),
177
+ Effect.tapCause(ControlServer.logDefect)
178
+ ),
179
+ "Run.Verify": Effect.fn("Gateway.verify")(
180
+ (input) => Effect.andThen(operator, Effect.flatMap(historyHost, (host) => host.verify(input))),
181
+ Effect.tapCause(ControlServer.logDefect)
182
+ )
183
+ })
184
+ })
185
+ )
186
+
187
+ /**
188
+ * The kind a `Watch` keepalive carries.
189
+ *
190
+ * `ControlRpcs.Watch` answers `ControlSchema.ControlEvent` and has no frame of
191
+ * its own for a keepalive, so the gateway publishes one as an event whose kind
192
+ * no emitter uses. A fold that does not know the kind ignores it, which is
193
+ * what every fold in this package and in `@smthrs/cli` `Forensics` already
194
+ * does with an unknown kind.
195
+ *
196
+ * @since 1.0.0
197
+ * @category models
198
+ */
199
+ export const watchHeartbeatKind = "control.gateway.heartbeat"
200
+
201
+ /**
202
+ * Merges a keepalive into a followed `Watch` stream.
203
+ *
204
+ * A followed stream is silent while a run is thinking, and a relay cuts an
205
+ * idle tunnel at 600 s. The Effect RPC client
206
+ * sends its own `Ping` every 5 s, but a non-Effect consumer behind a relay
207
+ * sends nothing, so the keepalive has to come from the server.
208
+ *
209
+ * The keepalive repeats the last delivered sequence and run-scoped cursor,
210
+ * including an expansion offset, so it cannot advance or rewind a checkpoint.
211
+ * It carries the watched run id so a client routing by run keeps routing.
212
+ * A snapshot read (`follow: false`) is left alone: it has to end.
213
+ */
214
+ const keptAlive = (
215
+ millis: number,
216
+ filter: ControlSchema.WatchFilter,
217
+ events: Stream.Stream<ControlSchema.ControlEvent, ControlError.ControlError>
218
+ ): Stream.Stream<ControlSchema.ControlEvent, ControlError.ControlError> => {
219
+ if (filter.follow === false) return events
220
+ let cursor = filter.afterCursor ??
221
+ (filter.afterSequence === undefined ? undefined : { sequence: filter.afterSequence })
222
+ let sequence = cursor?.sequence ?? 0
223
+ const tracked = Stream.map(events, (event) => {
224
+ sequence = event.sequence
225
+ cursor = event.cursor ?? { sequence }
226
+ return event
227
+ })
228
+ const beats = Stream.tick(millis).pipe(
229
+ Stream.drop(1),
230
+ Stream.mapEffect(() =>
231
+ Effect.map(
232
+ Effect.clockWith((clock) => clock.currentTimeMillis),
233
+ (occurredAt): ControlSchema.ControlEvent => ({
234
+ sequence,
235
+ ...(filter.runId === undefined || cursor === undefined ? {} : { cursor }),
236
+ kind: watchHeartbeatKind,
237
+ ...(filter.runId === undefined ? {} : { runId: filter.runId }),
238
+ occurredAt,
239
+ payload: null
240
+ })
241
+ )
242
+ )
243
+ )
244
+ return Stream.merge(tracked, beats, { haltStrategy: "left" })
245
+ }
246
+
247
+ /**
248
+ * The control service the `/rpc` mounts read through: the ambient one, with
249
+ * the keepalive merged into `watch`.
250
+ *
251
+ * Wrapping the service rather than re-declaring the handlers keeps
252
+ * `@smthrs/control` `ControlServer` the single definition of what every
253
+ * procedure does, including the principal it stamps on every mutation that
254
+ * records who asked. Only `watch` behaves differently, and only in what it
255
+ * adds.
256
+ *
257
+ * @since 1.0.0
258
+ * @category layers
259
+ */
260
+ export const layerKeepAlive = (
261
+ millis: number = heartbeatIntervalMillis
262
+ ): Layer.Layer<Control, never, Control> =>
263
+ Layer.effect(Control)(
264
+ Effect.map(Control, (control) =>
265
+ Control.of({ ...control, watch: (filter) => keptAlive(millis, filter, control.watch(filter)) }))
266
+ )
267
+
268
+ /**
269
+ * The read path the `/projections` mounts serve through: the ambient one, with
270
+ * a followed subscription's keepalive re-timed to the bind's cadence.
271
+ *
272
+ * `Projections.make` already merges a `heartbeat` frame at the cadence it was
273
+ * built with. A host that supplies `Projections.layer` and sets the cadence at
274
+ * the bind expects one option to govern both keepalives, so this replaces the
275
+ * service's beats with beats at the bind's cadence rather than adding a second
276
+ * channel. Snapshot reads are left alone: they have to end.
277
+ *
278
+ * @param millis how often an idle followed subscription emits a keepalive
279
+ * @since 1.0.0
280
+ * @category layers
281
+ */
282
+ export const layerProjectionsKeepAlive = (millis: number): Layer.Layer<Projections, never, Projections> =>
283
+ Layer.effect(Projections)(
284
+ Effect.map(Projections, (projections) =>
285
+ Projections.of({
286
+ ...projections,
287
+ subscribe: (selector, after) => keptAliveSubscription(millis, projections.subscribe(selector, after))
288
+ }))
289
+ )
290
+
291
+ /** Replaces a subscription's keepalive channel with one at the given cadence. */
292
+ const keptAliveSubscription = <F extends GatewaySchema.GatewayFrame>(
293
+ millis: number,
294
+ frames: Stream.Stream<F, GatewayError>
295
+ ): Stream.Stream<F | GatewaySchema.HeartbeatFrame, GatewayError> => {
296
+ // The first tick is dropped for the same reason `Projections` drops it: an
297
+ // immediate keepalive would arrive before the snapshot it keeps alive.
298
+ const beats: Stream.Stream<GatewaySchema.HeartbeatFrame> = Stream.tick(millis).pipe(
299
+ Stream.drop(1),
300
+ Stream.mapEffect(() =>
301
+ Effect.map(
302
+ Effect.clockWith((clock) => clock.currentTimeMillis),
303
+ (atMs): GatewaySchema.HeartbeatFrame => ({ _tag: "heartbeat", atMs })
304
+ )
305
+ )
306
+ )
307
+ return Stream.merge(
308
+ Stream.filter(frames, (frame) => frame._tag !== "heartbeat"),
309
+ beats,
310
+ { haltStrategy: "left" }
311
+ )
312
+ }
313
+
314
+ /**
315
+ * Mounts the control plane on `POST /rpc` and `/rpc/ws`, with the keepalive.
316
+ *
317
+ * @param millis how often an idle followed watch emits one, defaulting to
318
+ * `Projections.heartbeatIntervalMillis`
319
+ * @since 1.0.0
320
+ * @category layers
321
+ */
322
+ export const layerControlHttp = (millis?: number | undefined) =>
323
+ ControlServer.layerHttp.pipe(Layer.provide(layerKeepAlive(millis)))
324
+
325
+ const gateway = RpcServer.layer(GatewayRpcs, { disableFatalDefects: true })
326
+
327
+ const sync = RpcServer.layer(SyncRpcs, { disableFatalDefects: true })
328
+
329
+ /**
330
+ * Mounts the gateway's projections on `POST /projections` and
331
+ * `/projections/ws`.
332
+ *
333
+ * Both protocols are mounted together because a client reads a snapshot over
334
+ * the request/response path and follows deltas over the socket, and pointing
335
+ * those at two different servers would let them disagree.
336
+ *
337
+ * @since 1.0.0
338
+ * @category layers
339
+ */
340
+ export const layerProjectionsHttp = Layer.mergeAll(
341
+ gateway.pipe(
342
+ Layer.provide(layerHandlers),
343
+ Layer.provideMerge(RpcServer.layerProtocolHttp({ path: "/projections" })),
344
+ Layer.fresh
345
+ ),
346
+ gateway.pipe(
347
+ Layer.provide(layerHandlers),
348
+ Layer.provideMerge(RpcServer.layerProtocolWebsocket({ path: "/projections/ws" })),
349
+ Layer.fresh
350
+ )
351
+ )
352
+
353
+ /**
354
+ * Mounts the sync read path on `POST /sync` and `/sync/ws`.
355
+ *
356
+ * @since 1.0.0
357
+ * @category layers
358
+ */
359
+ export const layerSyncHttp = Layer.mergeAll(
360
+ sync.pipe(
361
+ Layer.provide(SyncServer.layerHandlers),
362
+ Layer.provideMerge(RpcServer.layerProtocolHttp({ path: "/sync" })),
363
+ Layer.fresh
364
+ ),
365
+ sync.pipe(
366
+ Layer.provide(SyncServer.layerHandlers),
367
+ Layer.provideMerge(RpcServer.layerProtocolWebsocket({ path: "/sync/ws" })),
368
+ Layer.fresh
369
+ )
370
+ )
371
+
372
+ /**
373
+ * The POST mounts that carry RPC request messages and nothing else.
374
+ *
375
+ * @since 1.0.0
376
+ * @category constants
377
+ */
378
+ export const rpcPaths: ReadonlyArray<string> = ["/rpc", "/projections", "/sync", "/branch"]
379
+
380
+ /**
381
+ * POST mounts whose request bodies are bounded before their handlers read.
382
+ *
383
+ * @since 1.0.0
384
+ * @category constants
385
+ */
386
+ export const boundedPostPaths: ReadonlyArray<string> = [
387
+ ...rpcPaths,
388
+ "/runtime/v1/command",
389
+ "/runtime/v1/observe"
390
+ ]
391
+
392
+ /**
393
+ * RPC paths whose upgrade or request must pass edge authentication.
394
+ *
395
+ * `POST /rpc` is deliberately not one of them. The control mount authenticates
396
+ * in band through `@smthrs/control` `ControlRpcs.ControlAuth`, whose declared
397
+ * error is `ControlError.Unauthorized`, and that typed control error is the
398
+ * refusal the control plane publishes: "Missing, malformed, empty, and
399
+ * incorrect credentials all return the same typed `Unauthorized` control
400
+ * error" (`packages/smithers/gateway/docs/guides/serve-beyond-loopback.md`). An edge 401 answered
401
+ * ahead of the mount, and `ControlClient` filters a non-2xx status, so every
402
+ * refusal reached a caller as a `TransportError`, the one error class
403
+ * `@smthrs/control` reserves for a request that failed *before* a declared
404
+ * control response reached it. It also made the gateway refuse a call
405
+ * differently from `NodeControl.layerServer` hosting the very same
406
+ * `ControlRpcs`. The body limit below still applies to `/rpc`, so an
407
+ * unauthenticated caller buys one bounded body read and nothing else.
408
+ *
409
+ * The `/rpc/ws` upgrade stays here. A socket is a resource the server holds
410
+ * open, the RPC middleware can only refuse frames on a socket that already
411
+ * exists, and a refused handshake has no RPC channel to answer a typed error
412
+ * on, so its refusal is a transport fact either way.
413
+ *
414
+ * @category constants
415
+ * @since 1.0.0
416
+ */
417
+ export const protectedPaths: ReadonlyArray<string> = [
418
+ "/projections",
419
+ "/branch",
420
+ "/sync",
421
+ "/rpc/ws",
422
+ "/projections/ws",
423
+ "/sync/ws",
424
+ "/runtime/v1/command",
425
+ "/runtime/v1/observe"
426
+ ]
427
+
428
+ /**
429
+ * The mount a request target reaches, spelled the way {@link rpcPaths} and
430
+ * {@link protectedPaths} spell it.
431
+ *
432
+ * The guard runs before the router and has to reach the router's verdict, or
433
+ * an alias walks past the credential check and the body limit and is answered
434
+ * by the mount anyway. Measured against `HttpRouter` on this tree, all of
435
+ * `/%72pc`, `/rpc;transport-parameter`, `/rpc/`, `//rpc`, `///rpc`, `/rpc//`,
436
+ * `/RPC`, `/./rpc`, and `/foo/../rpc` reach the `/rpc` handler, while
437
+ * `/rpc%2f`, `/rpc%3Bp`, `/%2frpc`, and `/rpc%20` do not. This is not only
438
+ * about crafted aliases: `@smthrs/control` `ControlClient`'s HTTP protocol
439
+ * posts every call to `/rpc/`, so a literal comparison skipped the body limit
440
+ * on the path the product's own client uses.
441
+ *
442
+ * Each segment is therefore taken without its `;` parameter, empty segments are
443
+ * dropped, and what is left is decoded with `decodeURI` and compared without
444
+ * regard to case. `decodeURI` is the decoder rather than `decodeURIComponent`
445
+ * because it leaves a reserved character encoded, which is what keeps
446
+ * `/rpc%2f` a different path here exactly as it is to the router. An invalid
447
+ * escape is left as written, since it names no mount either way.
448
+ *
449
+ * Dot segments are resolved by `URL` before the split. The normalization errs
450
+ * toward matching: a spelling this resolves to a mount that the router then
451
+ * refuses is answered 401 rather than 404, which costs an unauthenticated
452
+ * caller nothing it was entitled to.
453
+ *
454
+ * @param url the request target, origin-relative or absolute
455
+ * @since 1.0.0
456
+ * @category constructors
457
+ */
458
+ export const routedPath = (url: string): string => {
459
+ // An empty leading segment is a protocol-relative URL to `URL`, so `//rpc`
460
+ // would parse as the host `rpc` and lose the path entirely. Anchoring an
461
+ // origin-relative target on the origin keeps every segment. An absolute
462
+ // target that does not parse at all names no mount, so it is compared as
463
+ // written and reaches the router as the unknown route it is.
464
+ const target = url.startsWith("/") ? `http://gateway.invalid${url}` : url
465
+ const parsed = URL.parse(target, "http://gateway.invalid")
466
+ if (parsed === null) return url
467
+ const segments: Array<string> = []
468
+ for (const segment of parsed.pathname.split("/")) {
469
+ const parameter = segment.indexOf(";")
470
+ const named = parameter < 0 ? segment : segment.slice(0, parameter)
471
+ if (named === "") continue
472
+ try {
473
+ segments.push(decodeURI(named))
474
+ } catch {
475
+ segments.push(named)
476
+ }
477
+ }
478
+ return `/${segments.join("/")}`.toLowerCase()
479
+ }
480
+
481
+ /**
482
+ * The mount that exchanges a bearer credential for a WebSocket {@link Ticket}.
483
+ *
484
+ * @since 1.0.0
485
+ * @category constants
486
+ */
487
+ export const ticketPath = "/auth/ticket"
488
+
489
+ /**
490
+ * The protected socket mounts a {@link Ticket} opens.
491
+ *
492
+ * @since 1.0.0
493
+ * @category constants
494
+ */
495
+ export const ticketPaths: ReadonlyArray<string> = ["/rpc/ws", "/projections/ws", "/sync/ws"]
496
+
497
+ /**
498
+ * How long an unredeemed {@link Ticket} stays valid (thirty seconds).
499
+ *
500
+ * @since 1.0.0
501
+ * @category constants
502
+ */
503
+ export const defaultTicketMillis = 30_000
504
+
505
+ /** Outstanding tickets kept at once; the oldest is dropped past this. */
506
+ const maxOutstandingTickets = 1024
507
+
508
+ /**
509
+ * What `POST /auth/ticket` answers.
510
+ *
511
+ * A browser `WebSocket` cannot set an `Authorization` header, so a browser
512
+ * client first posts its bearer to `/auth/ticket`, then opens a protected
513
+ * socket as `<mount>?ticket=<ticket>` within `expiresInMs`. The first upgrade
514
+ * that presents the ticket consumes it, whether or not the upgrade completes;
515
+ * a replay or a late one is refused 401. The socket then runs under the
516
+ * bearer the ticket was issued for, so every frame authenticates as it would
517
+ * with the header.
518
+ *
519
+ * @since 1.0.0
520
+ * @category models
521
+ */
522
+ export const Ticket = Schema.Struct({
523
+ ticket: Schema.String,
524
+ expiresInMs: Schema.Number
525
+ })
526
+
527
+ /**
528
+ * What `POST /auth/ticket` answers.
529
+ *
530
+ * @since 1.0.0
531
+ * @category models
532
+ */
533
+ export type Ticket = typeof Ticket.Type
534
+
535
+ const encodeTicket = Schema.encodeUnknownSync(Ticket)
536
+
537
+ /** 256 random bits, hex-encoded so a query string carries them unescaped. */
538
+ const newTicket = (): string =>
539
+ Array.from(crypto.getRandomValues(new Uint8Array(32)), (byte) => byte.toString(16).padStart(2, "0")).join("")
540
+
541
+ /** The `ticket` query parameter of a request target, when it has one. */
542
+ const presentedTicket = (url: string): string | undefined => {
543
+ const parsed = URL.parse(url, "http://gateway.invalid")
544
+ const ticket = parsed?.searchParams.get("ticket")
545
+ return ticket === null || ticket === undefined || ticket === "" ? undefined : ticket
546
+ }
547
+
548
+ /**
549
+ * Default maximum request body accepted by an RPC mount (one MiB).
550
+ *
551
+ * @category constants
552
+ * @since 1.0.0
553
+ */
554
+ export const defaultMaxRequestBodyBytes = 1024 * 1024
555
+
556
+ /**
557
+ * Ingress policy enforced before an RPC transport parses a request.
558
+ *
559
+ * @category models
560
+ * @since 1.0.0
561
+ */
562
+ export interface IngressOptions {
563
+ /** Accepted Host header names, without ports. Defaults to loopback names. */
564
+ readonly allowedHosts?: ReadonlyArray<string> | undefined
565
+ readonly maxRequestBodyBytes?: number | undefined
566
+ /** Confine requests to loopback Host values and browser origins. */
567
+ readonly loopbackOnly?: boolean | undefined
568
+ /**
569
+ * Edge authentication for {@link protectedPaths}, told which mount the
570
+ * request reaches. When set, `POST /auth/ticket` exchanges a credential it
571
+ * accepts for a {@link Ticket}, and a redeemed ticket's credential is judged
572
+ * again for the mount its upgrade reaches.
573
+ */
574
+ readonly authorize?: (
575
+ headers: Readonly<Record<string, string>>,
576
+ path: string
577
+ ) => Effect.Effect<boolean>
578
+ /** How long an unredeemed ticket stays valid. Default {@link defaultTicketMillis}. */
579
+ readonly ticketMillis?: number | undefined
580
+ }
581
+
582
+ const encodeGatewayError = Schema.encodeUnknownSync(GatewayError)
583
+
584
+ /** Host parsing stays strict so URL normalization cannot admit a rebinding name. */
585
+ const requestAuthority = (host: string | undefined): URL | undefined => {
586
+ if (host === undefined || !/^(?:[a-zA-Z0-9.-]+|\[[a-fA-F0-9:]+\])(?::[0-9]+)?$/.test(host)) return undefined
587
+ const parsed = URL.parse(`http://${host}`)
588
+ return parsed === null ? undefined : parsed
589
+ }
590
+
591
+ const browserRequestRefusal = (
592
+ headers: Readonly<Record<string, string>>,
593
+ allowedHosts: ReadonlyArray<string>,
594
+ loopbackOnly: boolean
595
+ ): { readonly error: GatewayError; readonly status: number } | undefined => {
596
+ const authority = requestAuthority(headers.host)
597
+ if (
598
+ authority === undefined || !allowedHosts.includes(authority.hostname) ||
599
+ (loopbackOnly && !loopbackHost.test(headers.host!))
600
+ ) return { error: invalidHostRequest(), status: 421 }
601
+ const origin = headers.origin
602
+ // CLI clients do not send Origin. Browsers must prove they came from this
603
+ // exact authority, including the port, on HTTP requests AND WS upgrades.
604
+ if (origin === undefined) return undefined
605
+ const parsed = URL.parse(origin)
606
+ return parsed !== null && (parsed.protocol === "http:" || parsed.protocol === "https:") &&
607
+ parsed.origin === origin && parsed.host === authority.host
608
+ ? undefined
609
+ : { error: invalidOriginRequest(), status: 403 }
610
+ }
611
+
612
+ /** One refusal on the wire: the typed error, under the status it answers. */
613
+ const refuse = (error: GatewayError, status: number) =>
614
+ HttpServerResponse.jsonUnsafe(encodeGatewayError(error), { status })
615
+
616
+ const malformedRequest = (path: string) =>
617
+ new GatewayError({ code: "malformed_request", message: `POST ${path} carries no RPC request message` })
618
+
619
+ const unauthorizedRequest = () =>
620
+ new GatewayError({ code: "unauthorized", message: "A valid bearer credential is required" })
621
+
622
+ const invalidHostRequest = () =>
623
+ new GatewayError({ code: "invalid_host", message: "This gateway does not allow the supplied Host value" })
624
+
625
+ const invalidOriginRequest = () =>
626
+ new GatewayError({ code: "invalid_origin", message: "The browser Origin must match an allowed gateway Host" })
627
+
628
+ const requestTooLarge = (path: string, maxBytes: number) =>
629
+ new GatewayError({
630
+ code: "request_too_large",
631
+ message: `POST ${path} exceeds the ${maxBytes}-byte request limit`
632
+ })
633
+
634
+ /** The message `@effect/platform-node-shared` `NodeStream` gives a size overflow. */
635
+ const maxBytesExceeded = "maxBytes exceeded"
636
+
637
+ /**
638
+ * The names that mean this machine only, as a bind address is spelled.
639
+ *
640
+ * This is a list of spellings a person types, not a subnet test: `127.0.0.2`
641
+ * is loopback to the kernel and is not on it. The Node host's
642
+ * `isLoopbackHost`, the Host-header names below, and the ingress regex all
643
+ * derive from this one list so widening it is one edit.
644
+ *
645
+ * @since 1.0.0
646
+ * @category constants
647
+ */
648
+ export const loopbackHostNames: ReadonlyArray<string> = ["127.0.0.1", "localhost", "::1"]
649
+
650
+ /**
651
+ * {@link loopbackHostNames} as a Host header spells them: an IPv6 address is
652
+ * bracketed as a URL authority. This is the default `allowedHosts`.
653
+ *
654
+ * @since 1.0.0
655
+ * @category constants
656
+ */
657
+ export const loopbackHostHeaderNames: ReadonlyArray<string> = loopbackHostNames.map((name) =>
658
+ name.includes(":") ? `[${name}]` : name
659
+ )
660
+
661
+ const escapeRegExp = (text: string) => text.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")
662
+
663
+ const loopbackHost = new RegExp(`^(?:${loopbackHostHeaderNames.map(escapeRegExp).join("|")})(?::\\d{1,5})?$`, "i")
664
+
665
+ /**
666
+ * Whether a failed request-body read hit the configured size limit rather than
667
+ * failing for another reason.
668
+ *
669
+ * `@effect/platform-node` raises the limit as an `HttpServerError` whose
670
+ * `reason` is a `RequestParseError` carrying the `Error("maxBytes exceeded")`
671
+ * that `@effect/platform-node-shared` `NodeStream.toString` throws
672
+ * (`NodeHttpIncomingMessage.text` passes `MaxBodySize` in as `maxBytes`, and
673
+ * `NodeHttpServer`'s `ServerRequestImpl` wraps whatever it throws). Nothing on
674
+ * the wire distinguishes it otherwise, so the check is that exact shape and a
675
+ * version bump of those packages is the thing to re-read it against.
676
+ *
677
+ * Every other read failure, a transport reset among them, is not a size
678
+ * overflow. Answering 413 for all of them told a client to shrink a request
679
+ * that was never too big.
680
+ *
681
+ * @param error the failure a body read produced
682
+ * @since 1.0.0
683
+ * @category predicates
684
+ */
685
+ export const exceededBodyLimit = (error: unknown): boolean => {
686
+ const reason = typeof error === "object" && error !== null
687
+ ? (error as { readonly reason?: unknown }).reason
688
+ : undefined
689
+ const cause = typeof reason === "object" && reason !== null
690
+ ? (reason as { readonly cause?: unknown }).cause
691
+ : undefined
692
+ return cause instanceof Error && cause.message === maxBytesExceeded
693
+ }
694
+
695
+ /**
696
+ * The refusal a failed request-body read earns, and the status it answers
697
+ * under.
698
+ *
699
+ * A body over the limit is 413 `request_too_large`. Every other read failure,
700
+ * a truncated body or a reset transport among them, is 400
701
+ * `malformed_request`: the request cannot be retried smaller, because its size
702
+ * was never the problem.
703
+ *
704
+ * @param path the mount the request was made to
705
+ * @param maxBytes the configured limit
706
+ * @param error the failure the body read produced
707
+ * @since 1.0.0
708
+ * @category constructors
709
+ */
710
+ export const bodyRefusal = (
711
+ path: string,
712
+ maxBytes: number,
713
+ error: unknown
714
+ ): { readonly error: GatewayError; readonly status: number } =>
715
+ exceededBodyLimit(error)
716
+ ? { error: requestTooLarge(path, maxBytes), status: 413 }
717
+ : {
718
+ error: new GatewayError({
719
+ code: "malformed_request",
720
+ message: `POST ${path} carries a body the server could not read`
721
+ }),
722
+ status: 400
723
+ }
724
+
725
+ /**
726
+ * Whether a request body carries at least one RPC message the server can act
727
+ * on.
728
+ *
729
+ * The transport's own parser answers, not a second reading of the wire
730
+ * format: whatever `RpcSerialization` the host composed decodes the body, and
731
+ * the answer is no only when that decode throws or yields no tagged message.
732
+ * A body naming a procedure that does not exist is a yes — that is an
733
+ * RPC-level defect the protocol itself reports.
734
+ *
735
+ * A binary framing is always a yes. Its body is not text, so there is nothing
736
+ * to read here, and the mount owns it.
737
+ *
738
+ * @since 1.0.0
739
+ * @category predicates
740
+ */
741
+ export const carriesRpcRequest = (
742
+ serialization: RpcSerialization.RpcSerialization["Service"],
743
+ body: string
744
+ ): boolean => {
745
+ if (!serialization.contentType.includes("json")) return true
746
+ try {
747
+ const decoded = serialization.makeUnsafe().decode(body)
748
+ return decoded.length > 0 && decoded.every((message: unknown) =>
749
+ typeof message === "object" && message !== null &&
750
+ typeof (message as { readonly _tag?: unknown })._tag === "string"
751
+ )
752
+ } catch {
753
+ return false
754
+ }
755
+ }
756
+
757
+ /**
758
+ * Enforces local Host/Origin policy and edge authentication before reading a
759
+ * body or upgrading a socket, then validates RPC body size and framing.
760
+ *
761
+ * `effect/unstable/rpc` hands every decoded message to the server loop, and a
762
+ * body that decodes to something else — `{}`, `[]`, prose, nothing at all —
763
+ * reached it as a message with no tag and died there, so the gateway answered
764
+ * `500 Internal Server Error` with an empty body (release validation). That is the
765
+ * wrong half of the contract twice over: it tells an operator the gateway
766
+ * broke, and it tells a client to retry a request that can never succeed.
767
+ *
768
+ * The body reader is cached per request, so reading the body here does not
769
+ * consume the body the mount reads. It is read through the reader the mount
770
+ * uses, `text` under a JSON serialization and `arrayBuffer` under a binary
771
+ * one, so the bytes the mount decodes are the bytes the client sent.
772
+ *
773
+ * @since 1.0.0
774
+ * @category layers
775
+ */
776
+ export const layerIngress = (options: IngressOptions = {}) => {
777
+ const refusal = settingRefusal("The gateway request body limit", options.maxRequestBodyBytes) ??
778
+ settingRefusal("The gateway ticket lifetime", options.ticketMillis)
779
+ if (refusal !== undefined) return Layer.effectDiscard(Effect.fail(refusal))
780
+ const maxBytes = options.maxRequestBodyBytes ?? defaultMaxRequestBodyBytes
781
+ const allowedHosts = options.allowedHosts ?? loopbackHostHeaderNames
782
+ const ticketMillis = options.ticketMillis ?? defaultTicketMillis
783
+ return HttpRouter.middleware(
784
+ Effect.gen(function*() {
785
+ const serialization = yield* RpcSerialization.RpcSerialization
786
+ const binary = !serialization.contentType.includes("json")
787
+ /** Outstanding tickets: the Authorization header each was issued under, and when it lapses. */
788
+ const tickets = new Map<string, { readonly authorization: string; readonly expiresAt: number }>()
789
+ const issue = (authorization: string, now: number): Ticket => {
790
+ for (const [ticket, entry] of tickets) {
791
+ if (entry.expiresAt <= now) tickets.delete(ticket)
792
+ }
793
+ if (tickets.size >= maxOutstandingTickets) tickets.delete(tickets.keys().next().value!)
794
+ const ticket = newTicket()
795
+ tickets.set(ticket, { authorization, expiresAt: now + ticketMillis })
796
+ return { ticket, expiresInMs: ticketMillis }
797
+ }
798
+ // Lookup and delete run without yielding, so of two upgrades presenting
799
+ // one ticket exactly one gets its header.
800
+ const redeem = (url: string, now: number): string | undefined => {
801
+ const ticket = presentedTicket(url)
802
+ const entry = ticket === undefined ? undefined : tickets.get(ticket)
803
+ if (entry === undefined) return undefined
804
+ tickets.delete(ticket!)
805
+ return entry.expiresAt > now ? entry.authorization : undefined
806
+ }
807
+ return (httpEffect: Effect.Effect<HttpServerResponse.HttpServerResponse, Types.unhandled>) =>
808
+ Effect.gen(function*() {
809
+ const request = yield* HttpServerRequest.HttpServerRequest
810
+ const browserRefusal = browserRequestRefusal(request.headers, allowedHosts, options.loopbackOnly === true)
811
+ if (browserRefusal !== undefined) return refuse(browserRefusal.error, browserRefusal.status)
812
+ const path = routedPath(request.url)
813
+ if (path === ticketPath && request.method === "POST" && options.authorize !== undefined) {
814
+ const authorization = request.headers.authorization
815
+ if (authorization === undefined || !(yield* options.authorize(request.headers, path))) {
816
+ return refuse(unauthorizedRequest(), 401)
817
+ }
818
+ const now = yield* Effect.clockWith((clock) => clock.currentTimeMillis)
819
+ return HttpServerResponse.jsonUnsafe(encodeTicket(issue(authorization, now)), {
820
+ headers: { "cache-control": "no-store" }
821
+ })
822
+ }
823
+ if (protectedPaths.includes(path) && options.authorize !== undefined) {
824
+ const authorized = yield* options.authorize(request.headers, path)
825
+ if (!authorized) {
826
+ const now = yield* Effect.clockWith((clock) => clock.currentTimeMillis)
827
+ const authorization = ticketPaths.includes(path) ? redeem(request.url, now) : undefined
828
+ if (authorization === undefined) return refuse(unauthorizedRequest(), 401)
829
+ // A ticket stands for the credential it was issued under, which
830
+ // may not reach every mount a ticket can open.
831
+ const headers = Headers.set(request.headers, "authorization", authorization)
832
+ if (!(yield* options.authorize(headers, path))) return refuse(unauthorizedRequest(), 401)
833
+ // The socket protocol stamps the upgrade's headers on every
834
+ // frame, so the redeemed bearer authenticates each one.
835
+ return yield* Effect.provideService(
836
+ httpEffect,
837
+ HttpServerRequest.HttpServerRequest,
838
+ request.modify({ headers })
839
+ )
840
+ }
841
+ }
842
+ if (request.method !== "POST" || !boundedPostPaths.includes(path)) return yield* httpEffect
843
+ // A declared length is a hint that saves reading a body already
844
+ // known to be too big. It is never trusted the other way: a body
845
+ // that declares less than it sends is still measured by the read,
846
+ // and a chunked body declares nothing at all.
847
+ const declaredLength = Number(request.headers["content-length"])
848
+ if (Number.isFinite(declaredLength) && declaredLength > maxBytes) {
849
+ return refuse(requestTooLarge(path, maxBytes), 413)
850
+ }
851
+ // The body is read through the reader the mount will use. The Node
852
+ // adapter caches whichever reader ran first and derives the other
853
+ // from it, so a text read here would hand a binary mount a UTF-8
854
+ // re-encoding of its MessagePack bytes, with every invalid sequence
855
+ // replaced. A binary body is bounded here and framed by the mount.
856
+ const read = yield* (binary ? Effect.as(request.arrayBuffer, undefined) : request.text).pipe(
857
+ Effect.provideService(HttpServerRequest.MaxBodySize, ByteSize.bytes(maxBytes)),
858
+ Effect.match({
859
+ onFailure: (error): { readonly body: string | undefined; readonly error: unknown } => ({
860
+ body: undefined,
861
+ error
862
+ }),
863
+ onSuccess: (body) => ({ body, error: undefined })
864
+ })
865
+ )
866
+ if (read.error !== undefined) {
867
+ const answer = bodyRefusal(path, maxBytes, read.error)
868
+ return refuse(answer.error, answer.status)
869
+ }
870
+ if (read.body === undefined) return yield* httpEffect
871
+ return !rpcPaths.includes(path) || carriesRpcRequest(serialization, read.body)
872
+ ? yield* httpEffect
873
+ : refuse(malformedRequest(path), 400)
874
+ })
875
+ }),
876
+ { global: true }
877
+ )
878
+ }
879
+
880
+ /**
881
+ * How an assembled gateway is configured.
882
+ *
883
+ * @category models
884
+ * @since 1.0.0
885
+ */
886
+ export interface LayerOptions {
887
+ /**
888
+ * How often an idle followed `Watch` on `/rpc/ws` and an idle followed
889
+ * `Projection.Subscribe` on `/projections/ws` emit a keepalive. Unset, the
890
+ * `Watch` keepalive runs at `Projections.heartbeatIntervalMillis` and the
891
+ * supplied `Projections` service keeps the cadence it was built with.
892
+ */
893
+ readonly heartbeatMillis?: number | undefined
894
+ /** The ingress policy the RPC mounts run behind. */
895
+ readonly ingress?: IngressOptions | undefined
896
+ /** Enables the authenticated Go-to-Control runtime bridge. */
897
+ readonly runtimeBridge?: RuntimeBridge.Config | undefined
898
+ }
899
+
900
+ /**
901
+ * The whole gateway surface, as one application layer a host serves.
902
+ *
903
+ * The caller supplies the transport serialization, the authentication
904
+ * middleware for both RPC groups, and the services the mounts read through
905
+ * (`Control`, `Projections`, `SyncServer`). `@smthrs/gateway/node/NodeGateway`
906
+ * is the Node host that binds this to a socket.
907
+ *
908
+ * A keepalive cadence or a body limit that is not a positive safe integer is
909
+ * refused here, before anything binds, through the layer's typed
910
+ * `GatewayError` channel.
911
+ *
912
+ * @param health the identity `GET /health` answers with
913
+ * @param options the keepalive cadence and the ingress policy
914
+ * @since 1.0.0
915
+ * @category layers
916
+ */
917
+ export const layer = (health: Health, options: LayerOptions = {}) => {
918
+ const refusal = settingRefusal("The gateway keepalive cadence", options.heartbeatMillis)
919
+ if (refusal !== undefined) return Layer.effectDiscard(Effect.fail(refusal))
920
+ return Layer.mergeAll(
921
+ layerControlHttp(options.heartbeatMillis),
922
+ options.heartbeatMillis === undefined
923
+ ? layerProjectionsHttp
924
+ : layerProjectionsHttp.pipe(Layer.provide(layerProjectionsKeepAlive(options.heartbeatMillis))),
925
+ layerSyncHttp,
926
+ layerHealth(health),
927
+ ...(options.runtimeBridge === undefined
928
+ ? []
929
+ : [RuntimeBridge.layer(options.runtimeBridge), HostBranchPresence.layer(options.runtimeBridge)]),
930
+ layerIngress(options.ingress)
931
+ )
932
+ }