threadforge 0.1.1 → 0.2.2

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 (358) hide show
  1. package/README.md +69 -42
  2. package/bin/forge.js +2 -1058
  3. package/bin/host-commands.d.ts +2 -0
  4. package/bin/host-commands.d.ts.map +1 -0
  5. package/bin/host-commands.js +7 -8
  6. package/bin/platform-commands.d.ts +2 -0
  7. package/bin/platform-commands.d.ts.map +1 -0
  8. package/bin/platform-commands.js +118 -36
  9. package/dist/cli/base-command.d.ts +12 -0
  10. package/dist/cli/base-command.d.ts.map +1 -0
  11. package/dist/cli/base-command.js +25 -0
  12. package/dist/cli/base-command.js.map +1 -0
  13. package/dist/cli/commands/build.d.ts +10 -0
  14. package/dist/cli/commands/build.d.ts.map +1 -0
  15. package/dist/cli/commands/build.js +110 -0
  16. package/dist/cli/commands/build.js.map +1 -0
  17. package/dist/cli/commands/deploy.d.ts +12 -0
  18. package/dist/cli/commands/deploy.d.ts.map +1 -0
  19. package/dist/cli/commands/deploy.js +143 -0
  20. package/dist/cli/commands/deploy.js.map +1 -0
  21. package/dist/cli/commands/dev.d.ts +10 -0
  22. package/dist/cli/commands/dev.d.ts.map +1 -0
  23. package/dist/cli/commands/dev.js +138 -0
  24. package/dist/cli/commands/dev.js.map +1 -0
  25. package/dist/cli/commands/generate.d.ts +10 -0
  26. package/dist/cli/commands/generate.d.ts.map +1 -0
  27. package/dist/cli/commands/generate.js +76 -0
  28. package/dist/cli/commands/generate.js.map +1 -0
  29. package/dist/cli/commands/host.d.ts +8 -0
  30. package/dist/cli/commands/host.d.ts.map +1 -0
  31. package/dist/cli/commands/host.js +20 -0
  32. package/dist/cli/commands/host.js.map +1 -0
  33. package/dist/cli/commands/init.d.ts +16 -0
  34. package/dist/cli/commands/init.d.ts.map +1 -0
  35. package/dist/cli/commands/init.js +246 -0
  36. package/dist/cli/commands/init.js.map +1 -0
  37. package/dist/cli/commands/platform.d.ts +8 -0
  38. package/dist/cli/commands/platform.d.ts.map +1 -0
  39. package/dist/cli/commands/platform.js +20 -0
  40. package/dist/cli/commands/platform.js.map +1 -0
  41. package/dist/cli/commands/restart.d.ts +8 -0
  42. package/dist/cli/commands/restart.d.ts.map +1 -0
  43. package/dist/cli/commands/restart.js +13 -0
  44. package/dist/cli/commands/restart.js.map +1 -0
  45. package/dist/cli/commands/scaffold/frontend.d.ts +10 -0
  46. package/dist/cli/commands/scaffold/frontend.d.ts.map +1 -0
  47. package/dist/cli/commands/scaffold/frontend.js +130 -0
  48. package/dist/cli/commands/scaffold/frontend.js.map +1 -0
  49. package/dist/cli/commands/scaffold/react.d.ts +7 -0
  50. package/dist/cli/commands/scaffold/react.d.ts.map +1 -0
  51. package/dist/cli/commands/scaffold/react.js +12 -0
  52. package/dist/cli/commands/scaffold/react.js.map +1 -0
  53. package/dist/cli/commands/scale.d.ts +8 -0
  54. package/dist/cli/commands/scale.d.ts.map +1 -0
  55. package/dist/cli/commands/scale.js +13 -0
  56. package/dist/cli/commands/scale.js.map +1 -0
  57. package/dist/cli/commands/start.d.ts +10 -0
  58. package/dist/cli/commands/start.d.ts.map +1 -0
  59. package/dist/cli/commands/start.js +71 -0
  60. package/dist/cli/commands/start.js.map +1 -0
  61. package/dist/cli/commands/status.d.ts +11 -0
  62. package/dist/cli/commands/status.d.ts.map +1 -0
  63. package/dist/cli/commands/status.js +60 -0
  64. package/dist/cli/commands/status.js.map +1 -0
  65. package/dist/cli/commands/stop.d.ts +10 -0
  66. package/dist/cli/commands/stop.d.ts.map +1 -0
  67. package/dist/cli/commands/stop.js +89 -0
  68. package/dist/cli/commands/stop.js.map +1 -0
  69. package/dist/cli/util/config-discovery.d.ts +8 -0
  70. package/dist/cli/util/config-discovery.d.ts.map +1 -0
  71. package/dist/cli/util/config-discovery.js +70 -0
  72. package/dist/cli/util/config-discovery.js.map +1 -0
  73. package/dist/cli/util/config-patcher.d.ts +17 -0
  74. package/dist/cli/util/config-patcher.d.ts.map +1 -0
  75. package/dist/cli/util/config-patcher.js +439 -0
  76. package/dist/cli/util/config-patcher.js.map +1 -0
  77. package/dist/cli/util/frontend-dev.d.ts +8 -0
  78. package/dist/cli/util/frontend-dev.d.ts.map +1 -0
  79. package/dist/cli/util/frontend-dev.js +117 -0
  80. package/dist/cli/util/frontend-dev.js.map +1 -0
  81. package/dist/cli/util/process.d.ts +5 -0
  82. package/dist/cli/util/process.d.ts.map +1 -0
  83. package/dist/cli/util/process.js +17 -0
  84. package/dist/cli/util/process.js.map +1 -0
  85. package/dist/cli/util/templates.d.ts +10 -0
  86. package/dist/cli/util/templates.d.ts.map +1 -0
  87. package/dist/cli/util/templates.js +157 -0
  88. package/dist/cli/util/templates.js.map +1 -0
  89. package/dist/core/AlertSink.d.ts +83 -0
  90. package/dist/core/AlertSink.d.ts.map +1 -0
  91. package/dist/core/AlertSink.js +126 -0
  92. package/dist/core/AlertSink.js.map +1 -0
  93. package/dist/core/DirectMessageBus.d.ts +88 -0
  94. package/dist/core/DirectMessageBus.d.ts.map +1 -0
  95. package/dist/core/DirectMessageBus.js +352 -0
  96. package/dist/core/DirectMessageBus.js.map +1 -0
  97. package/dist/core/EndpointResolver.d.ts +111 -0
  98. package/dist/core/EndpointResolver.d.ts.map +1 -0
  99. package/dist/core/EndpointResolver.js +336 -0
  100. package/dist/core/EndpointResolver.js.map +1 -0
  101. package/dist/core/ForgeContext.d.ts +221 -0
  102. package/dist/core/ForgeContext.d.ts.map +1 -0
  103. package/dist/core/ForgeContext.js +1169 -0
  104. package/dist/core/ForgeContext.js.map +1 -0
  105. package/dist/core/ForgeEndpoints.d.ts +71 -0
  106. package/dist/core/ForgeEndpoints.d.ts.map +1 -0
  107. package/dist/core/ForgeEndpoints.js +442 -0
  108. package/dist/core/ForgeEndpoints.js.map +1 -0
  109. package/dist/core/ForgeHost.d.ts +82 -0
  110. package/dist/core/ForgeHost.d.ts.map +1 -0
  111. package/dist/core/ForgeHost.js +107 -0
  112. package/dist/core/ForgeHost.js.map +1 -0
  113. package/dist/core/ForgePlatform.d.ts +96 -0
  114. package/dist/core/ForgePlatform.d.ts.map +1 -0
  115. package/dist/core/ForgePlatform.js +136 -0
  116. package/dist/core/ForgePlatform.js.map +1 -0
  117. package/dist/core/ForgeWebSocket.d.ts +56 -0
  118. package/dist/core/ForgeWebSocket.d.ts.map +1 -0
  119. package/dist/core/ForgeWebSocket.js +415 -0
  120. package/dist/core/ForgeWebSocket.js.map +1 -0
  121. package/dist/core/Ingress.d.ts +329 -0
  122. package/dist/core/Ingress.d.ts.map +1 -0
  123. package/dist/core/Ingress.js +694 -0
  124. package/dist/core/Ingress.js.map +1 -0
  125. package/dist/core/Interceptors.d.ts +134 -0
  126. package/dist/core/Interceptors.d.ts.map +1 -0
  127. package/dist/core/Interceptors.js +416 -0
  128. package/dist/core/Interceptors.js.map +1 -0
  129. package/dist/core/Logger.d.ts +20 -0
  130. package/dist/core/Logger.d.ts.map +1 -0
  131. package/dist/core/Logger.js +77 -0
  132. package/dist/core/Logger.js.map +1 -0
  133. package/dist/core/MessageBus.d.ts +15 -0
  134. package/dist/core/MessageBus.d.ts.map +1 -0
  135. package/dist/core/MessageBus.js +18 -0
  136. package/dist/core/MessageBus.js.map +1 -0
  137. package/dist/core/Prometheus.d.ts +80 -0
  138. package/dist/core/Prometheus.d.ts.map +1 -0
  139. package/dist/core/Prometheus.js +332 -0
  140. package/dist/core/Prometheus.js.map +1 -0
  141. package/dist/core/RequestContext.d.ts +214 -0
  142. package/dist/core/RequestContext.d.ts.map +1 -0
  143. package/dist/core/RequestContext.js +556 -0
  144. package/dist/core/RequestContext.js.map +1 -0
  145. package/dist/core/Router.d.ts +45 -0
  146. package/dist/core/Router.d.ts.map +1 -0
  147. package/dist/core/Router.js +285 -0
  148. package/dist/core/Router.js.map +1 -0
  149. package/dist/core/RoutingStrategy.d.ts +116 -0
  150. package/dist/core/RoutingStrategy.d.ts.map +1 -0
  151. package/dist/core/RoutingStrategy.js +306 -0
  152. package/dist/core/RoutingStrategy.js.map +1 -0
  153. package/dist/core/RpcConfig.d.ts +72 -0
  154. package/dist/core/RpcConfig.d.ts.map +1 -0
  155. package/dist/core/RpcConfig.js +127 -0
  156. package/dist/core/RpcConfig.js.map +1 -0
  157. package/dist/core/SignatureCache.d.ts +81 -0
  158. package/dist/core/SignatureCache.d.ts.map +1 -0
  159. package/dist/core/SignatureCache.js +172 -0
  160. package/dist/core/SignatureCache.js.map +1 -0
  161. package/dist/core/StaticFileServer.d.ts +34 -0
  162. package/dist/core/StaticFileServer.d.ts.map +1 -0
  163. package/dist/core/StaticFileServer.js +497 -0
  164. package/dist/core/StaticFileServer.js.map +1 -0
  165. package/dist/core/Supervisor.d.ts +198 -0
  166. package/dist/core/Supervisor.d.ts.map +1 -0
  167. package/dist/core/Supervisor.js +1418 -0
  168. package/dist/core/Supervisor.js.map +1 -0
  169. package/dist/core/ThreadAllocator.d.ts +52 -0
  170. package/dist/core/ThreadAllocator.d.ts.map +1 -0
  171. package/dist/core/ThreadAllocator.js +174 -0
  172. package/dist/core/ThreadAllocator.js.map +1 -0
  173. package/dist/core/WorkerChannelManager.d.ts +130 -0
  174. package/dist/core/WorkerChannelManager.d.ts.map +1 -0
  175. package/dist/core/WorkerChannelManager.js +956 -0
  176. package/dist/core/WorkerChannelManager.js.map +1 -0
  177. package/dist/core/config-enums.d.ts +41 -0
  178. package/dist/core/config-enums.d.ts.map +1 -0
  179. package/dist/core/config-enums.js +59 -0
  180. package/dist/core/config-enums.js.map +1 -0
  181. package/dist/core/config.d.ts +159 -0
  182. package/dist/core/config.d.ts.map +1 -0
  183. package/dist/core/config.js +694 -0
  184. package/dist/core/config.js.map +1 -0
  185. package/dist/core/host-config.d.ts +146 -0
  186. package/dist/core/host-config.d.ts.map +1 -0
  187. package/dist/core/host-config.js +312 -0
  188. package/dist/core/host-config.js.map +1 -0
  189. package/dist/core/ipc-errors.d.ts +27 -0
  190. package/dist/core/ipc-errors.d.ts.map +1 -0
  191. package/dist/core/ipc-errors.js +36 -0
  192. package/dist/core/ipc-errors.js.map +1 -0
  193. package/dist/core/network-utils.d.ts +35 -0
  194. package/dist/core/network-utils.d.ts.map +1 -0
  195. package/dist/core/network-utils.js +145 -0
  196. package/dist/core/network-utils.js.map +1 -0
  197. package/dist/core/platform-config.d.ts +142 -0
  198. package/dist/core/platform-config.d.ts.map +1 -0
  199. package/dist/core/platform-config.js +299 -0
  200. package/dist/core/platform-config.js.map +1 -0
  201. package/dist/decorators/ServiceProxy.d.ts +175 -0
  202. package/dist/decorators/ServiceProxy.d.ts.map +1 -0
  203. package/dist/decorators/ServiceProxy.js +969 -0
  204. package/dist/decorators/ServiceProxy.js.map +1 -0
  205. package/dist/decorators/index.d.ts +146 -0
  206. package/dist/decorators/index.d.ts.map +1 -0
  207. package/dist/decorators/index.js +545 -0
  208. package/dist/decorators/index.js.map +1 -0
  209. package/dist/deploy/NginxGenerator.d.ts +165 -0
  210. package/dist/deploy/NginxGenerator.d.ts.map +1 -0
  211. package/dist/deploy/NginxGenerator.js +781 -0
  212. package/dist/deploy/NginxGenerator.js.map +1 -0
  213. package/dist/deploy/PlatformManifestGenerator.d.ts +43 -0
  214. package/dist/deploy/PlatformManifestGenerator.d.ts.map +1 -0
  215. package/dist/deploy/PlatformManifestGenerator.js +80 -0
  216. package/dist/deploy/PlatformManifestGenerator.js.map +1 -0
  217. package/dist/deploy/RouteManifestGenerator.d.ts +42 -0
  218. package/dist/deploy/RouteManifestGenerator.d.ts.map +1 -0
  219. package/dist/deploy/RouteManifestGenerator.js +105 -0
  220. package/dist/deploy/RouteManifestGenerator.js.map +1 -0
  221. package/dist/deploy/index.d.ts +210 -0
  222. package/dist/deploy/index.d.ts.map +1 -0
  223. package/dist/deploy/index.js +918 -0
  224. package/dist/deploy/index.js.map +1 -0
  225. package/dist/frontend/FrontendDevLifecycle.d.ts +26 -0
  226. package/dist/frontend/FrontendDevLifecycle.d.ts.map +1 -0
  227. package/dist/frontend/FrontendDevLifecycle.js +60 -0
  228. package/dist/frontend/FrontendDevLifecycle.js.map +1 -0
  229. package/dist/frontend/FrontendPluginOrchestrator.d.ts +64 -0
  230. package/dist/frontend/FrontendPluginOrchestrator.d.ts.map +1 -0
  231. package/dist/frontend/FrontendPluginOrchestrator.js +167 -0
  232. package/dist/frontend/FrontendPluginOrchestrator.js.map +1 -0
  233. package/dist/frontend/SiteResolver.d.ts +33 -0
  234. package/dist/frontend/SiteResolver.d.ts.map +1 -0
  235. package/dist/frontend/SiteResolver.js +53 -0
  236. package/dist/frontend/SiteResolver.js.map +1 -0
  237. package/dist/frontend/StaticMountRegistry.d.ts +36 -0
  238. package/dist/frontend/StaticMountRegistry.d.ts.map +1 -0
  239. package/dist/frontend/StaticMountRegistry.js +94 -0
  240. package/dist/frontend/StaticMountRegistry.js.map +1 -0
  241. package/dist/frontend/index.d.ts +7 -0
  242. package/dist/frontend/index.d.ts.map +1 -0
  243. package/{src → dist}/frontend/index.js +4 -2
  244. package/dist/frontend/index.js.map +1 -0
  245. package/dist/frontend/pathUtils.d.ts +8 -0
  246. package/dist/frontend/pathUtils.d.ts.map +1 -0
  247. package/dist/frontend/pathUtils.js +17 -0
  248. package/dist/frontend/pathUtils.js.map +1 -0
  249. package/dist/frontend/plugins/index.d.ts +2 -0
  250. package/dist/frontend/plugins/index.d.ts.map +1 -0
  251. package/{src → dist}/frontend/plugins/index.js +1 -1
  252. package/dist/frontend/plugins/index.js.map +1 -0
  253. package/dist/frontend/plugins/viteFrontend.d.ts +51 -0
  254. package/dist/frontend/plugins/viteFrontend.d.ts.map +1 -0
  255. package/dist/frontend/plugins/viteFrontend.js +134 -0
  256. package/dist/frontend/plugins/viteFrontend.js.map +1 -0
  257. package/dist/frontend/types.d.ts +25 -0
  258. package/dist/frontend/types.d.ts.map +1 -0
  259. package/dist/frontend/types.js +2 -0
  260. package/dist/frontend/types.js.map +1 -0
  261. package/dist/index.d.ts +17 -0
  262. package/dist/index.d.ts.map +1 -0
  263. package/dist/index.js +32 -0
  264. package/dist/index.js.map +1 -0
  265. package/dist/internals.d.ts +21 -0
  266. package/dist/internals.d.ts.map +1 -0
  267. package/{src → dist}/internals.js +12 -14
  268. package/dist/internals.js.map +1 -0
  269. package/dist/plugins/PluginManager.d.ts +209 -0
  270. package/dist/plugins/PluginManager.d.ts.map +1 -0
  271. package/dist/plugins/PluginManager.js +365 -0
  272. package/dist/plugins/PluginManager.js.map +1 -0
  273. package/dist/plugins/ScopedPostgres.d.ts +78 -0
  274. package/dist/plugins/ScopedPostgres.d.ts.map +1 -0
  275. package/dist/plugins/ScopedPostgres.js +190 -0
  276. package/dist/plugins/ScopedPostgres.js.map +1 -0
  277. package/dist/plugins/ScopedRedis.d.ts +88 -0
  278. package/dist/plugins/ScopedRedis.d.ts.map +1 -0
  279. package/dist/plugins/ScopedRedis.js +169 -0
  280. package/dist/plugins/ScopedRedis.js.map +1 -0
  281. package/dist/plugins/index.d.ts +289 -0
  282. package/dist/plugins/index.d.ts.map +1 -0
  283. package/dist/plugins/index.js +1942 -0
  284. package/dist/plugins/index.js.map +1 -0
  285. package/dist/plugins/types.d.ts +59 -0
  286. package/dist/plugins/types.d.ts.map +1 -0
  287. package/dist/plugins/types.js +2 -0
  288. package/dist/plugins/types.js.map +1 -0
  289. package/dist/registry/ServiceRegistry.d.ts +305 -0
  290. package/dist/registry/ServiceRegistry.d.ts.map +1 -0
  291. package/dist/registry/ServiceRegistry.js +735 -0
  292. package/dist/registry/ServiceRegistry.js.map +1 -0
  293. package/dist/scaling/ScaleAdvisor.d.ts +214 -0
  294. package/dist/scaling/ScaleAdvisor.d.ts.map +1 -0
  295. package/dist/scaling/ScaleAdvisor.js +526 -0
  296. package/dist/scaling/ScaleAdvisor.js.map +1 -0
  297. package/dist/services/Service.d.ts +164 -0
  298. package/dist/services/Service.d.ts.map +1 -0
  299. package/dist/services/Service.js +106 -0
  300. package/dist/services/Service.js.map +1 -0
  301. package/dist/services/worker-bootstrap.d.ts +15 -0
  302. package/dist/services/worker-bootstrap.d.ts.map +1 -0
  303. package/dist/services/worker-bootstrap.js +744 -0
  304. package/dist/services/worker-bootstrap.js.map +1 -0
  305. package/dist/templates/auth-service.d.ts +42 -0
  306. package/dist/templates/auth-service.d.ts.map +1 -0
  307. package/dist/templates/auth-service.js +54 -0
  308. package/dist/templates/auth-service.js.map +1 -0
  309. package/dist/templates/identity-service.d.ts +50 -0
  310. package/dist/templates/identity-service.d.ts.map +1 -0
  311. package/dist/templates/identity-service.js +62 -0
  312. package/dist/templates/identity-service.js.map +1 -0
  313. package/dist/types/contract.d.ts +120 -0
  314. package/dist/types/contract.d.ts.map +1 -0
  315. package/dist/types/contract.js +69 -0
  316. package/dist/types/contract.js.map +1 -0
  317. package/package.json +79 -20
  318. package/src/core/DirectMessageBus.js +0 -364
  319. package/src/core/EndpointResolver.js +0 -259
  320. package/src/core/ForgeContext.js +0 -2236
  321. package/src/core/ForgeHost.js +0 -122
  322. package/src/core/ForgePlatform.js +0 -145
  323. package/src/core/Ingress.js +0 -768
  324. package/src/core/Interceptors.js +0 -420
  325. package/src/core/MessageBus.js +0 -321
  326. package/src/core/Prometheus.js +0 -305
  327. package/src/core/RequestContext.js +0 -413
  328. package/src/core/RoutingStrategy.js +0 -330
  329. package/src/core/Supervisor.js +0 -1349
  330. package/src/core/ThreadAllocator.js +0 -196
  331. package/src/core/WorkerChannelManager.js +0 -879
  332. package/src/core/config.js +0 -637
  333. package/src/core/host-config.js +0 -311
  334. package/src/core/network-utils.js +0 -166
  335. package/src/core/platform-config.js +0 -308
  336. package/src/decorators/ServiceProxy.js +0 -904
  337. package/src/decorators/index.js +0 -571
  338. package/src/deploy/NginxGenerator.js +0 -865
  339. package/src/deploy/PlatformManifestGenerator.js +0 -96
  340. package/src/deploy/RouteManifestGenerator.js +0 -112
  341. package/src/deploy/index.js +0 -984
  342. package/src/frontend/FrontendDevLifecycle.js +0 -65
  343. package/src/frontend/FrontendPluginOrchestrator.js +0 -187
  344. package/src/frontend/SiteResolver.js +0 -63
  345. package/src/frontend/StaticMountRegistry.js +0 -90
  346. package/src/frontend/plugins/viteFrontend.js +0 -79
  347. package/src/frontend/types.js +0 -35
  348. package/src/index.js +0 -58
  349. package/src/plugins/PluginManager.js +0 -537
  350. package/src/plugins/ScopedPostgres.js +0 -192
  351. package/src/plugins/ScopedRedis.js +0 -142
  352. package/src/plugins/index.js +0 -1756
  353. package/src/registry/ServiceRegistry.js +0 -797
  354. package/src/scaling/ScaleAdvisor.js +0 -442
  355. package/src/services/Service.js +0 -195
  356. package/src/services/worker-bootstrap.js +0 -679
  357. package/src/templates/auth-service.js +0 -65
  358. package/src/templates/identity-service.js +0 -75
