@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,601 @@
1
+ /**
2
+ * `Scheduler` — thin TypeScript wrapper around `ream-scheduler-napi`.
3
+ *
4
+ * The actual cron parser, task registry, and tick loop live in Rust
5
+ * (Story 28.1). This class:
6
+ * - Lazily loads the NAPI `.node` binary on first instantiation.
7
+ * - Adapts the native surface to a typed TS API.
8
+ * - Translates `napi::Error` JSON payloads into `ReamError`.
9
+ * - Tracks registered cron expressions locally so `listTasks()` can
10
+ * return them (the Rust side does not expose task metadata today).
11
+ *
12
+ * @implements Story 28.2
13
+ */
14
+
15
+ import { performance } from 'node:perf_hooks'
16
+ import { ReamError } from '../errors/ReamError.js'
17
+ import { loadNapi } from '../helpers/napi-loader.js'
18
+ import type { LockBackend } from './locks/LockBackend.js'
19
+ import type { ErrorReporter } from './observability/ErrorReporter.js'
20
+ import type { ScheduleEvent } from './observability/ScheduleEvent.js'
21
+ import type { ScheduleEventSink } from './observability/ScheduleEventSink.js'
22
+ import type { TaskStats } from './observability/StatsTracker.js'
23
+ import { StatsTracker } from './observability/StatsTracker.js'
24
+
25
+ /** Payload delivered to every task invocation by the Rust ticker. */
26
+ export interface ScheduleInvocation {
27
+ taskName: string
28
+ scheduledForMs: number
29
+ }
30
+
31
+ /** Task listing entry returned by {@link Scheduler.listTasks}. */
32
+ export interface TaskInfo {
33
+ name: string
34
+ cronExpr: string
35
+ nextRun: number | null
36
+ }
37
+
38
+ /**
39
+ * Outcome returned by {@link Scheduler.runTaskNow}.
40
+ *
41
+ * - `completed` — the task body resolved without throwing.
42
+ * - `failed` — the task body threw / rejected; `error` carries a
43
+ * plain-object projection of the throw. Also used when an optional
44
+ * `timeoutMs` elapses before the task resolves.
45
+ * - `unknown` — no task is registered under the given name;
46
+ * `durationMs` is `0` and no events are emitted. The `errorReporter`
47
+ * (if configured) receives a synthetic error so the operator typo
48
+ * is observable in audit pipelines.
49
+ * - `already-running` — an in-process invocation of the same task is
50
+ * already in flight; this call was dropped to avoid double-execution.
51
+ * A `schedule.task.skipped` event with reason `'already-running'`
52
+ * was emitted.
53
+ */
54
+ export interface RunTaskOutcome {
55
+ outcome: 'completed' | 'failed' | 'unknown' | 'already-running'
56
+ durationMs: number
57
+ error?: { name: string; message: string; stack?: string }
58
+ }
59
+
60
+ /** Options accepted by {@link Scheduler.runTaskNow}. */
61
+ export interface RunTaskOptions {
62
+ /**
63
+ * Cancel the on-demand run after `timeoutMs` milliseconds. On
64
+ * timeout, `runTaskNow` returns `{ outcome: 'failed', error: ... }`
65
+ * with a `SCHEDULE_TASK_TIMEOUT` error. The user callback continues
66
+ * running in the background — JavaScript does not support
67
+ * pre-emptive cancellation; the timeout is observational only.
68
+ */
69
+ timeoutMs?: number
70
+ }
71
+
72
+ /** Shape of the native class exposed by `ream-scheduler-napi`. */
73
+ interface NativeScheduler {
74
+ register(
75
+ name: string,
76
+ cronExpr: string,
77
+ callback: (payload: ScheduleInvocation) => void | Promise<void>,
78
+ ): void
79
+ unregister(name: string): void
80
+ start(): void
81
+ stop(): void
82
+ nextRun(name: string): number | null
83
+ }
84
+
85
+ interface NativeModule {
86
+ RustScheduler: new () => NativeScheduler
87
+ }
88
+
89
+ /**
90
+ * Constructor options for {@link Scheduler}.
91
+ */
92
+ export interface SchedulerOptions {
93
+ /**
94
+ * Inject a pre-loaded NAPI module. Intended for tests that bypass
95
+ * the `.node` binary lookup — production callers should omit this
96
+ * and let `loadNapi` resolve the platform-suffixed binary.
97
+ */
98
+ nativeModule?: NativeModule
99
+ /**
100
+ * Distributed-lock backend. When provided, every task invocation
101
+ * is guarded by `acquire(name, defaultLockTtlMs)` / `release(name)`
102
+ * so horizontally-scaled deployments do not double-execute.
103
+ * Defaults to `undefined` (no locking — single-instance semantics).
104
+ */
105
+ lockBackend?: LockBackend
106
+ /**
107
+ * Lock TTL in milliseconds. Defaults to 60_000 (one minute). Only
108
+ * used when `lockBackend` is provided.
109
+ */
110
+ defaultLockTtlMs?: number
111
+ /**
112
+ * Destination for task-invocation events
113
+ * (`schedule.task.{started,completed,failed,skipped}`). Leave
114
+ * undefined to disable event emission — stats tracking still
115
+ * happens regardless, and `getStats` remains useful.
116
+ */
117
+ eventSink?: ScheduleEventSink
118
+ /**
119
+ * Callback invoked when a task throws. Typically bridges to
120
+ * `ErrorBoundary.serviceError`. Leave undefined to disable
121
+ * boundary reporting.
122
+ */
123
+ errorReporter?: ErrorReporter
124
+ }
125
+
126
+ const DEFAULT_LOCK_TTL_MS = 60_000
127
+
128
+ let native: NativeModule | undefined
129
+
130
+ function loadNative(): NativeModule {
131
+ if (native) return native
132
+ native = loadNapi<NativeModule>({
133
+ binaryName: 'scheduler',
134
+ callerMetaUrl: import.meta.url,
135
+ errorCodePrefix: 'SCHEDULER',
136
+ })
137
+ return native
138
+ }
139
+
140
+ /**
141
+ * Convert a thrown native error into a `ReamError`. Prefers parsing the
142
+ * JSON payload shipped by Rust (`ream_napi_core::ReamError::into()`);
143
+ * falls back to wrapping opaque errors under `SCHEDULER_NATIVE_ERROR`
144
+ * so scheduler context is never lost.
145
+ */
146
+ function translateNativeError(e: unknown): ReamError {
147
+ const asError = e as Error
148
+ const parsed = ReamError.fromNapi(asError)
149
+ // `fromNapi` returns an `UNKNOWN`-coded error when the message is not
150
+ // a JSON payload. Preserve that context under a scheduler-specific
151
+ // code so callers can trace the failure back to this layer.
152
+ if (parsed.code === 'UNKNOWN') {
153
+ return new ReamError(
154
+ 'SCHEDULER_NATIVE_ERROR',
155
+ `ream-scheduler NAPI call failed: ${asError.message}`,
156
+ {
157
+ hint: 'This is likely a bug in the scheduler. Please file an issue with the stack trace.',
158
+ },
159
+ )
160
+ }
161
+ return parsed
162
+ }
163
+
164
+ /**
165
+ * Cron-style scheduler backed by a Rust Tokio ticker.
166
+ *
167
+ * ```ts
168
+ * const scheduler = new Scheduler()
169
+ * scheduler.register('cleanup', '0 *\/5 * * *', () => { ... })
170
+ * scheduler.start()
171
+ * // later
172
+ * scheduler.stop()
173
+ * ```
174
+ *
175
+ * All schedules evaluate in UTC. Missed fires after a runtime stall are
176
+ * dropped, not replayed (see `ream-scheduler` crate docs).
177
+ */
178
+ export class Scheduler {
179
+ #inner: NativeScheduler
180
+ /** Mirror of registrations — Rust does not expose this today. */
181
+ #tasks = new Map<
182
+ string,
183
+ {
184
+ cronExpr: string
185
+ /** The user's raw callback — `runTaskNow` calls this directly
186
+ * via {@link Scheduler.#runOnce} so the admin-override path
187
+ * bypasses the lock backend but still runs through the shared
188
+ * observability core (events + stats + reporter). */
189
+ callback: (payload: ScheduleInvocation) => void | Promise<void>
190
+ }
191
+ >()
192
+ #lockBackend: LockBackend | null
193
+ #defaultLockTtlMs: number
194
+ #eventSink: ScheduleEventSink | null
195
+ #errorReporter: ErrorReporter | null
196
+ #stats = new StatsTracker()
197
+ /** Set of task names currently in flight — prevents concurrent
198
+ * double-execution when the ticker and `runTaskNow` (or two
199
+ * simultaneous `runTaskNow` calls) collide on the same name. */
200
+ #running = new Set<string>()
201
+
202
+ constructor(options: SchedulerOptions = {}) {
203
+ if (options.lockBackend !== undefined) {
204
+ const be = options.lockBackend as Partial<LockBackend>
205
+ if (typeof be.acquire !== 'function' || typeof be.release !== 'function') {
206
+ throw new ReamError(
207
+ 'SCHEDULE_INVALID_LOCK_BACKEND',
208
+ 'Scheduler options.lockBackend must implement acquire(name, ttlMs) and release(name)',
209
+ )
210
+ }
211
+ }
212
+ if (options.defaultLockTtlMs !== undefined) {
213
+ if (!Number.isFinite(options.defaultLockTtlMs) || options.defaultLockTtlMs <= 0) {
214
+ throw new ReamError(
215
+ 'SCHEDULE_INVALID_LOCK_TTL',
216
+ `Scheduler options.defaultLockTtlMs must be a finite positive number, got ${options.defaultLockTtlMs}`,
217
+ {
218
+ hint: 'Use a millisecond value greater than zero (typical range 1_000 – 600_000).',
219
+ },
220
+ )
221
+ }
222
+ }
223
+ if (options.eventSink !== undefined) {
224
+ const sink = options.eventSink as Partial<ScheduleEventSink>
225
+ if (typeof sink.emit !== 'function') {
226
+ throw new ReamError(
227
+ 'SCHEDULE_INVALID_EVENT_SINK',
228
+ 'Scheduler options.eventSink must implement emit(event)',
229
+ )
230
+ }
231
+ }
232
+ if (options.errorReporter !== undefined && typeof options.errorReporter !== 'function') {
233
+ throw new ReamError(
234
+ 'SCHEDULE_INVALID_ERROR_REPORTER',
235
+ 'Scheduler options.errorReporter must be a function',
236
+ )
237
+ }
238
+ const mod = options.nativeModule ?? loadNative()
239
+ this.#inner = new mod.RustScheduler()
240
+ this.#lockBackend = options.lockBackend ?? null
241
+ this.#defaultLockTtlMs = options.defaultLockTtlMs ?? DEFAULT_LOCK_TTL_MS
242
+ this.#eventSink = options.eventSink ?? null
243
+ this.#errorReporter = options.errorReporter ?? null
244
+ }
245
+
246
+ #emit(event: ScheduleEvent): void {
247
+ if (!this.#eventSink) return
248
+ try {
249
+ const result = this.#eventSink.emit(event)
250
+ // Detect a thenable and swallow any async rejection so the
251
+ // ticker cannot be crashed by a buggy async adapter. Use the
252
+ // `.then`-based detection + `Promise.resolve` wrap so non-native
253
+ // thenables (e.g. RxJS-style) are handled uniformly.
254
+ if (
255
+ result !== null &&
256
+ result !== undefined &&
257
+ typeof (result as { then?: unknown }).then === 'function'
258
+ ) {
259
+ Promise.resolve(result).catch(() => {})
260
+ }
261
+ } catch {
262
+ // Swallow — a buggy sink adapter must not kill the ticker.
263
+ }
264
+ }
265
+
266
+ /**
267
+ * Shared observability core: emit started, invoke the callback,
268
+ * emit completed/failed, record stats, forward to the error
269
+ * reporter. Returns the terminal outcome so callers (the ticker
270
+ * wrapped closure and `runTaskNow`) can act on the result without
271
+ * re-reading the stats map.
272
+ *
273
+ * Does NOT touch the lock backend — lock handling is the caller's
274
+ * responsibility so the admin-override path can bypass it cleanly.
275
+ */
276
+ async #runOnce(
277
+ name: string,
278
+ callback: (payload: ScheduleInvocation) => void | Promise<void>,
279
+ scheduledForMs: number,
280
+ ): Promise<
281
+ | { outcome: 'completed' | 'failed'; durationMs: number; error?: Error }
282
+ | { outcome: 'already-running'; durationMs: 0 }
283
+ > {
284
+ // Concurrent-execution guard. The distributed lock prevents
285
+ // double-fire across instances; this Set prevents double-fire
286
+ // WITHIN a single process (ticker racing with runTaskNow, or two
287
+ // operators hitting runTaskNow at once). A duplicate invocation
288
+ // is recorded as a skip with reason `'already-running'`.
289
+ if (this.#running.has(name)) {
290
+ this.#stats.recordSkipped(name)
291
+ this.#emit({ type: 'schedule.task.skipped', taskName: name, reason: 'already-running' })
292
+ return { outcome: 'already-running', durationMs: 0 }
293
+ }
294
+ this.#running.add(name)
295
+ const startedAt = Date.now()
296
+ const perfStart = performance.now()
297
+ this.#stats.recordStarted(name, startedAt)
298
+ this.#emit({
299
+ type: 'schedule.task.started',
300
+ taskName: name,
301
+ scheduledForMs,
302
+ startedAtMs: startedAt,
303
+ })
304
+ try {
305
+ await callback({ taskName: name, scheduledForMs })
306
+ const durationMs = performance.now() - perfStart
307
+ this.#stats.recordCompleted(name, durationMs)
308
+ this.#emit({ type: 'schedule.task.completed', taskName: name, durationMs })
309
+ return { outcome: 'completed', durationMs }
310
+ } catch (err) {
311
+ const durationMs = performance.now() - perfStart
312
+ this.#stats.recordFailed(name, durationMs)
313
+ const errObj = err instanceof Error ? err : new Error(String(err))
314
+ this.#emit({
315
+ type: 'schedule.task.failed',
316
+ taskName: name,
317
+ error: { name: errObj.name, message: errObj.message, stack: errObj.stack },
318
+ durationMs,
319
+ })
320
+ this.#report(err, name)
321
+ return { outcome: 'failed', durationMs, error: errObj }
322
+ } finally {
323
+ this.#running.delete(name)
324
+ }
325
+ }
326
+
327
+ #report(err: unknown, taskName: string): void {
328
+ if (!this.#errorReporter) return
329
+ try {
330
+ // The `ErrorReporter` type declares `void` return, but a user
331
+ // adapter that declares an `async` function still satisfies the
332
+ // callable shape and will return a Promise at runtime. Swallow
333
+ // any async rejection in addition to sync throws so a bad
334
+ // reporter never crashes the ticker.
335
+ const result = this.#errorReporter(err, { taskName }) as unknown
336
+ if (
337
+ result !== null &&
338
+ result !== undefined &&
339
+ typeof (result as { then?: unknown }).then === 'function'
340
+ ) {
341
+ Promise.resolve(result).catch(() => {})
342
+ }
343
+ } catch {
344
+ // Swallow — a reporter that itself throws cannot propagate.
345
+ }
346
+ }
347
+
348
+ /**
349
+ * Register a recurring task.
350
+ *
351
+ * The callback is invoked by the Rust ticker when the cron schedule
352
+ * fires. Exceptions thrown synchronously or rejected asynchronously
353
+ * are caught here so they never surface back to the Rust side; when
354
+ * an `eventSink` / `errorReporter` is configured, failures are
355
+ * emitted as `schedule.task.failed` events and forwarded to the
356
+ * reporter. Otherwise the error is dropped silently.
357
+ *
358
+ * Event ordering guarantees: within a single tick, events are emitted
359
+ * in the documented order (`started` → `completed` | `failed` |
360
+ * `skipped`). Ordering across ticks is NOT guaranteed when the sink
361
+ * returns a Promise — the scheduler does not chain sink promises, so
362
+ * a fast tick N+1 can deliver events before a slow tick N's events
363
+ * reach the sink. Sinks that need strict ordering should maintain
364
+ * their own sequencing.
365
+ */
366
+ register(
367
+ name: string,
368
+ cronExpr: string,
369
+ callback: (payload: ScheduleInvocation) => void | Promise<void>,
370
+ ): void {
371
+ if (typeof callback !== 'function') {
372
+ throw new ReamError(
373
+ 'SCHEDULE_INVALID_CALLBACK',
374
+ `Scheduler.register expected a function callback for task '${name}', got ${typeof callback}`,
375
+ )
376
+ }
377
+ if (name === '') {
378
+ throw new ReamError(
379
+ 'SCHEDULE_INVALID_TASK_NAME',
380
+ 'Scheduler.register requires a non-empty task name',
381
+ )
382
+ }
383
+ // Capture-at-register: the active lock backend is frozen per-task
384
+ // at registration time (observability sinks are read live via
385
+ // #runOnce so they can be replaced in the future non-breakingly).
386
+ const lockBackend = this.#lockBackend
387
+ const lockTtlMs = this.#defaultLockTtlMs
388
+
389
+ // Wrapped closure delegated to #runOnce for the observability
390
+ // core — lock handling layered around it when a backend is
391
+ // configured. `runTaskNow` reuses `#runOnce` directly, bypassing
392
+ // the lock.
393
+ const wrapped = async (payload: ScheduleInvocation): Promise<void> => {
394
+ if (lockBackend !== null) {
395
+ let acquired: boolean
396
+ try {
397
+ acquired = await lockBackend.acquire(name, lockTtlMs)
398
+ } catch (acquireErr) {
399
+ this.#stats.recordSkipped(name)
400
+ this.#emit({
401
+ type: 'schedule.task.skipped',
402
+ taskName: name,
403
+ reason: 'acquire-failed',
404
+ })
405
+ this.#report(acquireErr, name)
406
+ return
407
+ }
408
+ if (!acquired) {
409
+ this.#stats.recordSkipped(name)
410
+ this.#emit({ type: 'schedule.task.skipped', taskName: name, reason: 'locked' })
411
+ return
412
+ }
413
+ }
414
+ try {
415
+ await this.#runOnce(name, callback, payload.scheduledForMs)
416
+ } finally {
417
+ if (lockBackend !== null) {
418
+ try {
419
+ await lockBackend.release(name)
420
+ } catch {
421
+ // Swallow — lock auto-frees via TTL.
422
+ }
423
+ }
424
+ }
425
+ }
426
+ try {
427
+ this.#inner.register(name, cronExpr, wrapped)
428
+ } catch (e) {
429
+ throw translateNativeError(e)
430
+ }
431
+ this.#tasks.set(name, { cronExpr, callback })
432
+ }
433
+
434
+ /**
435
+ * Remove a registered task. Idempotent — unknown names are not an
436
+ * error. If the native side reports an unknown-task condition it is
437
+ * treated as already-removed rather than propagated.
438
+ */
439
+ unregister(name: string): void {
440
+ try {
441
+ this.#inner.unregister(name)
442
+ } catch (e) {
443
+ const err = translateNativeError(e)
444
+ // Rust's `unregister` is itself idempotent (Story 28.1) so a
445
+ // thrown error here is unexpected. Drop the local entry anyway
446
+ // — the doc promises idempotence — but surface any unexpected
447
+ // native failure that is not a missing-task situation.
448
+ if (err.code !== 'UNKNOWN_TASK' && err.code !== 'SCHEDULER_NATIVE_ERROR') {
449
+ this.#tasks.delete(name)
450
+ throw err
451
+ }
452
+ }
453
+ this.#tasks.delete(name)
454
+ }
455
+
456
+ /**
457
+ * Start the Rust ticker. Safe to call when already running — the
458
+ * underlying `ream-scheduler` core is idempotent (Story 28.1: the
459
+ * ticker's internal cancel channel is only installed once per start).
460
+ */
461
+ start(): void {
462
+ try {
463
+ this.#inner.start()
464
+ } catch (e) {
465
+ throw translateNativeError(e)
466
+ }
467
+ }
468
+
469
+ /**
470
+ * Cancel the Rust ticker. Safe to call when not running — the
471
+ * underlying `ream-scheduler` core treats `stop()` as a no-op when no
472
+ * cancel channel is installed.
473
+ */
474
+ stop(): void {
475
+ try {
476
+ this.#inner.stop()
477
+ } catch (e) {
478
+ throw translateNativeError(e)
479
+ }
480
+ }
481
+
482
+ /**
483
+ * Return the next fire time in ms since epoch, or `null` if the task
484
+ * is unknown.
485
+ */
486
+ nextRun(name: string): number | null {
487
+ try {
488
+ return this.#inner.nextRun(name)
489
+ } catch (e) {
490
+ throw translateNativeError(e)
491
+ }
492
+ }
493
+
494
+ /**
495
+ * Snapshot of every currently-registered task.
496
+ *
497
+ * NOTE: Makes one FFI call per task via `nextRun`. Acceptable for
498
+ * typical task counts (dozens); consumers iterating huge registries
499
+ * should cache the result.
500
+ */
501
+ listTasks(): TaskInfo[] {
502
+ const out: TaskInfo[] = []
503
+ for (const [name, { cronExpr }] of this.#tasks) {
504
+ out.push({ name, cronExpr, nextRun: this.nextRun(name) })
505
+ }
506
+ return out
507
+ }
508
+
509
+ /**
510
+ * Invoke a registered task once immediately, bypassing the cron
511
+ * schedule AND the configured lock backend.
512
+ *
513
+ * **Admin override:** `runTaskNow` does NOT consult the
514
+ * `lockBackend`. Running a task manually is an explicit operator
515
+ * action that must not be blocked by a peer instance holding the
516
+ * distributed lock. Regular ticker-driven fires continue to honor
517
+ * the lock.
518
+ *
519
+ * Observability is preserved — the same `started` / `completed`
520
+ * / `failed` events fire and the `errorReporter` runs exactly as
521
+ * for a normal tick, updating `getStats(name)` identically.
522
+ *
523
+ * Unknown task names return `{ outcome: 'unknown', durationMs: 0 }`
524
+ * without emitting any event — mirrors the never-throws contract
525
+ * of {@link Scheduler.getStats}.
526
+ */
527
+ async runTaskNow(name: string, options: RunTaskOptions = {}): Promise<RunTaskOutcome> {
528
+ const entry = this.#tasks.get(name)
529
+ if (!entry) {
530
+ // Notify the observability boundary so operator typos leave a
531
+ // trace. No event is emitted — events are reserved for
532
+ // real task invocations.
533
+ this.#report(
534
+ new ReamError(
535
+ 'SCHEDULE_TASK_UNKNOWN',
536
+ `runTaskNow called with unknown task name: '${name}'`,
537
+ ),
538
+ name,
539
+ )
540
+ return { outcome: 'unknown', durationMs: 0 }
541
+ }
542
+
543
+ const runPromise = this.#runOnce(name, entry.callback, Date.now())
544
+
545
+ // Optional timeout: whichever wins, runPromise stays running in
546
+ // the background — JS has no pre-emptive cancellation.
547
+ const result =
548
+ options.timeoutMs !== undefined && Number.isFinite(options.timeoutMs) && options.timeoutMs > 0
549
+ ? await Promise.race([
550
+ runPromise,
551
+ new Promise<{ outcome: 'failed'; durationMs: number; error: Error }>((resolve) => {
552
+ setTimeout(() => {
553
+ resolve({
554
+ outcome: 'failed',
555
+ durationMs: options.timeoutMs ?? 0,
556
+ error: new ReamError(
557
+ 'SCHEDULE_TASK_TIMEOUT',
558
+ `Task '${name}' did not complete within ${options.timeoutMs} ms (background execution continues)`,
559
+ ),
560
+ })
561
+ }, options.timeoutMs)
562
+ }),
563
+ ])
564
+ : await runPromise
565
+
566
+ if (result.outcome === 'already-running') {
567
+ return { outcome: 'already-running', durationMs: 0 }
568
+ }
569
+ if (result.outcome === 'failed') {
570
+ return {
571
+ outcome: 'failed',
572
+ durationMs: result.durationMs,
573
+ error: {
574
+ name: result.error?.name ?? 'Error',
575
+ message: result.error?.message ?? 'unknown error',
576
+ stack: result.error?.stack,
577
+ },
578
+ }
579
+ }
580
+ return { outcome: 'completed', durationMs: result.durationMs }
581
+ }
582
+
583
+ /**
584
+ * Return a snapshot of observability counters for `taskName`.
585
+ *
586
+ * Unknown task names return a zeroed snapshot (all counters `0`,
587
+ * all timestamps `null`) — never throws. The `nextRunMs` field is
588
+ * read live from the Rust layer on each call; a native-side failure
589
+ * is swallowed here so that `getStats` always returns.
590
+ */
591
+ getStats(taskName: string): TaskStats {
592
+ const snapshot = this.#stats.get(taskName)
593
+ let nextRunMs: number | null = null
594
+ try {
595
+ nextRunMs = this.nextRun(taskName)
596
+ } catch {
597
+ // Swallow — getStats guarantees no-throw.
598
+ }
599
+ return { ...snapshot, nextRunMs }
600
+ }
601
+ }
@@ -0,0 +1,24 @@
1
+ export type { LockBackend } from './locks/LockBackend.js'
2
+ export { MemoryLockBackend } from './locks/MemoryLockBackend.js'
3
+ export type { ErrorReporter } from './observability/ErrorReporter.js'
4
+ export type {
5
+ ScheduleEvent,
6
+ ScheduleTaskCompletedEvent,
7
+ ScheduleTaskFailedEvent,
8
+ ScheduleTaskSkippedEvent,
9
+ ScheduleTaskStartedEvent,
10
+ } from './observability/ScheduleEvent.js'
11
+ export type { ScheduleEventSink } from './observability/ScheduleEventSink.js'
12
+ export type { TaskStats } from './observability/StatsTracker.js'
13
+ export { StatsTracker } from './observability/StatsTracker.js'
14
+ export type { ScheduleMetadata } from './Schedule.js'
15
+ export { getScheduleMetadata, SCHEDULE_METADATA_KEY, Schedule } from './Schedule.js'
16
+ export type { ScheduleProviderOptions } from './ScheduleProvider.js'
17
+ export { ScheduleProvider } from './ScheduleProvider.js'
18
+ export type {
19
+ RunTaskOutcome,
20
+ ScheduleInvocation,
21
+ SchedulerOptions,
22
+ TaskInfo,
23
+ } from './Scheduler.js'
24
+ export { Scheduler } from './Scheduler.js'
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Pluggable distributed-lock interface for the scheduler.
3
+ *
4
+ * The scheduler consults a `LockBackend` immediately before firing a
5
+ * registered task so that in multi-instance deployments only one
6
+ * instance runs a given task on each tick. Redis, database
7
+ * advisory-lock, and other backends live in user-land — this module
8
+ * ships only the interface and a single-process `MemoryLockBackend`.
9
+ *
10
+ * @implements Story 28.3
11
+ */
12
+
13
+ export interface LockBackend {
14
+ /**
15
+ * Attempt to acquire an exclusive lock on `name` for up to `ttlMs`
16
+ * wall-clock milliseconds.
17
+ *
18
+ * Returns `true` when the caller now owns the lock, `false` when it
19
+ * is held by someone else. Implementations must treat an entry
20
+ * whose TTL has elapsed as free — expired locks auto-recover
21
+ * without manual cleanup, which is essential for crash tolerance.
22
+ *
23
+ * Implementations should be safe to call concurrently; the memory
24
+ * backend serializes access through a synchronous `Map` under the
25
+ * hood, remote backends typically rely on their server's atomic
26
+ * compare-and-set primitive.
27
+ */
28
+ acquire(name: string, ttlMs: number): Promise<boolean>
29
+
30
+ /**
31
+ * Release the lock on `name`. Idempotent — releasing an unknown or
32
+ * already-released lock must not throw. The scheduler calls this
33
+ * in a `finally` block immediately after the task completes so the
34
+ * next tick on any instance can re-acquire without waiting for the
35
+ * TTL.
36
+ */
37
+ release(name: string): Promise<void>
38
+ }