@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,74 @@
1
+ /**
2
+ * Base Provider class — modules register via providers.
3
+ *
4
+ * @implements FR20
5
+ *
6
+ * Lifecycle (AdonisJS-compatible):
7
+ * register() → boot() → start() → ready() → ... → shutdown()
8
+ */
9
+
10
+ import type { ConfigStore } from './ConfigLoader.js'
11
+ import type { Container } from './container/Container.js'
12
+
13
+ export interface AppContext {
14
+ container: Container
15
+ config: ConfigStore
16
+ }
17
+
18
+ /**
19
+ * Structural contract for a provider — all phases optional. Independently-
20
+ * published packages (spectrum, warden, blackhole, relay, ...) implement
21
+ * only the phases they need without taking a hard dependency on
22
+ * `@c9up/ream`. The lifecycle runner guards each call with a `typeof`
23
+ * check, so a missing method is opt-out, not a contract break.
24
+ *
25
+ * The abstract `Provider` class below is the convenience implementation
26
+ * for code that *does* live inside this package — it ships empty
27
+ * defaults for every phase. Both shapes satisfy `ProviderContract`.
28
+ */
29
+ export interface ProviderContract {
30
+ register?(): void
31
+ boot?(): Promise<void> | void
32
+ start?(): Promise<void> | void
33
+ ready?(): Promise<void> | void
34
+ shutdown?(): Promise<void> | void
35
+ }
36
+
37
+ /**
38
+ * Invoke an async lifecycle phase on a provider, ignoring it when the
39
+ * method is absent. Centralises the duck-type guard that the lifecycle
40
+ * runner has to do at every phase since `ProviderContract` makes them
41
+ * all optional.
42
+ */
43
+ export async function callProviderPhase(
44
+ provider: ProviderContract,
45
+ phase: 'boot' | 'start' | 'ready' | 'shutdown',
46
+ ): Promise<void> {
47
+ const fn = provider[phase]
48
+ if (typeof fn === 'function') {
49
+ await fn.call(provider)
50
+ }
51
+ }
52
+
53
+ export abstract class Provider implements ProviderContract {
54
+ protected app: AppContext
55
+
56
+ constructor(app: AppContext) {
57
+ this.app = app
58
+ }
59
+
60
+ /** Phase 1: Register bindings in the container (synchronous). */
61
+ register(): void {}
62
+
63
+ /** Phase 2: Boot — framework setup, verify connections. */
64
+ async boot(): Promise<void> {}
65
+
66
+ /** Phase 3: Start — before HTTP server starts. Import routes, warm caches. */
67
+ async start(): Promise<void> {}
68
+
69
+ /** Phase 4: Ready — app fully operational, HTTP server listening. */
70
+ async ready(): Promise<void> {}
71
+
72
+ /** Cleanup on shutdown (reverse order). */
73
+ async shutdown(): Promise<void> {}
74
+ }
@@ -0,0 +1,183 @@
1
+ /**
2
+ * BodyParser middleware — parses request bodies by content-type.
3
+ *
4
+ * Supports JSON, form-urlencoded, and raw text.
5
+ * Multipart (file uploads) handled separately.
6
+ * Configured via config/bodyparser.ts.
7
+ */
8
+
9
+ import type { HttpContext } from '../http/HttpContext.js'
10
+ import { hydrateMultipartPayload } from './MultipartFile.js'
11
+
12
+ export interface BodyParserConfig {
13
+ json?: {
14
+ enabled?: boolean
15
+ limit?: string // e.g. '1mb'
16
+ types?: string[]
17
+ }
18
+ form?: {
19
+ enabled?: boolean
20
+ limit?: string
21
+ types?: string[]
22
+ }
23
+ raw?: {
24
+ enabled?: boolean
25
+ limit?: string
26
+ types?: string[]
27
+ }
28
+ multipart?: {
29
+ enabled?: boolean
30
+ limit?: string
31
+ maxFields?: number
32
+ tmpDir?: string
33
+ types?: string[]
34
+ }
35
+ }
36
+
37
+ interface ResolvedBodyParserConfig {
38
+ json: Required<NonNullable<BodyParserConfig['json']>>
39
+ form: Required<NonNullable<BodyParserConfig['form']>>
40
+ raw: Required<NonNullable<BodyParserConfig['raw']>>
41
+ multipart: Required<NonNullable<BodyParserConfig['multipart']>>
42
+ }
43
+
44
+ const DEFAULT_CONFIG: ResolvedBodyParserConfig = {
45
+ json: {
46
+ enabled: true,
47
+ limit: '1mb',
48
+ types: ['application/json', 'application/vnd.api+json'],
49
+ },
50
+ form: {
51
+ enabled: true,
52
+ limit: '1mb',
53
+ types: ['application/x-www-form-urlencoded'],
54
+ },
55
+ raw: {
56
+ enabled: false,
57
+ limit: '1mb',
58
+ types: ['text/plain'],
59
+ },
60
+ multipart: {
61
+ enabled: true,
62
+ limit: '20mb',
63
+ maxFields: 500,
64
+ tmpDir: '/tmp',
65
+ types: ['multipart/form-data'],
66
+ },
67
+ }
68
+
69
+ export default class BodyParserMiddleware {
70
+ #config: ResolvedBodyParserConfig
71
+
72
+ constructor(config?: BodyParserConfig) {
73
+ this.#config = {
74
+ json: { ...DEFAULT_CONFIG.json, ...config?.json },
75
+ form: { ...DEFAULT_CONFIG.form, ...config?.form },
76
+ raw: { ...DEFAULT_CONFIG.raw, ...config?.raw },
77
+ multipart: { ...DEFAULT_CONFIG.multipart, ...config?.multipart },
78
+ }
79
+ }
80
+
81
+ async handle(ctx: HttpContext, next: () => Promise<void>) {
82
+ const contentType = ctx.request.header('content-type') ?? ''
83
+ const rawBody = ctx.request.raw()
84
+
85
+ // Size check
86
+ if (Buffer.byteLength(rawBody, 'utf8') > parseSize(this.#getLimit(contentType))) {
87
+ ctx.response.status(413).json({
88
+ error: { code: 'E_REQUEST_ENTITY_TOO_LARGE', message: 'Request body exceeds size limit' },
89
+ })
90
+ return
91
+ }
92
+
93
+ // JSON
94
+ if (this.#config.json.enabled && matchesType(contentType, this.#config.json.types)) {
95
+ // Request already lazy-parses JSON — nothing to do
96
+ }
97
+
98
+ // Form URL-encoded
99
+ if (this.#config.form.enabled && matchesType(contentType, this.#config.form.types)) {
100
+ ctx.request.setParsedBody(parseFormUrlEncoded(rawBody))
101
+ }
102
+
103
+ // Raw text — wrap the string under `_body` so consumers can still
104
+ // reach it through `request.input('_body')`. This matches the fallback
105
+ // shape used by `Request.#ensureParsedBody` when JSON yields a non-object.
106
+ if (this.#config.raw.enabled && matchesType(contentType, this.#config.raw.types)) {
107
+ ctx.request.setParsedBody({ _body: rawBody })
108
+ }
109
+
110
+ // Multipart — the Rust-side HyperServer parses the body server-side
111
+ // (multer crate) and ships the structured payload on `request.multipart`.
112
+ // JS just hydrates `MultipartFile` instances from the typed envelope.
113
+ if (this.#config.multipart.enabled && matchesType(contentType, this.#config.multipart.types)) {
114
+ const payload = ctx.request.multipart()
115
+ if (payload) {
116
+ const { fields, files } = hydrateMultipartPayload(payload)
117
+ ctx.request.setParsedBody(fields)
118
+ ctx.request.setFiles(files)
119
+ }
120
+ }
121
+
122
+ await next()
123
+ }
124
+
125
+ #getLimit(contentType: string): string {
126
+ if (matchesType(contentType, this.#config.json.types)) return this.#config.json.limit
127
+ if (matchesType(contentType, this.#config.form.types)) return this.#config.form.limit
128
+ if (matchesType(contentType, this.#config.multipart.types)) return this.#config.multipart.limit
129
+ return this.#config.raw.limit
130
+ }
131
+ }
132
+
133
+ function matchesType(contentType: string, types: string[]): boolean {
134
+ return types.some((t) => contentType.includes(t))
135
+ }
136
+
137
+ function parseSize(size: string): number {
138
+ const match = size.match(/^(\d+)(kb|mb|gb)?$/i)
139
+ if (!match) return 1024 * 1024 // default 1mb
140
+ const num = parseInt(match[1], 10)
141
+ switch (match[2]?.toLowerCase()) {
142
+ case 'kb':
143
+ return num * 1024
144
+ case 'mb':
145
+ return num * 1024 * 1024
146
+ case 'gb':
147
+ return num * 1024 * 1024 * 1024
148
+ default:
149
+ return num
150
+ }
151
+ }
152
+
153
+ function parseFormUrlEncoded(body: string): Record<string, unknown> {
154
+ const result: Record<string, unknown> = {}
155
+ if (!body) return result
156
+ for (const pair of body.split('&')) {
157
+ if (!pair) continue
158
+ const eqIdx = pair.indexOf('=')
159
+ if (eqIdx === -1) {
160
+ result[decodeFormComponent(pair)] = ''
161
+ } else {
162
+ result[decodeFormComponent(pair.slice(0, eqIdx))] = decodeFormComponent(pair.slice(eqIdx + 1))
163
+ }
164
+ }
165
+ return result
166
+ }
167
+
168
+ // `application/x-www-form-urlencoded` reserves `+` for space (RFC 1866 / WHATWG
169
+ // URL form spec), so the substitution must happen before percent-decoding —
170
+ // otherwise a literal `+` (encoded as `%2B`) would be turned into a space too.
171
+ //
172
+ // Malformed percent-encoding (e.g. a truncated `a=%E0%A4%A`) makes
173
+ // decodeURIComponent throw URIError; mirror Request.ts#safeDecode and fall back
174
+ // to the substituted-but-undecoded value so an invalid body is a parse miss,
175
+ // not a 500.
176
+ function decodeFormComponent(value: string): string {
177
+ const spaced = value.replace(/\+/g, ' ')
178
+ try {
179
+ return decodeURIComponent(spaced)
180
+ } catch {
181
+ return spaced
182
+ }
183
+ }
@@ -0,0 +1,181 @@
1
+ /**
2
+ * MultipartFile — represents an uploaded file.
3
+ *
4
+ * Like AdonisJS MultipartFile:
5
+ * const avatar = request.file('avatar', { size: '2mb', extnames: ['jpg', 'png'] })
6
+ * await avatar.moveToDisk('uploads')
7
+ */
8
+
9
+ import { randomBytes } from 'node:crypto'
10
+ import { Readable } from 'node:stream'
11
+
12
+ export interface FileValidationOptions {
13
+ size?: string
14
+ extnames?: string[]
15
+ }
16
+
17
+ export class MultipartFile {
18
+ /** Original filename from the client. */
19
+ readonly clientName: string
20
+
21
+ /** File extension (without dot). */
22
+ readonly extname: string
23
+
24
+ /** MIME type from the Content-Type header. */
25
+ readonly type: string
26
+
27
+ /** File size in bytes. */
28
+ readonly size: number
29
+
30
+ /** Raw file content as Buffer. */
31
+ readonly content: Buffer
32
+
33
+ /** Field name in the form. */
34
+ readonly fieldName: string
35
+
36
+ /** Validation errors. */
37
+ readonly errors: string[] = []
38
+
39
+ /** Whether the file has been moved. */
40
+ #moved = false
41
+
42
+ constructor(options: {
43
+ fieldName: string
44
+ clientName: string
45
+ type: string
46
+ content: Buffer
47
+ }) {
48
+ this.fieldName = options.fieldName
49
+ this.clientName = options.clientName
50
+ this.type = options.type
51
+ this.content = options.content
52
+ this.size = options.content.length
53
+ const segments = options.clientName.split('.')
54
+ this.extname = segments.length > 1 ? (segments.at(-1) ?? '').toLowerCase() : ''
55
+ }
56
+
57
+ /** Validate file against size and extension rules. */
58
+ validate(options: FileValidationOptions): boolean {
59
+ if (options.size) {
60
+ const maxBytes = parseFileSize(options.size)
61
+ if (this.size > maxBytes) {
62
+ this.errors.push(`File size ${this.size} exceeds limit ${options.size}`)
63
+ }
64
+ }
65
+ if (options.extnames && options.extnames.length > 0) {
66
+ if (!options.extnames.includes(this.extname)) {
67
+ this.errors.push(
68
+ `Extension '${this.extname}' not allowed. Allowed: ${options.extnames.join(', ')}`,
69
+ )
70
+ }
71
+ }
72
+ return this.errors.length === 0
73
+ }
74
+
75
+ get isValid(): boolean {
76
+ return this.errors.length === 0
77
+ }
78
+
79
+ /**
80
+ * Move file to a directory on disk. When `name` is supplied it must be
81
+ * a plain filename — no path separators, no `.`/`..` segments. The
82
+ * common footgun is passing the client-supplied `clientName` directly
83
+ * (`await file.moveToDisk('uploads', file.clientName)`); a malicious
84
+ * upload could otherwise traverse out of `directory` via `../../etc/...`.
85
+ */
86
+ async moveToDisk(directory: string, name?: string): Promise<string> {
87
+ const { mkdirSync, writeFileSync } = await import('node:fs')
88
+ const { join } = await import('node:path')
89
+
90
+ if (name !== undefined && !isSafeFilename(name)) {
91
+ throw new Error(
92
+ `MultipartFile.moveToDisk: 'name' must be a plain filename (no path separators, '.', or '..'). Got: ${JSON.stringify(name)}`,
93
+ )
94
+ }
95
+
96
+ mkdirSync(directory, { recursive: true })
97
+ const fileName = name ?? `${randomBytes(16).toString('hex')}.${this.extname}`
98
+ // Defense-in-depth: the default branch interpolates `extname` (derived
99
+ // from attacker-controlled `clientName`) into the filename, bypassing
100
+ // the `name` guard above. Re-validate the synthesized fileName before
101
+ // join to reject a hostile extname (separators, NUL).
102
+ if (!isSafeFilename(fileName)) {
103
+ throw new Error(
104
+ `MultipartFile.moveToDisk: derived filename '${fileName}' is unsafe (separator or invalid char in clientName-derived extname).`,
105
+ )
106
+ }
107
+ const filePath = join(directory, fileName)
108
+ writeFileSync(filePath, this.content)
109
+ this.#moved = true
110
+ return filePath
111
+ }
112
+
113
+ get isMoved(): boolean {
114
+ return this.#moved
115
+ }
116
+
117
+ /**
118
+ * Return a fresh `Readable` over the file's bytes. The underlying buffer is
119
+ * not consumed — repeated calls always start at byte 0, so callers can pipe
120
+ * the same file into multiple sinks (e.g. validation hash + disk write)
121
+ * without `Buffer.from(this.content)` boilerplate.
122
+ */
123
+ stream(): Readable {
124
+ return Readable.from(this.content, { objectMode: false })
125
+ }
126
+ }
127
+
128
+ function isSafeFilename(name: string): boolean {
129
+ if (name.length === 0) return false
130
+ if (name === '.' || name === '..') return false
131
+ if (/[\\/\0]/.test(name)) return false
132
+ return true
133
+ }
134
+
135
+ function parseFileSize(size: string): number {
136
+ const match = size.match(/^(\d+)(kb|mb|gb)?$/i)
137
+ if (!match) return 1024 * 1024
138
+ const num = parseInt(match[1], 10)
139
+ switch (match[2]?.toLowerCase()) {
140
+ case 'kb':
141
+ return num * 1024
142
+ case 'mb':
143
+ return num * 1024 * 1024
144
+ case 'gb':
145
+ return num * 1024 * 1024 * 1024
146
+ default:
147
+ return num
148
+ }
149
+ }
150
+
151
+ /**
152
+ * Hydrate `MultipartFile` instances from the typed payload the Rust
153
+ * HyperServer attaches to the request. The wire-level parser (multer crate,
154
+ * see `crates/ream-http/src/multipart.rs`) handles the actual RFC 7578
155
+ * parsing — JS just decodes the base64 envelope and wraps the result.
156
+ */
157
+ export function hydrateMultipartPayload(payload: {
158
+ fields: Array<{ name: string; value: string }>
159
+ files: Array<{
160
+ fieldName: string
161
+ clientName: string
162
+ contentType: string
163
+ size: number
164
+ contentB64: string
165
+ }>
166
+ }): { fields: Record<string, string>; files: MultipartFile[] } {
167
+ const fields: Record<string, string> = {}
168
+ for (const field of payload.fields) {
169
+ fields[field.name] = field.value
170
+ }
171
+ const files = payload.files.map(
172
+ (f) =>
173
+ new MultipartFile({
174
+ fieldName: f.fieldName,
175
+ clientName: f.clientName,
176
+ type: f.contentType,
177
+ content: Buffer.from(f.contentB64, 'base64'),
178
+ }),
179
+ )
180
+ return { fields, files }
181
+ }
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Parse a human-readable size string (e.g. '1mb', '512kb', '2gb') to bytes.
3
+ * Single source of truth — used by both BodyParserMiddleware and MultipartFile.
4
+ */
5
+ export function parseSize(size: string): number {
6
+ const match = size.match(/^(\d+)(kb|mb|gb)?$/i)
7
+ if (!match) return 1024 * 1024 // default 1mb
8
+ const num = parseInt(match[1], 10)
9
+ switch (match[2]?.toLowerCase()) {
10
+ case 'kb':
11
+ return num * 1024
12
+ case 'mb':
13
+ return num * 1024 * 1024
14
+ case 'gb':
15
+ return num * 1024 * 1024 * 1024
16
+ default:
17
+ return num
18
+ }
19
+ }
@@ -0,0 +1,78 @@
1
+ /**
2
+ * Default HyperServer factory — encapsulates the platform-suffix mapping
3
+ * and the require() lookup so consumer apps stop hand-rolling the same
4
+ * ~25 lines in every `bin/server.ts`.
5
+ *
6
+ * Usage in an app:
7
+ *
8
+ * import { createHyperServerFactory } from '@c9up/ream/bootstrap'
9
+ * new Ignitor(APP_ROOT, {
10
+ * port: 3000,
11
+ * serverFactory: await createHyperServerFactory(),
12
+ * })
13
+ *
14
+ * Returns `undefined` when no prebuilt binary matches the host platform —
15
+ * callers can branch on that to fall back to a JS-only server in tests.
16
+ *
17
+ * Binary lives at the package root (`./index.<platform>.node`),
18
+ * matching the @c9up/chronos / @c9up/sigil convention.
19
+ */
20
+
21
+ import { createRequire } from 'node:module'
22
+ import { dirname, join } from 'node:path'
23
+ import { arch, platform } from 'node:process'
24
+ import { fileURLToPath } from 'node:url'
25
+ import type { HyperServerLike } from '../Ignitor.js'
26
+
27
+ const PLATFORM_SUFFIX: Record<string, string> = {
28
+ 'linux-x64': 'linux-x64-gnu',
29
+ 'linux-arm64': 'linux-arm64-gnu',
30
+ 'darwin-x64': 'darwin-x64',
31
+ 'darwin-arm64': 'darwin-arm64',
32
+ 'win32-x64': 'win32-x64-msvc',
33
+ }
34
+
35
+ interface HyperServerCtor {
36
+ new (port: number): HyperServerLike
37
+ }
38
+
39
+ interface HyperServerNapiModule {
40
+ HyperServer: HyperServerCtor
41
+ }
42
+
43
+ function isHyperServerNapi(value: unknown): value is HyperServerNapiModule {
44
+ return (
45
+ typeof value === 'object' &&
46
+ value !== null &&
47
+ 'HyperServer' in value &&
48
+ typeof (value as { HyperServer: unknown }).HyperServer === 'function'
49
+ )
50
+ }
51
+
52
+ /**
53
+ * Resolve the prebuilt HyperServer NAPI binary for the current platform.
54
+ * Returns a `serverFactory` compatible with `IgnitorConfig`, or `undefined`
55
+ * when no binary is available (unsupported platform / missing build).
56
+ */
57
+ export function createHyperServerFactory(): ((port: number) => HyperServerLike) | undefined {
58
+ const suffix = PLATFORM_SUFFIX[`${platform}-${arch}`]
59
+ if (!suffix) return undefined
60
+
61
+ const require2 = createRequire(import.meta.url)
62
+ const here = dirname(fileURLToPath(import.meta.url))
63
+ // bootstrap/hyper-server.ts lives at src/bootstrap/, so the package root
64
+ // is two directories up.
65
+ const binaryPath = join(here, '..', '..', `index.${suffix}.node`)
66
+
67
+ let mod: unknown
68
+ try {
69
+ mod = require2(binaryPath)
70
+ } catch {
71
+ return undefined
72
+ }
73
+
74
+ if (!isHyperServerNapi(mod)) return undefined
75
+
76
+ const Ctor = mod.HyperServer
77
+ return (port: number) => new Ctor(port)
78
+ }
@@ -0,0 +1,82 @@
1
+ /**
2
+ * CommandRunner — minimal CLI command framework.
3
+ *
4
+ * Registers commands and dispatches based on argv.
5
+ * Like AdonisJS Ace but lightweight.
6
+ */
7
+
8
+ export interface Command {
9
+ name: string
10
+ description: string
11
+ run(args: string[], flags: Record<string, string | boolean>): Promise<void>
12
+ }
13
+
14
+ export class CommandRunner {
15
+ #commands: Map<string, Command> = new Map()
16
+
17
+ register(command: Command): this {
18
+ this.#commands.set(command.name, command)
19
+ return this
20
+ }
21
+
22
+ async handle(argv: string[]): Promise<void> {
23
+ const { commandName, args, flags } = parseArgv(argv)
24
+
25
+ if (!commandName || commandName === 'help') {
26
+ this.#printHelp()
27
+ return
28
+ }
29
+
30
+ const command = this.#commands.get(commandName)
31
+ if (!command) {
32
+ console.error(`Unknown command: ${commandName}`)
33
+ console.error(`Run without arguments to see available commands.`)
34
+ process.exitCode = 1
35
+ return
36
+ }
37
+
38
+ await command.run(args, flags)
39
+ }
40
+
41
+ #printHelp(): void {
42
+ console.log('\nAvailable commands:\n')
43
+ for (const [name, cmd] of this.#commands) {
44
+ console.log(` ${name.padEnd(25)} ${cmd.description}`)
45
+ }
46
+ console.log('')
47
+ }
48
+
49
+ getCommands(): Map<string, Command> {
50
+ return new Map(this.#commands)
51
+ }
52
+ }
53
+
54
+ function parseArgv(argv: string[]): {
55
+ commandName: string | undefined
56
+ args: string[]
57
+ flags: Record<string, string | boolean>
58
+ } {
59
+ const flags: Record<string, string | boolean> = {}
60
+ const positional: string[] = []
61
+
62
+ for (const arg of argv) {
63
+ if (arg.startsWith('--')) {
64
+ const eqIdx = arg.indexOf('=')
65
+ if (eqIdx !== -1) {
66
+ flags[arg.slice(2, eqIdx)] = arg.slice(eqIdx + 1)
67
+ } else {
68
+ flags[arg.slice(2)] = true
69
+ }
70
+ } else if (arg.startsWith('-') && arg.length === 2) {
71
+ flags[arg.slice(1)] = true
72
+ } else {
73
+ positional.push(arg)
74
+ }
75
+ }
76
+
77
+ return {
78
+ commandName: positional[0],
79
+ args: positional.slice(1),
80
+ flags,
81
+ }
82
+ }