@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,85 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Default single-process lock backend backed by a `Map`.
|
|
3
|
+
*
|
|
4
|
+
* Correct for apps that run in a single Node.js process. For
|
|
5
|
+
* horizontally-scaled deployments, implement `LockBackend` against a
|
|
6
|
+
* shared store (Redis `SET NX PX`, Postgres advisory locks, etc.) —
|
|
7
|
+
* those drivers live in user-land per the project's "agnostic per
|
|
8
|
+
* package" rule.
|
|
9
|
+
*
|
|
10
|
+
* Expiry is evaluated **lazily** at `acquire` time — no background
|
|
11
|
+
* timer, no `setInterval`. This preserves Story 28.1's
|
|
12
|
+
* Rust-ticker-only philosophy: the only timers in the scheduler live
|
|
13
|
+
* in the Rust Tokio runtime.
|
|
14
|
+
*
|
|
15
|
+
* An opportunistic sweep runs at most once per `acquire` call to
|
|
16
|
+
* cap unbounded growth: when the map exceeds `SWEEP_THRESHOLD`
|
|
17
|
+
* entries, expired entries are pruned in O(N) without blocking
|
|
18
|
+
* the normal path.
|
|
19
|
+
*
|
|
20
|
+
* @implements Story 28.3
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
import { ReamError } from '../../errors/ReamError.js'
|
|
24
|
+
import type { LockBackend } from './LockBackend.js'
|
|
25
|
+
|
|
26
|
+
const SWEEP_THRESHOLD = 256
|
|
27
|
+
|
|
28
|
+
function assertValidTtl(ttlMs: number): void {
|
|
29
|
+
if (!Number.isFinite(ttlMs) || ttlMs <= 0) {
|
|
30
|
+
throw new ReamError(
|
|
31
|
+
'SCHEDULE_INVALID_LOCK_TTL',
|
|
32
|
+
`Lock TTL must be a finite positive number, got ${ttlMs}`,
|
|
33
|
+
{
|
|
34
|
+
hint: 'Use a millisecond value greater than zero (typical range 1_000 – 600_000).',
|
|
35
|
+
},
|
|
36
|
+
)
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export class MemoryLockBackend implements LockBackend {
|
|
41
|
+
#locks = new Map<string, number>()
|
|
42
|
+
|
|
43
|
+
async acquire(name: string, ttlMs: number): Promise<boolean> {
|
|
44
|
+
assertValidTtl(ttlMs)
|
|
45
|
+
const now = Date.now()
|
|
46
|
+
|
|
47
|
+
// Opportunistic sweep: when the map grows past the threshold,
|
|
48
|
+
// drop expired entries. Bounded by the current map size, runs at
|
|
49
|
+
// most once per acquire, never schedules async work.
|
|
50
|
+
if (this.#locks.size >= SWEEP_THRESHOLD) {
|
|
51
|
+
for (const [key, expiry] of this.#locks) {
|
|
52
|
+
if (expiry <= now) this.#locks.delete(key)
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const existing = this.#locks.get(name)
|
|
57
|
+
if (existing !== undefined && existing > now) {
|
|
58
|
+
return false
|
|
59
|
+
}
|
|
60
|
+
this.#locks.set(name, now + ttlMs)
|
|
61
|
+
return true
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
async release(name: string): Promise<void> {
|
|
65
|
+
this.#locks.delete(name)
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* @internal Test-only helper: force an entry into the map with an
|
|
70
|
+
* arbitrary expiry. Used to simulate a crashed holder whose lock
|
|
71
|
+
* has already expired without advancing `Date.now()`. Not part of
|
|
72
|
+
* the public {@link LockBackend} contract — never call from
|
|
73
|
+
* production code.
|
|
74
|
+
*/
|
|
75
|
+
__setExpiryForTesting(name: string, expiryMs: number): void {
|
|
76
|
+
this.#locks.set(name, expiryMs)
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/** @internal Test-only helper — return the current map size. */
|
|
80
|
+
__sizeForTesting(): number {
|
|
81
|
+
return this.#locks.size
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export default MemoryLockBackend
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Callback invoked when a scheduled task throws. Typically bridges
|
|
3
|
+
* to `ErrorBoundary.serviceError` — but the scheduler does not
|
|
4
|
+
* import `ErrorBoundary` directly; the user wires the adapter
|
|
5
|
+
* per the "agnostic per package" rule.
|
|
6
|
+
*
|
|
7
|
+
* The scheduler wraps the call in its own `try/catch`, so an
|
|
8
|
+
* `ErrorReporter` that itself throws cannot crash the Rust ticker.
|
|
9
|
+
*
|
|
10
|
+
* @implements Story 28.4
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
export type ErrorReporter = (err: unknown, context: { taskName: string }) => void
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Discriminated union of events emitted by the scheduler's task
|
|
3
|
+
* invocation wrapper.
|
|
4
|
+
*
|
|
5
|
+
* All payloads are JSON-serializable so downstream sinks (the event bus,
|
|
6
|
+
* structured logs, metrics backends) can forward them without
|
|
7
|
+
* touching live `Error` instances.
|
|
8
|
+
*
|
|
9
|
+
* @implements Story 28.4
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
export interface ScheduleTaskStartedEvent {
|
|
13
|
+
type: 'schedule.task.started'
|
|
14
|
+
taskName: string
|
|
15
|
+
/** ms-epoch instant the Rust ticker chose for this fire. */
|
|
16
|
+
scheduledForMs: number
|
|
17
|
+
/** ms-epoch at which the JS wrapper began running the user callback. */
|
|
18
|
+
startedAtMs: number
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface ScheduleTaskCompletedEvent {
|
|
22
|
+
type: 'schedule.task.completed'
|
|
23
|
+
taskName: string
|
|
24
|
+
/** Wall-clock milliseconds spent running the callback. */
|
|
25
|
+
durationMs: number
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface ScheduleTaskFailedEvent {
|
|
29
|
+
type: 'schedule.task.failed'
|
|
30
|
+
taskName: string
|
|
31
|
+
/** Plain-object projection of the thrown error (no live `Error`). */
|
|
32
|
+
error: { name: string; message: string; stack?: string }
|
|
33
|
+
/** Wall-clock milliseconds until the throw. */
|
|
34
|
+
durationMs: number
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export interface ScheduleTaskSkippedEvent {
|
|
38
|
+
type: 'schedule.task.skipped'
|
|
39
|
+
taskName: string
|
|
40
|
+
/** Reason this tick did not execute the task body.
|
|
41
|
+
* - `'locked'` — distributed-lock backend reported the slot as
|
|
42
|
+
* already held by another instance.
|
|
43
|
+
* - `'acquire-failed'` — the lock backend itself threw/rejected
|
|
44
|
+
* (e.g. Redis connection drop); the scheduler treats the tick
|
|
45
|
+
* as skipped for observability rather than silently dropping.
|
|
46
|
+
* - `'already-running'` — an in-process invocation of the same
|
|
47
|
+
* task is already in flight (ticker + concurrent `runTaskNow`,
|
|
48
|
+
* or two simultaneous `runTaskNow` calls). The duplicate is
|
|
49
|
+
* dropped to prevent double-execution of non-idempotent tasks.
|
|
50
|
+
*
|
|
51
|
+
* Left open for future reasons (e.g. `'paused'`). Consumers that
|
|
52
|
+
* branch on `reason` should handle unknown values defensively
|
|
53
|
+
* (log + treat as skip) so adding a new variant is non-breaking. */
|
|
54
|
+
reason: 'locked' | 'acquire-failed' | 'already-running'
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export type ScheduleEvent =
|
|
58
|
+
| ScheduleTaskStartedEvent
|
|
59
|
+
| ScheduleTaskCompletedEvent
|
|
60
|
+
| ScheduleTaskFailedEvent
|
|
61
|
+
| ScheduleTaskSkippedEvent
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Destination for scheduler task-invocation events.
|
|
3
|
+
*
|
|
4
|
+
* This interface is intentionally minimal so users can adapt to any
|
|
5
|
+
* event bus (the bus, a raw `EventEmitter`, a structured logger). The
|
|
6
|
+
* scheduler does NOT import the event bus directly — writing the
|
|
7
|
+
* bridge is the user's responsibility per the project's "agnostic
|
|
8
|
+
* per package" rule.
|
|
9
|
+
*
|
|
10
|
+
* The scheduler treats `emit` as fire-and-forget: it does not await
|
|
11
|
+
* the return value, and any sync throw is caught at the call site so
|
|
12
|
+
* a buggy adapter cannot crash the Rust ticker.
|
|
13
|
+
*
|
|
14
|
+
* @implements Story 28.4
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import type { ScheduleEvent } from './ScheduleEvent.js'
|
|
18
|
+
|
|
19
|
+
export interface ScheduleEventSink {
|
|
20
|
+
emit(event: ScheduleEvent): void | Promise<void>
|
|
21
|
+
}
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Per-task run statistics. Populated by the scheduler's invocation
|
|
3
|
+
* wrapper and surfaced via `Scheduler.getStats(taskName)`.
|
|
4
|
+
*
|
|
5
|
+
* `nextRunMs` is NOT tracked here — it is read live from the Rust
|
|
6
|
+
* NAPI layer every time `Scheduler.getStats` is called. This tracker
|
|
7
|
+
* only owns counters that accumulate over time.
|
|
8
|
+
*
|
|
9
|
+
* @implements Story 28.4
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
export interface TaskStats {
|
|
13
|
+
/** ms-epoch of the most recent fire that the wrapper STARTED (lock-skipped fires do not update this). `null` if never started. */
|
|
14
|
+
lastRunMs: number | null
|
|
15
|
+
/** ms-epoch of the next scheduled fire as reported by the Rust core. `null` when the task is unknown or the core returns null. */
|
|
16
|
+
nextRunMs: number | null
|
|
17
|
+
/** Total terminal invocations: completed + failed + skipped. */
|
|
18
|
+
runCount: number
|
|
19
|
+
/** Completed (callback resolved without throwing). */
|
|
20
|
+
successCount: number
|
|
21
|
+
/** Failed (callback threw / rejected). */
|
|
22
|
+
errorCount: number
|
|
23
|
+
/** Skipped (lock backend reported the task already held by another instance). */
|
|
24
|
+
skippedCount: number
|
|
25
|
+
/**
|
|
26
|
+
* Rolling average wall-clock duration over completed + failed
|
|
27
|
+
* runs (skipped runs have no duration and are excluded). `0`
|
|
28
|
+
* until at least one duration has been recorded.
|
|
29
|
+
*/
|
|
30
|
+
avgDurationMs: number
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
interface InternalStats {
|
|
34
|
+
lastRunMs: number | null
|
|
35
|
+
runCount: number
|
|
36
|
+
successCount: number
|
|
37
|
+
errorCount: number
|
|
38
|
+
skippedCount: number
|
|
39
|
+
totalDurationMs: number
|
|
40
|
+
durationRunCount: number
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function zeroInternal(): InternalStats {
|
|
44
|
+
return {
|
|
45
|
+
lastRunMs: null,
|
|
46
|
+
runCount: 0,
|
|
47
|
+
successCount: 0,
|
|
48
|
+
errorCount: 0,
|
|
49
|
+
skippedCount: 0,
|
|
50
|
+
totalDurationMs: 0,
|
|
51
|
+
durationRunCount: 0,
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* O(1) in-memory counter map — one entry per task name seen. Never
|
|
57
|
+
* grows beyond the number of distinct task names the scheduler has
|
|
58
|
+
* seen; no per-run history retained.
|
|
59
|
+
*/
|
|
60
|
+
export class StatsTracker {
|
|
61
|
+
#stats = new Map<string, InternalStats>()
|
|
62
|
+
|
|
63
|
+
#ensure(name: string): InternalStats {
|
|
64
|
+
let s = this.#stats.get(name)
|
|
65
|
+
if (!s) {
|
|
66
|
+
s = zeroInternal()
|
|
67
|
+
this.#stats.set(name, s)
|
|
68
|
+
}
|
|
69
|
+
return s
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
recordStarted(name: string, startedAtMs: number): void {
|
|
73
|
+
this.#ensure(name).lastRunMs = startedAtMs
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
recordCompleted(name: string, durationMs: number): void {
|
|
77
|
+
const s = this.#ensure(name)
|
|
78
|
+
s.runCount++
|
|
79
|
+
s.successCount++
|
|
80
|
+
// Guard against non-finite durations — a caller bug passing NaN or
|
|
81
|
+
// Infinity would permanently poison the rolling average. Still
|
|
82
|
+
// count the run; only skip the duration accumulation.
|
|
83
|
+
if (Number.isFinite(durationMs)) {
|
|
84
|
+
s.totalDurationMs += durationMs
|
|
85
|
+
s.durationRunCount++
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
recordFailed(name: string, durationMs: number): void {
|
|
90
|
+
const s = this.#ensure(name)
|
|
91
|
+
s.runCount++
|
|
92
|
+
s.errorCount++
|
|
93
|
+
if (Number.isFinite(durationMs)) {
|
|
94
|
+
s.totalDurationMs += durationMs
|
|
95
|
+
s.durationRunCount++
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
recordSkipped(name: string): void {
|
|
100
|
+
const s = this.#ensure(name)
|
|
101
|
+
s.runCount++
|
|
102
|
+
s.skippedCount++
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Return a defensive snapshot. Unknown tasks yield a zeroed
|
|
107
|
+
* snapshot with `null` timestamps. `nextRunMs` is always `null`
|
|
108
|
+
* here — the caller (`Scheduler.getStats`) overlays the live
|
|
109
|
+
* value from the NAPI layer.
|
|
110
|
+
*/
|
|
111
|
+
get(name: string): TaskStats {
|
|
112
|
+
const s = this.#stats.get(name)
|
|
113
|
+
if (!s) {
|
|
114
|
+
return {
|
|
115
|
+
lastRunMs: null,
|
|
116
|
+
nextRunMs: null,
|
|
117
|
+
runCount: 0,
|
|
118
|
+
successCount: 0,
|
|
119
|
+
errorCount: 0,
|
|
120
|
+
skippedCount: 0,
|
|
121
|
+
avgDurationMs: 0,
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
return {
|
|
125
|
+
lastRunMs: s.lastRunMs,
|
|
126
|
+
nextRunMs: null,
|
|
127
|
+
runCount: s.runCount,
|
|
128
|
+
successCount: s.successCount,
|
|
129
|
+
errorCount: s.errorCount,
|
|
130
|
+
skippedCount: s.skippedCount,
|
|
131
|
+
avgDurationMs: s.durationRunCount > 0 ? s.totalDurationMs / s.durationRunCount : 0,
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cookie signing and encryption.
|
|
3
|
+
* Uses Rust NAPI via crypto facade when available.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { createCipheriv, createDecipheriv, createHmac, randomBytes } from 'node:crypto'
|
|
7
|
+
import { hmacSign, hmacVerify } from './crypto.js'
|
|
8
|
+
|
|
9
|
+
export class CookieSigner {
|
|
10
|
+
#secret: string
|
|
11
|
+
#keyBuffer: Buffer
|
|
12
|
+
|
|
13
|
+
constructor(secret: string) {
|
|
14
|
+
this.#secret = secret
|
|
15
|
+
// Derive a 32-byte key for AES-256-GCM via HKDF-like derivation
|
|
16
|
+
this.#keyBuffer = Buffer.from(createHmac('sha256', secret).update('cookie-key').digest())
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
sign(value: string): string {
|
|
20
|
+
const sig = hmacSign(value, this.#secret)
|
|
21
|
+
return `${value}.${sig}`
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
unsign(signed: string): string | null {
|
|
25
|
+
const lastDot = signed.lastIndexOf('.')
|
|
26
|
+
if (lastDot === -1) return null
|
|
27
|
+
const value = signed.slice(0, lastDot)
|
|
28
|
+
const sig = signed.slice(lastDot + 1)
|
|
29
|
+
return hmacVerify(value, sig, this.#secret) ? value : null
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
encrypt(value: string): string {
|
|
33
|
+
const iv = randomBytes(12)
|
|
34
|
+
const cipher = createCipheriv('aes-256-gcm', this.#keyBuffer, iv)
|
|
35
|
+
const encrypted = Buffer.concat([cipher.update(value, 'utf8'), cipher.final()])
|
|
36
|
+
const tag = cipher.getAuthTag()
|
|
37
|
+
return `${iv.toString('base64url')}.${encrypted.toString('base64url')}.${tag.toString('base64url')}`
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
decrypt(encrypted: string): string | null {
|
|
41
|
+
const parts = encrypted.split('.')
|
|
42
|
+
if (parts.length !== 3) return null
|
|
43
|
+
try {
|
|
44
|
+
const iv = Buffer.from(parts[0], 'base64url')
|
|
45
|
+
const data = Buffer.from(parts[1], 'base64url')
|
|
46
|
+
const tag = Buffer.from(parts[2], 'base64url')
|
|
47
|
+
const decipher = createDecipheriv('aes-256-gcm', this.#keyBuffer, iv)
|
|
48
|
+
decipher.setAuthTag(tag)
|
|
49
|
+
return Buffer.concat([decipher.update(data), decipher.final()]).toString('utf8')
|
|
50
|
+
} catch {
|
|
51
|
+
return null
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Signed URLs — HMAC-SHA256 with expiration.
|
|
3
|
+
* Uses Rust NAPI via crypto facade when available.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { constantTimeEq, hmacSign } from './crypto.js'
|
|
7
|
+
|
|
8
|
+
export interface SignedUrlConfig {
|
|
9
|
+
secret: string
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export class SignedUrl {
|
|
13
|
+
#secret: string
|
|
14
|
+
|
|
15
|
+
constructor(config: SignedUrlConfig) {
|
|
16
|
+
this.#secret = config.secret
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
make(path: string, options?: { expiresIn?: string | number; purpose?: string }): string {
|
|
20
|
+
const url = new URL(path, 'http://localhost')
|
|
21
|
+
// Audit 2026-05-22: `expiresIn !== undefined` instead of truthy check.
|
|
22
|
+
// `expiresIn: 0` previously fell through the falsy branch and produced
|
|
23
|
+
// an URL with no `expires` param — a never-expiring signed URL when the
|
|
24
|
+
// caller explicitly asked for "expires immediately". The new check
|
|
25
|
+
// honours 0 (and any other numeric input) by stamping the expires param;
|
|
26
|
+
// verify() then rejects it as soon as the wall clock advances.
|
|
27
|
+
if (options?.expiresIn !== undefined) {
|
|
28
|
+
url.searchParams.set(
|
|
29
|
+
'expires',
|
|
30
|
+
String(Math.floor(Date.now() / 1000) + parseExpiry(options.expiresIn)),
|
|
31
|
+
)
|
|
32
|
+
}
|
|
33
|
+
if (options?.purpose) {
|
|
34
|
+
url.searchParams.set('purpose', options.purpose)
|
|
35
|
+
}
|
|
36
|
+
url.searchParams.set('signature', hmacSign(url.pathname + url.search, this.#secret))
|
|
37
|
+
return url.pathname + url.search
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
verify(urlString: string, purpose?: string): boolean {
|
|
41
|
+
const url = new URL(urlString, 'http://localhost')
|
|
42
|
+
const providedSig = url.searchParams.get('signature')
|
|
43
|
+
if (!providedSig) return false
|
|
44
|
+
const expires = url.searchParams.get('expires')
|
|
45
|
+
if (expires && Math.floor(Date.now() / 1000) > parseInt(expires, 10)) return false
|
|
46
|
+
if (purpose && url.searchParams.get('purpose') !== purpose) return false
|
|
47
|
+
|
|
48
|
+
url.searchParams.delete('signature')
|
|
49
|
+
const expectedSig = hmacSign(url.pathname + url.search, this.#secret)
|
|
50
|
+
return constantTimeEq(providedSig, expectedSig)
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function parseExpiry(value: string | number): number {
|
|
55
|
+
if (typeof value === 'number') return value
|
|
56
|
+
const match = value.match(/^(\d+)(s|m|h|d)$/)
|
|
57
|
+
if (!match) return 3600
|
|
58
|
+
const num = parseInt(match[1], 10)
|
|
59
|
+
switch (match[2]) {
|
|
60
|
+
case 's':
|
|
61
|
+
return num
|
|
62
|
+
case 'm':
|
|
63
|
+
return num * 60
|
|
64
|
+
case 'h':
|
|
65
|
+
return num * 3600
|
|
66
|
+
case 'd':
|
|
67
|
+
return num * 86400
|
|
68
|
+
default:
|
|
69
|
+
return 3600
|
|
70
|
+
}
|
|
71
|
+
}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Crypto facade — resolves Rust NAPI bindings at load time.
|
|
3
|
+
* If the binary isn't available, falls back to Node.js crypto.
|
|
4
|
+
*
|
|
5
|
+
* Usage:
|
|
6
|
+
* import { hmacSign, hmacVerify, randomHex, constantTimeEq } from './crypto.js'
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { createHmac, randomBytes as nodeRandomBytes, timingSafeEqual } from 'node:crypto'
|
|
10
|
+
|
|
11
|
+
interface NapiCrypto {
|
|
12
|
+
hmacSign(data: string, secret: string): string
|
|
13
|
+
hmacVerify(data: string, signature: string, secret: string): boolean
|
|
14
|
+
randomBytesBase64(len: number): string
|
|
15
|
+
randomHex(len: number): string
|
|
16
|
+
constantTimeEq(a: string, b: string): boolean
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
let napi: NapiCrypto | null = null
|
|
20
|
+
|
|
21
|
+
try {
|
|
22
|
+
// Optional pre-install path: a NAPI loader may set `globalThis.__reamNapi`
|
|
23
|
+
// before this module loads. We validate the shape rather than declare a
|
|
24
|
+
// global `var`, so the loader contract stays runtime-only.
|
|
25
|
+
const preinstalled = readPreinstalledNapi()
|
|
26
|
+
if (preinstalled) napi = preinstalled
|
|
27
|
+
} catch {
|
|
28
|
+
/* binary not available */
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** @internal Set the NAPI bindings (called once by the NAPI loader). */
|
|
32
|
+
export function setNapi(bindings: NapiCrypto): void {
|
|
33
|
+
napi = bindings
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** Check if Rust NAPI crypto is available. */
|
|
37
|
+
export function hasNativeCrypto(): boolean {
|
|
38
|
+
return napi !== null
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// ─── Exports ────────────────────────────────────────────────
|
|
42
|
+
|
|
43
|
+
export function hmacSign(data: string, secret: string): string {
|
|
44
|
+
if (napi) return napi.hmacSign(data, secret)
|
|
45
|
+
return createHmac('sha256', secret).update(data).digest('base64url')
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function hmacVerify(data: string, signature: string, secret: string): boolean {
|
|
49
|
+
if (napi) return napi.hmacVerify(data, signature, secret)
|
|
50
|
+
const expected = createHmac('sha256', secret).update(data).digest('base64url')
|
|
51
|
+
const a = Buffer.from(signature)
|
|
52
|
+
const b = Buffer.from(expected)
|
|
53
|
+
if (a.length !== b.length) return false
|
|
54
|
+
return timingSafeEqual(a, b)
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export function randomBytesBase64(len: number): string {
|
|
58
|
+
if (napi) return napi.randomBytesBase64(len)
|
|
59
|
+
return nodeRandomBytes(len).toString('base64url')
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export function randomHex(len: number): string {
|
|
63
|
+
if (napi) return napi.randomHex(len)
|
|
64
|
+
return nodeRandomBytes(len).toString('hex')
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export function constantTimeEq(a: string, b: string): boolean {
|
|
68
|
+
if (napi) return napi.constantTimeEq(a, b)
|
|
69
|
+
const bufA = Buffer.from(a)
|
|
70
|
+
const bufB = Buffer.from(b)
|
|
71
|
+
if (bufA.length !== bufB.length) return false
|
|
72
|
+
return timingSafeEqual(bufA, bufB)
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function readPreinstalledNapi(): NapiCrypto | null {
|
|
76
|
+
// `globalThis` is typed without an index signature, so we go through Reflect
|
|
77
|
+
// and validate the shape downstream — no global `var` declaration needed.
|
|
78
|
+
const candidate: unknown = Reflect.get(globalThis, '__reamNapi')
|
|
79
|
+
return isNapiCrypto(candidate) ? candidate : null
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function isNapiCrypto(value: unknown): value is NapiCrypto {
|
|
83
|
+
if (typeof value !== 'object' || value === null) return false
|
|
84
|
+
return (
|
|
85
|
+
hasFn(value, 'hmacSign') &&
|
|
86
|
+
hasFn(value, 'hmacVerify') &&
|
|
87
|
+
hasFn(value, 'randomBytesBase64') &&
|
|
88
|
+
hasFn(value, 'randomHex') &&
|
|
89
|
+
hasFn(value, 'constantTimeEq')
|
|
90
|
+
)
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function hasFn<K extends string>(
|
|
94
|
+
value: object,
|
|
95
|
+
key: K,
|
|
96
|
+
): value is Record<K, (...args: unknown[]) => unknown> {
|
|
97
|
+
return key in value && typeof Reflect.get(value, key) === 'function'
|
|
98
|
+
}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import type { HttpKernelRequest, HttpKernelResponse } from '../HttpKernel.js'
|
|
2
|
+
import { loadNapi } from '../helpers/napi-loader.js'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Wire-level shield configuration handed off to the Rust filter at boot.
|
|
6
|
+
* Each flag toggles one rule on/off; they all default to true on the Rust
|
|
7
|
+
* side, so omitting `configureShield()` entirely leaves the server with no
|
|
8
|
+
* filter installed.
|
|
9
|
+
*/
|
|
10
|
+
export interface RustShieldConfig {
|
|
11
|
+
pathTraversal: boolean
|
|
12
|
+
paramPollution: boolean
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface RustRateLimitConfig {
|
|
16
|
+
max: number
|
|
17
|
+
windowSecs: number
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface HyperServerLike {
|
|
21
|
+
onRequest(callback: (request: HttpKernelRequest) => Promise<HttpKernelResponse>): void
|
|
22
|
+
configureShield(config: RustShieldConfig): void
|
|
23
|
+
configureTrustedProxies(cidrs: string[]): void
|
|
24
|
+
configureRateLimit(config: RustRateLimitConfig | null): void
|
|
25
|
+
listen(): Promise<void>
|
|
26
|
+
port(): Promise<number>
|
|
27
|
+
close(): Promise<void>
|
|
28
|
+
/**
|
|
29
|
+
* Reserve a streaming-response slot. The handler must call this and
|
|
30
|
+
* pass the returned id back inside `HttpKernelResponse.streamId`
|
|
31
|
+
* BEFORE returning, so the hyper-side response builder can feed the
|
|
32
|
+
* body from the matching registry entry.
|
|
33
|
+
*/
|
|
34
|
+
registerStream(streamId: string): Promise<boolean>
|
|
35
|
+
/**
|
|
36
|
+
* Push a UTF-8 chunk onto a registered stream. Returns `false` once
|
|
37
|
+
* the client has disconnected — callers stop pushing.
|
|
38
|
+
*/
|
|
39
|
+
writeStream(streamId: string, chunk: string): Promise<boolean>
|
|
40
|
+
/**
|
|
41
|
+
* End a stream from the server side. Idempotent.
|
|
42
|
+
*/
|
|
43
|
+
closeStream(streamId: string): Promise<boolean>
|
|
44
|
+
/**
|
|
45
|
+
* Install a one-shot callback fired the moment the matching stream
|
|
46
|
+
* receiver is dropped — i.e. the client disconnected OR
|
|
47
|
+
* `closeStream` was called. Used by the JS SDK to drop bookkeeping
|
|
48
|
+
* for subscriptions and SSE viewers.
|
|
49
|
+
*/
|
|
50
|
+
onStreamDisconnect(streamId: string, callback: () => void): void
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
interface NativeModule {
|
|
54
|
+
HyperServer: new (port: number) => HyperServerLike
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
let native: NativeModule | undefined
|
|
58
|
+
|
|
59
|
+
function loadNative(): NativeModule {
|
|
60
|
+
if (native) return native
|
|
61
|
+
native = loadNapi<NativeModule>({
|
|
62
|
+
binaryName: 'index',
|
|
63
|
+
callerMetaUrl: import.meta.url,
|
|
64
|
+
errorCodePrefix: 'HYPER_SERVER',
|
|
65
|
+
})
|
|
66
|
+
return native
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/** HyperServer — Rust-powered HTTP server. */
|
|
70
|
+
export class HyperServer implements HyperServerLike {
|
|
71
|
+
#inner: HyperServerLike
|
|
72
|
+
|
|
73
|
+
constructor(port: number) {
|
|
74
|
+
const mod = loadNative()
|
|
75
|
+
this.#inner = new mod.HyperServer(port)
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
onRequest(callback: (request: HttpKernelRequest) => Promise<HttpKernelResponse>): void {
|
|
79
|
+
this.#inner.onRequest(callback)
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
configureShield(config: RustShieldConfig): void {
|
|
83
|
+
this.#inner.configureShield(config)
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
configureTrustedProxies(cidrs: string[]): void {
|
|
87
|
+
this.#inner.configureTrustedProxies(cidrs)
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
configureRateLimit(config: RustRateLimitConfig | null): void {
|
|
91
|
+
this.#inner.configureRateLimit(config)
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
listen(): Promise<void> {
|
|
95
|
+
return this.#inner.listen()
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
port(): Promise<number> {
|
|
99
|
+
return this.#inner.port()
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
close(): Promise<void> {
|
|
103
|
+
return this.#inner.close()
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
registerStream(streamId: string): Promise<boolean> {
|
|
107
|
+
return this.#inner.registerStream(streamId)
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
writeStream(streamId: string, chunk: string): Promise<boolean> {
|
|
111
|
+
return this.#inner.writeStream(streamId, chunk)
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
closeStream(streamId: string): Promise<boolean> {
|
|
115
|
+
return this.#inner.closeStream(streamId)
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
onStreamDisconnect(streamId: string, callback: () => void): void {
|
|
119
|
+
this.#inner.onStreamDisconnect(streamId, callback)
|
|
120
|
+
}
|
|
121
|
+
}
|