@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,540 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Request — wraps raw HTTP request data from the NAPI layer.
|
|
3
|
+
*
|
|
4
|
+
* Provides a fluent, AdonisJS-compatible API for reading request data.
|
|
5
|
+
* JSON body parsing is lazy — deferred until first access via input()/all()/body().
|
|
6
|
+
*
|
|
7
|
+
* @implements FR21
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import type { Dict } from '../types/helpers.js'
|
|
11
|
+
|
|
12
|
+
export interface RawRequest {
|
|
13
|
+
method: string
|
|
14
|
+
path: string
|
|
15
|
+
query: string
|
|
16
|
+
headers: Dict
|
|
17
|
+
body: string
|
|
18
|
+
bodyEncoding?: 'utf8' | 'base64'
|
|
19
|
+
/** Socket-level peer address (filled by the HTTP server). */
|
|
20
|
+
remoteAddr?: string
|
|
21
|
+
/**
|
|
22
|
+
* Resolved client IP — pre-computed by the Rust-side HyperServer using the
|
|
23
|
+
* configured trusted-proxy CIDR list. JS reads this field directly; CIDR
|
|
24
|
+
* matching no longer happens in TS. When absent (mock/test fixtures) the
|
|
25
|
+
* `ip()` accessor falls back to legacy header parsing.
|
|
26
|
+
*/
|
|
27
|
+
ip?: string
|
|
28
|
+
/**
|
|
29
|
+
* Pre-parsed `multipart/form-data` payload — the HyperServer parses bodies
|
|
30
|
+
* server-side via `multer` and ships fields/files in this typed shape.
|
|
31
|
+
* Files are base64-encoded so the payload stays JSON-safe.
|
|
32
|
+
*/
|
|
33
|
+
multipart?: {
|
|
34
|
+
fields: Array<{ name: string; value: string }>
|
|
35
|
+
files: Array<{
|
|
36
|
+
fieldName: string
|
|
37
|
+
clientName: string
|
|
38
|
+
contentType: string
|
|
39
|
+
size: number
|
|
40
|
+
contentB64: string
|
|
41
|
+
}>
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Pre-parsed `Cookie:` header — name → value map computed by the Rust
|
|
45
|
+
* cookie crate. JS reads via `request.cookie(name)`. When absent (test
|
|
46
|
+
* fixtures), the accessor falls back to inline header parsing.
|
|
47
|
+
*/
|
|
48
|
+
cookies?: Dict
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export class Request {
|
|
52
|
+
#raw: RawRequest
|
|
53
|
+
#params: Dict
|
|
54
|
+
#parsedBody: Dict<unknown> | undefined
|
|
55
|
+
#parsedQs: Dict<unknown> | undefined
|
|
56
|
+
#merged: Dict<unknown> | undefined
|
|
57
|
+
#files: Map<string, import('../bodyparser/MultipartFile.js').MultipartFile[]> = new Map()
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* CSRF token for this request (AdonisJS idiom: `request.csrfToken`). Seeded
|
|
61
|
+
* by the `@c9up/blackhole` middleware when CSRF is enabled; `undefined`
|
|
62
|
+
* otherwise. Also published to `ctx.store` for templating helpers.
|
|
63
|
+
*/
|
|
64
|
+
csrfToken?: string
|
|
65
|
+
|
|
66
|
+
constructor(raw: RawRequest, params: Dict = {}) {
|
|
67
|
+
this.#raw = raw
|
|
68
|
+
this.#params = params
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// ─── HTTP accessors ───────────────────────────────────────
|
|
72
|
+
|
|
73
|
+
/** HTTP method (GET, POST, etc.). */
|
|
74
|
+
method(): string {
|
|
75
|
+
return this.#raw.method
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/** Request URL (path + query string). */
|
|
79
|
+
url(includeQs = true): string {
|
|
80
|
+
if (includeQs && this.#raw.query) {
|
|
81
|
+
return `${this.#raw.path}?${this.#raw.query}`
|
|
82
|
+
}
|
|
83
|
+
return this.#raw.path
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/** Request path (without query string). */
|
|
87
|
+
path(): string {
|
|
88
|
+
return this.#raw.path
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Resolved client IP. The HyperServer computes this once per request from
|
|
93
|
+
* the socket peer + `X-Forwarded-For` / `X-Real-IP` headers, gated by the
|
|
94
|
+
* configured trusted-proxy CIDR list, and ships the resolved value on
|
|
95
|
+
* `request.ip`. JS reads that field as-is.
|
|
96
|
+
*
|
|
97
|
+
* When `#raw.ip` is missing — only happens in test fixtures that
|
|
98
|
+
* hand-build `RawRequest` without going through the Rust pipeline — fall
|
|
99
|
+
* back to the socket peer (`remoteAddr`). Do NOT consult `X-Forwarded-For`
|
|
100
|
+
* or `X-Real-IP` here: those headers are attacker-controlled on direct
|
|
101
|
+
* requests and the trusted-proxy gate lives in Rust. Honouring them in
|
|
102
|
+
* the TS fallback would let test fixtures masking real spoofing
|
|
103
|
+
* regressions (two semantics for the same API). Fixtures that need to
|
|
104
|
+
* simulate a proxy-resolved IP must set `#raw.ip` directly to the
|
|
105
|
+
* desired resolved value.
|
|
106
|
+
*/
|
|
107
|
+
ip(): string {
|
|
108
|
+
if (this.#raw.ip) return this.#raw.ip
|
|
109
|
+
return this.#raw.remoteAddr ?? '127.0.0.1'
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Return all cookies sent on this request as a name → value map. The
|
|
114
|
+
* HyperServer parses the `Cookie:` header once via the `cookie` crate
|
|
115
|
+
* (RFC 6265) and ships the result on `RawRequest.cookies`. Test fixtures
|
|
116
|
+
* that don't pre-fill the field fall back to inline parsing of the raw
|
|
117
|
+
* header — same tolerant `;`-split with quoted-value passthrough.
|
|
118
|
+
*/
|
|
119
|
+
cookies(): Dict {
|
|
120
|
+
if (this.#raw.cookies) return { ...this.#raw.cookies }
|
|
121
|
+
const header = this.#raw.headers.cookie
|
|
122
|
+
if (!header) return {}
|
|
123
|
+
const result: Dict = {}
|
|
124
|
+
for (const pair of header.split(';')) {
|
|
125
|
+
const trimmed = pair.trim()
|
|
126
|
+
if (!trimmed) continue
|
|
127
|
+
const eqIdx = trimmed.indexOf('=')
|
|
128
|
+
if (eqIdx === -1) continue
|
|
129
|
+
const k = trimmed.slice(0, eqIdx).trim()
|
|
130
|
+
const v = trimmed.slice(eqIdx + 1)
|
|
131
|
+
// Empty value (`name=`) is valid per RFC 6265 — used in session/logout
|
|
132
|
+
// flows that explicitly clear cookies. Only the key being empty is
|
|
133
|
+
// grounds to skip. Aligns the fallback with the pre-parsed path
|
|
134
|
+
// (HyperServer's cookie crate also accepts empty values).
|
|
135
|
+
if (k) result[k] = v
|
|
136
|
+
}
|
|
137
|
+
return result
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/** Pre-parsed cookie value by name, or `null` when absent. */
|
|
141
|
+
cookie(name: string): string | null {
|
|
142
|
+
const cookies = this.#raw.cookies ?? this.cookies()
|
|
143
|
+
return cookies[name] ?? null
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/** Get the raw body as a string (decoded from base64 if binary). */
|
|
147
|
+
raw(): string {
|
|
148
|
+
if (this.#raw.bodyEncoding === 'base64') {
|
|
149
|
+
return Buffer.from(this.#raw.body, 'base64').toString('utf8')
|
|
150
|
+
}
|
|
151
|
+
return this.#raw.body
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/** Get the raw body as a Buffer (preserves binary integrity for file uploads). */
|
|
155
|
+
rawBuffer(): Buffer {
|
|
156
|
+
if (this.#raw.bodyEncoding === 'base64') {
|
|
157
|
+
return Buffer.from(this.#raw.body, 'base64')
|
|
158
|
+
}
|
|
159
|
+
return Buffer.from(this.#raw.body, 'utf8')
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// ─── Headers ──────────────────────────────────────────────
|
|
163
|
+
|
|
164
|
+
/** Get a single request header (case-insensitive). */
|
|
165
|
+
header(key: string): string | undefined {
|
|
166
|
+
return this.#raw.headers[key.toLowerCase()]
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/** Get all request headers. */
|
|
170
|
+
headers(): Readonly<Dict> {
|
|
171
|
+
return this.#raw.headers
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// ─── Route params ─────────────────────────────────────────
|
|
175
|
+
|
|
176
|
+
/** Get a single route parameter. */
|
|
177
|
+
param(key: string, defaultValue?: string): string | undefined {
|
|
178
|
+
return this.#params[key] ?? defaultValue
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/** Get all route parameters. */
|
|
182
|
+
params(): Readonly<Dict> {
|
|
183
|
+
return this.#params
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// ─── Query string ─────────────────────────────────────────
|
|
187
|
+
|
|
188
|
+
/** Get parsed query string as an object. */
|
|
189
|
+
qs(): Dict<unknown> {
|
|
190
|
+
if (!this.#parsedQs) {
|
|
191
|
+
this.#parsedQs = parseQueryString(this.#raw.query)
|
|
192
|
+
}
|
|
193
|
+
return { ...this.#parsedQs }
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// ─── Body ─────────────────────────────────────────────────
|
|
197
|
+
|
|
198
|
+
/** Get the parsed request body. */
|
|
199
|
+
body(): unknown {
|
|
200
|
+
this.#ensureParsedBody()
|
|
201
|
+
return this.#parsedBody
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// ─── Merged input (body + qs) ─────────────────────────────
|
|
205
|
+
|
|
206
|
+
/** Get a single input value from body or query string. */
|
|
207
|
+
input<T = unknown>(key: string, defaultValue?: T): T {
|
|
208
|
+
const merged = this.all()
|
|
209
|
+
if (key in merged) {
|
|
210
|
+
// biome-ignore lint/suspicious/noExplicitAny: generic accessor — caller brands the value type via T
|
|
211
|
+
return merged[key] as any as T
|
|
212
|
+
}
|
|
213
|
+
// biome-ignore lint/suspicious/noExplicitAny: generic default — caller brands the value type via T
|
|
214
|
+
return defaultValue as any as T
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/** Get all input (query string merged with body). */
|
|
218
|
+
all(): Dict<unknown> {
|
|
219
|
+
if (!this.#merged) {
|
|
220
|
+
this.#ensureParsedBody()
|
|
221
|
+
this.#merged = { ...this.qs(), ...this.#parsedBody }
|
|
222
|
+
}
|
|
223
|
+
return { ...this.#merged }
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/** Cherry-pick specific keys from input. */
|
|
227
|
+
only<K extends string>(keys: K[]): Partial<Record<K, unknown>> {
|
|
228
|
+
const merged = this.all()
|
|
229
|
+
const result: Partial<Record<K, unknown>> = {}
|
|
230
|
+
for (const key of keys) {
|
|
231
|
+
if (key in merged) {
|
|
232
|
+
result[key] = merged[key]
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
return result
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/** Get all input except specific keys. */
|
|
239
|
+
except(keys: string[]): Dict<unknown> {
|
|
240
|
+
const merged = this.all()
|
|
241
|
+
const keySet = new Set(keys)
|
|
242
|
+
const result: Dict<unknown> = {}
|
|
243
|
+
for (const [k, v] of Object.entries(merged)) {
|
|
244
|
+
if (!keySet.has(k)) result[k] = v
|
|
245
|
+
}
|
|
246
|
+
return result
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
// ─── Content negotiation ──────────────────────────────────
|
|
250
|
+
|
|
251
|
+
/** Check if the request content-type matches any of the given types. */
|
|
252
|
+
is(types: string[]): string | null {
|
|
253
|
+
const ct = this.#raw.headers['content-type'] ?? ''
|
|
254
|
+
for (const type of types) {
|
|
255
|
+
if (type === 'json' && ct.includes('application/json')) return 'json'
|
|
256
|
+
if (type === 'html' && ct.includes('text/html')) return 'html'
|
|
257
|
+
if (type === 'xml' && ct.includes('xml')) return 'xml'
|
|
258
|
+
if (type === 'multipart' && ct.includes('multipart/form-data')) return 'multipart'
|
|
259
|
+
if (ct.includes(type)) return type
|
|
260
|
+
}
|
|
261
|
+
return null
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
/**
|
|
265
|
+
* Content negotiation — which of the offered types does the client accept,
|
|
266
|
+
* honouring RFC 7231 §5.3.2 quality values?
|
|
267
|
+
*
|
|
268
|
+
* Walks the parsed Accept header in descending q-order and returns the
|
|
269
|
+
* first server-offered type that matches. The server's preference order
|
|
270
|
+
* (`types` argument) is the tiebreaker only when multiple client entries
|
|
271
|
+
* share the same q. A bare `accepts(types)` therefore respects the
|
|
272
|
+
* client's stated priorities (`application/json;q=0.9, text/html;q=0.1`
|
|
273
|
+
* beats a server passing `['html', 'json']`).
|
|
274
|
+
*
|
|
275
|
+
* Aliases (`'json'` ↔ `application/json`, `'html'` ↔ `text/html`,
|
|
276
|
+
* `'xml'` ↔ any subtype containing `xml`) are honoured.
|
|
277
|
+
*/
|
|
278
|
+
accepts(types: string[]): string | null {
|
|
279
|
+
const header = this.#raw.headers.accept ?? '*/*'
|
|
280
|
+
const entries = parseAcceptHeader(header)
|
|
281
|
+
if (entries.length === 0) return null
|
|
282
|
+
// Group by q so the highest q always wins; within one q-bucket, server
|
|
283
|
+
// offer order is the tiebreaker (devs control that order intentionally).
|
|
284
|
+
const buckets = bucketByQ(entries)
|
|
285
|
+
for (const bucket of buckets) {
|
|
286
|
+
for (const type of types) {
|
|
287
|
+
for (const entry of bucket) {
|
|
288
|
+
if (acceptMatches(entry.value, type)) return type
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
return null
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
/**
|
|
296
|
+
* Language negotiation from Accept-Language honouring RFC 4647 / 7231
|
|
297
|
+
* quality values. Returns the first offered language whose tag (or its
|
|
298
|
+
* primary subtag) matches the highest-q client entry, falling back to
|
|
299
|
+
* `langs[0]` when nothing matches and the client header is empty/absent.
|
|
300
|
+
*/
|
|
301
|
+
language(langs: string[]): string | null {
|
|
302
|
+
const header = this.#raw.headers['accept-language'] ?? ''
|
|
303
|
+
if (!header.trim()) return langs[0] ?? null
|
|
304
|
+
const entries = parseAcceptHeader(header)
|
|
305
|
+
if (entries.length === 0) return null
|
|
306
|
+
const buckets = bucketByQ(entries)
|
|
307
|
+
for (const bucket of buckets) {
|
|
308
|
+
for (const lang of langs) {
|
|
309
|
+
for (const entry of bucket) {
|
|
310
|
+
if (languageMatches(entry.value, lang)) return lang
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
return null
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
// ─── Internals ────────────────────────────────────────────
|
|
318
|
+
|
|
319
|
+
// ─── Files ─────────────────────────────────────────────────
|
|
320
|
+
|
|
321
|
+
/** Get an uploaded file by field name. */
|
|
322
|
+
file(
|
|
323
|
+
fieldName: string,
|
|
324
|
+
options?: import('../bodyparser/MultipartFile.js').FileValidationOptions,
|
|
325
|
+
): import('../bodyparser/MultipartFile.js').MultipartFile | null {
|
|
326
|
+
const files = this.#files.get(fieldName)
|
|
327
|
+
if (!files || files.length === 0) return null
|
|
328
|
+
const file = files[0]
|
|
329
|
+
if (options) file.validate(options)
|
|
330
|
+
return file
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
/** Get all uploaded files for a field name. */
|
|
334
|
+
files(
|
|
335
|
+
fieldName: string,
|
|
336
|
+
options?: import('../bodyparser/MultipartFile.js').FileValidationOptions,
|
|
337
|
+
): import('../bodyparser/MultipartFile.js').MultipartFile[] {
|
|
338
|
+
const files = this.#files.get(fieldName) ?? []
|
|
339
|
+
if (options) {
|
|
340
|
+
for (const f of files) f.validate(options)
|
|
341
|
+
}
|
|
342
|
+
return files
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
/** Get all uploaded files. */
|
|
346
|
+
allFiles(): Map<string, import('../bodyparser/MultipartFile.js').MultipartFile[]> {
|
|
347
|
+
return this.#files
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
/**
|
|
351
|
+
* Pre-parsed `multipart/form-data` payload as the HyperServer wrote it.
|
|
352
|
+
* Returns `undefined` when the request wasn't multipart (or the test
|
|
353
|
+
* fixture didn't fill the field). BodyParser hydrates `MultipartFile`
|
|
354
|
+
* instances from this — application code should keep using
|
|
355
|
+
* `request.file()` / `request.files()`.
|
|
356
|
+
*/
|
|
357
|
+
multipart(): NonNullable<RawRequest['multipart']> | undefined {
|
|
358
|
+
return this.#raw.multipart
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
/** @internal Set files (called by BodyParser middleware). */
|
|
362
|
+
setFiles(files: import('../bodyparser/MultipartFile.js').MultipartFile[]): void {
|
|
363
|
+
this.#files.clear()
|
|
364
|
+
for (const file of files) {
|
|
365
|
+
const existing = this.#files.get(file.fieldName) ?? []
|
|
366
|
+
existing.push(file)
|
|
367
|
+
this.#files.set(file.fieldName, existing)
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
// ─── Internals ────────────────────────────────────────────
|
|
372
|
+
|
|
373
|
+
/** @internal Set the parsed body (called by BodyParser middleware). */
|
|
374
|
+
setParsedBody(body: Dict<unknown>): void {
|
|
375
|
+
this.#parsedBody = body
|
|
376
|
+
this.#merged = undefined // reset merged cache
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
#ensureParsedBody(): void {
|
|
380
|
+
if (this.#parsedBody !== undefined) return
|
|
381
|
+
const raw = this.#raw.body
|
|
382
|
+
if (!raw || raw.length === 0) {
|
|
383
|
+
this.#parsedBody = {}
|
|
384
|
+
return
|
|
385
|
+
}
|
|
386
|
+
try {
|
|
387
|
+
const parsed: unknown = JSON.parse(raw)
|
|
388
|
+
this.#parsedBody = isPlainObject(parsed) ? parsed : { _body: parsed }
|
|
389
|
+
} catch {
|
|
390
|
+
this.#parsedBody = {}
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
/** Parse a query string (key=value&key2=value2) into an object. */
|
|
396
|
+
/**
|
|
397
|
+
* Test whether an IPv4 address is within a CIDR range. Bare IP entries
|
|
398
|
+
* (`10.0.0.42`) are accepted as `/32`. IPv6 falls back to string equality
|
|
399
|
+
* — a full v6 implementation isn't required by the current security tests.
|
|
400
|
+
*/
|
|
401
|
+
/**
|
|
402
|
+
* Type guard for plain JSON-decoded objects. Excludes `null` (which is
|
|
403
|
+
* `typeof === 'object'`) and arrays so consumers can index by string key
|
|
404
|
+
* without a separate runtime check.
|
|
405
|
+
*/
|
|
406
|
+
function isPlainObject(value: unknown): value is Dict<unknown> {
|
|
407
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value)
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
/**
|
|
411
|
+
* Decode a percent-encoded segment without throwing on malformed input.
|
|
412
|
+
* Browsers happily send `q=%E0%A4%A` (truncated UTF-8 sequences) — refusing
|
|
413
|
+
* the whole request would let any client knock the route handler offline.
|
|
414
|
+
*/
|
|
415
|
+
function safeDecode(value: string): string {
|
|
416
|
+
try {
|
|
417
|
+
return decodeURIComponent(value)
|
|
418
|
+
} catch {
|
|
419
|
+
return value
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
function parseQueryString(qs: string): Dict<unknown> {
|
|
424
|
+
if (!qs) return {}
|
|
425
|
+
const result: Dict<unknown> = {}
|
|
426
|
+
for (const pair of qs.split('&')) {
|
|
427
|
+
const eqIdx = pair.indexOf('=')
|
|
428
|
+
if (eqIdx === -1) {
|
|
429
|
+
result[safeDecode(pair)] = ''
|
|
430
|
+
} else {
|
|
431
|
+
const key = safeDecode(pair.slice(0, eqIdx))
|
|
432
|
+
const value = safeDecode(pair.slice(eqIdx + 1))
|
|
433
|
+
result[key] = value
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
return result
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
interface AcceptEntry {
|
|
440
|
+
value: string
|
|
441
|
+
q: number
|
|
442
|
+
index: number
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
/**
|
|
446
|
+
* Parse an Accept-style header (Accept, Accept-Language, Accept-Encoding…)
|
|
447
|
+
* into entries sorted by descending q-value, with the client's original
|
|
448
|
+
* order as a stable tiebreaker. Entries with `q=0` are dropped (RFC 7231:
|
|
449
|
+
* "the value 0 means not acceptable"). A bare wildcard entry is preserved.
|
|
450
|
+
*/
|
|
451
|
+
function parseAcceptHeader(header: string): AcceptEntry[] {
|
|
452
|
+
const entries: AcceptEntry[] = []
|
|
453
|
+
let index = 0
|
|
454
|
+
for (const raw of header.split(',')) {
|
|
455
|
+
const part = raw.trim()
|
|
456
|
+
if (!part) continue
|
|
457
|
+
const [valueRaw, ...params] = part.split(';')
|
|
458
|
+
const value = (valueRaw ?? '').trim().toLowerCase()
|
|
459
|
+
if (!value) continue
|
|
460
|
+
let q = 1
|
|
461
|
+
for (const param of params) {
|
|
462
|
+
const [k, v] = param.split('=')
|
|
463
|
+
if ((k ?? '').trim().toLowerCase() === 'q') {
|
|
464
|
+
const parsed = Number.parseFloat((v ?? '').trim())
|
|
465
|
+
if (Number.isFinite(parsed)) q = parsed
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
if (q <= 0) continue
|
|
469
|
+
entries.push({ value, q, index })
|
|
470
|
+
index += 1
|
|
471
|
+
}
|
|
472
|
+
entries.sort((a, b) => b.q - a.q || a.index - b.index)
|
|
473
|
+
return entries
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
/**
|
|
477
|
+
* Match an Accept entry value (already lowercased) against an offered
|
|
478
|
+
* type. Honours short aliases (`json` ↔ `application/json`, `html` ↔
|
|
479
|
+
* `text/html`, `xml` ↔ any subtype containing `xml`) and the full
|
|
480
|
+
* wildcard (matches anything). A `type/<wildcard>` entry matches any
|
|
481
|
+
* subtype with the same primary type.
|
|
482
|
+
*/
|
|
483
|
+
function acceptMatches(entry: string, offered: string): boolean {
|
|
484
|
+
if (entry === '*/*' || entry === '*') return true
|
|
485
|
+
const o = offered.trim().toLowerCase()
|
|
486
|
+
if (entry === o) return true
|
|
487
|
+
// Expand short aliases to their canonical media-type so the wildcard
|
|
488
|
+
// and exact-match checks below see the full type.
|
|
489
|
+
const expanded = expandAlias(o)
|
|
490
|
+
if (expanded && entry === expanded) return true
|
|
491
|
+
if (o === 'xml' && entry.includes('xml')) return true
|
|
492
|
+
// type/<wildcard> entry (e.g. `text/*` matches `text/html`).
|
|
493
|
+
if (entry.endsWith('/*')) {
|
|
494
|
+
const prefix = entry.slice(0, -1) // keep trailing slash
|
|
495
|
+
if (expanded?.startsWith(prefix)) return true
|
|
496
|
+
return o.startsWith(prefix)
|
|
497
|
+
}
|
|
498
|
+
return false
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
function expandAlias(short: string): string | null {
|
|
502
|
+
if (short === 'json') return 'application/json'
|
|
503
|
+
if (short === 'html') return 'text/html'
|
|
504
|
+
if (short === 'text') return 'text/plain'
|
|
505
|
+
if (short === 'form') return 'application/x-www-form-urlencoded'
|
|
506
|
+
if (short === 'multipart') return 'multipart/form-data'
|
|
507
|
+
return null
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
function bucketByQ<T extends { q: number }>(entries: T[]): T[][] {
|
|
511
|
+
const buckets: T[][] = []
|
|
512
|
+
let current: T[] = []
|
|
513
|
+
let lastQ = Number.POSITIVE_INFINITY
|
|
514
|
+
for (const entry of entries) {
|
|
515
|
+
if (entry.q !== lastQ) {
|
|
516
|
+
if (current.length > 0) buckets.push(current)
|
|
517
|
+
current = []
|
|
518
|
+
lastQ = entry.q
|
|
519
|
+
}
|
|
520
|
+
current.push(entry)
|
|
521
|
+
}
|
|
522
|
+
if (current.length > 0) buckets.push(current)
|
|
523
|
+
return buckets
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
/**
|
|
527
|
+
* Language tag match. Returns true when the Accept-Language entry equals
|
|
528
|
+
* the offered tag (case-insensitive), or when the entry's primary subtag
|
|
529
|
+
* matches the offered tag (RFC 4647 basic filtering: `en` matches `en-US`
|
|
530
|
+
* but not vice versa). Wildcard `*` matches anything.
|
|
531
|
+
*/
|
|
532
|
+
function languageMatches(entry: string, offered: string): boolean {
|
|
533
|
+
if (entry === '*') return true
|
|
534
|
+
const o = offered.trim().toLowerCase()
|
|
535
|
+
if (entry === o) return true
|
|
536
|
+
if (entry.startsWith(`${o}-`)) return true
|
|
537
|
+
const primary = entry.split('-')[0]
|
|
538
|
+
if (primary && primary === o.split('-')[0]) return true
|
|
539
|
+
return false
|
|
540
|
+
}
|