@c9up/ream 0.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (451) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +70 -0
  3. package/dist/Application.d.ts +71 -0
  4. package/dist/Application.d.ts.map +1 -0
  5. package/dist/Application.js +151 -0
  6. package/dist/Application.js.map +1 -0
  7. package/dist/Codemods.d.ts +16 -0
  8. package/dist/Codemods.d.ts.map +1 -0
  9. package/dist/Codemods.js +277 -0
  10. package/dist/Codemods.js.map +1 -0
  11. package/dist/ConfigLoader.d.ts +29 -0
  12. package/dist/ConfigLoader.d.ts.map +1 -0
  13. package/dist/ConfigLoader.js +43 -0
  14. package/dist/ConfigLoader.js.map +1 -0
  15. package/dist/ErrorBoundary.d.ts +51 -0
  16. package/dist/ErrorBoundary.d.ts.map +1 -0
  17. package/dist/ErrorBoundary.js +122 -0
  18. package/dist/ErrorBoundary.js.map +1 -0
  19. package/dist/GracefulShutdown.d.ts +28 -0
  20. package/dist/GracefulShutdown.d.ts.map +1 -0
  21. package/dist/GracefulShutdown.js +55 -0
  22. package/dist/GracefulShutdown.js.map +1 -0
  23. package/dist/HealthCheck.d.ts +42 -0
  24. package/dist/HealthCheck.d.ts.map +1 -0
  25. package/dist/HealthCheck.js +65 -0
  26. package/dist/HealthCheck.js.map +1 -0
  27. package/dist/HotReload.d.ts +28 -0
  28. package/dist/HotReload.d.ts.map +1 -0
  29. package/dist/HotReload.js +60 -0
  30. package/dist/HotReload.js.map +1 -0
  31. package/dist/HttpKernel.d.ts +62 -0
  32. package/dist/HttpKernel.d.ts.map +1 -0
  33. package/dist/HttpKernel.js +210 -0
  34. package/dist/HttpKernel.js.map +1 -0
  35. package/dist/Ignitor.d.ts +195 -0
  36. package/dist/Ignitor.d.ts.map +1 -0
  37. package/dist/Ignitor.js +505 -0
  38. package/dist/Ignitor.js.map +1 -0
  39. package/dist/Provider.d.ts +54 -0
  40. package/dist/Provider.d.ts.map +1 -0
  41. package/dist/Provider.js +37 -0
  42. package/dist/Provider.js.map +1 -0
  43. package/dist/bodyparser/BodyParserMiddleware.d.ts +38 -0
  44. package/dist/bodyparser/BodyParserMiddleware.d.ts.map +1 -0
  45. package/dist/bodyparser/BodyParserMiddleware.js +143 -0
  46. package/dist/bodyparser/BodyParserMiddleware.js.map +1 -0
  47. package/dist/bodyparser/MultipartFile.d.ts +77 -0
  48. package/dist/bodyparser/MultipartFile.d.ts.map +1 -0
  49. package/dist/bodyparser/MultipartFile.js +138 -0
  50. package/dist/bodyparser/MultipartFile.js.map +1 -0
  51. package/dist/bodyparser/parseSize.d.ts +6 -0
  52. package/dist/bodyparser/parseSize.d.ts.map +1 -0
  53. package/dist/bodyparser/parseSize.js +21 -0
  54. package/dist/bodyparser/parseSize.js.map +1 -0
  55. package/dist/bootstrap/hyper-server.d.ts +27 -0
  56. package/dist/bootstrap/hyper-server.d.ts.map +1 -0
  57. package/dist/bootstrap/hyper-server.js +63 -0
  58. package/dist/bootstrap/hyper-server.js.map +1 -0
  59. package/dist/console/CommandRunner.d.ts +18 -0
  60. package/dist/console/CommandRunner.d.ts.map +1 -0
  61. package/dist/console/CommandRunner.js +65 -0
  62. package/dist/console/CommandRunner.js.map +1 -0
  63. package/dist/container/Container.d.ts +60 -0
  64. package/dist/container/Container.d.ts.map +1 -0
  65. package/dist/container/Container.js +329 -0
  66. package/dist/container/Container.js.map +1 -0
  67. package/dist/container/types.d.ts +25 -0
  68. package/dist/container/types.d.ts.map +1 -0
  69. package/dist/container/types.js +6 -0
  70. package/dist/container/types.js.map +1 -0
  71. package/dist/decorators/Lazy.d.ts +19 -0
  72. package/dist/decorators/Lazy.d.ts.map +1 -0
  73. package/dist/decorators/Lazy.js +53 -0
  74. package/dist/decorators/Lazy.js.map +1 -0
  75. package/dist/decorators/Service.d.ts +48 -0
  76. package/dist/decorators/Service.d.ts.map +1 -0
  77. package/dist/decorators/Service.js +70 -0
  78. package/dist/decorators/Service.js.map +1 -0
  79. package/dist/errors/FuzzyMatcher.d.ts +41 -0
  80. package/dist/errors/FuzzyMatcher.d.ts.map +1 -0
  81. package/dist/errors/FuzzyMatcher.js +81 -0
  82. package/dist/errors/FuzzyMatcher.js.map +1 -0
  83. package/dist/errors/ModuleErrors.d.ts +41 -0
  84. package/dist/errors/ModuleErrors.d.ts.map +1 -0
  85. package/dist/errors/ModuleErrors.js +65 -0
  86. package/dist/errors/ModuleErrors.js.map +1 -0
  87. package/dist/errors/PipelineStageError.d.ts +31 -0
  88. package/dist/errors/PipelineStageError.d.ts.map +1 -0
  89. package/dist/errors/PipelineStageError.js +77 -0
  90. package/dist/errors/PipelineStageError.js.map +1 -0
  91. package/dist/errors/ReamError.d.ts +50 -0
  92. package/dist/errors/ReamError.d.ts.map +1 -0
  93. package/dist/errors/ReamError.js +108 -0
  94. package/dist/errors/ReamError.js.map +1 -0
  95. package/dist/events/Emitter.d.ts +124 -0
  96. package/dist/events/Emitter.d.ts.map +1 -0
  97. package/dist/events/Emitter.js +271 -0
  98. package/dist/events/Emitter.js.map +1 -0
  99. package/dist/events/EventsProvider.d.ts +28 -0
  100. package/dist/events/EventsProvider.d.ts.map +1 -0
  101. package/dist/events/EventsProvider.js +36 -0
  102. package/dist/events/EventsProvider.js.map +1 -0
  103. package/dist/events/config.d.ts +8 -0
  104. package/dist/events/config.d.ts.map +1 -0
  105. package/dist/events/config.js +4 -0
  106. package/dist/events/config.js.map +1 -0
  107. package/dist/events/configure.d.ts +10 -0
  108. package/dist/events/configure.d.ts.map +1 -0
  109. package/dist/events/configure.js +11 -0
  110. package/dist/events/configure.js.map +1 -0
  111. package/dist/events/helix/helpers.d.ts +62 -0
  112. package/dist/events/helix/helpers.d.ts.map +1 -0
  113. package/dist/events/helix/helpers.js +187 -0
  114. package/dist/events/helix/helpers.js.map +1 -0
  115. package/dist/events/index.d.ts +17 -0
  116. package/dist/events/index.d.ts.map +1 -0
  117. package/dist/events/index.js +15 -0
  118. package/dist/events/index.js.map +1 -0
  119. package/dist/events/native.d.ts +21 -0
  120. package/dist/events/native.d.ts.map +1 -0
  121. package/dist/events/native.js +17 -0
  122. package/dist/events/native.js.map +1 -0
  123. package/dist/events/services/main.d.ts +18 -0
  124. package/dist/events/services/main.d.ts.map +1 -0
  125. package/dist/events/services/main.js +31 -0
  126. package/dist/events/services/main.js.map +1 -0
  127. package/dist/events/testing/FakeBus.d.ts +66 -0
  128. package/dist/events/testing/FakeBus.d.ts.map +1 -0
  129. package/dist/events/testing/FakeBus.js +356 -0
  130. package/dist/events/testing/FakeBus.js.map +1 -0
  131. package/dist/graphql/GraphQLEngine.d.ts +69 -0
  132. package/dist/graphql/GraphQLEngine.d.ts.map +1 -0
  133. package/dist/graphql/GraphQLEngine.js +309 -0
  134. package/dist/graphql/GraphQLEngine.js.map +1 -0
  135. package/dist/graphql/GraphQLProvider.d.ts +32 -0
  136. package/dist/graphql/GraphQLProvider.d.ts.map +1 -0
  137. package/dist/graphql/GraphQLProvider.js +49 -0
  138. package/dist/graphql/GraphQLProvider.js.map +1 -0
  139. package/dist/helpers/base64.d.ts +40 -0
  140. package/dist/helpers/base64.d.ts.map +1 -0
  141. package/dist/helpers/base64.js +70 -0
  142. package/dist/helpers/base64.js.map +1 -0
  143. package/dist/helpers/index.d.ts +7 -0
  144. package/dist/helpers/index.d.ts.map +1 -0
  145. package/dist/helpers/index.js +5 -0
  146. package/dist/helpers/index.js.map +1 -0
  147. package/dist/helpers/is.d.ts +101 -0
  148. package/dist/helpers/is.d.ts.map +1 -0
  149. package/dist/helpers/is.js +151 -0
  150. package/dist/helpers/is.js.map +1 -0
  151. package/dist/helpers/napi-loader.d.ts +46 -0
  152. package/dist/helpers/napi-loader.d.ts.map +1 -0
  153. package/dist/helpers/napi-loader.js +69 -0
  154. package/dist/helpers/napi-loader.js.map +1 -0
  155. package/dist/helpers/safeEqual.d.ts +9 -0
  156. package/dist/helpers/safeEqual.d.ts.map +1 -0
  157. package/dist/helpers/safeEqual.js +16 -0
  158. package/dist/helpers/safeEqual.js.map +1 -0
  159. package/dist/helpers/string.d.ts +161 -0
  160. package/dist/helpers/string.d.ts.map +1 -0
  161. package/dist/helpers/string.js +429 -0
  162. package/dist/helpers/string.js.map +1 -0
  163. package/dist/helpers/types.d.ts +83 -0
  164. package/dist/helpers/types.d.ts.map +1 -0
  165. package/dist/helpers/types.js +6 -0
  166. package/dist/helpers/types.js.map +1 -0
  167. package/dist/http/Exception.d.ts +103 -0
  168. package/dist/http/Exception.d.ts.map +1 -0
  169. package/dist/http/Exception.js +228 -0
  170. package/dist/http/Exception.js.map +1 -0
  171. package/dist/http/HttpContext.d.ts +69 -0
  172. package/dist/http/HttpContext.d.ts.map +1 -0
  173. package/dist/http/HttpContext.js +55 -0
  174. package/dist/http/HttpContext.js.map +1 -0
  175. package/dist/http/RedirectBuilder.d.ts +31 -0
  176. package/dist/http/RedirectBuilder.d.ts.map +1 -0
  177. package/dist/http/RedirectBuilder.js +75 -0
  178. package/dist/http/RedirectBuilder.js.map +1 -0
  179. package/dist/http/Request.d.ts +160 -0
  180. package/dist/http/Request.d.ts.map +1 -0
  181. package/dist/http/Request.js +484 -0
  182. package/dist/http/Request.js.map +1 -0
  183. package/dist/http/Response.d.ts +114 -0
  184. package/dist/http/Response.d.ts.map +1 -0
  185. package/dist/http/Response.js +303 -0
  186. package/dist/http/Response.js.map +1 -0
  187. package/dist/http/SseStream.d.ts +89 -0
  188. package/dist/http/SseStream.d.ts.map +1 -0
  189. package/dist/http/SseStream.js +228 -0
  190. package/dist/http/SseStream.js.map +1 -0
  191. package/dist/http/urlDecode.d.ts +8 -0
  192. package/dist/http/urlDecode.d.ts.map +1 -0
  193. package/dist/http/urlDecode.js +18 -0
  194. package/dist/http/urlDecode.js.map +1 -0
  195. package/dist/index.d.ts +92 -0
  196. package/dist/index.d.ts.map +1 -0
  197. package/dist/index.js +67 -0
  198. package/dist/index.js.map +1 -0
  199. package/dist/middleware/Pipeline.d.ts +36 -0
  200. package/dist/middleware/Pipeline.d.ts.map +1 -0
  201. package/dist/middleware/Pipeline.js +104 -0
  202. package/dist/middleware/Pipeline.js.map +1 -0
  203. package/dist/openapi/OpenApiGenerator.d.ts +58 -0
  204. package/dist/openapi/OpenApiGenerator.d.ts.map +1 -0
  205. package/dist/openapi/OpenApiGenerator.js +192 -0
  206. package/dist/openapi/OpenApiGenerator.js.map +1 -0
  207. package/dist/openapi/OpenApiMiddleware.d.ts +24 -0
  208. package/dist/openapi/OpenApiMiddleware.d.ts.map +1 -0
  209. package/dist/openapi/OpenApiMiddleware.js +78 -0
  210. package/dist/openapi/OpenApiMiddleware.js.map +1 -0
  211. package/dist/openapi/OpenApiProvider.d.ts +36 -0
  212. package/dist/openapi/OpenApiProvider.d.ts.map +1 -0
  213. package/dist/openapi/OpenApiProvider.js +42 -0
  214. package/dist/openapi/OpenApiProvider.js.map +1 -0
  215. package/dist/resources/ApiResource.d.ts +45 -0
  216. package/dist/resources/ApiResource.d.ts.map +1 -0
  217. package/dist/resources/ApiResource.js +58 -0
  218. package/dist/resources/ApiResource.js.map +1 -0
  219. package/dist/router/Router.d.ts +205 -0
  220. package/dist/router/Router.d.ts.map +1 -0
  221. package/dist/router/Router.js +552 -0
  222. package/dist/router/Router.js.map +1 -0
  223. package/dist/rpc/RpcProvider.d.ts +34 -0
  224. package/dist/rpc/RpcProvider.d.ts.map +1 -0
  225. package/dist/rpc/RpcProvider.js +37 -0
  226. package/dist/rpc/RpcProvider.js.map +1 -0
  227. package/dist/rpc/RpcRouter.d.ts +61 -0
  228. package/dist/rpc/RpcRouter.d.ts.map +1 -0
  229. package/dist/rpc/RpcRouter.js +187 -0
  230. package/dist/rpc/RpcRouter.js.map +1 -0
  231. package/dist/scheduler/Schedule.d.ts +53 -0
  232. package/dist/scheduler/Schedule.d.ts.map +1 -0
  233. package/dist/scheduler/Schedule.js +76 -0
  234. package/dist/scheduler/Schedule.js.map +1 -0
  235. package/dist/scheduler/ScheduleProvider.d.ts +56 -0
  236. package/dist/scheduler/ScheduleProvider.d.ts.map +1 -0
  237. package/dist/scheduler/ScheduleProvider.js +145 -0
  238. package/dist/scheduler/ScheduleProvider.js.map +1 -0
  239. package/dist/scheduler/Scheduler.d.ts +209 -0
  240. package/dist/scheduler/Scheduler.d.ts.map +1 -0
  241. package/dist/scheduler/Scheduler.js +432 -0
  242. package/dist/scheduler/Scheduler.js.map +1 -0
  243. package/dist/scheduler/index.d.ts +14 -0
  244. package/dist/scheduler/index.d.ts.map +1 -0
  245. package/dist/scheduler/index.js +6 -0
  246. package/dist/scheduler/index.js.map +1 -0
  247. package/dist/scheduler/locks/LockBackend.d.ts +37 -0
  248. package/dist/scheduler/locks/LockBackend.d.ts.map +1 -0
  249. package/dist/scheduler/locks/LockBackend.js +13 -0
  250. package/dist/scheduler/locks/LockBackend.js.map +1 -0
  251. package/dist/scheduler/locks/MemoryLockBackend.d.ts +39 -0
  252. package/dist/scheduler/locks/MemoryLockBackend.d.ts.map +1 -0
  253. package/dist/scheduler/locks/MemoryLockBackend.js +71 -0
  254. package/dist/scheduler/locks/MemoryLockBackend.js.map +1 -0
  255. package/dist/scheduler/observability/ErrorReporter.d.ts +15 -0
  256. package/dist/scheduler/observability/ErrorReporter.d.ts.map +1 -0
  257. package/dist/scheduler/observability/ErrorReporter.js +13 -0
  258. package/dist/scheduler/observability/ErrorReporter.js.map +1 -0
  259. package/dist/scheduler/observability/ScheduleEvent.d.ts +57 -0
  260. package/dist/scheduler/observability/ScheduleEvent.d.ts.map +1 -0
  261. package/dist/scheduler/observability/ScheduleEvent.js +12 -0
  262. package/dist/scheduler/observability/ScheduleEvent.js.map +1 -0
  263. package/dist/scheduler/observability/ScheduleEventSink.d.ts +20 -0
  264. package/dist/scheduler/observability/ScheduleEventSink.d.ts.map +1 -0
  265. package/dist/scheduler/observability/ScheduleEventSink.js +17 -0
  266. package/dist/scheduler/observability/ScheduleEventSink.js.map +1 -0
  267. package/dist/scheduler/observability/StatsTracker.d.ts +50 -0
  268. package/dist/scheduler/observability/StatsTracker.d.ts.map +1 -0
  269. package/dist/scheduler/observability/StatsTracker.js +96 -0
  270. package/dist/scheduler/observability/StatsTracker.js.map +1 -0
  271. package/dist/security/CookieSigner.d.ts +13 -0
  272. package/dist/security/CookieSigner.d.ts.map +1 -0
  273. package/dist/security/CookieSigner.js +51 -0
  274. package/dist/security/CookieSigner.js.map +1 -0
  275. package/dist/security/SignedUrl.d.ts +17 -0
  276. package/dist/security/SignedUrl.d.ts.map +1 -0
  277. package/dist/security/SignedUrl.js +63 -0
  278. package/dist/security/SignedUrl.js.map +1 -0
  279. package/dist/security/crypto.d.ts +25 -0
  280. package/dist/security/crypto.d.ts.map +1 -0
  281. package/dist/security/crypto.js +82 -0
  282. package/dist/security/crypto.js.map +1 -0
  283. package/dist/server/HyperServer.d.ts +64 -0
  284. package/dist/server/HyperServer.d.ts.map +1 -0
  285. package/dist/server/HyperServer.js +54 -0
  286. package/dist/server/HyperServer.js.map +1 -0
  287. package/dist/server/Server.d.ts +68 -0
  288. package/dist/server/Server.d.ts.map +1 -0
  289. package/dist/server/Server.js +113 -0
  290. package/dist/server/Server.js.map +1 -0
  291. package/dist/services/app.d.ts +14 -0
  292. package/dist/services/app.d.ts.map +1 -0
  293. package/dist/services/app.js +27 -0
  294. package/dist/services/app.js.map +1 -0
  295. package/dist/services/router.d.ts +21 -0
  296. package/dist/services/router.d.ts.map +1 -0
  297. package/dist/services/router.js +36 -0
  298. package/dist/services/router.js.map +1 -0
  299. package/dist/services/server.d.ts +16 -0
  300. package/dist/services/server.d.ts.map +1 -0
  301. package/dist/services/server.js +32 -0
  302. package/dist/services/server.js.map +1 -0
  303. package/dist/session/Session.d.ts +88 -0
  304. package/dist/session/Session.d.ts.map +1 -0
  305. package/dist/session/Session.js +186 -0
  306. package/dist/session/Session.js.map +1 -0
  307. package/dist/session/SessionMiddleware.d.ts +16 -0
  308. package/dist/session/SessionMiddleware.d.ts.map +1 -0
  309. package/dist/session/SessionMiddleware.js +132 -0
  310. package/dist/session/SessionMiddleware.js.map +1 -0
  311. package/dist/session/drivers/CookieDriver.d.ts +17 -0
  312. package/dist/session/drivers/CookieDriver.d.ts.map +1 -0
  313. package/dist/session/drivers/CookieDriver.js +42 -0
  314. package/dist/session/drivers/CookieDriver.js.map +1 -0
  315. package/dist/session/drivers/MemoryDriver.d.ts +9 -0
  316. package/dist/session/drivers/MemoryDriver.d.ts.map +1 -0
  317. package/dist/session/drivers/MemoryDriver.js +24 -0
  318. package/dist/session/drivers/MemoryDriver.js.map +1 -0
  319. package/dist/storage/S3Driver.d.ts +27 -0
  320. package/dist/storage/S3Driver.d.ts.map +1 -0
  321. package/dist/storage/S3Driver.js +135 -0
  322. package/dist/storage/S3Driver.js.map +1 -0
  323. package/dist/storage/StaticMiddleware.d.ts +27 -0
  324. package/dist/storage/StaticMiddleware.d.ts.map +1 -0
  325. package/dist/storage/StaticMiddleware.js +153 -0
  326. package/dist/storage/StaticMiddleware.js.map +1 -0
  327. package/dist/storage/StaticProvider.d.ts +26 -0
  328. package/dist/storage/StaticProvider.d.ts.map +1 -0
  329. package/dist/storage/StaticProvider.js +31 -0
  330. package/dist/storage/StaticProvider.js.map +1 -0
  331. package/dist/storage/StorageManager.d.ts +31 -0
  332. package/dist/storage/StorageManager.d.ts.map +1 -0
  333. package/dist/storage/StorageManager.js +120 -0
  334. package/dist/storage/StorageManager.js.map +1 -0
  335. package/dist/testing/RequestBuilder.d.ts +67 -0
  336. package/dist/testing/RequestBuilder.d.ts.map +1 -0
  337. package/dist/testing/RequestBuilder.js +202 -0
  338. package/dist/testing/RequestBuilder.js.map +1 -0
  339. package/dist/testing/TestClient.d.ts +96 -0
  340. package/dist/testing/TestClient.d.ts.map +1 -0
  341. package/dist/testing/TestClient.js +263 -0
  342. package/dist/testing/TestClient.js.map +1 -0
  343. package/dist/types/helpers.d.ts +47 -0
  344. package/dist/types/helpers.d.ts.map +1 -0
  345. package/dist/types/helpers.js +5 -0
  346. package/dist/types/helpers.js.map +1 -0
  347. package/events.darwin-arm64.node +0 -0
  348. package/events.darwin-x64.node +0 -0
  349. package/events.linux-arm64-gnu.node +0 -0
  350. package/events.linux-x64-gnu.node +0 -0
  351. package/events.win32-x64-msvc.node +0 -0
  352. package/index.darwin-arm64.node +0 -0
  353. package/index.darwin-x64.node +0 -0
  354. package/index.linux-arm64-gnu.node +0 -0
  355. package/index.linux-x64-gnu.node +0 -0
  356. package/index.win32-x64-msvc.node +0 -0
  357. package/package.json +106 -0
  358. package/scheduler.darwin-arm64.node +0 -0
  359. package/scheduler.darwin-x64.node +0 -0
  360. package/scheduler.linux-arm64-gnu.node +0 -0
  361. package/scheduler.linux-x64-gnu.node +0 -0
  362. package/scheduler.win32-x64-msvc.node +0 -0
  363. package/scripts/copy-napi.mjs +113 -0
  364. package/src/Application.ts +177 -0
  365. package/src/Codemods.ts +343 -0
  366. package/src/ConfigLoader.ts +49 -0
  367. package/src/ErrorBoundary.ts +157 -0
  368. package/src/GracefulShutdown.ts +80 -0
  369. package/src/HealthCheck.ts +96 -0
  370. package/src/HotReload.ts +78 -0
  371. package/src/HttpKernel.ts +305 -0
  372. package/src/Ignitor.ts +656 -0
  373. package/src/Provider.ts +74 -0
  374. package/src/bodyparser/BodyParserMiddleware.ts +183 -0
  375. package/src/bodyparser/MultipartFile.ts +181 -0
  376. package/src/bodyparser/parseSize.ts +19 -0
  377. package/src/bootstrap/hyper-server.ts +78 -0
  378. package/src/console/CommandRunner.ts +82 -0
  379. package/src/container/Container.ts +383 -0
  380. package/src/container/types.ts +30 -0
  381. package/src/decorators/Lazy.ts +57 -0
  382. package/src/decorators/Service.ts +85 -0
  383. package/src/errors/FuzzyMatcher.ts +93 -0
  384. package/src/errors/ModuleErrors.ts +73 -0
  385. package/src/errors/PipelineStageError.ts +97 -0
  386. package/src/errors/ReamError.ts +138 -0
  387. package/src/events/Emitter.ts +333 -0
  388. package/src/events/EventsProvider.ts +58 -0
  389. package/src/events/config.ts +10 -0
  390. package/src/events/configure.ts +19 -0
  391. package/src/events/helix/helpers.ts +260 -0
  392. package/src/events/index.ts +22 -0
  393. package/src/events/native.ts +37 -0
  394. package/src/events/services/main.ts +39 -0
  395. package/src/events/testing/FakeBus.ts +420 -0
  396. package/src/graphql/GraphQLEngine.ts +421 -0
  397. package/src/graphql/GraphQLProvider.ts +59 -0
  398. package/src/helpers/base64.ts +72 -0
  399. package/src/helpers/index.ts +6 -0
  400. package/src/helpers/is.ts +162 -0
  401. package/src/helpers/napi-loader.ts +106 -0
  402. package/src/helpers/safeEqual.ts +15 -0
  403. package/src/helpers/string.ts +469 -0
  404. package/src/helpers/types.ts +100 -0
  405. package/src/http/Exception.ts +272 -0
  406. package/src/http/HttpContext.ts +108 -0
  407. package/src/http/RedirectBuilder.ts +89 -0
  408. package/src/http/Request.ts +540 -0
  409. package/src/http/Response.ts +351 -0
  410. package/src/http/SseStream.ts +250 -0
  411. package/src/http/urlDecode.ts +18 -0
  412. package/src/index.ts +263 -0
  413. package/src/middleware/Pipeline.ts +136 -0
  414. package/src/openapi/OpenApiGenerator.ts +254 -0
  415. package/src/openapi/OpenApiMiddleware.ts +101 -0
  416. package/src/openapi/OpenApiProvider.ts +67 -0
  417. package/src/resources/ApiResource.ts +79 -0
  418. package/src/router/Router.ts +733 -0
  419. package/src/rpc/RpcProvider.ts +54 -0
  420. package/src/rpc/RpcRouter.ts +243 -0
  421. package/src/scheduler/Schedule.ts +96 -0
  422. package/src/scheduler/ScheduleProvider.ts +177 -0
  423. package/src/scheduler/Scheduler.ts +601 -0
  424. package/src/scheduler/index.ts +24 -0
  425. package/src/scheduler/locks/LockBackend.ts +38 -0
  426. package/src/scheduler/locks/MemoryLockBackend.ts +85 -0
  427. package/src/scheduler/observability/ErrorReporter.ts +13 -0
  428. package/src/scheduler/observability/ScheduleEvent.ts +61 -0
  429. package/src/scheduler/observability/ScheduleEventSink.ts +21 -0
  430. package/src/scheduler/observability/StatsTracker.ts +134 -0
  431. package/src/security/CookieSigner.ts +54 -0
  432. package/src/security/SignedUrl.ts +71 -0
  433. package/src/security/crypto.ts +98 -0
  434. package/src/server/HyperServer.ts +121 -0
  435. package/src/server/Server.ts +147 -0
  436. package/src/services/app.ts +34 -0
  437. package/src/services/router.ts +44 -0
  438. package/src/services/server.ts +40 -0
  439. package/src/session/Session.ts +231 -0
  440. package/src/session/SessionMiddleware.ts +153 -0
  441. package/src/session/drivers/CookieDriver.ts +48 -0
  442. package/src/session/drivers/MemoryDriver.ts +29 -0
  443. package/src/storage/S3Driver.ts +171 -0
  444. package/src/storage/StaticMiddleware.ts +182 -0
  445. package/src/storage/StaticProvider.ts +40 -0
  446. package/src/storage/StorageManager.ts +142 -0
  447. package/src/testing/RequestBuilder.ts +275 -0
  448. package/src/testing/TestClient.ts +344 -0
  449. package/src/types/helpers.ts +52 -0
  450. package/swcrc.app.json +17 -0
  451. package/tsconfig.app.json +21 -0
