@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,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
|
+
}
|