threadforge 0.1.1 → 0.2.1

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 +52 -20
  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 +78 -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,321 +0,0 @@
1
- import { EventEmitter } from "node:events";
2
-
3
- /**
4
- * MessageBus
5
- *
6
- * @deprecated Use DirectMessageBus instead. MessageBus uses older cluster IPC
7
- * with round-robin dispatch. DirectMessageBus provides Unix Domain Socket mesh
8
- * for direct worker-to-worker communication with better performance.
9
- *
10
- * Provides inter-service communication within a ThreadForge runtime.
11
- * Uses IPC channels for cluster workers and MessagePort for worker_threads.
12
- *
13
- * Features:
14
- * - Named channels per service
15
- * - Round-robin dispatch across workers of the same service
16
- * - Request/response pattern support
17
- * - Broadcast to all workers of a service
18
- */
19
- export class MessageBus extends EventEmitter {
20
- constructor() {
21
- super();
22
- console.warn('[ThreadForge] MessageBus is deprecated. Use DirectMessageBus instead.');
23
-
24
- /** @type {Map<string, Array<{pid: number, send: Function}>>} */
25
- this.channels = new Map();
26
-
27
- /** @type {Map<string, number>} round-robin index per service */
28
- this.rrIndex = new Map();
29
-
30
- /** @type {Map<string, {resolve: Function, reject: Function, timer: NodeJS.Timeout}>} */
31
- this.pendingRequests = new Map();
32
-
33
- this.requestCounter = 0;
34
- }
35
-
36
- /**
37
- * Register a worker for a service.
38
- *
39
- * @param {string} serviceName
40
- * @param {Object} worker - cluster.Worker or worker_threads.Worker
41
- * @param {'cluster' | 'worker'} mode
42
- */
43
- registerWorker(serviceName, worker, mode = "cluster") {
44
- if (!this.channels.has(serviceName)) {
45
- this.channels.set(serviceName, []);
46
- this.rrIndex.set(serviceName, 0);
47
- }
48
-
49
- const channel = this.channels.get(serviceName);
50
-
51
- if (mode === "cluster") {
52
- channel.push({
53
- pid: worker.id,
54
- send: (msg) => worker.send(msg),
55
- worker,
56
- });
57
-
58
- // Listen for messages from this worker and route them
59
- worker.on("message", (msg) => this._handleMessage(serviceName, msg, worker));
60
- } else {
61
- // worker_threads mode
62
- channel.push({
63
- pid: worker.threadId,
64
- send: (msg) => worker.postMessage(msg),
65
- worker,
66
- });
67
-
68
- worker.on("message", (msg) => this._handleMessage(serviceName, msg, worker));
69
- }
70
- }
71
-
72
- /**
73
- * Remove all workers for a service.
74
- */
75
- unregisterService(serviceName) {
76
- this.channels.delete(serviceName);
77
- this.rrIndex.delete(serviceName);
78
- }
79
-
80
- /**
81
- * Remove a specific worker.
82
- */
83
- unregisterWorker(serviceName, pidOrThreadId) {
84
- const channel = this.channels.get(serviceName);
85
- if (!channel) return;
86
-
87
- const idx = channel.findIndex((c) => c.pid === pidOrThreadId);
88
- if (idx !== -1) {
89
- channel.splice(idx, 1);
90
- }
91
-
92
- // F-6: Reject pending requests dispatched to the dead worker immediately
93
- // instead of making callers wait for the full timeout.
94
- for (const [reqId, entry] of this.pendingRequests) {
95
- if (entry.targetPid === pidOrThreadId) {
96
- clearTimeout(entry.timer);
97
- this.pendingRequests.delete(reqId);
98
- entry.reject(new Error(`Worker ${pidOrThreadId} for "${serviceName}" exited while handling request`));
99
- }
100
- }
101
-
102
- if (channel.length === 0) {
103
- this.channels.delete(serviceName);
104
- this.rrIndex.delete(serviceName);
105
- } else {
106
- // CR-IPC-12: Use modulo to preserve distribution during scale-down
107
- const currentIdx = this.rrIndex.get(serviceName) ?? 0;
108
- if (currentIdx >= channel.length) {
109
- this.rrIndex.set(serviceName, currentIdx % channel.length);
110
- }
111
- }
112
- }
113
-
114
- /**
115
- * Send a fire-and-forget message to a service (round-robin across its workers).
116
- *
117
- * @param {string} from - Sending service name
118
- * @param {string} target - Target service name
119
- * @param {*} payload - Message payload (must be structured-cloneable)
120
- */
121
- send(from, target, payload) {
122
- const channel = this.channels.get(target);
123
- if (!channel || channel.length === 0) {
124
- throw new Error(`[MessageBus] No workers registered for service "${target}"`);
125
- }
126
-
127
- const idx = this.rrIndex.get(target);
128
- const worker = channel[idx % channel.length];
129
- this.rrIndex.set(target, (idx + 1) % 1_000_000_000);
130
-
131
- // CR-IPC-6: Let send errors propagate to callers
132
- worker.send({
133
- type: "forge:message",
134
- from,
135
- payload,
136
- });
137
- }
138
-
139
- /**
140
- * Broadcast a message to ALL workers of a service.
141
- */
142
- broadcast(from, target, payload) {
143
- const channel = this.channels.get(target);
144
- if (!channel || channel.length === 0) {
145
- throw new Error(`[MessageBus] No workers registered for service "${target}"`);
146
- }
147
-
148
- let successCount = 0;
149
- for (const worker of channel) {
150
- try {
151
- worker.send({
152
- type: "forge:message",
153
- from,
154
- payload,
155
- });
156
- successCount++;
157
- } catch (err) {
158
- this.logger?.warn?.(`Broadcast failed for worker ${worker.pid}: ${err.message}`);
159
- }
160
- }
161
- return successCount;
162
- }
163
-
164
- /**
165
- * Broadcast a message to ALL services (all workers).
166
- */
167
- broadcastAll(from, payload) {
168
- for (const [serviceName] of this.channels) {
169
- if (serviceName !== from) {
170
- this.broadcast(from, serviceName, payload);
171
- }
172
- }
173
- }
174
-
175
- /**
176
- * Send a request and wait for a response.
177
- * Implements a request/reply pattern over IPC.
178
- *
179
- * @param {string} from
180
- * @param {string} target
181
- * @param {*} payload
182
- * @param {number} [timeoutMs=5000]
183
- * @returns {Promise<*>}
184
- */
185
- request(from, target, payload, timeoutMs = 5000) {
186
- const channel = this.channels.get(target);
187
- if (!channel || channel.length === 0) {
188
- return Promise.reject(new Error(`[MessageBus] No workers registered for service "${target}"`));
189
- }
190
-
191
- if (this.pendingRequests.size >= 10000) {
192
- return Promise.reject(new Error(
193
- `Too many pending requests (${this.pendingRequests.size}). Target service "${target}" may be unresponsive.`
194
- ));
195
- }
196
-
197
- this.requestCounter = (this.requestCounter + 1) % 1_000_000_000;
198
- const requestId = `req_sup_${this.requestCounter}_${Date.now()}_${Math.random().toString(36).slice(2, 18)}`;
199
-
200
- return new Promise((resolve, reject) => {
201
- const timer = setTimeout(() => {
202
- this.pendingRequests.delete(requestId);
203
- reject(new Error(`[MessageBus] Request to "${target}" timed out after ${timeoutMs}ms`));
204
- }, timeoutMs);
205
-
206
- const idx = this.rrIndex.get(target);
207
- const worker = channel[idx % channel.length];
208
-
209
- // F-6: Track target PID so pending requests can be rejected on worker death
210
- this.pendingRequests.set(requestId, { resolve, reject, timer, targetPid: worker.pid });
211
- this.rrIndex.set(target, (idx + 1) % 1_000_000_000);
212
-
213
- try {
214
- worker.send({
215
- type: "forge:request",
216
- requestId,
217
- from,
218
- payload,
219
- });
220
- } catch (err) {
221
- clearTimeout(timer);
222
- this.pendingRequests.delete(requestId);
223
- reject(err);
224
- }
225
- });
226
- }
227
-
228
- /**
229
- * Handle an incoming message from a worker.
230
- */
231
- _handleMessage(sourceName, msg, callerWorker) {
232
- if (!msg || !msg.type) return;
233
-
234
- switch (msg.type) {
235
- case "forge:send": {
236
- // Worker wants to send to another service
237
- this.send(sourceName, msg.target, msg.payload);
238
- break;
239
- }
240
-
241
- case "forge:broadcast": {
242
- this.broadcast(sourceName, msg.target, msg.payload);
243
- break;
244
- }
245
-
246
- case "forge:request": {
247
- // Worker wants to make a request to another service
248
- this.request(sourceName, msg.target, msg.payload, msg.timeout)
249
- .then((result) => {
250
- // Send response only to the requesting worker (not all workers)
251
- try {
252
- callerWorker.send({
253
- type: "forge:response",
254
- requestId: msg.requestId,
255
- payload: result,
256
- error: null,
257
- });
258
- } catch (sendErr) {
259
- console.error('[MessageBus] Failed to send response:', sendErr.message);
260
- }
261
- })
262
- .catch((err) => {
263
- try {
264
- callerWorker.send({
265
- type: "forge:response",
266
- requestId: msg.requestId,
267
- payload: null,
268
- error: { message: err.message, code: err.code, statusCode: err.statusCode },
269
- });
270
- } catch (sendErr) {
271
- console.error('[MessageBus] Failed to send error response:', sendErr.message);
272
- }
273
- });
274
- break;
275
- }
276
-
277
- case "forge:response": {
278
- // Response to a pending request
279
- const pending = this.pendingRequests.get(msg.requestId);
280
- if (pending) {
281
- clearTimeout(pending.timer);
282
- this.pendingRequests.delete(msg.requestId);
283
- if (msg.error) {
284
- const errObj = typeof msg.error === 'object' ? msg.error : { message: msg.error };
285
- const err = new Error(errObj.message);
286
- if (errObj.code) err.code = errObj.code;
287
- if (errObj.statusCode) err.statusCode = errObj.statusCode;
288
- pending.reject(err);
289
- } else {
290
- pending.resolve(msg.payload);
291
- }
292
- }
293
- break;
294
- }
295
-
296
- case "forge:metric": {
297
- this.emit("metric", { service: sourceName, ...msg });
298
- break;
299
- }
300
-
301
- case "forge:log": {
302
- this.emit("log", { service: sourceName, ...msg });
303
- break;
304
- }
305
- }
306
- }
307
-
308
- /**
309
- * Get stats about registered channels.
310
- */
311
- stats() {
312
- const result = {};
313
- for (const [name, workers] of this.channels) {
314
- result[name] = {
315
- workerCount: workers.length,
316
- pids: workers.map((w) => w.pid),
317
- };
318
- }
319
- return result;
320
- }
321
- }
@@ -1,305 +0,0 @@
1
- /**
2
- * Prometheus Metrics
3
- *
4
- * Proper Prometheus exposition format. Auto-tracks:
5
- *
6
- * forge_http_request_duration_seconds — histogram, per route + status + tenant
7
- * forge_http_requests_total — counter, per route + status + tenant
8
- * forge_http_active_requests — gauge, current in-flight
9
- * forge_rpc_duration_seconds — histogram, per target + method
10
- * forge_rpc_total — counter, per target + method + success/error
11
- * forge_circuit_breaker_state — gauge, 0=closed 1=open 2=half-open
12
- * forge_concurrency_limit — gauge, per-service adaptive limit
13
- * forge_concurrency_in_flight — gauge, per-service in-flight
14
- * forge_ws_connections_active — gauge, current WebSocket connections
15
- * forge_ws_messages_total — counter, WebSocket messages
16
- *
17
- * GET /metrics → text/plain Prometheus format
18
- *
19
- * Usage:
20
- * const metrics = new PrometheusMetrics('users', 0);
21
- * metrics.httpRequestDuration(0.045, { method: 'GET', path: '/users/:id', status: 200, tenant_id: 't_acme' });
22
- * metrics.expose() → "# HELP forge_http_request_duration_seconds ..."
23
- */
24
-
25
- const ALLOWED_HTTP_LABELS = new Set(["method", "status", "route_pattern", "service", "status_code"]);
26
- const ALLOWED_RPC_LABELS = new Set(["target", "method", "result", "service"]);
27
-
28
- /** P2: Normalize error codes to a finite set to prevent cardinality bombs */
29
- export function normalizeErrorCode(err) {
30
- if (err.code === 'ETIMEDOUT' || err.code === 'TIMEOUT') return 'timeout';
31
- if (err.code === 'ECONNREFUSED' || err.code === 'ECONNRESET') return 'connection';
32
- if (err.statusCode >= 500) return 'server_error';
33
- if (err.statusCode >= 400) return 'client_error';
34
- if (err.code === 'CIRCUIT_OPEN') return 'circuit_open';
35
- return 'unknown';
36
- }
37
-
38
- /** P9: Cache label key string construction */
39
- const _labelKeyCache = new Map();
40
- const MAX_LABEL_KEY_CACHE = 10000;
41
-
42
- function filterLabels(labels, allowed) {
43
- const filtered = {};
44
- for (const key of Object.keys(labels)) {
45
- if (allowed.has(key)) filtered[key] = labels[key];
46
- }
47
- return filtered;
48
- }
49
-
50
- export class PrometheusMetrics {
51
- constructor(serviceName, workerId) {
52
- this.serviceName = serviceName;
53
- this.workerId = workerId;
54
- this.startTime = Date.now();
55
-
56
- // Histogram buckets (seconds)
57
- this.httpBuckets = [0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1, 2.5, 5, 10];
58
- this.rpcBuckets = [0.001, 0.005, 0.01, 0.025, 0.05, 0.1, 0.5, 1, 5];
59
-
60
- /** @type {Map<string, number>} */
61
- this._counters = new Map();
62
- /** @type {Map<string, number>} */
63
- this._gauges = new Map();
64
- /** @type {Map<string, {count: number, sum: number, buckets: number[], bucketBounds: number[]}>} */
65
- this._histograms = new Map();
66
- }
67
-
68
- // ── Auto-tracked methods ──
69
-
70
- httpRequestStart() {
71
- this._gaugeInc("forge_http_active_requests", { service: this.serviceName });
72
- }
73
-
74
- httpRequestEnd(durationSecs, labels = {}) {
75
- const filtered = filterLabels({ service: this.serviceName, ...labels }, ALLOWED_HTTP_LABELS);
76
- this._gaugeDec("forge_http_active_requests", { service: this.serviceName });
77
- this._histogramObserve(
78
- "forge_http_request_duration_seconds",
79
- durationSecs,
80
- filtered,
81
- this.httpBuckets,
82
- );
83
- this._counterInc("forge_http_requests_total", filtered);
84
- }
85
-
86
- rpcCall(durationSecs, labels = {}) {
87
- const filtered = filterLabels({ service: this.serviceName, ...labels }, ALLOWED_RPC_LABELS);
88
- this._histogramObserve(
89
- "forge_rpc_duration_seconds",
90
- durationSecs,
91
- {
92
- source: this.serviceName,
93
- ...filtered,
94
- },
95
- this.rpcBuckets,
96
- );
97
- this._counterInc("forge_rpc_total", {
98
- source: this.serviceName,
99
- ...filtered,
100
- });
101
- }
102
-
103
- wsConnectionOpen() {
104
- this._gaugeInc("forge_ws_connections_active", { service: this.serviceName });
105
- }
106
-
107
- wsConnectionClose() {
108
- this._gaugeDec("forge_ws_connections_active", { service: this.serviceName });
109
- }
110
-
111
- wsMessage(direction = "inbound") {
112
- this._counterInc("forge_ws_messages_total", {
113
- service: this.serviceName,
114
- direction,
115
- });
116
- }
117
-
118
- concurrencyUpdate(target, limit, inFlight) {
119
- this._gaugeSet("forge_concurrency_limit", limit, {
120
- source: this.serviceName,
121
- target,
122
- });
123
- this._gaugeSet("forge_concurrency_in_flight", inFlight, {
124
- source: this.serviceName,
125
- target,
126
- });
127
- }
128
-
129
- circuitBreakerState(target, state) {
130
- // 0=closed, 1=open, 2=half-open
131
- const val = state === "open" ? 1 : state === "half-open" ? 2 : 0;
132
- this._gaugeSet("forge_circuit_breaker_state", val, {
133
- source: this.serviceName,
134
- target,
135
- });
136
- }
137
-
138
- // ── Generic methods (for custom metrics) ──
139
-
140
- counter(name, value = 1, labels = {}) {
141
- this._counterInc(name, { service: this.serviceName, ...labels }, value);
142
- }
143
-
144
- gauge(name, value, labels = {}) {
145
- this._gaugeSet(name, value, { service: this.serviceName, ...labels });
146
- }
147
-
148
- histogram(name, value, labels = {}, buckets) {
149
- this._histogramObserve(name, value, { service: this.serviceName, ...labels }, buckets || this.httpBuckets);
150
- }
151
-
152
- // ── Exposition ──
153
-
154
- /**
155
- * Render all metrics in Prometheus text exposition format.
156
- */
157
- expose() {
158
- const lines = [];
159
- const now = Date.now();
160
-
161
- // Uptime
162
- lines.push(`# HELP forge_uptime_seconds Service uptime`);
163
- lines.push(`# TYPE forge_uptime_seconds gauge`);
164
- lines.push(
165
- `forge_uptime_seconds{service="${this.serviceName}",worker="${this.workerId}"} ${((now - this.startTime) / 1000).toFixed(1)}`,
166
- );
167
- lines.push("");
168
-
169
- // Counters
170
- const counterNames = new Set([...this._counters.keys()].map((k) => k.split("{")[0]));
171
- for (const name of counterNames) {
172
- lines.push(`# HELP ${name} ${name}`);
173
- lines.push(`# TYPE ${name} counter`);
174
- for (const [key, val] of this._counters) {
175
- if (key === name || key.startsWith(name + "{")) {
176
- lines.push(`${key} ${val}`);
177
- }
178
- }
179
- lines.push("");
180
- }
181
-
182
- // Gauges
183
- const gaugeNames = new Set([...this._gauges.keys()].map((k) => k.split("{")[0]));
184
- for (const name of gaugeNames) {
185
- lines.push(`# HELP ${name} ${name}`);
186
- lines.push(`# TYPE ${name} gauge`);
187
- for (const [key, val] of this._gauges) {
188
- if (key === name || key.startsWith(name + "{")) {
189
- lines.push(`${key} ${val}`);
190
- }
191
- }
192
- lines.push("");
193
- }
194
-
195
- // Histograms
196
- const histNames = new Set([...this._histograms.keys()].map((k) => k.split("{")[0]));
197
- for (const name of histNames) {
198
- lines.push(`# HELP ${name} ${name}`);
199
- lines.push(`# TYPE ${name} histogram`);
200
- for (const [key, hist] of this._histograms) {
201
- if (key !== name && !key.startsWith(name + "{")) continue;
202
- const labelPart = key.includes("{") ? key.slice(key.indexOf("{")) : "";
203
- const prefix = name;
204
-
205
- // Bucket lines
206
- const buckets = hist.bucketBounds;
207
- for (let i = 0; i < buckets.length; i++) {
208
- const le = buckets[i];
209
- const bucketLabel = labelPart ? labelPart.replace("}", `,le="${le}"}`) : `{le="${le}"}`;
210
- lines.push(`${prefix}_bucket${bucketLabel} ${hist.buckets[i]}`);
211
- }
212
- const infLabel = labelPart ? labelPart.replace("}", `,le="+Inf"}`) : `{le="+Inf"}`;
213
- lines.push(`${prefix}_bucket${infLabel} ${hist.count}`);
214
- lines.push(`${prefix}_sum${labelPart} ${hist.sum.toFixed(6)}`);
215
- lines.push(`${prefix}_count${labelPart} ${hist.count}`);
216
- }
217
- lines.push("");
218
- }
219
-
220
- // Process metrics
221
- const mem = process.memoryUsage();
222
- lines.push(`# HELP process_resident_memory_bytes Resident memory size in bytes`);
223
- lines.push(`# TYPE process_resident_memory_bytes gauge`);
224
- lines.push(`process_resident_memory_bytes{service="${this.serviceName}"} ${mem.rss}`);
225
- lines.push(`# HELP process_heap_used_bytes Node.js heap used in bytes`);
226
- lines.push(`# TYPE process_heap_used_bytes gauge`);
227
- lines.push(`process_heap_used_bytes{service="${this.serviceName}"} ${mem.heapUsed}`);
228
- lines.push("");
229
-
230
- return `${lines.join("\n")}\n`;
231
- }
232
-
233
- // ── Internal ──
234
-
235
- _labelStr(labels) {
236
- const key = JSON.stringify(labels);
237
- if (_labelKeyCache.has(key)) return _labelKeyCache.get(key);
238
-
239
- const validKeyRe = /^[a-zA-Z_][a-zA-Z0-9_]*$/;
240
- const parts = [];
241
- for (const k of Object.keys(labels)) {
242
- const v = labels[k];
243
- if (v === undefined || v === null) continue;
244
- // H7: Sanitize label keys
245
- const safeKey = validKeyRe.test(k) ? k : k.replace(/[^a-zA-Z0-9_]/g, '_').replace(/^[^a-zA-Z_]/, '_');
246
- // Truncate label values to 256 chars and escape special characters
247
- const safeVal = String(v).slice(0, 256).replace(/\\/g, '\\\\').replace(/"/g, '\\"').replace(/\n/g, '\\n').replace(/\r/g, '\\r').replace(/\t/g, '\\t');
248
- parts.push(`${safeKey}="${safeVal}"`);
249
- }
250
- const result = parts.length > 0 ? `{${parts.join(",")}}` : "";
251
-
252
- // P9-fix: Evict oldest 20% when cache is full (was: stop caching entirely)
253
- if (_labelKeyCache.size >= MAX_LABEL_KEY_CACHE) {
254
- const evictCount = Math.ceil(MAX_LABEL_KEY_CACHE * 0.2);
255
- const iter = _labelKeyCache.keys();
256
- for (let i = 0; i < evictCount; i++) {
257
- const k = iter.next().value;
258
- if (k !== undefined) _labelKeyCache.delete(k);
259
- }
260
- }
261
- _labelKeyCache.set(key, result);
262
- return result;
263
- }
264
-
265
- _counterInc(name, labels = {}, value = 1) {
266
- const key = `${name}${this._labelStr(labels)}`;
267
- this._counters.set(key, (this._counters.get(key) ?? 0) + value);
268
- }
269
-
270
- _gaugeSet(name, value, labels = {}) {
271
- const key = `${name}${this._labelStr(labels)}`;
272
- this._gauges.set(key, value);
273
- }
274
-
275
- _gaugeInc(name, labels = {}) {
276
- const key = `${name}${this._labelStr(labels)}`;
277
- this._gauges.set(key, (this._gauges.get(key) ?? 0) + 1);
278
- }
279
-
280
- _gaugeDec(name, labels = {}) {
281
- const key = `${name}${this._labelStr(labels)}`;
282
- this._gauges.set(key, Math.max(0, (this._gauges.get(key) ?? 0) - 1));
283
- }
284
-
285
- _histogramObserve(name, value, labels = {}, buckets) {
286
- const key = `${name}${this._labelStr(labels)}`;
287
- let hist = this._histograms.get(key);
288
- if (!hist) {
289
- hist = { count: 0, sum: 0, buckets: new Array(buckets.length).fill(0), bucketBounds: buckets };
290
- this._histograms.set(key, hist);
291
- }
292
- hist.count++;
293
- hist.sum += value;
294
- // P18: Early break — once value <= buckets[i], all subsequent buckets also match
295
- // since buckets are sorted ascending. Increment from this point to end.
296
- for (let i = 0; i < buckets.length; i++) {
297
- if (value <= buckets[i]) {
298
- for (let j = i; j < buckets.length; j++) {
299
- hist.buckets[j]++;
300
- }
301
- break;
302
- }
303
- }
304
- }
305
- }