@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.
- package/LICENSE +21 -0
- package/README.md +70 -0
- package/dist/Application.d.ts +71 -0
- package/dist/Application.d.ts.map +1 -0
- package/dist/Application.js +151 -0
- package/dist/Application.js.map +1 -0
- package/dist/Codemods.d.ts +16 -0
- package/dist/Codemods.d.ts.map +1 -0
- package/dist/Codemods.js +277 -0
- package/dist/Codemods.js.map +1 -0
- package/dist/ConfigLoader.d.ts +29 -0
- package/dist/ConfigLoader.d.ts.map +1 -0
- package/dist/ConfigLoader.js +43 -0
- package/dist/ConfigLoader.js.map +1 -0
- package/dist/ErrorBoundary.d.ts +51 -0
- package/dist/ErrorBoundary.d.ts.map +1 -0
- package/dist/ErrorBoundary.js +122 -0
- package/dist/ErrorBoundary.js.map +1 -0
- package/dist/GracefulShutdown.d.ts +28 -0
- package/dist/GracefulShutdown.d.ts.map +1 -0
- package/dist/GracefulShutdown.js +55 -0
- package/dist/GracefulShutdown.js.map +1 -0
- package/dist/HealthCheck.d.ts +42 -0
- package/dist/HealthCheck.d.ts.map +1 -0
- package/dist/HealthCheck.js +65 -0
- package/dist/HealthCheck.js.map +1 -0
- package/dist/HotReload.d.ts +28 -0
- package/dist/HotReload.d.ts.map +1 -0
- package/dist/HotReload.js +60 -0
- package/dist/HotReload.js.map +1 -0
- package/dist/HttpKernel.d.ts +62 -0
- package/dist/HttpKernel.d.ts.map +1 -0
- package/dist/HttpKernel.js +210 -0
- package/dist/HttpKernel.js.map +1 -0
- package/dist/Ignitor.d.ts +195 -0
- package/dist/Ignitor.d.ts.map +1 -0
- package/dist/Ignitor.js +505 -0
- package/dist/Ignitor.js.map +1 -0
- package/dist/Provider.d.ts +54 -0
- package/dist/Provider.d.ts.map +1 -0
- package/dist/Provider.js +37 -0
- package/dist/Provider.js.map +1 -0
- package/dist/bodyparser/BodyParserMiddleware.d.ts +38 -0
- package/dist/bodyparser/BodyParserMiddleware.d.ts.map +1 -0
- package/dist/bodyparser/BodyParserMiddleware.js +143 -0
- package/dist/bodyparser/BodyParserMiddleware.js.map +1 -0
- package/dist/bodyparser/MultipartFile.d.ts +77 -0
- package/dist/bodyparser/MultipartFile.d.ts.map +1 -0
- package/dist/bodyparser/MultipartFile.js +138 -0
- package/dist/bodyparser/MultipartFile.js.map +1 -0
- package/dist/bodyparser/parseSize.d.ts +6 -0
- package/dist/bodyparser/parseSize.d.ts.map +1 -0
- package/dist/bodyparser/parseSize.js +21 -0
- package/dist/bodyparser/parseSize.js.map +1 -0
- package/dist/bootstrap/hyper-server.d.ts +27 -0
- package/dist/bootstrap/hyper-server.d.ts.map +1 -0
- package/dist/bootstrap/hyper-server.js +63 -0
- package/dist/bootstrap/hyper-server.js.map +1 -0
- package/dist/console/CommandRunner.d.ts +18 -0
- package/dist/console/CommandRunner.d.ts.map +1 -0
- package/dist/console/CommandRunner.js +65 -0
- package/dist/console/CommandRunner.js.map +1 -0
- package/dist/container/Container.d.ts +60 -0
- package/dist/container/Container.d.ts.map +1 -0
- package/dist/container/Container.js +329 -0
- package/dist/container/Container.js.map +1 -0
- package/dist/container/types.d.ts +25 -0
- package/dist/container/types.d.ts.map +1 -0
- package/dist/container/types.js +6 -0
- package/dist/container/types.js.map +1 -0
- package/dist/decorators/Lazy.d.ts +19 -0
- package/dist/decorators/Lazy.d.ts.map +1 -0
- package/dist/decorators/Lazy.js +53 -0
- package/dist/decorators/Lazy.js.map +1 -0
- package/dist/decorators/Service.d.ts +48 -0
- package/dist/decorators/Service.d.ts.map +1 -0
- package/dist/decorators/Service.js +70 -0
- package/dist/decorators/Service.js.map +1 -0
- package/dist/errors/FuzzyMatcher.d.ts +41 -0
- package/dist/errors/FuzzyMatcher.d.ts.map +1 -0
- package/dist/errors/FuzzyMatcher.js +81 -0
- package/dist/errors/FuzzyMatcher.js.map +1 -0
- package/dist/errors/ModuleErrors.d.ts +41 -0
- package/dist/errors/ModuleErrors.d.ts.map +1 -0
- package/dist/errors/ModuleErrors.js +65 -0
- package/dist/errors/ModuleErrors.js.map +1 -0
- package/dist/errors/PipelineStageError.d.ts +31 -0
- package/dist/errors/PipelineStageError.d.ts.map +1 -0
- package/dist/errors/PipelineStageError.js +77 -0
- package/dist/errors/PipelineStageError.js.map +1 -0
- package/dist/errors/ReamError.d.ts +50 -0
- package/dist/errors/ReamError.d.ts.map +1 -0
- package/dist/errors/ReamError.js +108 -0
- package/dist/errors/ReamError.js.map +1 -0
- package/dist/events/Emitter.d.ts +124 -0
- package/dist/events/Emitter.d.ts.map +1 -0
- package/dist/events/Emitter.js +271 -0
- package/dist/events/Emitter.js.map +1 -0
- package/dist/events/EventsProvider.d.ts +28 -0
- package/dist/events/EventsProvider.d.ts.map +1 -0
- package/dist/events/EventsProvider.js +36 -0
- package/dist/events/EventsProvider.js.map +1 -0
- package/dist/events/config.d.ts +8 -0
- package/dist/events/config.d.ts.map +1 -0
- package/dist/events/config.js +4 -0
- package/dist/events/config.js.map +1 -0
- package/dist/events/configure.d.ts +10 -0
- package/dist/events/configure.d.ts.map +1 -0
- package/dist/events/configure.js +11 -0
- package/dist/events/configure.js.map +1 -0
- package/dist/events/helix/helpers.d.ts +62 -0
- package/dist/events/helix/helpers.d.ts.map +1 -0
- package/dist/events/helix/helpers.js +187 -0
- package/dist/events/helix/helpers.js.map +1 -0
- package/dist/events/index.d.ts +17 -0
- package/dist/events/index.d.ts.map +1 -0
- package/dist/events/index.js +15 -0
- package/dist/events/index.js.map +1 -0
- package/dist/events/native.d.ts +21 -0
- package/dist/events/native.d.ts.map +1 -0
- package/dist/events/native.js +17 -0
- package/dist/events/native.js.map +1 -0
- package/dist/events/services/main.d.ts +18 -0
- package/dist/events/services/main.d.ts.map +1 -0
- package/dist/events/services/main.js +31 -0
- package/dist/events/services/main.js.map +1 -0
- package/dist/events/testing/FakeBus.d.ts +66 -0
- package/dist/events/testing/FakeBus.d.ts.map +1 -0
- package/dist/events/testing/FakeBus.js +356 -0
- package/dist/events/testing/FakeBus.js.map +1 -0
- package/dist/graphql/GraphQLEngine.d.ts +69 -0
- package/dist/graphql/GraphQLEngine.d.ts.map +1 -0
- package/dist/graphql/GraphQLEngine.js +309 -0
- package/dist/graphql/GraphQLEngine.js.map +1 -0
- package/dist/graphql/GraphQLProvider.d.ts +32 -0
- package/dist/graphql/GraphQLProvider.d.ts.map +1 -0
- package/dist/graphql/GraphQLProvider.js +49 -0
- package/dist/graphql/GraphQLProvider.js.map +1 -0
- package/dist/helpers/base64.d.ts +40 -0
- package/dist/helpers/base64.d.ts.map +1 -0
- package/dist/helpers/base64.js +70 -0
- package/dist/helpers/base64.js.map +1 -0
- package/dist/helpers/index.d.ts +7 -0
- package/dist/helpers/index.d.ts.map +1 -0
- package/dist/helpers/index.js +5 -0
- package/dist/helpers/index.js.map +1 -0
- package/dist/helpers/is.d.ts +101 -0
- package/dist/helpers/is.d.ts.map +1 -0
- package/dist/helpers/is.js +151 -0
- package/dist/helpers/is.js.map +1 -0
- package/dist/helpers/napi-loader.d.ts +46 -0
- package/dist/helpers/napi-loader.d.ts.map +1 -0
- package/dist/helpers/napi-loader.js +69 -0
- package/dist/helpers/napi-loader.js.map +1 -0
- package/dist/helpers/safeEqual.d.ts +9 -0
- package/dist/helpers/safeEqual.d.ts.map +1 -0
- package/dist/helpers/safeEqual.js +16 -0
- package/dist/helpers/safeEqual.js.map +1 -0
- package/dist/helpers/string.d.ts +161 -0
- package/dist/helpers/string.d.ts.map +1 -0
- package/dist/helpers/string.js +429 -0
- package/dist/helpers/string.js.map +1 -0
- package/dist/helpers/types.d.ts +83 -0
- package/dist/helpers/types.d.ts.map +1 -0
- package/dist/helpers/types.js +6 -0
- package/dist/helpers/types.js.map +1 -0
- package/dist/http/Exception.d.ts +103 -0
- package/dist/http/Exception.d.ts.map +1 -0
- package/dist/http/Exception.js +228 -0
- package/dist/http/Exception.js.map +1 -0
- package/dist/http/HttpContext.d.ts +69 -0
- package/dist/http/HttpContext.d.ts.map +1 -0
- package/dist/http/HttpContext.js +55 -0
- package/dist/http/HttpContext.js.map +1 -0
- package/dist/http/RedirectBuilder.d.ts +31 -0
- package/dist/http/RedirectBuilder.d.ts.map +1 -0
- package/dist/http/RedirectBuilder.js +75 -0
- package/dist/http/RedirectBuilder.js.map +1 -0
- package/dist/http/Request.d.ts +160 -0
- package/dist/http/Request.d.ts.map +1 -0
- package/dist/http/Request.js +484 -0
- package/dist/http/Request.js.map +1 -0
- package/dist/http/Response.d.ts +114 -0
- package/dist/http/Response.d.ts.map +1 -0
- package/dist/http/Response.js +303 -0
- package/dist/http/Response.js.map +1 -0
- package/dist/http/SseStream.d.ts +89 -0
- package/dist/http/SseStream.d.ts.map +1 -0
- package/dist/http/SseStream.js +228 -0
- package/dist/http/SseStream.js.map +1 -0
- package/dist/http/urlDecode.d.ts +8 -0
- package/dist/http/urlDecode.d.ts.map +1 -0
- package/dist/http/urlDecode.js +18 -0
- package/dist/http/urlDecode.js.map +1 -0
- package/dist/index.d.ts +92 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +67 -0
- package/dist/index.js.map +1 -0
- package/dist/middleware/Pipeline.d.ts +36 -0
- package/dist/middleware/Pipeline.d.ts.map +1 -0
- package/dist/middleware/Pipeline.js +104 -0
- package/dist/middleware/Pipeline.js.map +1 -0
- package/dist/openapi/OpenApiGenerator.d.ts +58 -0
- package/dist/openapi/OpenApiGenerator.d.ts.map +1 -0
- package/dist/openapi/OpenApiGenerator.js +192 -0
- package/dist/openapi/OpenApiGenerator.js.map +1 -0
- package/dist/openapi/OpenApiMiddleware.d.ts +24 -0
- package/dist/openapi/OpenApiMiddleware.d.ts.map +1 -0
- package/dist/openapi/OpenApiMiddleware.js +78 -0
- package/dist/openapi/OpenApiMiddleware.js.map +1 -0
- package/dist/openapi/OpenApiProvider.d.ts +36 -0
- package/dist/openapi/OpenApiProvider.d.ts.map +1 -0
- package/dist/openapi/OpenApiProvider.js +42 -0
- package/dist/openapi/OpenApiProvider.js.map +1 -0
- package/dist/resources/ApiResource.d.ts +45 -0
- package/dist/resources/ApiResource.d.ts.map +1 -0
- package/dist/resources/ApiResource.js +58 -0
- package/dist/resources/ApiResource.js.map +1 -0
- package/dist/router/Router.d.ts +205 -0
- package/dist/router/Router.d.ts.map +1 -0
- package/dist/router/Router.js +552 -0
- package/dist/router/Router.js.map +1 -0
- package/dist/rpc/RpcProvider.d.ts +34 -0
- package/dist/rpc/RpcProvider.d.ts.map +1 -0
- package/dist/rpc/RpcProvider.js +37 -0
- package/dist/rpc/RpcProvider.js.map +1 -0
- package/dist/rpc/RpcRouter.d.ts +61 -0
- package/dist/rpc/RpcRouter.d.ts.map +1 -0
- package/dist/rpc/RpcRouter.js +187 -0
- package/dist/rpc/RpcRouter.js.map +1 -0
- package/dist/scheduler/Schedule.d.ts +53 -0
- package/dist/scheduler/Schedule.d.ts.map +1 -0
- package/dist/scheduler/Schedule.js +76 -0
- package/dist/scheduler/Schedule.js.map +1 -0
- package/dist/scheduler/ScheduleProvider.d.ts +56 -0
- package/dist/scheduler/ScheduleProvider.d.ts.map +1 -0
- package/dist/scheduler/ScheduleProvider.js +145 -0
- package/dist/scheduler/ScheduleProvider.js.map +1 -0
- package/dist/scheduler/Scheduler.d.ts +209 -0
- package/dist/scheduler/Scheduler.d.ts.map +1 -0
- package/dist/scheduler/Scheduler.js +432 -0
- package/dist/scheduler/Scheduler.js.map +1 -0
- package/dist/scheduler/index.d.ts +14 -0
- package/dist/scheduler/index.d.ts.map +1 -0
- package/dist/scheduler/index.js +6 -0
- package/dist/scheduler/index.js.map +1 -0
- package/dist/scheduler/locks/LockBackend.d.ts +37 -0
- package/dist/scheduler/locks/LockBackend.d.ts.map +1 -0
- package/dist/scheduler/locks/LockBackend.js +13 -0
- package/dist/scheduler/locks/LockBackend.js.map +1 -0
- package/dist/scheduler/locks/MemoryLockBackend.d.ts +39 -0
- package/dist/scheduler/locks/MemoryLockBackend.d.ts.map +1 -0
- package/dist/scheduler/locks/MemoryLockBackend.js +71 -0
- package/dist/scheduler/locks/MemoryLockBackend.js.map +1 -0
- package/dist/scheduler/observability/ErrorReporter.d.ts +15 -0
- package/dist/scheduler/observability/ErrorReporter.d.ts.map +1 -0
- package/dist/scheduler/observability/ErrorReporter.js +13 -0
- package/dist/scheduler/observability/ErrorReporter.js.map +1 -0
- package/dist/scheduler/observability/ScheduleEvent.d.ts +57 -0
- package/dist/scheduler/observability/ScheduleEvent.d.ts.map +1 -0
- package/dist/scheduler/observability/ScheduleEvent.js +12 -0
- package/dist/scheduler/observability/ScheduleEvent.js.map +1 -0
- package/dist/scheduler/observability/ScheduleEventSink.d.ts +20 -0
- package/dist/scheduler/observability/ScheduleEventSink.d.ts.map +1 -0
- package/dist/scheduler/observability/ScheduleEventSink.js +17 -0
- package/dist/scheduler/observability/ScheduleEventSink.js.map +1 -0
- package/dist/scheduler/observability/StatsTracker.d.ts +50 -0
- package/dist/scheduler/observability/StatsTracker.d.ts.map +1 -0
- package/dist/scheduler/observability/StatsTracker.js +96 -0
- package/dist/scheduler/observability/StatsTracker.js.map +1 -0
- package/dist/security/CookieSigner.d.ts +13 -0
- package/dist/security/CookieSigner.d.ts.map +1 -0
- package/dist/security/CookieSigner.js +51 -0
- package/dist/security/CookieSigner.js.map +1 -0
- package/dist/security/SignedUrl.d.ts +17 -0
- package/dist/security/SignedUrl.d.ts.map +1 -0
- package/dist/security/SignedUrl.js +63 -0
- package/dist/security/SignedUrl.js.map +1 -0
- package/dist/security/crypto.d.ts +25 -0
- package/dist/security/crypto.d.ts.map +1 -0
- package/dist/security/crypto.js +82 -0
- package/dist/security/crypto.js.map +1 -0
- package/dist/server/HyperServer.d.ts +64 -0
- package/dist/server/HyperServer.d.ts.map +1 -0
- package/dist/server/HyperServer.js +54 -0
- package/dist/server/HyperServer.js.map +1 -0
- package/dist/server/Server.d.ts +68 -0
- package/dist/server/Server.d.ts.map +1 -0
- package/dist/server/Server.js +113 -0
- package/dist/server/Server.js.map +1 -0
- package/dist/services/app.d.ts +14 -0
- package/dist/services/app.d.ts.map +1 -0
- package/dist/services/app.js +27 -0
- package/dist/services/app.js.map +1 -0
- package/dist/services/router.d.ts +21 -0
- package/dist/services/router.d.ts.map +1 -0
- package/dist/services/router.js +36 -0
- package/dist/services/router.js.map +1 -0
- package/dist/services/server.d.ts +16 -0
- package/dist/services/server.d.ts.map +1 -0
- package/dist/services/server.js +32 -0
- package/dist/services/server.js.map +1 -0
- package/dist/session/Session.d.ts +88 -0
- package/dist/session/Session.d.ts.map +1 -0
- package/dist/session/Session.js +186 -0
- package/dist/session/Session.js.map +1 -0
- package/dist/session/SessionMiddleware.d.ts +16 -0
- package/dist/session/SessionMiddleware.d.ts.map +1 -0
- package/dist/session/SessionMiddleware.js +132 -0
- package/dist/session/SessionMiddleware.js.map +1 -0
- package/dist/session/drivers/CookieDriver.d.ts +17 -0
- package/dist/session/drivers/CookieDriver.d.ts.map +1 -0
- package/dist/session/drivers/CookieDriver.js +42 -0
- package/dist/session/drivers/CookieDriver.js.map +1 -0
- package/dist/session/drivers/MemoryDriver.d.ts +9 -0
- package/dist/session/drivers/MemoryDriver.d.ts.map +1 -0
- package/dist/session/drivers/MemoryDriver.js +24 -0
- package/dist/session/drivers/MemoryDriver.js.map +1 -0
- package/dist/storage/S3Driver.d.ts +27 -0
- package/dist/storage/S3Driver.d.ts.map +1 -0
- package/dist/storage/S3Driver.js +135 -0
- package/dist/storage/S3Driver.js.map +1 -0
- package/dist/storage/StaticMiddleware.d.ts +27 -0
- package/dist/storage/StaticMiddleware.d.ts.map +1 -0
- package/dist/storage/StaticMiddleware.js +153 -0
- package/dist/storage/StaticMiddleware.js.map +1 -0
- package/dist/storage/StaticProvider.d.ts +26 -0
- package/dist/storage/StaticProvider.d.ts.map +1 -0
- package/dist/storage/StaticProvider.js +31 -0
- package/dist/storage/StaticProvider.js.map +1 -0
- package/dist/storage/StorageManager.d.ts +31 -0
- package/dist/storage/StorageManager.d.ts.map +1 -0
- package/dist/storage/StorageManager.js +120 -0
- package/dist/storage/StorageManager.js.map +1 -0
- package/dist/testing/RequestBuilder.d.ts +67 -0
- package/dist/testing/RequestBuilder.d.ts.map +1 -0
- package/dist/testing/RequestBuilder.js +202 -0
- package/dist/testing/RequestBuilder.js.map +1 -0
- package/dist/testing/TestClient.d.ts +96 -0
- package/dist/testing/TestClient.d.ts.map +1 -0
- package/dist/testing/TestClient.js +263 -0
- package/dist/testing/TestClient.js.map +1 -0
- package/dist/types/helpers.d.ts +47 -0
- package/dist/types/helpers.d.ts.map +1 -0
- package/dist/types/helpers.js +5 -0
- package/dist/types/helpers.js.map +1 -0
- package/events.darwin-arm64.node +0 -0
- package/events.darwin-x64.node +0 -0
- package/events.linux-arm64-gnu.node +0 -0
- package/events.linux-x64-gnu.node +0 -0
- package/events.win32-x64-msvc.node +0 -0
- package/index.darwin-arm64.node +0 -0
- package/index.darwin-x64.node +0 -0
- package/index.linux-arm64-gnu.node +0 -0
- package/index.linux-x64-gnu.node +0 -0
- package/index.win32-x64-msvc.node +0 -0
- package/package.json +106 -0
- package/scheduler.darwin-arm64.node +0 -0
- package/scheduler.darwin-x64.node +0 -0
- package/scheduler.linux-arm64-gnu.node +0 -0
- package/scheduler.linux-x64-gnu.node +0 -0
- package/scheduler.win32-x64-msvc.node +0 -0
- package/scripts/copy-napi.mjs +113 -0
- package/src/Application.ts +177 -0
- package/src/Codemods.ts +343 -0
- package/src/ConfigLoader.ts +49 -0
- package/src/ErrorBoundary.ts +157 -0
- package/src/GracefulShutdown.ts +80 -0
- package/src/HealthCheck.ts +96 -0
- package/src/HotReload.ts +78 -0
- package/src/HttpKernel.ts +305 -0
- package/src/Ignitor.ts +656 -0
- package/src/Provider.ts +74 -0
- package/src/bodyparser/BodyParserMiddleware.ts +183 -0
- package/src/bodyparser/MultipartFile.ts +181 -0
- package/src/bodyparser/parseSize.ts +19 -0
- package/src/bootstrap/hyper-server.ts +78 -0
- package/src/console/CommandRunner.ts +82 -0
- package/src/container/Container.ts +383 -0
- package/src/container/types.ts +30 -0
- package/src/decorators/Lazy.ts +57 -0
- package/src/decorators/Service.ts +85 -0
- package/src/errors/FuzzyMatcher.ts +93 -0
- package/src/errors/ModuleErrors.ts +73 -0
- package/src/errors/PipelineStageError.ts +97 -0
- package/src/errors/ReamError.ts +138 -0
- package/src/events/Emitter.ts +333 -0
- package/src/events/EventsProvider.ts +58 -0
- package/src/events/config.ts +10 -0
- package/src/events/configure.ts +19 -0
- package/src/events/helix/helpers.ts +260 -0
- package/src/events/index.ts +22 -0
- package/src/events/native.ts +37 -0
- package/src/events/services/main.ts +39 -0
- package/src/events/testing/FakeBus.ts +420 -0
- package/src/graphql/GraphQLEngine.ts +421 -0
- package/src/graphql/GraphQLProvider.ts +59 -0
- package/src/helpers/base64.ts +72 -0
- package/src/helpers/index.ts +6 -0
- package/src/helpers/is.ts +162 -0
- package/src/helpers/napi-loader.ts +106 -0
- package/src/helpers/safeEqual.ts +15 -0
- package/src/helpers/string.ts +469 -0
- package/src/helpers/types.ts +100 -0
- package/src/http/Exception.ts +272 -0
- package/src/http/HttpContext.ts +108 -0
- package/src/http/RedirectBuilder.ts +89 -0
- package/src/http/Request.ts +540 -0
- package/src/http/Response.ts +351 -0
- package/src/http/SseStream.ts +250 -0
- package/src/http/urlDecode.ts +18 -0
- package/src/index.ts +263 -0
- package/src/middleware/Pipeline.ts +136 -0
- package/src/openapi/OpenApiGenerator.ts +254 -0
- package/src/openapi/OpenApiMiddleware.ts +101 -0
- package/src/openapi/OpenApiProvider.ts +67 -0
- package/src/resources/ApiResource.ts +79 -0
- package/src/router/Router.ts +733 -0
- package/src/rpc/RpcProvider.ts +54 -0
- package/src/rpc/RpcRouter.ts +243 -0
- package/src/scheduler/Schedule.ts +96 -0
- package/src/scheduler/ScheduleProvider.ts +177 -0
- package/src/scheduler/Scheduler.ts +601 -0
- package/src/scheduler/index.ts +24 -0
- package/src/scheduler/locks/LockBackend.ts +38 -0
- package/src/scheduler/locks/MemoryLockBackend.ts +85 -0
- package/src/scheduler/observability/ErrorReporter.ts +13 -0
- package/src/scheduler/observability/ScheduleEvent.ts +61 -0
- package/src/scheduler/observability/ScheduleEventSink.ts +21 -0
- package/src/scheduler/observability/StatsTracker.ts +134 -0
- package/src/security/CookieSigner.ts +54 -0
- package/src/security/SignedUrl.ts +71 -0
- package/src/security/crypto.ts +98 -0
- package/src/server/HyperServer.ts +121 -0
- package/src/server/Server.ts +147 -0
- package/src/services/app.ts +34 -0
- package/src/services/router.ts +44 -0
- package/src/services/server.ts +40 -0
- package/src/session/Session.ts +231 -0
- package/src/session/SessionMiddleware.ts +153 -0
- package/src/session/drivers/CookieDriver.ts +48 -0
- package/src/session/drivers/MemoryDriver.ts +29 -0
- package/src/storage/S3Driver.ts +171 -0
- package/src/storage/StaticMiddleware.ts +182 -0
- package/src/storage/StaticProvider.ts +40 -0
- package/src/storage/StorageManager.ts +142 -0
- package/src/testing/RequestBuilder.ts +275 -0
- package/src/testing/TestClient.ts +344 -0
- package/src/types/helpers.ts +52 -0
- package/swcrc.app.json +17 -0
- package/tsconfig.app.json +21 -0
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `RpcProvider` — wires a JSON-RPC 2.0 endpoint into the Ream pipeline.
|
|
3
|
+
*
|
|
4
|
+
* - `register()` — bind a shared `RpcRouter` (with the IoC container, so
|
|
5
|
+
* `namespace()` controllers are DI-resolved) under the token `'rpc'`. Apps
|
|
6
|
+
* register methods by resolving it:
|
|
7
|
+
*
|
|
8
|
+
* const rpc = app.container.make<RpcRouter>('rpc')
|
|
9
|
+
* rpc.method('task.validate', handler).guard('jwt')
|
|
10
|
+
* rpc.namespace('user', UserController)
|
|
11
|
+
*
|
|
12
|
+
* - `boot()` — mount `POST <config.rpc.path ?? '/rpc'>` on the core router,
|
|
13
|
+
* dispatching every request body (single or batch) to `RpcRouter.handle()`.
|
|
14
|
+
*
|
|
15
|
+
* @implements Story 24.1, 24.2
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import type { HttpContext } from '../http/HttpContext.js'
|
|
19
|
+
import type { AppContext } from '../Provider.js'
|
|
20
|
+
import { Provider } from '../Provider.js'
|
|
21
|
+
import type { Router } from '../router/Router.js'
|
|
22
|
+
import { RpcRouter } from './RpcRouter.js'
|
|
23
|
+
|
|
24
|
+
export interface RpcProviderOptions {
|
|
25
|
+
/** Override the `RpcRouter` instance — used by tests. */
|
|
26
|
+
router?: RpcRouter
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface RpcConfig {
|
|
30
|
+
/** HTTP path the JSON-RPC endpoint is mounted at. Default `/rpc`. */
|
|
31
|
+
path?: string
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export class RpcProvider extends Provider {
|
|
35
|
+
readonly rpc: RpcRouter
|
|
36
|
+
|
|
37
|
+
constructor(app: AppContext, options: RpcProviderOptions = {}) {
|
|
38
|
+
super(app)
|
|
39
|
+
this.rpc = options.router ?? new RpcRouter()
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
override register(): void {
|
|
43
|
+
this.rpc.useContainer(this.app.container)
|
|
44
|
+
this.app.container.singleton('rpc', () => this.rpc)
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
override async boot(): Promise<void> {
|
|
48
|
+
const path = this.app.config.get<RpcConfig>('rpc')?.path ?? '/rpc'
|
|
49
|
+
const router = this.app.container.make<Router>('router')
|
|
50
|
+
// RpcRouter.handle() reads the registered methods at request time, so the
|
|
51
|
+
// mount can happen before apps finish registering methods.
|
|
52
|
+
router.post(path, (ctx: HttpContext) => this.rpc.handle(ctx))
|
|
53
|
+
}
|
|
54
|
+
}
|
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* JSON-RPC 2.0 Router — dispatches RPC method calls to handlers.
|
|
3
|
+
*
|
|
4
|
+
* Same middleware/guard/validate pipeline as REST routes.
|
|
5
|
+
*
|
|
6
|
+
* Usage:
|
|
7
|
+
* rpc.method('task.validate', handler).guard('jwt').role('cs_member')
|
|
8
|
+
* rpc.namespace('task', TaskController) // every public method, DI-resolved per call
|
|
9
|
+
* rpc.group({ guard: 'jwt' }, (r) => { r.method('user.find', handler) })
|
|
10
|
+
*
|
|
11
|
+
* @implements MISS-27
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import type { HttpContext } from '../http/HttpContext.js'
|
|
15
|
+
|
|
16
|
+
export type RpcHandler = (ctx: HttpContext, params: unknown) => Promise<unknown> | unknown
|
|
17
|
+
|
|
18
|
+
export interface RpcMethodDefinition {
|
|
19
|
+
name: string
|
|
20
|
+
handler: RpcHandler
|
|
21
|
+
guards: string[]
|
|
22
|
+
roles: string[]
|
|
23
|
+
permissions: string[]
|
|
24
|
+
middleware: string[]
|
|
25
|
+
validator?: string
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export class RpcMethodBuilder {
|
|
29
|
+
#def: RpcMethodDefinition
|
|
30
|
+
|
|
31
|
+
constructor(def: RpcMethodDefinition) {
|
|
32
|
+
this.#def = def
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
guard(...guards: string[]): this {
|
|
36
|
+
this.#def.guards.push(...guards)
|
|
37
|
+
return this
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
role(...roles: string[]): this {
|
|
41
|
+
this.#def.roles.push(...roles)
|
|
42
|
+
return this
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
permission(...permissions: string[]): this {
|
|
46
|
+
this.#def.permissions.push(...permissions)
|
|
47
|
+
return this
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
validate(validator: string): this {
|
|
51
|
+
this.#def.validator = validator
|
|
52
|
+
return this
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
middleware(...names: string[]): this {
|
|
56
|
+
this.#def.middleware.push(...names)
|
|
57
|
+
return this
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
interface RpcErrorResponse {
|
|
62
|
+
jsonrpc: '2.0'
|
|
63
|
+
error: { code: number; message: string }
|
|
64
|
+
id: unknown
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/** Build a JSON-RPC 2.0 error envelope. */
|
|
68
|
+
function rpcError(code: number, message: string, id: unknown): RpcErrorResponse {
|
|
69
|
+
return { jsonrpc: '2.0', error: { code, message }, id }
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
type ParsedRpcRequest =
|
|
73
|
+
| { ok: true; method: string; params: unknown; id: unknown }
|
|
74
|
+
| { ok: false; response: RpcErrorResponse }
|
|
75
|
+
|
|
76
|
+
/** Validate the JSON-RPC envelope and extract method/params/id. */
|
|
77
|
+
function parseRpcRequest(request: unknown): ParsedRpcRequest {
|
|
78
|
+
if (!request || typeof request !== 'object') {
|
|
79
|
+
return { ok: false, response: rpcError(-32600, 'Invalid Request', null) }
|
|
80
|
+
}
|
|
81
|
+
// request is narrowed to a non-null object by the typeof check above.
|
|
82
|
+
const jsonrpc =
|
|
83
|
+
'jsonrpc' in request && typeof request.jsonrpc === 'string' ? request.jsonrpc : undefined
|
|
84
|
+
const method =
|
|
85
|
+
'method' in request && typeof request.method === 'string' ? request.method : undefined
|
|
86
|
+
const params = 'params' in request ? request.params : undefined
|
|
87
|
+
const id = 'id' in request ? request.id : undefined
|
|
88
|
+
if (jsonrpc !== '2.0' || !method) {
|
|
89
|
+
return { ok: false, response: rpcError(-32600, 'Invalid Request', id ?? null) }
|
|
90
|
+
}
|
|
91
|
+
return { ok: true, method, params, id }
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Enforce the method's guards/roles/permissions against the request auth state.
|
|
96
|
+
* Returns the JSON-RPC error code+message to reject with, or undefined when the
|
|
97
|
+
* caller is authorized.
|
|
98
|
+
*/
|
|
99
|
+
function checkRpcAuthorization(
|
|
100
|
+
ctx: HttpContext,
|
|
101
|
+
def: RpcMethodDefinition,
|
|
102
|
+
): { code: number; message: string } | undefined {
|
|
103
|
+
const needsAuth = def.guards.length > 0 || def.roles.length > 0 || def.permissions.length > 0
|
|
104
|
+
if (needsAuth && !ctx.auth?.authenticated) {
|
|
105
|
+
return { code: -32003, message: 'Unauthorized' }
|
|
106
|
+
}
|
|
107
|
+
if (def.roles.length > 0) {
|
|
108
|
+
const userRoles = ctx.auth?.roles ?? []
|
|
109
|
+
if (!def.roles.some((r: string) => userRoles.includes(r))) {
|
|
110
|
+
return { code: -32003, message: 'Insufficient role' }
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
if (def.permissions.length > 0) {
|
|
114
|
+
const userPerms = ctx.auth?.permissions ?? []
|
|
115
|
+
if (!def.permissions.every((p: string) => userPerms.includes(p))) {
|
|
116
|
+
return { code: -32003, message: 'Insufficient permissions' }
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
return undefined
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export class RpcRouter {
|
|
123
|
+
#methods: Map<string, RpcMethodDefinition> = new Map()
|
|
124
|
+
#groupConfig: { guards: string[]; roles: string[]; middleware: string[] } | null = null
|
|
125
|
+
#container?: { make<T>(target: new (...args: unknown[]) => T): T }
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Set the IoC container used to resolve `namespace()` controllers — mirrors
|
|
129
|
+
* `GraphQLEngine.useContainer()` so RPC and GraphQL resolve class handlers the
|
|
130
|
+
* same way (fresh DI per call). Without a container, controllers fall back to
|
|
131
|
+
* `new Controller()`.
|
|
132
|
+
*/
|
|
133
|
+
useContainer(container: { make<T>(target: new (...args: unknown[]) => T): T }): void {
|
|
134
|
+
this.#container = container
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/** Register an RPC method. */
|
|
138
|
+
method(name: string, handler: RpcHandler): RpcMethodBuilder {
|
|
139
|
+
const def: RpcMethodDefinition = {
|
|
140
|
+
name,
|
|
141
|
+
handler,
|
|
142
|
+
guards: [...(this.#groupConfig?.guards ?? [])],
|
|
143
|
+
roles: [...(this.#groupConfig?.roles ?? [])],
|
|
144
|
+
permissions: [],
|
|
145
|
+
middleware: [...(this.#groupConfig?.middleware ?? [])],
|
|
146
|
+
}
|
|
147
|
+
this.#methods.set(name, def)
|
|
148
|
+
return new RpcMethodBuilder(def)
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/** Register methods from a controller — auto-registers all public methods. */
|
|
152
|
+
namespace(
|
|
153
|
+
prefix: string,
|
|
154
|
+
controller: new (...args: unknown[]) => Record<string, (...args: unknown[]) => unknown>,
|
|
155
|
+
): void {
|
|
156
|
+
const proto = controller.prototype
|
|
157
|
+
const methods = Object.getOwnPropertyNames(proto).filter(
|
|
158
|
+
(m) => m !== 'constructor' && typeof proto[m] === 'function',
|
|
159
|
+
)
|
|
160
|
+
for (const methodName of methods) {
|
|
161
|
+
this.method(`${prefix}.${methodName}`, async (ctx, params) => {
|
|
162
|
+
// Resolve through the container on every call (fresh DI per request,
|
|
163
|
+
// like GraphQLEngine), falling back to a bare `new` when unset.
|
|
164
|
+
const instance = this.#container ? this.#container.make(controller) : new controller()
|
|
165
|
+
return instance[methodName](ctx, params)
|
|
166
|
+
})
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/** Group methods with shared guards/middleware. */
|
|
171
|
+
group(
|
|
172
|
+
config: { guard?: string; guards?: string[]; middleware?: string[] },
|
|
173
|
+
callback: (rpc: RpcRouter) => void,
|
|
174
|
+
): void {
|
|
175
|
+
const prevConfig = this.#groupConfig
|
|
176
|
+
this.#groupConfig = {
|
|
177
|
+
guards: [...(config.guards ?? []), ...(config.guard ? [config.guard] : [])],
|
|
178
|
+
roles: [],
|
|
179
|
+
middleware: config.middleware ?? [],
|
|
180
|
+
}
|
|
181
|
+
callback(this)
|
|
182
|
+
this.#groupConfig = prevConfig
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/** Handle an incoming JSON-RPC request. */
|
|
186
|
+
async handle(ctx: HttpContext): Promise<void> {
|
|
187
|
+
const body = ctx.request.body()
|
|
188
|
+
|
|
189
|
+
// Batch support (max 50 to prevent DoS)
|
|
190
|
+
if (Array.isArray(body)) {
|
|
191
|
+
if (body.length > 50) {
|
|
192
|
+
ctx.response.status(400).json({
|
|
193
|
+
jsonrpc: '2.0',
|
|
194
|
+
error: { code: -32600, message: 'Batch too large (max 50)' },
|
|
195
|
+
id: null,
|
|
196
|
+
})
|
|
197
|
+
return
|
|
198
|
+
}
|
|
199
|
+
// Note: batch items share the same ctx (auth state). Each is processed sequentially.
|
|
200
|
+
const results: unknown[] = []
|
|
201
|
+
for (const req of body) {
|
|
202
|
+
results.push(await this.#processOne(ctx, req))
|
|
203
|
+
}
|
|
204
|
+
ctx.response.status(200).json(results)
|
|
205
|
+
return
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
const result = await this.#processOne(ctx, body)
|
|
209
|
+
ctx.response.status(200).json(result)
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
async #processOne(ctx: HttpContext, request: unknown): Promise<unknown> {
|
|
213
|
+
const parsed = parseRpcRequest(request)
|
|
214
|
+
if (!parsed.ok) return parsed.response
|
|
215
|
+
const { method, params, id } = parsed
|
|
216
|
+
|
|
217
|
+
const def = this.#methods.get(method)
|
|
218
|
+
if (!def) return rpcError(-32601, 'Method not found', id)
|
|
219
|
+
|
|
220
|
+
try {
|
|
221
|
+
const denied = checkRpcAuthorization(ctx, def)
|
|
222
|
+
if (denied) return rpcError(denied.code, denied.message, id)
|
|
223
|
+
|
|
224
|
+
const result = await def.handler(ctx, params)
|
|
225
|
+
return { jsonrpc: '2.0', result, id }
|
|
226
|
+
} catch (err) {
|
|
227
|
+
// Don't leak internal error details to caller
|
|
228
|
+
const isDev = process.env.NODE_ENV !== 'production'
|
|
229
|
+
const message = isDev && err instanceof Error ? err.message : 'Internal error'
|
|
230
|
+
return rpcError(-32603, message, id)
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/** Get all registered method names. */
|
|
235
|
+
getMethods(): string[] {
|
|
236
|
+
return [...this.#methods.keys()]
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/** Get method definition for introspection. */
|
|
240
|
+
getMethod(name: string): RpcMethodDefinition | undefined {
|
|
241
|
+
return this.#methods.get(name)
|
|
242
|
+
}
|
|
243
|
+
}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@Schedule('cron-expr')` method decorator — declares a recurring task.
|
|
3
|
+
*
|
|
4
|
+
* Tasks are discovered at boot by `ScheduleProvider` iterating the IoC
|
|
5
|
+
* service registry, reading this metadata, and registering each entry
|
|
6
|
+
* with the Rust-backed `Scheduler` (Story 28.1). The Rust ticker owns
|
|
7
|
+
* the loop; TypeScript only declares *what* runs and *when*.
|
|
8
|
+
*
|
|
9
|
+
* @implements Story 28.2
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import 'reflect-metadata'
|
|
13
|
+
import { ReamError } from '../errors/ReamError.js'
|
|
14
|
+
import type { Constructor } from '../helpers/types.js'
|
|
15
|
+
|
|
16
|
+
export const SCHEDULE_METADATA_KEY = Symbol('ream:schedule')
|
|
17
|
+
|
|
18
|
+
export interface ScheduleMetadata {
|
|
19
|
+
cronExpr: string
|
|
20
|
+
methodName: string | symbol
|
|
21
|
+
target: Constructor
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Declares that a method should run on a cron schedule.
|
|
26
|
+
*
|
|
27
|
+
* Expressions are standard 5-field cron (minute hour day-of-month month
|
|
28
|
+
* day-of-week), evaluated in **UTC** by the Rust core — callers that
|
|
29
|
+
* want local-time semantics must pre-translate.
|
|
30
|
+
*
|
|
31
|
+
* Multiple `@Schedule` decorators on different methods of the same class
|
|
32
|
+
* accumulate into an array rather than overwriting each other. Metadata
|
|
33
|
+
* is stored with `Reflect.defineMetadata` under a stable symbol key on
|
|
34
|
+
* the class constructor; inherited metadata is NOT shared (each class
|
|
35
|
+
* owns its own array — subclasses that add schedules do not mutate the
|
|
36
|
+
* base class's array).
|
|
37
|
+
*
|
|
38
|
+
* Getters, setters, and static methods are rejected at decoration time
|
|
39
|
+
* with a clear `ReamError` so that misuse surfaces early rather than at
|
|
40
|
+
* first fire.
|
|
41
|
+
*
|
|
42
|
+
* Usage:
|
|
43
|
+
* ```ts
|
|
44
|
+
* @Service()
|
|
45
|
+
* class Cleanup {
|
|
46
|
+
* @Schedule('0 *\/5 * * *')
|
|
47
|
+
* async runCleanup() { ... }
|
|
48
|
+
* }
|
|
49
|
+
* ```
|
|
50
|
+
*/
|
|
51
|
+
export function Schedule(cronExpr: string): MethodDecorator {
|
|
52
|
+
return (target, propertyKey, descriptor) => {
|
|
53
|
+
// Reject static methods. For a static, `target` IS the constructor
|
|
54
|
+
// (callable function); for an instance method, `target` is the
|
|
55
|
+
// prototype. We need the real class either way.
|
|
56
|
+
if (typeof target === 'function') {
|
|
57
|
+
throw new ReamError(
|
|
58
|
+
'SCHEDULE_INVALID_TARGET',
|
|
59
|
+
`@Schedule cannot be applied to static method '${String(propertyKey)}'`,
|
|
60
|
+
{
|
|
61
|
+
hint: 'Move the scheduled logic to an instance method, or register the task manually via Scheduler.register().',
|
|
62
|
+
},
|
|
63
|
+
)
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Reject getters/setters — `descriptor.value` is `undefined` for
|
|
67
|
+
// accessors, and the ticker needs a callable method.
|
|
68
|
+
if (descriptor && typeof descriptor.value !== 'function') {
|
|
69
|
+
throw new ReamError(
|
|
70
|
+
'SCHEDULE_INVALID_TARGET',
|
|
71
|
+
`@Schedule cannot be applied to non-method '${String(propertyKey)}' (getters and setters are not supported)`,
|
|
72
|
+
{
|
|
73
|
+
hint: 'Apply @Schedule to a regular method, not to an accessor.',
|
|
74
|
+
},
|
|
75
|
+
)
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const ctor = target.constructor as Constructor
|
|
79
|
+
// getOwnMetadata — do NOT walk the prototype chain. A subclass must
|
|
80
|
+
// not mutate the base class's shared array; each class owns its own
|
|
81
|
+
// schedule list. Matches the convention in decorators/Service.ts.
|
|
82
|
+
const existing: ScheduleMetadata[] = Reflect.getOwnMetadata(SCHEDULE_METADATA_KEY, ctor) ?? []
|
|
83
|
+
existing.push({ cronExpr, methodName: propertyKey, target: ctor })
|
|
84
|
+
Reflect.defineMetadata(SCHEDULE_METADATA_KEY, existing, ctor)
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Return every `@Schedule` entry declared on `target` (empty array if
|
|
90
|
+
* none). Returns a shallow copy so callers cannot mutate the stored
|
|
91
|
+
* metadata by accident.
|
|
92
|
+
*/
|
|
93
|
+
export function getScheduleMetadata(target: Constructor): ScheduleMetadata[] {
|
|
94
|
+
const stored: ScheduleMetadata[] = Reflect.getOwnMetadata(SCHEDULE_METADATA_KEY, target) ?? []
|
|
95
|
+
return [...stored]
|
|
96
|
+
}
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `ScheduleProvider` — wires `@Schedule`-decorated methods into the
|
|
3
|
+
* Rust-backed [`Scheduler`].
|
|
4
|
+
*
|
|
5
|
+
* Lifecycle:
|
|
6
|
+
* - `boot()` — walks the IoC service registry, reads each class's
|
|
7
|
+
* `@Schedule` metadata, and registers a callback per entry.
|
|
8
|
+
* Idempotent: calling `boot()` twice is a no-op on the second call.
|
|
9
|
+
* Partial failures are rolled back — if the Nth task's registration
|
|
10
|
+
* throws, tasks 1..N-1 registered in the same boot are unregistered
|
|
11
|
+
* before the error propagates, so the scheduler is never left in a
|
|
12
|
+
* half-registered state.
|
|
13
|
+
* - `start()` — launches the Rust tick loop.
|
|
14
|
+
* - `shutdown()` — cancels the Rust tick loop.
|
|
15
|
+
*
|
|
16
|
+
* The registered callback resolves the service **at invocation time**
|
|
17
|
+
* (not registration time) via `container.make(target)`, so every run
|
|
18
|
+
* receives freshly-DI'd dependencies for transient-scoped services.
|
|
19
|
+
*
|
|
20
|
+
* @implements Story 28.2
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
import { getServiceRegistry } from '../decorators/Service.js'
|
|
24
|
+
import { ReamError } from '../errors/ReamError.js'
|
|
25
|
+
import type { AppContext } from '../Provider.js'
|
|
26
|
+
import { Provider } from '../Provider.js'
|
|
27
|
+
import { getScheduleMetadata } from './Schedule.js'
|
|
28
|
+
import { Scheduler } from './Scheduler.js'
|
|
29
|
+
|
|
30
|
+
export interface ScheduleProviderOptions {
|
|
31
|
+
/**
|
|
32
|
+
* Override the `Scheduler` instance — used by tests to inject a
|
|
33
|
+
* mock. Production code relies on the default (`new Scheduler()`).
|
|
34
|
+
*/
|
|
35
|
+
scheduler?: Scheduler
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export class ScheduleProvider extends Provider {
|
|
39
|
+
readonly scheduler: Scheduler
|
|
40
|
+
#booted = false
|
|
41
|
+
|
|
42
|
+
constructor(app: AppContext, options: ScheduleProviderOptions = {}) {
|
|
43
|
+
super(app)
|
|
44
|
+
this.scheduler = options.scheduler ?? new Scheduler()
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Phase 1 — bind the scheduler into the IoC container under the
|
|
49
|
+
* stable token `'scheduler'`. CLI entrypoints
|
|
50
|
+
* (`ream schedule:list` / `ream schedule:run <name>`) retrieve the
|
|
51
|
+
* scheduler via `container.resolve('scheduler')` once the app has
|
|
52
|
+
* been booted through `Ignitor`. Consumers that import the
|
|
53
|
+
* provider directly still read the `readonly scheduler` field.
|
|
54
|
+
*
|
|
55
|
+
* Idempotent: re-registering the same provider instance (hot
|
|
56
|
+
* reload, repeated `app.start()` in tests) re-binds the same
|
|
57
|
+
* scheduler without error. A DIFFERENT `ScheduleProvider` trying
|
|
58
|
+
* to claim the `'scheduler'` token throws
|
|
59
|
+
* `SCHEDULE_PROVIDER_ALREADY_REGISTERED` so dueling providers do
|
|
60
|
+
* not silently overwrite each other.
|
|
61
|
+
*/
|
|
62
|
+
override register(): void {
|
|
63
|
+
const scheduler = this.scheduler
|
|
64
|
+
const container = this.app.container
|
|
65
|
+
// Probe for an existing binding. The `make` / `resolve` API
|
|
66
|
+
// throws when unbound; use it to distinguish "same instance"
|
|
67
|
+
// from "another provider already claimed the token".
|
|
68
|
+
let existing: unknown
|
|
69
|
+
try {
|
|
70
|
+
existing = container.resolve('scheduler')
|
|
71
|
+
} catch {
|
|
72
|
+
existing = undefined
|
|
73
|
+
}
|
|
74
|
+
if (existing !== undefined) {
|
|
75
|
+
if (existing === scheduler) {
|
|
76
|
+
// Same provider re-running its register phase — no-op.
|
|
77
|
+
return
|
|
78
|
+
}
|
|
79
|
+
throw new ReamError(
|
|
80
|
+
'SCHEDULE_PROVIDER_ALREADY_REGISTERED',
|
|
81
|
+
"Container token 'scheduler' is already bound to a different instance",
|
|
82
|
+
{
|
|
83
|
+
hint: 'Only one ScheduleProvider can own the scheduler binding. Remove the duplicate provider from your reamrc.ts.',
|
|
84
|
+
},
|
|
85
|
+
)
|
|
86
|
+
}
|
|
87
|
+
container.singleton('scheduler', () => scheduler)
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
override async boot(): Promise<void> {
|
|
91
|
+
if (this.#booted) return
|
|
92
|
+
const registry = getServiceRegistry()
|
|
93
|
+
const registered: string[] = []
|
|
94
|
+
|
|
95
|
+
try {
|
|
96
|
+
for (const [target] of registry) {
|
|
97
|
+
const schedules = getScheduleMetadata(target)
|
|
98
|
+
if (schedules.length === 0) continue
|
|
99
|
+
|
|
100
|
+
if (!target.name) {
|
|
101
|
+
throw new ReamError(
|
|
102
|
+
'SCHEDULE_ANONYMOUS_CLASS',
|
|
103
|
+
'Anonymous classes cannot declare @Schedule — task names would collide',
|
|
104
|
+
{
|
|
105
|
+
hint: 'Name the class explicitly (e.g. `export class Jobs { ... }`) so task names are unique and debuggable.',
|
|
106
|
+
},
|
|
107
|
+
)
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
for (const { cronExpr, methodName } of schedules) {
|
|
111
|
+
// Reject symbol method keys — `String(Symbol('x'))` yields
|
|
112
|
+
// `Symbol(x)` which collides across distinct symbol instances
|
|
113
|
+
// sharing the same description. String keys are the
|
|
114
|
+
// documented contract.
|
|
115
|
+
if (typeof methodName === 'symbol') {
|
|
116
|
+
throw new ReamError(
|
|
117
|
+
'SCHEDULE_SYMBOL_METHOD',
|
|
118
|
+
`@Schedule on symbol-keyed method is not supported on class '${target.name}'`,
|
|
119
|
+
{
|
|
120
|
+
hint: 'Use a regular named method so the task can be addressed uniquely.',
|
|
121
|
+
},
|
|
122
|
+
)
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const taskName = `${target.name}.${methodName}`
|
|
126
|
+
try {
|
|
127
|
+
this.scheduler.register(taskName, cronExpr, async () => {
|
|
128
|
+
// Resolve from the container on every fire so transient
|
|
129
|
+
// services get a fresh instance each run.
|
|
130
|
+
const instance = this.app.container.make<Record<string, unknown>>(target)
|
|
131
|
+
const method = instance[methodName]
|
|
132
|
+
if (typeof method !== 'function') {
|
|
133
|
+
throw new ReamError(
|
|
134
|
+
'SCHEDULE_METHOD_NOT_FOUND',
|
|
135
|
+
`Scheduled method ${taskName} is not callable on resolved instance`,
|
|
136
|
+
)
|
|
137
|
+
}
|
|
138
|
+
await (method as (...args: unknown[]) => unknown).call(instance)
|
|
139
|
+
})
|
|
140
|
+
registered.push(taskName)
|
|
141
|
+
} catch (cause) {
|
|
142
|
+
const inner = cause as ReamError & { message?: string; hint?: string }
|
|
143
|
+
throw new ReamError(
|
|
144
|
+
'SCHEDULE_INVALID_CRON',
|
|
145
|
+
`Failed to register scheduled task '${taskName}': ${inner.message ?? String(cause)}`,
|
|
146
|
+
{
|
|
147
|
+
context: { task: taskName, cronExpr },
|
|
148
|
+
hint: inner.hint,
|
|
149
|
+
},
|
|
150
|
+
)
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
this.#booted = true
|
|
155
|
+
} catch (err) {
|
|
156
|
+
// Roll back any partial registrations so a retry does not hit
|
|
157
|
+
// DUPLICATE_TASK and the scheduler is never half-initialized.
|
|
158
|
+
for (const name of registered) {
|
|
159
|
+
try {
|
|
160
|
+
this.scheduler.unregister(name)
|
|
161
|
+
} catch {
|
|
162
|
+
// Best-effort rollback; swallow secondary failures so the
|
|
163
|
+
// original error reaches the caller unchanged.
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
throw err
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
override async start(): Promise<void> {
|
|
171
|
+
this.scheduler.start()
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
override async shutdown(): Promise<void> {
|
|
175
|
+
this.scheduler.stop()
|
|
176
|
+
}
|
|
177
|
+
}
|