@@ -1,364 +0,0 @@
1
- import { EventEmitter } from "node:events";
2
- import crypto from "node:crypto";
3
- import fs from "node:fs";
4
- import os from "node:os";
5
- import path from "node:path";
6
-
7
- const EXIT_SOCKET_DIRS = new Set();
8
- let EXIT_CLEANUP_INSTALLED = false;
9
-
10
- function cleanupSocketDir(socketDir) {
11
- try {
12
- if (fs.existsSync(socketDir)) {
13
- for (const f of fs.readdirSync(socketDir)) {
14
- try { fs.unlinkSync(path.join(socketDir, f)); } catch {}
15
- }
16
- try { fs.rmdirSync(socketDir); } catch {}
17
- }
18
- } catch {}
19
- }
20
-
21
- function installExitCleanupHook() {
22
- if (EXIT_CLEANUP_INSTALLED) return;
23
- EXIT_CLEANUP_INSTALLED = true;
24
- process.once("exit", () => {
25
- for (const socketDir of EXIT_SOCKET_DIRS) {
26
- cleanupSocketDir(socketDir);
27
- }
28
- EXIT_SOCKET_DIRS.clear();
29
- });
30
- }
31
-
32
- function isExpectedChannelCloseError(err) {
33
- if (!err) return false;
34
- const code = err.code;
35
- if (
36
- code === "EPIPE" ||
37
- code === "ECONNRESET" ||
38
- code === "ERR_IPC_CHANNEL_CLOSED" ||
39
- code === "ERR_IPC_DISCONNECTED"
40
- ) {
41
- return true;
42
- }
43
- const msg = String(err.message ?? "").toLowerCase();
44
- return (
45
- msg.includes("channel closed") ||
46
- msg.includes("ipc channel is already disconnected") ||
47
- msg.includes("broken pipe")
48
- );
49
- }
50
-
51
- function isWorkerSendable(worker) {
52
- if (!worker) return false;
53
- if (typeof worker.isDead === "function" && worker.isDead()) return false;
54
- if (typeof worker.isConnected === "function" && !worker.isConnected()) return false;
55
- if (worker.process?.connected === false) return false;
56
- return true;
57
- }
58
-
59
- /**
60
- * DirectMessageBus — Unix Domain Socket Mesh (Supervisor Side)
61
- *
62
- * The fundamental problem: cluster.Worker.send() can only transfer
63
- * sockets/servers, not MessagePort objects. So we can't use MessageChannel
64
- * for direct worker-to-worker communication in cluster mode.
65
- *
66
- * Solution: Each worker opens a Unix domain socket server. Workers connect
67
- * directly to each other via these sockets. The supervisor only tells
68
- * workers WHERE to connect (socket paths), then gets out of the way.
69
- *
70
- * Architecture:
71
- *
72
- * SETUP (supervisor involved briefly):
73
- * 1. Each worker starts a UDS server at /tmp/forge-{pid}/{service}-{worker}.sock
74
- * 2. Worker reports its socket path to supervisor via IPC
75
- * 3. Supervisor broadcasts the full socket registry to all workers
76
- * 4. Workers establish direct connections to each other
77
- *
78
- * RUNTIME (supervisor NOT involved in message routing):
79
- * Worker A ──UDS──► Worker B (direct, length-prefixed JSON)
80
- * Worker B ──UDS──► Worker A
81
- *
82
- * Supervisor only handles:
83
- * - Socket path registry distribution (one-time + on new workers)
84
- * - Health checks (periodic pull, not per-message)
85
- * - Worker lifecycle (restart, scale)
86
- */
87
- export class DirectMessageBus extends EventEmitter {
88
- constructor() {
89
- super();
90
-
91
- /** @type {Map<string, Array<{id: number, worker: object, mode: string, socketPath?: string}>>} */
92
- this.workers = new Map();
93
-
94
- /** @type {Map<string, string>} "serviceName:workerId" → socket path */
95
- this.socketRegistry = new Map();
96
-
97
- /** @type {Set<number>} Track registered worker IDs to prevent duplicate listeners */
98
- this._registeredWorkerIds = new Set();
99
-
100
- /** @type {WeakMap<object, Function>} Shared per-worker error handlers */
101
- this._workerErrorHandlers = new WeakMap();
102
-
103
- /** @type {Map<string, Set<string>>} CR-IPC-9: worker key → set of connected peer keys */
104
- this._connections = new Map();
105
-
106
- // S-IPC-2: Random suffix prevents socket path prediction
107
- this._socketDir = path.join(os.tmpdir(), `forge-${process.pid}-${crypto.randomBytes(4).toString('hex')}`);
108
- this._broadcastTimer = null;
109
- try {
110
- fs.mkdirSync(this._socketDir, { recursive: true });
111
- } catch (e) {
112
- if (e.code !== "EEXIST") console.error("[DirectMessageBus] mkdirSync failed:", e.message);
113
- }
114
- EXIT_SOCKET_DIRS.add(this._socketDir);
115
- installExitCleanupHook();
116
- }
117
-
118
- get socketDir() {
119
- return this._socketDir;
120
- }
121
-
122
- /**
123
- * Register a worker. Tell it to start a UDS server, then
124
- * broadcast the updated registry to all workers.
125
- */
126
- registerWorker(serviceName, worker, mode = "cluster") {
127
- if (!this.workers.has(serviceName)) {
128
- this.workers.set(serviceName, []);
129
- }
130
-
131
- const workerId = mode === "cluster" ? worker.id : worker.threadId;
132
-
133
- const entry = {
134
- id: workerId,
135
- worker,
136
- mode,
137
- socketPath: null,
138
- };
139
-
140
- this.workers.get(serviceName).push(entry);
141
-
142
- // Attach one shared error listener per worker so late IPC channel errors
143
- // during churn don't surface as unhandled EventEmitter errors.
144
- if (!this._workerErrorHandlers.has(worker)) {
145
- const errorHandler = (err) => {
146
- if (!isExpectedChannelCloseError(err)) {
147
- console.error("[DirectMessageBus] Worker error:", err?.message ?? err);
148
- }
149
- };
150
- this._workerErrorHandlers.set(worker, errorHandler);
151
- worker.on("error", errorHandler);
152
- }
153
- entry._errorHandler = this._workerErrorHandlers.get(worker);
154
-
155
- // Prevent duplicate message listener registration
156
- // If workerId is already registered (e.g., crashed worker's ID reused), clean up stale listeners
157
- if (this._registeredWorkerIds.has(workerId)) {
158
- // Find and clean up stale entry with this workerId across all services
159
- for (const [svcName, entries] of this.workers) {
160
- const staleIdx = entries.findIndex((e) => e.id === workerId && e.worker !== worker);
161
- if (staleIdx !== -1) {
162
- const stale = entries[staleIdx];
163
- if (stale._messageHandler) stale.worker.off("message", stale._messageHandler);
164
- if (stale.socketPath) {
165
- try { fs.unlinkSync(stale.socketPath); } catch {}
166
- this.socketRegistry.delete(`${svcName}:${workerId}`);
167
- }
168
- entries.splice(staleIdx, 1);
169
- if (entries.length === 0) this.workers.delete(svcName);
170
- }
171
- }
172
- this._registeredWorkerIds.delete(workerId);
173
- }
174
- this._registeredWorkerIds.add(workerId);
175
-
176
- // Listen for supervisor-level IPC from this worker
177
- const messageHandler = (msg) => {
178
- if (!msg || !msg.type) return;
179
-
180
- switch (msg.type) {
181
- case "forge:socket-ready":
182
- // In colocated groups a worker may host multiple services, each with its
183
- // own socket. Ignore ready messages for sibling services on this entry.
184
- if (msg.serviceName && msg.serviceName !== serviceName) break;
185
- entry.socketPath = msg.socketPath;
186
- this.socketRegistry.set(`${serviceName}:${msg.workerId}`, msg.socketPath);
187
- this._scheduleBroadcast();
188
- break;
189
-
190
- case "forge:worker-ready":
191
- this._sendInitSocket(worker, serviceName, entry.id);
192
- this._sendRegistryTo(worker);
193
- break;
194
-
195
- case "forge:channel-ready": {
196
- // CR-IPC-9: Track established connections
197
- const workerKey = `${serviceName}:${entry.id}`;
198
- if (!this._connections.has(workerKey)) {
199
- this._connections.set(workerKey, new Set());
200
- }
201
- if (msg.peerKey) {
202
- this._connections.get(workerKey).add(msg.peerKey);
203
- }
204
- break;
205
- }
206
-
207
- case "forge:metric":
208
- case "forge:log":
209
- case "forge:health-response": {
210
- const eventName = msg.type.replace("forge:", "");
211
- this.emit(eventName, { service: serviceName, ...msg });
212
- break;
213
- }
214
- }
215
- };
216
- entry._messageHandler = messageHandler;
217
- worker.on("message", messageHandler);
218
-
219
- // Tell the new worker to start its UDS server
220
- this._sendInitSocket(worker, serviceName, entry.id);
221
- this._sendRegistryTo(worker);
222
- }
223
-
224
- _scheduleBroadcast() {
225
- // CR-IPC-5: Don't cancel existing timer — let the first registration broadcast on schedule
226
- if (this._broadcastTimer) return;
227
- this._broadcastTimer = setTimeout(() => {
228
- this._broadcastTimer = null;
229
- this._broadcastRegistry();
230
- }, 50 + Math.floor(Math.random() * 50));
231
- }
232
-
233
- _sendInitSocket(worker, serviceName, workerId) {
234
- if (!isWorkerSendable(worker)) return;
235
- try {
236
- worker.send({
237
- type: "forge:init-socket",
238
- socketDir: this._socketDir,
239
- serviceName,
240
- workerId,
241
- });
242
- } catch (e) {
243
- if (!isExpectedChannelCloseError(e)) {
244
- console.error("[DirectMessageBus] Failed to send init-socket:", e.message);
245
- }
246
- }
247
- }
248
-
249
- _sendRegistryTo(worker) {
250
- if (!isWorkerSendable(worker)) return;
251
- try {
252
- const msg = {
253
- type: "forge:socket-registry",
254
- registry: Object.fromEntries(this.socketRegistry),
255
- };
256
- // S-IPC-1: Include cluster secret for handshake authentication
257
- const secret = process.env.FORGE_CLUSTER_SECRET;
258
- if (secret) {
259
- msg.secret = secret;
260
- }
261
- worker.send(msg);
262
- } catch (e) {
263
- if (!isExpectedChannelCloseError(e)) {
264
- console.error("[DirectMessageBus] Failed to send registry:", e.message);
265
- }
266
- }
267
- }
268
-
269
- _broadcastRegistry() {
270
- for (const [, workers] of this.workers) {
271
- for (const entry of workers) {
272
- this._sendRegistryTo(entry.worker);
273
- }
274
- }
275
- }
276
-
277
- unregisterWorker(serviceName, workerId, options = {}) {
278
- const workers = this.workers.get(serviceName);
279
- if (!workers) return;
280
-
281
- const idx = workers.findIndex((w) => w.id === workerId);
282
- if (idx === -1) return;
283
-
284
- const entry = workers[idx];
285
- if (entry._messageHandler) {
286
- entry.worker.off("message", entry._messageHandler);
287
- }
288
- if (entry.socketPath) {
289
- try {
290
- fs.unlinkSync(entry.socketPath);
291
- } catch (e) {
292
- if (e.code !== "ENOENT") console.error("[DirectMessageBus] unlink failed:", e.message);
293
- }
294
- this.socketRegistry.delete(`${serviceName}:${workerId}`);
295
- }
296
- workers.splice(idx, 1);
297
-
298
- // Clean up the registered worker ID tracking
299
- this._registeredWorkerIds.delete(workerId);
300
-
301
- if (workers.length === 0) {
302
- this.workers.delete(serviceName);
303
- }
304
-
305
- if (!options.suppressBroadcast && this.workers.size > 0) {
306
- this._broadcastRegistry();
307
- }
308
- }
309
-
310
- unregisterService(serviceName) {
311
- const workers = [...(this.workers.get(serviceName) ?? [])];
312
- for (const w of workers) {
313
- this.unregisterWorker(serviceName, w.id);
314
- }
315
- }
316
-
317
- requestHealthChecks() {
318
- for (const [, workers] of this.workers) {
319
- for (const entry of workers) {
320
- if (!isWorkerSendable(entry.worker)) continue;
321
- try {
322
- entry.worker.send({ type: "forge:health-check", timestamp: Date.now() });
323
- } catch (err) {
324
- if (!isExpectedChannelCloseError(err)) {
325
- console.error("[DirectMessageBus] Failed to send health-check:", err.message);
326
- }
327
- }
328
- }
329
- }
330
- }
331
-
332
- stats() {
333
- const result = {};
334
- for (const [name, workers] of this.workers) {
335
- result[name] = {
336
- workerCount: workers.length,
337
- ids: workers.map((w) => w.id),
338
- directSockets: workers.filter((w) => w.socketPath).length,
339
- };
340
- }
341
- return result;
342
- }
343
-
344
- /**
345
- * CR-IPC-9: Get the connection matrix for debugging.
346
- * @returns {Object<string, string[]>} worker key → array of connected peer keys
347
- */
348
- getConnectionMatrix() {
349
- const result = {};
350
- for (const [key, peers] of this._connections) {
351
- result[key] = [...peers];
352
- }
353
- return result;
354
- }
355
-
356
- cleanup() {
357
- if (this._broadcastTimer) {
358
- clearTimeout(this._broadcastTimer);
359
- this._broadcastTimer = null;
360
- }
361
- cleanupSocketDir(this._socketDir);
362
- EXIT_SOCKET_DIRS.delete(this._socketDir);
363
- }
364
- }
@@ -1,259 +0,0 @@
1
- /**
2
- * EndpointResolver
3
- *
4
- * Resolves service names to { host, port, remote } endpoints.
5
- * Supports:
6
- * - Single endpoints (one instance per service)
7
- * - Multi-instance arrays (round-robin selection)
8
- * - Dynamic updates via set/remove (used by ServiceRegistry)
9
- * - Fallback from FORGE_SERVICE_ENDPOINTS to FORGE_SERVICE_PORTS
10
- * - C3: File fallback via FORGE_SERVICE_ENDPOINTS_FILE for large maps
11
- */
12
- import { readFileSync } from "node:fs";
13
-
14
- /** Validate that an endpoint has the expected shape */
15
- function validateEndpoint(ep) {
16
- if (!ep || typeof ep !== 'object') return false;
17
- if (typeof ep.host !== 'string' || !ep.host) return false;
18
- if (ep.port !== undefined && (typeof ep.port !== 'number' || ep.port < 1 || ep.port > 65535)) return false;
19
- return true;
20
- }
21
-
22
- export class EndpointResolver {
23
- constructor() {
24
- /** @type {Map<string, Array<{host: string, port: number, remote: boolean}>>} */
25
- this._endpoints = new Map();
26
-
27
- /** @type {Map<string, number>} round-robin counters */
28
- this._counters = new Map();
29
-
30
- /** @type {Map<string, Object>} per-service routing strategies (optional override) */
31
- this._strategies = new Map();
32
-
33
- // P-10: Cache wrapped endpoint entries to avoid per-resolve() allocation
34
- /** @type {Map<string, Array<{key: string, host: string, port: number, remote: boolean}>>} */
35
- this._wrappedCache = new Map();
36
- }
37
-
38
- /**
39
- * Set a routing strategy for a specific service.
40
- * When set, resolve() delegates to the strategy's pick() method
41
- * instead of using built-in round-robin.
42
- *
43
- * The strategy must implement pick(entries, callContext) where entries
44
- * are wrapped as { key: "host:port", host, port, remote }.
45
- *
46
- * @param {string} serviceName
47
- * @param {Object} strategy - Must have a pick(workers, callContext) method
48
- */
49
- setStrategy(serviceName, strategy) {
50
- if (!strategy || typeof strategy.pick !== 'function') {
51
- throw new Error(`Strategy for "${serviceName}" must have a pick() method`);
52
- }
53
- this._strategies.set(serviceName, strategy);
54
- }
55
-
56
- /**
57
- * Create an EndpointResolver from environment variables.
58
- *
59
- * Parses FORGE_SERVICE_ENDPOINTS first (full endpoint map with host/port/remote).
60
- * Falls back to FORGE_SERVICE_PORTS (localhost-only port map) when endpoints
61
- * aren't available.
62
- *
63
- * @returns {EndpointResolver}
64
- */
65
- static fromEnv() {
66
- const resolver = new EndpointResolver();
67
-
68
- // C3: Support file fallback for large endpoint maps (written by Supervisor)
69
- let endpointsJson = process.env.FORGE_SERVICE_ENDPOINTS;
70
- if (!endpointsJson && process.env.FORGE_SERVICE_ENDPOINTS_FILE) {
71
- try {
72
- endpointsJson = readFileSync(process.env.FORGE_SERVICE_ENDPOINTS_FILE, "utf8");
73
- } catch (e) {
74
- console.error("[EndpointResolver] Failed to read FORGE_SERVICE_ENDPOINTS_FILE:", e.message);
75
- }
76
- }
77
- if (endpointsJson) {
78
- try {
79
- const parsed = JSON.parse(endpointsJson);
80
- for (const [name, value] of Object.entries(parsed)) {
81
- if (Array.isArray(value)) {
82
- const valid = value.filter(ep => validateEndpoint(ep));
83
- if (valid.length > 0) resolver._endpoints.set(name, valid);
84
- } else if (validateEndpoint(value)) {
85
- resolver._endpoints.set(name, [value]);
86
- }
87
- }
88
- } catch (e) {
89
- console.error("[EndpointResolver] Failed to parse FORGE_SERVICE_ENDPOINTS:", e.message);
90
- }
91
- }
92
-
93
- // Merge FORGE_SERVICE_PORTS as fallback for any services not in endpoints
94
- const portsJson = process.env.FORGE_SERVICE_PORTS;
95
- if (portsJson) {
96
- try {
97
- const ports = JSON.parse(portsJson);
98
- for (const [name, port] of Object.entries(ports)) {
99
- const p = Number(port);
100
- if (!resolver._endpoints.has(name) && Number.isInteger(p) && p >= 1 && p <= 65535) {
101
- resolver._endpoints.set(name, [{ host: "127.0.0.1", port: p, remote: false }]);
102
- }
103
- }
104
- } catch (e) {
105
- console.error("[EndpointResolver] Failed to parse FORGE_SERVICE_PORTS:", e.message);
106
- }
107
- }
108
-
109
- return resolver;
110
- }
111
-
112
- /**
113
- * Resolve an endpoint for a service.
114
- * Delegates to a configured RoutingStrategy when available,
115
- * otherwise round-robins across instances.
116
- *
117
- * @param {string} serviceName
118
- * @param {Object} [callContext] - Optional call context passed to strategy.pick()
119
- * @returns {{ host: string, port: number, remote: boolean } | null}
120
- */
121
- resolve(serviceName, callContext) {
122
- const endpoints = this._endpoints.get(serviceName);
123
- if (!endpoints || endpoints.length === 0) return null;
124
-
125
- if (endpoints.length === 1) return endpoints[0];
126
-
127
- // Delegate to configured strategy if present
128
- const strategy = this._strategies.get(serviceName);
129
- if (strategy) {
130
- // P-10: Use cached wrapped entries to avoid per-call allocation
131
- let entries = this._wrappedCache.get(serviceName);
132
- if (!entries) {
133
- entries = endpoints.map(ep => ({
134
- key: `${ep.host}:${ep.port}`,
135
- host: ep.host,
136
- port: ep.port,
137
- remote: ep.remote,
138
- }));
139
- this._wrappedCache.set(serviceName, entries);
140
- }
141
- const picked = strategy.pick(entries, callContext);
142
- if (picked) {
143
- // Handle broadcast (returns array) — return first for resolve(), use all() for broadcast
144
- if (Array.isArray(picked)) return picked[0] ?? null;
145
- return { host: picked.host, port: picked.port, remote: picked.remote };
146
- }
147
- // Strategy returned null — fall through to round-robin
148
- }
149
-
150
- // Round-robin: read-then-write is atomic in single-threaded Node.js
151
- // (no microtask or I/O can interleave between get and set)
152
- const idx = (this._counters.get(serviceName) ?? 0);
153
- this._counters.set(serviceName, (idx + 1) % 1_000_000_000);
154
- const endpoint = endpoints[idx % endpoints.length];
155
- return endpoint;
156
- }
157
-
158
- /**
159
- * Get all endpoints for a service (used for broadcast/event delivery).
160
- *
161
- * @param {string} serviceName
162
- * @returns {Array<{ host: string, port: number, remote: boolean }>}
163
- */
164
- all(serviceName) {
165
- return (this._endpoints.get(serviceName) ?? []).map(e => ({ ...e }));
166
- }
167
-
168
- /**
169
- * Add or update an endpoint for a service.
170
- * Used by dynamic registry discovery.
171
- *
172
- * @param {string} serviceName
173
- * @param {{ host: string, port: number, remote?: boolean }} endpoint
174
- */
175
- set(serviceName, endpoint) {
176
- if (endpoint.port !== undefined && (!Number.isInteger(endpoint.port) || endpoint.port < 1 || endpoint.port > 65535)) {
177
- throw new Error(`Invalid port for service "${serviceName}": ${endpoint.port}`);
178
- }
179
- const ep = { remote: true, ...endpoint };
180
- const existing = this._endpoints.get(serviceName) ?? [];
181
-
182
- // Replace if same host:port exists, otherwise append
183
- const idx = existing.findIndex((e) => e.host === ep.host && e.port === ep.port);
184
- if (idx !== -1) {
185
- existing[idx] = ep;
186
- } else {
187
- existing.push(ep);
188
- }
189
-
190
- this._endpoints.set(serviceName, existing);
191
- // P-10: Invalidate wrapped cache when endpoints change
192
- this._wrappedCache.delete(serviceName);
193
- }
194
-
195
- /**
196
- * Remove a specific instance of a service.
197
- * Used when a remote node goes down.
198
- *
199
- * @param {string} serviceName
200
- * @param {string} host
201
- * @param {number} port
202
- */
203
- remove(serviceName, host, port) {
204
- const existing = this._endpoints.get(serviceName);
205
- if (!existing) return;
206
-
207
- const filtered = existing.filter((e) => !(e.host === host && e.port === port));
208
- // P-10: Invalidate wrapped cache when endpoints change
209
- this._wrappedCache.delete(serviceName);
210
- if (filtered.length === 0) {
211
- this._endpoints.delete(serviceName);
212
- // CR-IPC-7: Clean up counter when all endpoints removed
213
- this._counters.delete(serviceName);
214
- } else {
215
- this._endpoints.set(serviceName, filtered);
216
- // Let counter naturally wrap via modulo in resolve()
217
- }
218
- }
219
-
220
- /**
221
- * COR-C1: Acquire a pending slot for a resolved endpoint.
222
- * Delegates to the service's routing strategy if it supports acquire().
223
- * Must be paired with releaseEndpoint() in a finally block.
224
- *
225
- * @param {string} serviceName
226
- * @param {string} endpointKey - "host:port" key from resolve()
227
- */
228
- acquireEndpoint(serviceName, endpointKey) {
229
- const strategy = this._strategies.get(serviceName);
230
- if (strategy && typeof strategy.acquire === 'function') {
231
- strategy.acquire(endpointKey);
232
- }
233
- }
234
-
235
- /**
236
- * COR-C1: Release a pending slot for a resolved endpoint.
237
- * Delegates to the service's routing strategy if it supports release().
238
- *
239
- * @param {string} serviceName
240
- * @param {string} endpointKey - "host:port" key from resolve()
241
- */
242
- releaseEndpoint(serviceName, endpointKey) {
243
- const strategy = this._strategies.get(serviceName);
244
- if (strategy && typeof strategy.release === 'function') {
245
- strategy.release(endpointKey);
246
- }
247
- }
248
-
249
- /**
250
- * Check if a service has any known endpoints.
251
- *
252
- * @param {string} serviceName
253
- * @returns {boolean}
254
- */
255
- has(serviceName) {
256
- const eps = this._endpoints.get(serviceName);
257
- return eps != null && eps.length > 0;
258
- }
259
- }