@@ -0,0 +1,343 @@
1
+ import * as fs from 'node:fs'
2
+ import * as path from 'node:path'
3
+
4
+ export interface Codemods {
5
+ addProvider(importPath: string): Promise<void>
6
+ addEnvVars(vars: Record<string, string>): Promise<void>
7
+ writeFile(filePath: string, content: string, options?: { force?: boolean }): Promise<void>
8
+ registerCommand(importPath: string): Promise<void>
9
+ registerMiddleware(importPath: string, options?: { tier?: 'server' | 'router' }): Promise<void>
10
+ }
11
+
12
+ type MiddlewareTier = 'server' | 'router'
13
+
14
+ function isMiddlewareTier(value: unknown): value is MiddlewareTier {
15
+ return value === 'server' || value === 'router'
16
+ }
17
+
18
+ function findTierBlock(
19
+ content: string,
20
+ tier: MiddlewareTier,
21
+ ): { start: number; end: number } | null {
22
+ // Word-boundary prefix `(?:^|[^A-Za-z0-9_$.])` rejects `customServer.use` / `myRouter.use`
23
+ // when scanning for the canonical `server` / `router` tier. The `.` exclusion also rejects
24
+ // `obj.server.use` shapes. Match[1] captures the literal `<tier>.use([` so we can compute
25
+ // the post-`[` cursor without including the boundary char.
26
+ const marker = new RegExp(
27
+ `(?:^|[^A-Za-z0-9_$.])(${tier}\\.use\\s*\\(\\s*(?:\\/\\*[\\s\\S]*?\\*\\/\\s*)?\\[)`,
28
+ )
29
+ const match = marker.exec(content)
30
+ if (match === null || match[1] === undefined) return null
31
+ const start = match.index + match[0].length
32
+ // The array close pattern `\]\s*,?\s*\)` matches `])`, `],)`, `],\n)`, `]\n)` — every
33
+ // canonical kernel shape produced by `ream new` or hand-formatted by Prettier. Using
34
+ // this pattern (vs a bare `]`) avoids early-truncating the slice on nested `]` inside
35
+ // an array entry — important for the dedup window since we read the slice content for
36
+ // cross-tier collision detection AND per-tier idempotency.
37
+ const closeMatch = /\]\s*,?\s*\)/.exec(content.slice(start))
38
+ if (closeMatch === null) return null
39
+ return { start, end: start + closeMatch.index }
40
+ }
41
+
42
+ /**
43
+ * Detect line ending (CRLF on Windows checkouts, LF elsewhere) so codemods preserve the file's existing convention.
44
+ * Majority-based: a stray CRLF in an otherwise-LF file must not flip the whole insertion to CRLF.
45
+ */
46
+ function detectLineEnding(content: string): '\r\n' | '\n' {
47
+ const crlfCount = (content.match(/\r\n/g) ?? []).length
48
+ const bareLfCount = (content.match(/(?<!\r)\n/g) ?? []).length
49
+ return crlfCount > bareLfCount ? '\r\n' : '\n'
50
+ }
51
+
52
+ /**
53
+ * Refuse to follow a symlink that escapes the project root. Mirrors the guard `writeFile` enforces
54
+ * (lines 137-155) so `registerCommand` and `registerMiddleware` cannot be tricked into writing
55
+ * outside `root` via a symlinked `reamrc.ts` or `start/kernel.ts`.
56
+ */
57
+ function assertCanonicallyInside(root: string, filePath: string, displayName: string): void {
58
+ if (!fs.existsSync(filePath)) return
59
+ const canonRoot = fs.realpathSync(root)
60
+ const canonFile = fs.realpathSync(filePath)
61
+ if (!canonFile.startsWith(canonRoot + path.sep) && canonFile !== canonRoot) {
62
+ throw new Error(
63
+ `[configure] Symlink escape detected: ${displayName} resolves outside project root`,
64
+ )
65
+ }
66
+ }
67
+
68
+ export function createCodemods(options?: { force?: boolean; cwd?: string }): Codemods {
69
+ const force = options?.force ?? false
70
+ const root = path.resolve(options?.cwd ?? process.cwd())
71
+
72
+ return {
73
+ /**
74
+ * Insert a provider import into `reamrc.ts` by string manipulation.
75
+ *
76
+ * KNOWN LIMITATION: this is a regex-based codemod, not a TS AST
77
+ * transform. It works for the canonical `providers: [ ... ]` shape
78
+ * emitted by `ream new` and tolerates comments/whitespace around
79
+ * the array literal. It does NOT handle unusual shapes such as
80
+ * providers declared via a spread, a ternary, a `const providers =
81
+ * [...]` extracted above the config object, or a `providers:`
82
+ * identifier that appears inside a string literal or block comment
83
+ * elsewhere in the file. For those cases, edit `reamrc.ts` by hand.
84
+ * A full AST-based rewrite is tracked separately.
85
+ */
86
+ async addProvider(importPath: string): Promise<void> {
87
+ const rcPath = path.join(root, 'reamrc.ts')
88
+ if (!fs.existsSync(rcPath)) {
89
+ throw new Error(
90
+ `[configure] reamrc.ts not found — cannot register provider ${importPath}. Run 'ream new' first.`,
91
+ )
92
+ }
93
+ // Same symlink-containment guard registerCommand/registerMiddleware/
94
+ // writeFile apply — a symlinked reamrc.ts must not redirect the
95
+ // write outside the project root.
96
+ assertCanonicallyInside(root, rcPath, 'reamrc.ts')
97
+
98
+ let content = fs.readFileSync(rcPath, 'utf8')
99
+ // Dedup check — any mention of the exact import path is treated
100
+ // as an existing registration. This is a coarse heuristic but
101
+ // safe because `importPath` is typically a package path like
102
+ // `@c9up/atlas/providers`, which should not appear in comments
103
+ // or unrelated strings in a typical `reamrc.ts`.
104
+ if (content.includes(`'${importPath}'`) || content.includes(`"${importPath}"`)) return
105
+
106
+ const entry = ` () => import('${importPath}'),`
107
+ // Marker tolerates extra whitespace and optional trailing comments
108
+ // between `providers:` and `[`. Still brittle to string-literal
109
+ // false-positives (see doc above).
110
+ const marker = /providers\s*:\s*(?:\/\*[\s\S]*?\*\/\s*)?\[/
111
+ const match = marker.exec(content)
112
+ if (!match) {
113
+ throw new Error(
114
+ `[configure] Could not find 'providers: [' in reamrc.ts — provider ${importPath} not added. Check your reamrc.ts format.`,
115
+ )
116
+ }
117
+ const insertAt = match.index + match[0].length
118
+ const eol = detectLineEnding(content)
119
+ content = `${content.slice(0, insertAt)}${eol}${entry}${content.slice(insertAt)}`
120
+ fs.writeFileSync(rcPath, content)
121
+ },
122
+
123
+ async addEnvVars(vars: Record<string, string>): Promise<void> {
124
+ const envPath = path.join(root, '.env')
125
+ // A symlinked .env must not redirect the write outside the root.
126
+ assertCanonicallyInside(root, envPath, '.env')
127
+ let content = fs.existsSync(envPath) ? fs.readFileSync(envPath, 'utf8') : ''
128
+ let added = 0
129
+
130
+ for (const [key, value] of Object.entries(vars)) {
131
+ const pattern = new RegExp(`^${key.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}=`, 'm')
132
+ if (!pattern.test(content)) {
133
+ content += `${key}=${value}\n`
134
+ added++
135
+ }
136
+ }
137
+
138
+ if (added > 0) fs.writeFileSync(envPath, content)
139
+ },
140
+
141
+ async writeFile(filePath: string, content: string, opts?: { force?: boolean }): Promise<void> {
142
+ if (path.isAbsolute(filePath)) {
143
+ throw new Error(`[configure] Absolute paths not allowed: ${filePath}`)
144
+ }
145
+ const resolved = path.resolve(root, filePath)
146
+ if (!resolved.startsWith(root + path.sep) && resolved !== root) {
147
+ throw new Error(`[configure] Refusing to write outside project root: ${filePath}`)
148
+ }
149
+
150
+ const shouldForce = opts?.force ?? force
151
+ if (fs.existsSync(resolved) && !shouldForce) return
152
+
153
+ const dir = path.dirname(resolved)
154
+ fs.mkdirSync(dir, { recursive: true })
155
+
156
+ const canonRoot = fs.realpathSync(root)
157
+
158
+ if (fs.existsSync(dir)) {
159
+ const canonDir = fs.realpathSync(dir)
160
+ if (!canonDir.startsWith(canonRoot + path.sep) && canonDir !== canonRoot) {
161
+ throw new Error(
162
+ `[configure] Symlink escape detected: ${filePath} — directory resolves outside project root`,
163
+ )
164
+ }
165
+ }
166
+
167
+ if (fs.existsSync(resolved)) {
168
+ const canonFile = fs.realpathSync(resolved)
169
+ if (!canonFile.startsWith(canonRoot + path.sep)) {
170
+ throw new Error(
171
+ `[configure] Symlink escape detected: ${filePath} — file resolves outside project root`,
172
+ )
173
+ }
174
+ }
175
+
176
+ fs.writeFileSync(resolved, content)
177
+ },
178
+
179
+ /**
180
+ * Insert a command import into `reamrc.ts` `commands: [ ... ]` array.
181
+ *
182
+ * Mirrors `addProvider`'s shape: regex-based codemod (NOT a TS AST
183
+ * transform), idempotent string-match dedup against the exact import
184
+ * path quoted, `[configure]` error prefix on failure.
185
+ *
186
+ * Bootstraps the `commands:` field when absent — inserts immediately
187
+ * after the existing `providers: [...]` block when present, otherwise
188
+ * before the closing `})` of `defineConfig({...})`. The field is
189
+ * optional in `ReamrcConfig` (Ignitor.ts:55), so adding it is
190
+ * non-breaking.
191
+ *
192
+ * Indent convention: inserted entries use 4-space indent, matching the
193
+ * canonical depth-2 nesting inside `defineConfig({ providers: [ ... ] })`.
194
+ * (registerMiddleware uses 2-space indent because its target is the
195
+ * depth-1 `<tier>.use([ ... ])` call in `start/kernel.ts`.)
196
+ *
197
+ * Marker anchoring: top-level fields of `defineConfig({...})` are
198
+ * anchored to start-of-line + ≤4 leading spaces to reject false matches
199
+ * inside nested objects (e.g. a `tests: { commands: [...] }` field
200
+ * higher in `reamrc.ts`).
201
+ */
202
+ async registerCommand(importPath: string): Promise<void> {
203
+ const rcPath = path.join(root, 'reamrc.ts')
204
+ if (!fs.existsSync(rcPath)) {
205
+ throw new Error(
206
+ `[configure] reamrc.ts not found — cannot register command ${importPath}. Run 'ream new' first.`,
207
+ )
208
+ }
209
+ assertCanonicallyInside(root, rcPath, 'reamrc.ts')
210
+
211
+ let content = fs.readFileSync(rcPath, 'utf8')
212
+ if (content.includes(`'${importPath}'`) || content.includes(`"${importPath}"`)) return
213
+
214
+ const eol = detectLineEnding(content)
215
+ const entry = ` () => import('${importPath}'),`
216
+
217
+ // Anchor `commands:` to start-of-line + ≤4 leading spaces (top-level field of
218
+ // `defineConfig({...})`). Rejects nested matches like `tests: { commands: [...] }`.
219
+ const arrayMarker = /^[ \t]{0,4}commands\s*:\s*(?:\/\*[\s\S]*?\*\/\s*)?\[/m
220
+ const arrayMatch = arrayMarker.exec(content)
221
+ if (arrayMatch !== null) {
222
+ const insertAt = arrayMatch.index + arrayMatch[0].length
223
+ content = `${content.slice(0, insertAt)}${eol}${entry}${content.slice(insertAt)}`
224
+ fs.writeFileSync(rcPath, content)
225
+ return
226
+ }
227
+
228
+ const block = ` commands: [${eol}${entry}${eol} ],${eol}`
229
+
230
+ // Same start-of-line anchor as the commands marker — rejects nested `providers:`.
231
+ // Note: the non-greedy `\[[\s\S]*?\]` still finds the FIRST `]` after `providers:`,
232
+ // so a top-level `providers: [...]` containing nested `]` (e.g. tuple-typed entries)
233
+ // can still mis-match. Rare in practice; if hit, the user's reamrc.ts is in a shape
234
+ // the regex codemod cannot safely manipulate — they should add `commands: []`
235
+ // manually and re-run, or fall through to the defineConfig-close fallback below.
236
+ const providersEndMarker = /^[ \t]{0,4}providers\s*:\s*\[[\s\S]*?\],?[ \t]*\r?\n/m
237
+ const providersEnd = providersEndMarker.exec(content)
238
+ if (providersEnd !== null) {
239
+ const insertAt = providersEnd.index + providersEnd[0].length
240
+ content = `${content.slice(0, insertAt)}${block}${content.slice(insertAt)}`
241
+ fs.writeFileSync(rcPath, content)
242
+ return
243
+ }
244
+
245
+ // Use the LAST `})` in the file (conventionally the `defineConfig({...})` close in a
246
+ // canonical `export default defineConfig({...})` reamrc). `lastIndexOf` avoids the
247
+ // /m-anchored `exec` returning the FIRST line ending in `})` — which would mis-target
248
+ // a helper above defineConfig like `const helper = makeProviders({ eager: true })`.
249
+ const defineCloseIdx = content.lastIndexOf('})')
250
+ if (defineCloseIdx !== -1) {
251
+ content = `${content.slice(0, defineCloseIdx)}${block}${content.slice(defineCloseIdx)}`
252
+ fs.writeFileSync(rcPath, content)
253
+ return
254
+ }
255
+
256
+ throw new Error(
257
+ `[configure] Could not find 'defineConfig({ ... })' in reamrc.ts — command ${importPath} not added. Check your reamrc.ts format.`,
258
+ )
259
+ },
260
+
261
+ /**
262
+ * Insert a middleware import into `start/kernel.ts` `<tier>.use([ ... ])`
263
+ * array. `tier` defaults to `'router'` (matched routes only) — the
264
+ * conservative choice that does not run on 404s. Set `tier: 'server'`
265
+ * for middleware that must run on every request including 404s
266
+ * (security headers, request-id, structured logging).
267
+ *
268
+ * Mirrors `addProvider`'s shape: regex-based codemod, idempotent
269
+ * per-tier string-match dedup, `[configure]` error prefix.
270
+ *
271
+ * KNOWN LIMITATION: does NOT bootstrap a missing `<tier>.use([])`
272
+ * block. Unlike `commands`, the `server.use` / `router.use` calls
273
+ * are user-authored idiom in `start/kernel.ts`, not a config-shape
274
+ * contract — synthesising them would require also synthesising the
275
+ * `import server from '@c9up/ream/services/server'` boilerplate, and
276
+ * is footgun-prone if the user has renamed identifiers. When the
277
+ * targeted block is absent, this method errors and asks the user
278
+ * to add the block manually.
279
+ *
280
+ * Cross-tier collision is rejected: registering the same import path
281
+ * in BOTH `server` and `router` tiers is almost certainly a mistake;
282
+ * the second call throws `[configure] middleware <importPath> is
283
+ * already registered in <other-tier> tier`.
284
+ *
285
+ * Indent convention: inserted entries use 2-space indent, matching
286
+ * the canonical depth-1 nesting inside `<tier>.use([ ... ])` at the
287
+ * top of `start/kernel.ts`. (registerCommand uses 4-space indent
288
+ * because its target is the depth-2 `defineConfig({ commands: [ ... ] })`
289
+ * block in `reamrc.ts`.)
290
+ */
291
+ async registerMiddleware(
292
+ importPath: string,
293
+ options?: { tier?: MiddlewareTier },
294
+ ): Promise<void> {
295
+ const requestedTier = options?.tier ?? 'router'
296
+ if (!isMiddlewareTier(requestedTier)) {
297
+ throw new Error(
298
+ `[configure] Invalid middleware tier: '${String(requestedTier)}'. Expected 'server' or 'router'.`,
299
+ )
300
+ }
301
+
302
+ const tier: MiddlewareTier = requestedTier
303
+ const otherTier: MiddlewareTier = tier === 'server' ? 'router' : 'server'
304
+
305
+ const kernelPath = path.join(root, 'start', 'kernel.ts')
306
+ if (!fs.existsSync(kernelPath)) {
307
+ throw new Error(
308
+ `[configure] start/kernel.ts not found — cannot register middleware ${importPath}. Run 'ream new' first or create the file from the installation guide.`,
309
+ )
310
+ }
311
+ assertCanonicallyInside(root, kernelPath, 'start/kernel.ts')
312
+
313
+ let content = fs.readFileSync(kernelPath, 'utf8')
314
+
315
+ const otherBlock = findTierBlock(content, otherTier)
316
+ if (otherBlock !== null) {
317
+ const otherSlice = content.slice(otherBlock.start, otherBlock.end)
318
+ if (otherSlice.includes(`'${importPath}'`) || otherSlice.includes(`"${importPath}"`)) {
319
+ throw new Error(
320
+ `[configure] middleware ${importPath} is already registered in ${otherTier} tier — cannot also register in ${tier} tier. Choose one tier or remove the existing registration.`,
321
+ )
322
+ }
323
+ }
324
+
325
+ const targetBlock = findTierBlock(content, tier)
326
+ if (targetBlock === null) {
327
+ throw new Error(
328
+ `[configure] Could not find '${tier}.use([' in start/kernel.ts — middleware ${importPath} not added. Add the block manually or check your start/kernel.ts format.`,
329
+ )
330
+ }
331
+
332
+ const targetSlice = content.slice(targetBlock.start, targetBlock.end)
333
+ if (targetSlice.includes(`'${importPath}'`) || targetSlice.includes(`"${importPath}"`)) {
334
+ return
335
+ }
336
+
337
+ const eol = detectLineEnding(content)
338
+ const entry = ` () => import('${importPath}'),`
339
+ content = `${content.slice(0, targetBlock.start)}${eol}${entry}${content.slice(targetBlock.start)}`
340
+ fs.writeFileSync(kernelPath, content)
341
+ },
342
+ }
343
+ }
@@ -0,0 +1,49 @@
1
+ /**
2
+ * Configuration loader — reads config/*.ts + .env files.
3
+ *
4
+ * @implements FR18, FR19
5
+ */
6
+
7
+ /**
8
+ * In-memory config store. In production this is populated from config/*.ts
9
+ * files via `defineConfig()`. The class is its own contract — `AppContext.config`
10
+ * is typed against it directly (structural).
11
+ */
12
+ export class ConfigStore {
13
+ private store: Map<string, unknown> = new Map()
14
+
15
+ get<T = unknown>(key: string): T | undefined {
16
+ return this.store.get(key) as T | undefined
17
+ }
18
+
19
+ set(key: string, value: unknown): void {
20
+ this.store.set(key, value)
21
+ }
22
+
23
+ has(key: string): boolean {
24
+ return this.store.has(key)
25
+ }
26
+
27
+ /** Load config from an object (used for testing and initial setup). */
28
+ loadFromObject(config: Record<string, unknown>): void {
29
+ for (const [key, value] of Object.entries(config)) {
30
+ this.store.set(key, value)
31
+ }
32
+ }
33
+ }
34
+
35
+ /**
36
+ * Read an environment variable with optional default.
37
+ * @implements FR19
38
+ */
39
+ export function env(key: string, defaultValue?: string): string | undefined {
40
+ return process.env[key] ?? defaultValue
41
+ }
42
+
43
+ /**
44
+ * Define typed configuration for a module.
45
+ * Returns the config object as-is (type-checked by the module's defineConfig).
46
+ */
47
+ export function defineModuleConfig<T>(config: T): T {
48
+ return config
49
+ }
@@ -0,0 +1,157 @@
1
+ /**
2
+ * Root Error Boundary — all errors flow through the bus.
3
+ *
4
+ * @implements Story 4.8
5
+ *
6
+ * Catches all uncaught errors and emits them as event-bus events:
7
+ * - service.error — handler/business logic failures
8
+ * - security.rejected — Blackhole rejections
9
+ * - system.error — infrastructure failures (NAPI, DB)
10
+ * - system.fatal — unrecoverable process-level errors
11
+ */
12
+
13
+ export type ErrorSeverity = 'info' | 'warning' | 'critical'
14
+
15
+ export interface ErrorEvent {
16
+ type: 'service.error' | 'security.rejected' | 'system.error' | 'system.fatal' | 'system.info'
17
+ source: string
18
+ message: string
19
+ severity: ErrorSeverity
20
+ correlationId?: string
21
+ originalError?: string
22
+ timestamp: string
23
+ }
24
+
25
+ export type ErrorEmitter = (event: ErrorEvent) => void
26
+
27
+ /**
28
+ * Root Error Boundary.
29
+ *
30
+ * Wraps the entire application lifecycle:
31
+ * - Catches unhandledRejection and uncaughtException
32
+ * - Emits structured error events via the provided emitter (event bus)
33
+ * - In dev mode, also logs to console
34
+ */
35
+ export class ErrorBoundary {
36
+ private emitter: ErrorEmitter
37
+ private devMode: boolean
38
+ private installed = false
39
+ private rejectionHandler?: (reason: unknown) => void
40
+ private exceptionHandler?: (error: Error) => void
41
+
42
+ constructor(emitter: ErrorEmitter, devMode = false) {
43
+ this.emitter = emitter
44
+ this.devMode = devMode
45
+ }
46
+
47
+ /** Install global error handlers. */
48
+ install(): void {
49
+ if (this.installed) return
50
+
51
+ // Skip global install under vitest — each test instantiates its own
52
+ // Ignitor and rarely calls `.stop()`, so per-test listeners pile up on
53
+ // the singleton `process` and trigger Node's MaxListenersExceeded
54
+ // warning. Production code-paths still get coverage via direct
55
+ // `handleError`/`serviceError` calls; only the Node-level signal
56
+ // bridging is suppressed.
57
+ if (process.env.VITEST === 'true' || process.env.NODE_ENV === 'test') {
58
+ this.installed = true
59
+ return
60
+ }
61
+
62
+ this.rejectionHandler = (reason) => {
63
+ this.handleError('system.fatal', 'UnhandledRejection', reason)
64
+ }
65
+
66
+ this.exceptionHandler = (error) => {
67
+ this.handleError('system.fatal', 'UncaughtException', error)
68
+ // Node.js is in undefined state after uncaughtException — must exit
69
+ process.exit(1)
70
+ }
71
+
72
+ process.on('unhandledRejection', this.rejectionHandler)
73
+ process.on('uncaughtException', this.exceptionHandler)
74
+
75
+ this.installed = true
76
+ }
77
+
78
+ /** Uninstall global error handlers (for testing). Only removes OUR handlers. */
79
+ uninstall(): void {
80
+ if (this.rejectionHandler) {
81
+ process.removeListener('unhandledRejection', this.rejectionHandler)
82
+ }
83
+ if (this.exceptionHandler) {
84
+ process.removeListener('uncaughtException', this.exceptionHandler)
85
+ }
86
+ this.rejectionHandler = undefined
87
+ this.exceptionHandler = undefined
88
+ this.installed = false
89
+ }
90
+
91
+ /** Emit a service error (handler failure). */
92
+ serviceError(source: string, error: unknown, correlationId?: string): void {
93
+ this.handleError('service.error', source, error, correlationId)
94
+ }
95
+
96
+ /** Emit a security rejection. */
97
+ securityRejected(source: string, reason: string, correlationId?: string): void {
98
+ const event: ErrorEvent = {
99
+ type: 'security.rejected',
100
+ source,
101
+ message: reason,
102
+ severity: 'warning',
103
+ correlationId,
104
+ timestamp: new Date().toISOString(),
105
+ }
106
+ this.emit(event)
107
+ }
108
+
109
+ /** Emit a system error (infrastructure failure). */
110
+ systemError(source: string, error: unknown, correlationId?: string): void {
111
+ this.handleError('system.error', source, error, correlationId)
112
+ }
113
+
114
+ private handleError(
115
+ type: ErrorEvent['type'],
116
+ source: string,
117
+ error: unknown,
118
+ correlationId?: string,
119
+ ): void {
120
+ const message = error instanceof Error ? error.message : String(error)
121
+ const stack = error instanceof Error ? error.stack : undefined
122
+
123
+ const event: ErrorEvent = {
124
+ type,
125
+ source,
126
+ message,
127
+ severity:
128
+ type === 'system.fatal' ? 'critical' : type === 'system.error' ? 'critical' : 'warning',
129
+ correlationId,
130
+ originalError: stack ?? message,
131
+ timestamp: new Date().toISOString(),
132
+ }
133
+
134
+ this.emit(event)
135
+ }
136
+
137
+ private emit(event: ErrorEvent): void {
138
+ try {
139
+ this.emitter(event)
140
+ } catch {
141
+ // If the emitter itself fails, log to stderr as last resort
142
+ process.stderr.write(`[ErrorBoundary] Failed to emit: ${JSON.stringify(event)}\n`)
143
+ }
144
+
145
+ if (this.devMode) {
146
+ const prefix =
147
+ event.type === 'system.fatal'
148
+ ? '✗ FATAL'
149
+ : event.type === 'system.error'
150
+ ? '✗ ERROR'
151
+ : event.type === 'security.rejected'
152
+ ? '⚠ SECURITY'
153
+ : '✗ SERVICE'
154
+ process.stderr.write(`${prefix} [${event.source}] ${event.message}\n`)
155
+ }
156
+ }
157
+ }
@@ -0,0 +1,80 @@
1
+ /**
2
+ * Graceful Shutdown — handles SIGTERM/SIGINT with drain timeout.
3
+ *
4
+ * @implements FR82
5
+ */
6
+
7
+ export interface ShutdownOptions {
8
+ /** Maximum time to wait for drain in ms (default: 30000). */
9
+ drainTimeout?: number
10
+ /** Callback to execute during shutdown. */
11
+ onShutdown: () => Promise<void>
12
+ /** Optional logger. */
13
+ logger?: { info: (msg: string) => void; error: (msg: string) => void }
14
+ }
15
+
16
+ export interface ShutdownHandle {
17
+ /** Manually trigger shutdown (for testing). */
18
+ trigger: () => Promise<void>
19
+ /** Remove signal listeners (for testing cleanup). */
20
+ cleanup: () => void
21
+ }
22
+
23
+ /**
24
+ * Install graceful shutdown handlers for SIGTERM and SIGINT.
25
+ * Returns a handle to manually trigger shutdown or clean up listeners.
26
+ */
27
+ export function installGracefulShutdown(options: ShutdownOptions): ShutdownHandle {
28
+ const drainTimeout = options.drainTimeout ?? 30_000
29
+ const logger = options.logger ?? { info: () => {}, error: () => {} }
30
+ let shutdownInProgress = false
31
+
32
+ const shutdown = async () => {
33
+ if (shutdownInProgress) return
34
+ shutdownInProgress = true
35
+
36
+ logger.info('Graceful shutdown initiated...')
37
+
38
+ let timeoutHandle: ReturnType<typeof setTimeout> | undefined
39
+
40
+ const drainPromise = options.onShutdown().catch((err) => {
41
+ logger.error(`Shutdown error: ${err instanceof Error ? err.message : String(err)}`)
42
+ })
43
+
44
+ const timeoutPromise = new Promise<void>((resolve) => {
45
+ timeoutHandle = setTimeout(() => {
46
+ logger.error(`Drain timeout exceeded (${drainTimeout}ms) — forcing shutdown`)
47
+ resolve()
48
+ }, drainTimeout)
49
+ })
50
+
51
+ await Promise.race([drainPromise, timeoutPromise])
52
+ if (timeoutHandle) clearTimeout(timeoutHandle)
53
+
54
+ logger.info('Shutdown complete')
55
+ process.exit(0)
56
+ }
57
+
58
+ const onSigterm = () => {
59
+ shutdown().catch((err) => {
60
+ logger.error(`Fatal: ${err}`)
61
+ process.exit(1)
62
+ })
63
+ }
64
+ const onSigint = () => {
65
+ shutdown().catch((err) => {
66
+ logger.error(`Fatal: ${err}`)
67
+ process.exit(1)
68
+ })
69
+ }
70
+
71
+ process.on('SIGTERM', onSigterm)
72
+ process.on('SIGINT', onSigint)
73
+
74
+ const cleanup = () => {
75
+ process.off('SIGTERM', onSigterm)
76
+ process.off('SIGINT', onSigint)
77
+ }
78
+
79
+ return { trigger: shutdown, cleanup }
80
+ }