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,537 +0,0 @@
1
- /**
2
- * ThreadForge Plugin System
3
- *
4
- * Plugins add infrastructure to your app without boilerplate.
5
- * They handle connection management, health checks, nginx routing,
6
- * graceful shutdown, and inject clients into every service.
7
- *
8
- * ═══════════════════════════════════════════════════════════════
9
- * USAGE
10
- * ═══════════════════════════════════════════════════════════════
11
- *
12
- * // forge.config.ts
13
- * import { defineServices } from 'threadforge';
14
- * import { redis, postgres, s3 } from 'threadforge/plugins';
15
- *
16
- * export default defineServices({
17
- * plugins: [
18
- * redis({ url: 'redis://localhost:6379' }),
19
- * postgres({ url: 'postgres://user:pass@localhost:5432/mydb' }),
20
- * s3({ bucket: 'my-uploads', region: 'us-east-1' }),
21
- * ],
22
- *
23
- * users: {
24
- * entry: './services/users.ts',
25
- * port: 3001,
26
- * plugins: ['redis', 'postgres'], // opt-in per service
27
- * },
28
- *
29
- * uploads: {
30
- * entry: './services/uploads.ts',
31
- * port: 3002,
32
- * plugins: ['s3'],
33
- * },
34
- * });
35
- *
36
- * In your service:
37
- *
38
- * @Prefix('/api/users')
39
- * class UserService extends Service {
40
- * async onStart(ctx) {
41
- * // Injected by plugins — ready to use, connection managed
42
- * const cached = await this.redis.get('user:123');
43
- * const rows = await this.pg.query('SELECT * FROM users WHERE id = $1', [123]);
44
- * }
45
- * }
46
- *
47
- * ═══════════════════════════════════════════════════════════════
48
- * PLUGIN LIFECYCLE
49
- * ═══════════════════════════════════════════════════════════════
50
- *
51
- * forge start
52
- * │
53
- * ▼ Supervisor reads config, finds plugins
54
- * │
55
- * ▼ plugin.validate() — check config, throw if bad
56
- * ▼ plugin.env() — env vars for workers (connection strings)
57
- * ▼ plugin.nginx() — nginx location blocks (admin UIs, proxying)
58
- * │
59
- * ▼ Workers fork
60
- * │
61
- * ▼ plugin.connect() — create client, connect, return instance
62
- * ▼ service.redis = client — injected onto service instance
63
- * ▼ service.onStart(ctx) — your code runs, clients ready
64
- * │
65
- * ▼ plugin.healthCheck() — called by /health endpoint
66
- * │
67
- * ▼ shutdown
68
- * ▼ plugin.disconnect() — close connections, drain pools
69
- *
70
- * ═══════════════════════════════════════════════════════════════
71
- * WRITING A PLUGIN
72
- * ═══════════════════════════════════════════════════════════════
73
- *
74
- * export function myPlugin(options) {
75
- * return {
76
- * name: 'myPlugin',
77
- * version: '1.0.0',
78
- *
79
- * // What property name to inject on the service (this.myPlugin)
80
- * inject: 'myPlugin',
81
- *
82
- * // Validate config before startup (fail fast)
83
- * validate() { ... },
84
- *
85
- * // Env vars to pass to workers
86
- * env() { return { MY_PLUGIN_URL: options.url }; },
87
- *
88
- * // Create and return the client (called once per worker)
89
- * async connect(ctx) {
90
- * const client = new MyClient(options.url);
91
- * await client.connect();
92
- * return client;
93
- * },
94
- *
95
- * // Health check (called by GET /health)
96
- * async healthCheck(client) {
97
- * await client.ping();
98
- * return { status: 'ok' };
99
- * },
100
- *
101
- * // Clean shutdown
102
- * async disconnect(client) {
103
- * await client.quit();
104
- * },
105
- *
106
- * // Nginx config blocks (optional)
107
- * nginx() {
108
- * return {
109
- * upstreams: [{ name: 'myPlugin', host: 'localhost', port: 8080 }],
110
- * locations: [{ path: '/admin/myplugin', proxy_pass: 'myPlugin' }],
111
- * };
112
- * },
113
- *
114
- * // Prometheus metrics (optional)
115
- * metrics(client) {
116
- * return { pool_size: client.pool.totalCount };
117
- * },
118
- * };
119
- * }
120
- */
121
-
122
- // ─── Plugin Interface ──────────────────────────────────────
123
-
124
- /**
125
- * @typedef {Object} ForgePlugin
126
- * @property {string} name - Unique plugin name
127
- * @property {string} [version] - Plugin version
128
- * @property {string} inject - Property name on service (this.redis, this.pg, etc.)
129
- * @property {Function} [validate] - Validate config, throw on error
130
- * @property {Function} [env] - Return env vars for workers: () => Record<string, string>
131
- * @property {Function} connect - Create client: (ctx: ForgeContext) => Promise<any>
132
- * @property {Function} [healthCheck] - Check health: (client: any) => Promise<{status: string}>
133
- * @property {Function} [disconnect] - Close connections: (client: any) => Promise<void>
134
- * @property {Function} [nginx] - Nginx config: () => { upstreams?, locations? }
135
- * @property {Function} [metrics] - Prometheus metrics: (client: any) => Record<string, number>
136
- * @property {Function} [middleware] - HTTP middleware: (client: any) => (req, res, next) => void
137
- * @property {Function} [onWsUpgrade] - WebSocket upgrade hook: (client, ctx) => Promise<boolean|{allow:boolean,statusCode?:number,reason?:string}>
138
- * @property {Function} [onWsConnect] - WebSocket connect hook: (client, ctx) => Promise<void>
139
- * @property {Function} [onWsMessage] - WebSocket message hook: (client, ctx) => Promise<void>
140
- * @property {Function} [onWsClose] - WebSocket close hook: (client, ctx) => Promise<void>
141
- */
142
-
143
- // ─── Timeout Helper ────────────────────────────────────────
144
-
145
- /**
146
- * Race a promise against a timeout. Resolves/rejects with the promise
147
- * result if it finishes first, otherwise rejects with a timeout error.
148
- */
149
- function withTimeout(promise, ms, label) {
150
- return new Promise((resolve, reject) => {
151
- const timer = setTimeout(() => {
152
- reject(new Error(`${label} timed out after ${ms}ms`));
153
- }, ms);
154
- promise.then(
155
- (val) => {
156
- clearTimeout(timer);
157
- resolve(val);
158
- },
159
- (err) => {
160
- clearTimeout(timer);
161
- reject(err);
162
- },
163
- );
164
- });
165
- }
166
-
167
- // Default timeouts (ms)
168
- const CONNECT_TIMEOUT = 10_000;
169
- const HEALTH_TIMEOUT = 5_000;
170
- const DISCONNECT_TIMEOUT = 5_000;
171
-
172
- // ─── Plugin Manager ────────────────────────────────────────
173
-
174
- /**
175
- * Manages plugin lifecycle across the supervisor and workers.
176
- */
177
- export class PluginManager {
178
- constructor(options = {}) {
179
- /** @type {Map<string, ForgePlugin>} */
180
- this.plugins = new Map();
181
-
182
- /** @type {Map<string, any>} plugin name → connected client */
183
- this.clients = new Map();
184
-
185
- /** @type {Map<string, Promise>} plugin name → in-flight connect promise (C-PLUGIN-2) */
186
- this._connecting = new Map();
187
-
188
- this._logger = options.logger ?? {
189
- info: (...args) => console.log('[PluginManager]', ...args),
190
- warn: (...args) => console.warn('[PluginManager]', ...args),
191
- error: (...args) => console.error('[PluginManager]', ...args),
192
- };
193
- }
194
-
195
- /**
196
- * Register plugins from config.
197
- */
198
- register(plugins) {
199
- if (!plugins || !Array.isArray(plugins)) return;
200
-
201
- // Build set of existing inject names
202
- const injectNames = new Set();
203
- for (const [, p] of this.plugins) {
204
- if (p.inject) injectNames.add(p.inject);
205
- }
206
-
207
- for (const plugin of plugins) {
208
- if (!plugin.name) throw new Error("Plugin must have a name");
209
- if (!plugin.inject) throw new Error(`Plugin "${plugin.name}" must have an inject property`);
210
- if (!plugin.connect) throw new Error(`Plugin "${plugin.name}" must have a connect() method`);
211
- for (const optionalFn of [
212
- "validate",
213
- "env",
214
- "healthCheck",
215
- "disconnect",
216
- "nginx",
217
- "metrics",
218
- "middleware",
219
- "onWsUpgrade",
220
- "onWsConnect",
221
- "onWsMessage",
222
- "onWsClose",
223
- ]) {
224
- if (plugin[optionalFn] !== undefined && typeof plugin[optionalFn] !== "function") {
225
- throw new Error(`Plugin "${plugin.name}" optional hook "${optionalFn}" must be a function`);
226
- }
227
- }
228
-
229
- if (this.plugins.has(plugin.name)) {
230
- throw new Error(`Duplicate plugin: "${plugin.name}"`);
231
- }
232
-
233
- if (injectNames.has(plugin.inject)) {
234
- throw new Error(`Duplicate plugin inject name: "${plugin.inject}"`);
235
- }
236
- injectNames.add(plugin.inject);
237
-
238
- this.plugins.set(plugin.name, plugin);
239
- }
240
- }
241
-
242
- /**
243
- * Validate all plugins (called by Supervisor before forking).
244
- * Throws on first error.
245
- */
246
- validate() {
247
- for (const [name, plugin] of this.plugins) {
248
- if (plugin.validate) {
249
- try {
250
- plugin.validate();
251
- } catch (err) {
252
- throw new Error(`Plugin "${name}" validation failed: ${err.message}`);
253
- }
254
- }
255
- }
256
- }
257
-
258
- /**
259
- * Collect env vars from all plugins (supervisor passes to workers).
260
- */
261
- env() {
262
- const merged = {};
263
- for (const [, plugin] of this.plugins) {
264
- if (plugin.env) {
265
- const pluginEnv = plugin.env();
266
- for (const key of Object.keys(pluginEnv)) {
267
- if (merged[key] !== undefined) {
268
- throw new Error(`Plugin env var conflict: "${key}" is set by multiple plugins`);
269
- }
270
- }
271
- Object.assign(merged, pluginEnv);
272
- }
273
- }
274
- return merged;
275
- }
276
-
277
- /**
278
- * Connect all plugins for a specific service.
279
- * Only connects plugins the service opts into.
280
- *
281
- * @param {string[]} servicePlugins - Plugin names this service uses
282
- * @param {any} ctx - ForgeContext
283
- * @returns {Promise<Map<string, any>>} inject name → client
284
- */
285
- async connectForService(servicePlugins, ctx) {
286
- const clients = new Map();
287
-
288
- // If service doesn't specify plugins, connect ALL registered plugins
289
- const pluginNames = servicePlugins ?? [...this.plugins.keys()];
290
-
291
- // H-PLUGIN-2: Connect independent plugins in parallel for faster startup.
292
- // Separate into cached/in-flight (resolved immediately) and fresh (connected concurrently).
293
- const pendingConnects = []; // Array of { name, plugin, promise }
294
-
295
- for (const name of pluginNames) {
296
- const plugin = this.plugins.get(name);
297
- if (!plugin) {
298
- ctx.logger.warn(`Plugin "${name}" not registered, skipping`);
299
- continue;
300
- }
301
-
302
- // Reuse existing connection if already connected in this worker
303
- if (this.clients.has(name)) {
304
- const cached = this.clients.get(name);
305
- if (cached !== null && typeof cached === 'object' && cached._pluginError === true) {
306
- const attempts = cached.attempts ?? 0;
307
- const retryAfterMs = Math.min(5000 * 2 ** attempts, 30000);
308
- if (Date.now() - cached.timestamp < retryAfterMs) {
309
- const errMsg = cached.errorMessage;
310
- const errorProxy = new Proxy({}, {
311
- get(_, prop) {
312
- if (prop === 'then') return undefined;
313
- throw new Error(`Plugin "${name}" is unavailable (connection failed: ${errMsg})`);
314
- }
315
- });
316
- clients.set(plugin.inject, errorProxy);
317
- continue;
318
- }
319
- this.clients.delete(name);
320
- } else if (cached !== null && !(typeof cached === 'object' && cached._pluginError === true)) {
321
- clients.set(plugin.inject, cached);
322
- continue;
323
- }
324
- }
325
-
326
- // C-PLUGIN-2: Prevent double-connect — join in-flight connection
327
- if (this._connecting.has(name)) {
328
- pendingConnects.push({ name, plugin, promise: this._connecting.get(name), isJoin: true });
329
- continue;
330
- }
331
-
332
- // Fresh connection — launch now, await later (parallel)
333
- const connectPromise = (async () => {
334
- const startTime = performance.now();
335
- const client = await withTimeout(plugin.connect(ctx), CONNECT_TIMEOUT, `Plugin "${name}" connect`);
336
- const duration = performance.now() - startTime;
337
- ctx.metrics?.histogram?.('forge_plugin_connect_duration_ms', duration, { plugin: name, status: 'success' });
338
- return client;
339
- })();
340
- this._connecting.set(name, connectPromise);
341
- pendingConnects.push({ name, plugin, promise: connectPromise, isJoin: false });
342
- }
343
-
344
- // Await all pending connections concurrently
345
- const results = await Promise.allSettled(pendingConnects.map(p => p.promise));
346
- for (let i = 0; i < pendingConnects.length; i++) {
347
- const { name, plugin, isJoin } = pendingConnects[i];
348
- const result = results[i];
349
-
350
- if (result.status === 'fulfilled') {
351
- const client = result.value;
352
- if (!isJoin) {
353
- this.clients.set(name, client);
354
- ctx.logger.info(`Plugin connected: ${name}`, { inject: `this.${plugin.inject}` });
355
- }
356
- clients.set(plugin.inject, client);
357
- } else {
358
- const err = result.reason;
359
- if (!isJoin) {
360
- ctx.metrics?.increment?.('forge_plugin_connect_failures_total', { plugin: name });
361
- ctx.logger.warn(`Plugin "${name}" connect failed (degraded mode): ${err.message}`);
362
- const prevCached = this.clients.get(name);
363
- const attempts = (prevCached?._pluginError ? (prevCached.attempts ?? 0) : 0) + 1;
364
- this.clients.set(name, { _pluginError: true, timestamp: Date.now(), errorMessage: err.message, attempts });
365
- }
366
- const pluginName = name;
367
- const errMsg = err.message;
368
- const errorProxy = new Proxy({}, {
369
- get(_, prop) {
370
- if (prop === Symbol.toPrimitive || prop === "then" || prop === Symbol.iterator) return undefined;
371
- throw new Error(`Plugin "${pluginName}" is unavailable (connect failed: ${errMsg}). Check your ${pluginName} configuration.`);
372
- },
373
- });
374
- clients.set(plugin.inject, errorProxy);
375
- }
376
- if (!isJoin) this._connecting.delete(name);
377
- }
378
-
379
- return clients;
380
- }
381
-
382
- /**
383
- * Run health checks for all connected plugins.
384
- */
385
- async healthCheck() {
386
- const results = {};
387
-
388
- for (const [name, plugin] of this.plugins) {
389
- const client = this.clients.get(name);
390
-
391
- if (!client || (typeof client === 'object' && client._pluginError === true)) {
392
- results[name] = this.clients.has(name)
393
- ? { status: "error", error: "Plugin failed to connect" }
394
- : { status: "not-checked" };
395
- continue;
396
- }
397
-
398
- if (!plugin.healthCheck) {
399
- results[name] = { status: "unknown" };
400
- continue;
401
- }
402
-
403
- try {
404
- results[name] = await withTimeout(plugin.healthCheck(client), HEALTH_TIMEOUT, `Plugin "${name}" healthCheck`);
405
- } catch (err) {
406
- results[name] = { status: "error", error: err.message };
407
- }
408
- }
409
-
410
- return results;
411
- }
412
-
413
- /**
414
- * Collect Prometheus metrics from all plugins.
415
- */
416
- collectMetrics(metricsCollector) {
417
- for (const [name, plugin] of this.plugins) {
418
- const client = this.clients.get(name);
419
- if (!client || !plugin.metrics) continue;
420
-
421
- try {
422
- const pluginMetrics = plugin.metrics(client);
423
- for (const [key, value] of Object.entries(pluginMetrics)) {
424
- metricsCollector.gauge(`forge_plugin_${name}_${key}`, value);
425
- }
426
- } catch (err) {
427
- this._logger?.warn?.(`Plugin metrics collection failed`, { error: err.message });
428
- }
429
- }
430
- }
431
-
432
- /**
433
- * Disconnect all plugins (graceful shutdown).
434
- */
435
- async disconnectAll(logger) {
436
- const errors = [];
437
-
438
- for (const [name, plugin] of this.plugins) {
439
- const client = this.clients.get(name);
440
- if (!client || (typeof client === 'object' && client._pluginError === true)) {
441
- this.clients.delete(name);
442
- continue;
443
- }
444
-
445
- try {
446
- if (plugin.disconnect) {
447
- await withTimeout(plugin.disconnect(client), DISCONNECT_TIMEOUT, `Plugin "${name}" disconnect`);
448
- }
449
- logger?.info(`Plugin disconnected: ${name}`);
450
- } catch (err) {
451
- errors.push({ name, error: err.message });
452
- logger?.error(`Plugin "${name}" disconnect failed: ${err.message}`);
453
- // Force cleanup as best effort
454
- try {
455
- if (typeof client.end === 'function') await client.end().catch(() => {});
456
- else if (typeof client.quit === 'function') await client.quit().catch(() => {});
457
- else if (typeof client.destroy === 'function') client.destroy();
458
- } catch {}
459
- }
460
-
461
- this.clients.delete(name);
462
- }
463
-
464
- return errors;
465
- }
466
-
467
- /**
468
- * Get nginx config blocks from all plugins.
469
- */
470
- nginxBlocks() {
471
- const blocks = { upstreams: [], locations: [] };
472
-
473
- for (const [, plugin] of this.plugins) {
474
- if (!plugin.nginx) continue;
475
- const nginx = plugin.nginx();
476
- if (nginx.upstreams) blocks.upstreams.push(...nginx.upstreams);
477
- if (nginx.locations) blocks.locations.push(...nginx.locations);
478
- }
479
-
480
- return blocks;
481
- }
482
-
483
- /**
484
- * Get middleware from all plugins (for service HTTP server).
485
- */
486
- getMiddleware(servicePlugins) {
487
- const middleware = [];
488
- const names = servicePlugins ?? [...this.plugins.keys()];
489
-
490
- for (const name of names) {
491
- const plugin = this.plugins.get(name);
492
- const client = this.clients.get(name);
493
- if (!plugin?.middleware || !client) continue;
494
- middleware.push(plugin.middleware(client));
495
- }
496
-
497
- return middleware;
498
- }
499
-
500
- /**
501
- * Get websocket lifecycle hooks from all plugins for a service.
502
- *
503
- * @param {string[] | null | undefined} servicePlugins
504
- * @returns {Array<{name: string, onWsUpgrade?: Function, onWsConnect?: Function, onWsMessage?: Function, onWsClose?: Function}>}
505
- */
506
- getWebSocketHooks(servicePlugins) {
507
- const hooks = [];
508
- const names = servicePlugins ?? [...this.plugins.keys()];
509
-
510
- for (const name of names) {
511
- const plugin = this.plugins.get(name);
512
- const client = this.clients.get(name);
513
-
514
- if (!plugin || !client) continue;
515
- if (typeof client === "object" && client._pluginError === true) continue;
516
- if (!plugin.onWsUpgrade && !plugin.onWsConnect && !plugin.onWsMessage && !plugin.onWsClose) continue;
517
-
518
- hooks.push({
519
- name,
520
- onWsUpgrade: plugin.onWsUpgrade
521
- ? (ctx) => plugin.onWsUpgrade(client, ctx)
522
- : undefined,
523
- onWsConnect: plugin.onWsConnect
524
- ? (ctx) => plugin.onWsConnect(client, ctx)
525
- : undefined,
526
- onWsMessage: plugin.onWsMessage
527
- ? (ctx) => plugin.onWsMessage(client, ctx)
528
- : undefined,
529
- onWsClose: plugin.onWsClose
530
- ? (ctx) => plugin.onWsClose(client, ctx)
531
- : undefined,
532
- });
533
- }
534
-
535
- return hooks;
536
- }
537
- }