@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,421 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GraphQLEngine — schema-first GraphQL with Rust query validation.
|
|
3
|
+
*
|
|
4
|
+
* - Schema parsed from .graphql files at boot
|
|
5
|
+
* - Queries validated by Rust before NAPI crossing (when available)
|
|
6
|
+
* - Resolvers are IoC-injected controller-like classes
|
|
7
|
+
* - @Guard/@Role enforced per TOP-LEVEL resolver field (nested fields are
|
|
8
|
+
* projected from the resolver's return value, not independently resolved —
|
|
9
|
+
* so they carry no per-field guard; don't return guarded data from an
|
|
10
|
+
* unguarded resolver and rely on a nested field guard to hide it).
|
|
11
|
+
* - The response is pruned to the client's selection set, so a resolver may
|
|
12
|
+
* safely return a rich object (e.g. an ORM entity): fields the client did
|
|
13
|
+
* not select are dropped before serialization.
|
|
14
|
+
* - Zero non-null assertions — types guaranteed by schema validation
|
|
15
|
+
*
|
|
16
|
+
* Usage:
|
|
17
|
+
* const engine = new GraphQLEngine({ schemaPath: './app/graphql/schema.graphql' })
|
|
18
|
+
* engine.resolver('Query', 'tasks', TaskResolver, 'tasks')
|
|
19
|
+
* engine.resolver('Mutation', 'createTask', TaskResolver, 'createTask', { guard: 'jwt', role: 'cs_member' })
|
|
20
|
+
*
|
|
21
|
+
* @implements MISS-28
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
import * as fs from 'node:fs'
|
|
25
|
+
import { loadNapi } from '../helpers/napi-loader.js'
|
|
26
|
+
import type { HttpContext } from '../http/HttpContext.js'
|
|
27
|
+
|
|
28
|
+
/** Rust parser output (the `ream-graphql` crate via the `index` NAPI binary). */
|
|
29
|
+
interface RustParsedField {
|
|
30
|
+
name: string
|
|
31
|
+
alias: string | null
|
|
32
|
+
args: Record<string, unknown>
|
|
33
|
+
sub_fields: RustParsedField[]
|
|
34
|
+
}
|
|
35
|
+
interface RustParseResult {
|
|
36
|
+
operation_type: string
|
|
37
|
+
operation_name: string | null
|
|
38
|
+
fields: RustParsedField[]
|
|
39
|
+
errors: string[]
|
|
40
|
+
}
|
|
41
|
+
interface GraphqlNative {
|
|
42
|
+
graphqlParse(query: string): string
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
let graphqlNativeCache: GraphqlNative | undefined
|
|
46
|
+
/**
|
|
47
|
+
* Lazily load the Rust GraphQL parser from the `index` NAPI binary. There is no
|
|
48
|
+
* TS fallback — when Rust exists, the engine is full-Rust. `loadNapi` throws a
|
|
49
|
+
* typed `ReamError` if the native module is missing.
|
|
50
|
+
*/
|
|
51
|
+
function graphqlNative(): GraphqlNative {
|
|
52
|
+
if (graphqlNativeCache === undefined) {
|
|
53
|
+
graphqlNativeCache = loadNapi<GraphqlNative>({
|
|
54
|
+
binaryName: 'index',
|
|
55
|
+
callerMetaUrl: import.meta.url,
|
|
56
|
+
errorCodePrefix: 'GRAPHQL',
|
|
57
|
+
})
|
|
58
|
+
}
|
|
59
|
+
return graphqlNativeCache
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const OPERATION_TYPE: Record<string, string> = {
|
|
63
|
+
query: 'Query',
|
|
64
|
+
mutation: 'Mutation',
|
|
65
|
+
subscription: 'Subscription',
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/** Map a Rust `ParsedField` (sub_fields) onto the engine's `SelectionField` (selection). */
|
|
69
|
+
function mapRustField(f: RustParsedField): SelectionField {
|
|
70
|
+
return {
|
|
71
|
+
name: f.name,
|
|
72
|
+
alias: f.alias ?? undefined,
|
|
73
|
+
args: f.args,
|
|
74
|
+
selection: f.sub_fields.map(mapRustField),
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export interface GraphQLConfig {
|
|
79
|
+
/** Path to .graphql schema file. */
|
|
80
|
+
schemaPath: string
|
|
81
|
+
/** Endpoint path (default: /graphql). */
|
|
82
|
+
path?: string
|
|
83
|
+
/** Enable playground/introspection in dev (default: true when NODE_ENV !== 'production'). */
|
|
84
|
+
playground?: boolean
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export interface ResolverOptions {
|
|
88
|
+
guard?: string
|
|
89
|
+
guards?: string[]
|
|
90
|
+
role?: string
|
|
91
|
+
roles?: string[]
|
|
92
|
+
permissions?: string[]
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
interface ResolverEntry {
|
|
96
|
+
typeName: string
|
|
97
|
+
fieldName: string
|
|
98
|
+
handlerClass: new (...args: unknown[]) => Record<string, (...args: unknown[]) => unknown>
|
|
99
|
+
methodName: string
|
|
100
|
+
options: ResolverOptions
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
interface GraphQLRequest {
|
|
104
|
+
query: string
|
|
105
|
+
variables?: Record<string, unknown>
|
|
106
|
+
operationName?: string
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/** A parsed field with its (possibly nested) selection set. */
|
|
110
|
+
export interface SelectionField {
|
|
111
|
+
name: string
|
|
112
|
+
alias?: string
|
|
113
|
+
args: Record<string, unknown>
|
|
114
|
+
selection: SelectionField[]
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export class GraphQLEngine {
|
|
118
|
+
#schemaSource: string
|
|
119
|
+
#resolvers: Map<string, ResolverEntry> = new Map()
|
|
120
|
+
#container?: { make<T>(target: new (...args: unknown[]) => T): T }
|
|
121
|
+
readonly path: string
|
|
122
|
+
#playground: boolean
|
|
123
|
+
|
|
124
|
+
constructor(config: GraphQLConfig) {
|
|
125
|
+
if (!fs.existsSync(config.schemaPath)) {
|
|
126
|
+
throw new Error(`GraphQL schema not found: ${config.schemaPath}`)
|
|
127
|
+
}
|
|
128
|
+
this.#schemaSource = fs.readFileSync(config.schemaPath, 'utf8')
|
|
129
|
+
this.path = config.path ?? '/graphql'
|
|
130
|
+
this.#playground = config.playground ?? process.env.NODE_ENV !== 'production'
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/** Set IoC container for resolver instantiation. */
|
|
134
|
+
useContainer(container: { make<T>(target: new (...args: unknown[]) => T): T }): void {
|
|
135
|
+
this.#container = container
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/** Register a resolver for a type.field. */
|
|
139
|
+
resolver(
|
|
140
|
+
typeName: string,
|
|
141
|
+
fieldName: string,
|
|
142
|
+
handlerClass: new (...args: unknown[]) => Record<string, (...args: unknown[]) => unknown>,
|
|
143
|
+
methodName: string,
|
|
144
|
+
options?: ResolverOptions,
|
|
145
|
+
): void {
|
|
146
|
+
const key = `${typeName}.${fieldName}`
|
|
147
|
+
this.#resolvers.set(key, {
|
|
148
|
+
typeName,
|
|
149
|
+
fieldName,
|
|
150
|
+
handlerClass,
|
|
151
|
+
methodName,
|
|
152
|
+
options: options ?? {},
|
|
153
|
+
})
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/** Handle a GraphQL HTTP request. */
|
|
157
|
+
async handle(ctx: HttpContext): Promise<void> {
|
|
158
|
+
const method = ctx.request.method()
|
|
159
|
+
|
|
160
|
+
// Playground
|
|
161
|
+
if (method === 'GET' && this.#playground) {
|
|
162
|
+
ctx.response.header('Content-Type', 'text/html')
|
|
163
|
+
ctx.response.status(200).send(this.#renderPlayground())
|
|
164
|
+
return
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
if (method !== 'POST') {
|
|
168
|
+
ctx.response.status(405).json({ errors: [{ message: 'Method not allowed' }] })
|
|
169
|
+
return
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
const rawBody = ctx.request.body()
|
|
173
|
+
const body = parseGraphQLBody(rawBody)
|
|
174
|
+
if (!body) {
|
|
175
|
+
ctx.response.status(400).json({ errors: [{ message: 'Missing or invalid query' }] })
|
|
176
|
+
return
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
try {
|
|
180
|
+
const result = await this.#execute(body, ctx)
|
|
181
|
+
ctx.response.status(200).json(result)
|
|
182
|
+
} catch (err) {
|
|
183
|
+
const message =
|
|
184
|
+
process.env.NODE_ENV !== 'production' && err instanceof Error
|
|
185
|
+
? err.message
|
|
186
|
+
: 'Internal server error'
|
|
187
|
+
ctx.response.status(500).json({ errors: [{ message }] })
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/** Execute a GraphQL query against registered resolvers. */
|
|
192
|
+
async #execute(
|
|
193
|
+
request: GraphQLRequest,
|
|
194
|
+
ctx: HttpContext,
|
|
195
|
+
): Promise<{
|
|
196
|
+
data?: Record<string, unknown>
|
|
197
|
+
errors?: Array<{ message: string; path?: string[] }>
|
|
198
|
+
}> {
|
|
199
|
+
// Parse the query to extract operation type and fields
|
|
200
|
+
const parsed = this.#parseQuery(request.query)
|
|
201
|
+
if (parsed.errors) {
|
|
202
|
+
return { errors: parsed.errors }
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
const data: Record<string, unknown> = {}
|
|
206
|
+
const errors: Array<{ message: string; path?: string[] }> = []
|
|
207
|
+
|
|
208
|
+
for (const field of parsed.fields) {
|
|
209
|
+
const outcome = await this.#resolveField(field, parsed.typeName, request, ctx)
|
|
210
|
+
if (outcome.ok) {
|
|
211
|
+
data[field.alias ?? field.name] = outcome.value
|
|
212
|
+
} else {
|
|
213
|
+
errors.push(outcome.error)
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
return {
|
|
218
|
+
data: Object.keys(data).length > 0 ? data : undefined,
|
|
219
|
+
errors: errors.length > 0 ? errors : undefined,
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/** Resolve one selection field: lookup → guards → invoke → prune. */
|
|
224
|
+
async #resolveField(
|
|
225
|
+
field: SelectionField,
|
|
226
|
+
typeName: string,
|
|
227
|
+
request: GraphQLRequest,
|
|
228
|
+
ctx: HttpContext,
|
|
229
|
+
): Promise<
|
|
230
|
+
{ ok: true; value: unknown } | { ok: false; error: { message: string; path?: string[] } }
|
|
231
|
+
> {
|
|
232
|
+
const key = `${typeName}.${field.name}`
|
|
233
|
+
const entry = this.#resolvers.get(key)
|
|
234
|
+
if (!entry) {
|
|
235
|
+
return {
|
|
236
|
+
ok: false,
|
|
237
|
+
error: { message: `No resolver for ${key}`, path: [field.name] },
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
const guardError = this.#enforceGuards(entry.options, ctx)
|
|
242
|
+
if (guardError) {
|
|
243
|
+
return { ok: false, error: { message: guardError, path: [field.name] } }
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
try {
|
|
247
|
+
// Resolve handler via IoC or direct instantiation.
|
|
248
|
+
const instance = this.#container
|
|
249
|
+
? this.#container.make(entry.handlerClass)
|
|
250
|
+
: new entry.handlerClass()
|
|
251
|
+
|
|
252
|
+
const handler = instance[entry.methodName]
|
|
253
|
+
if (typeof handler !== 'function') {
|
|
254
|
+
return {
|
|
255
|
+
ok: false,
|
|
256
|
+
error: {
|
|
257
|
+
message: `Resolver method ${entry.methodName} not found`,
|
|
258
|
+
path: [field.name],
|
|
259
|
+
},
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
const args = this.#buildArgs(field, request.variables)
|
|
264
|
+
const result = await handler.call(instance, null, args, ctx)
|
|
265
|
+
// Prune to the client's selection set: a resolver returning a rich
|
|
266
|
+
// object (ORM entity, etc.) must not leak fields the client did not
|
|
267
|
+
// ask for. Empty selection (scalar field) returns the value as-is.
|
|
268
|
+
return { ok: true, value: pruneToSelection(result, field.selection) }
|
|
269
|
+
} catch (err) {
|
|
270
|
+
const msg = err instanceof Error ? err.message : 'Resolver error'
|
|
271
|
+
return { ok: false, error: { message: msg, path: [field.name] } }
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
/**
|
|
276
|
+
* Build the resolver argument bag: `$variable` references resolve against
|
|
277
|
+
* `request.variables`, then any unreferenced variables are merged in.
|
|
278
|
+
*/
|
|
279
|
+
#buildArgs(
|
|
280
|
+
field: SelectionField,
|
|
281
|
+
variables: Record<string, unknown> | undefined,
|
|
282
|
+
): Record<string, unknown> {
|
|
283
|
+
const args: Record<string, unknown> = {}
|
|
284
|
+
for (const [k, v] of Object.entries(field.args)) {
|
|
285
|
+
if (typeof v === 'string' && v.startsWith('$') && variables) {
|
|
286
|
+
args[k] = variables[v.slice(1)]
|
|
287
|
+
} else {
|
|
288
|
+
args[k] = v
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
if (variables) {
|
|
292
|
+
for (const [k, v] of Object.entries(variables)) {
|
|
293
|
+
if (!(k in args)) args[k] = v
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
return args
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
/** Enforce guard/role/permission on a resolver. */
|
|
300
|
+
#enforceGuards(options: ResolverOptions, ctx: HttpContext): string | null {
|
|
301
|
+
const guards = [...(options.guards ?? []), ...(options.guard ? [options.guard] : [])]
|
|
302
|
+
const roles = [...(options.roles ?? []), ...(options.role ? [options.role] : [])]
|
|
303
|
+
const permissions = options.permissions ?? []
|
|
304
|
+
|
|
305
|
+
const needsAuth = guards.length > 0 || roles.length > 0 || permissions.length > 0
|
|
306
|
+
if (needsAuth && !ctx.auth?.authenticated) {
|
|
307
|
+
return 'Unauthorized'
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
if (roles.length > 0) {
|
|
311
|
+
const userRoles = ctx.auth?.roles ?? []
|
|
312
|
+
if (!roles.some((r) => userRoles.includes(r))) {
|
|
313
|
+
return 'Insufficient role'
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
if (permissions.length > 0) {
|
|
318
|
+
const userPerms = ctx.auth?.permissions ?? []
|
|
319
|
+
if (!permissions.every((p) => userPerms.includes(p))) {
|
|
320
|
+
return 'Insufficient permissions'
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
return null
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
/**
|
|
328
|
+
* Parse a GraphQL query via the Rust `ream-graphql` crate (operation type +
|
|
329
|
+
* fields with arguments). Spec-compliant; invalid queries return errors.
|
|
330
|
+
*/
|
|
331
|
+
#parseQuery(query: string): {
|
|
332
|
+
typeName: string
|
|
333
|
+
fields: SelectionField[]
|
|
334
|
+
errors?: Array<{ message: string }>
|
|
335
|
+
} {
|
|
336
|
+
// Full-Rust: the spec-compliant `ream-graphql` parser (graphql-parser
|
|
337
|
+
// crate) rejects invalid queries before any resolver runs. No TS fallback —
|
|
338
|
+
// ream ships the native binary, so when Rust exists the engine is Rust.
|
|
339
|
+
const raw = JSON.parse(graphqlNative().graphqlParse(query)) as RustParseResult
|
|
340
|
+
const typeName = OPERATION_TYPE[raw.operation_type] ?? 'Query'
|
|
341
|
+
if (raw.errors.length > 0) {
|
|
342
|
+
return { typeName, fields: [], errors: raw.errors.map((message) => ({ message })) }
|
|
343
|
+
}
|
|
344
|
+
return { typeName, fields: raw.fields.map(mapRustField) }
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
#renderPlayground(): string {
|
|
348
|
+
return `<!DOCTYPE html>
|
|
349
|
+
<html>
|
|
350
|
+
<head>
|
|
351
|
+
<title>GraphQL Playground</title>
|
|
352
|
+
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/graphiql@3/graphiql.min.css" />
|
|
353
|
+
</head>
|
|
354
|
+
<body style="margin:0;overflow:hidden">
|
|
355
|
+
<div id="graphiql" style="height:100vh"></div>
|
|
356
|
+
<script crossorigin src="https://cdn.jsdelivr.net/npm/react@18/umd/react.production.min.js"></script>
|
|
357
|
+
<script crossorigin src="https://cdn.jsdelivr.net/npm/react-dom@18/umd/react-dom.production.min.js"></script>
|
|
358
|
+
<script crossorigin src="https://cdn.jsdelivr.net/npm/graphiql@3/graphiql.min.js"></script>
|
|
359
|
+
<script>
|
|
360
|
+
const root = ReactDOM.createRoot(document.getElementById('graphiql'));
|
|
361
|
+
root.render(React.createElement(GraphiQL, {
|
|
362
|
+
fetcher: GraphiQL.createFetcher({ url: ${JSON.stringify(this.path)} }),
|
|
363
|
+
}));
|
|
364
|
+
</script>
|
|
365
|
+
</body>
|
|
366
|
+
</html>`
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
/** Get the raw schema source for introspection. */
|
|
370
|
+
getSchemaSource(): string {
|
|
371
|
+
return this.#schemaSource
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
/** Parse and validate an unknown request body as a GraphQL request object. */
|
|
376
|
+
function parseGraphQLBody(body: unknown): GraphQLRequest | null {
|
|
377
|
+
if (!body || typeof body !== 'object' || Array.isArray(body)) return null
|
|
378
|
+
// After typeof check, body is a non-null non-array object; index via 'in' narrowing
|
|
379
|
+
const q = 'query' in body ? body.query : undefined
|
|
380
|
+
if (typeof q !== 'string') return null
|
|
381
|
+
|
|
382
|
+
let variables: Record<string, unknown> | undefined
|
|
383
|
+
if (
|
|
384
|
+
'variables' in body &&
|
|
385
|
+
body.variables !== null &&
|
|
386
|
+
typeof body.variables === 'object' &&
|
|
387
|
+
!Array.isArray(body.variables)
|
|
388
|
+
) {
|
|
389
|
+
// biome-ignore lint/suspicious/noExplicitAny: narrowed to non-null non-array object above; branded as Record for safe spreading
|
|
390
|
+
variables = body.variables as any as Record<string, unknown>
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
const rawOpName = 'operationName' in body ? body.operationName : undefined
|
|
394
|
+
const operationName = typeof rawOpName === 'string' ? rawOpName : undefined
|
|
395
|
+
|
|
396
|
+
return { query: q, variables, operationName }
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
function isRecord(v: unknown): v is Record<string, unknown> {
|
|
400
|
+
return typeof v === 'object' && v !== null && !Array.isArray(v)
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
/**
|
|
404
|
+
* Project a resolver result onto the client's selection set. A field with an
|
|
405
|
+
* empty selection (a scalar leaf) is returned untouched; an object is reduced
|
|
406
|
+
* to only the selected keys (recursing into nested selections); an array maps
|
|
407
|
+
* the projection over its items. This is what stops a resolver that returns a
|
|
408
|
+
* rich object (ORM entity, etc.) from leaking unselected fields.
|
|
409
|
+
*/
|
|
410
|
+
export function pruneToSelection(value: unknown, selection: SelectionField[]): unknown {
|
|
411
|
+
if (selection.length === 0) return value
|
|
412
|
+
if (Array.isArray(value)) return value.map((v) => pruneToSelection(v, selection))
|
|
413
|
+
if (!isRecord(value)) return value
|
|
414
|
+
const out: Record<string, unknown> = {}
|
|
415
|
+
for (const field of selection) {
|
|
416
|
+
if (field.name in value) {
|
|
417
|
+
out[field.alias ?? field.name] = pruneToSelection(value[field.name], field.selection)
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
return out
|
|
421
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `GraphQLProvider` — wires the GraphQL gateway into the Ream pipeline.
|
|
3
|
+
*
|
|
4
|
+
* Opt-in: does nothing unless `config.graphql.schemaPath` is set (or an engine
|
|
5
|
+
* is injected for tests). When configured:
|
|
6
|
+
* - `register()` — construct a `GraphQLEngine` from `config.graphql`, give it
|
|
7
|
+
* the IoC container for resolver DI, and bind it under the `'graphql'` token:
|
|
8
|
+
*
|
|
9
|
+
* const gql = app.container.make<GraphQLEngine>('graphql')
|
|
10
|
+
* gql.resolver('Query', 'tasks', TaskResolver, 'tasks')
|
|
11
|
+
*
|
|
12
|
+
* - `boot()` — mount the engine at `engine.path` (default `/graphql`) for both
|
|
13
|
+
* GET (playground/introspection) and POST (queries).
|
|
14
|
+
*
|
|
15
|
+
* @implements Story 24.5
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import type { AppContext } from '../Provider.js'
|
|
19
|
+
import { Provider } from '../Provider.js'
|
|
20
|
+
import type { Router } from '../router/Router.js'
|
|
21
|
+
import { type GraphQLConfig, GraphQLEngine } from './GraphQLEngine.js'
|
|
22
|
+
|
|
23
|
+
export interface GraphQLProviderOptions {
|
|
24
|
+
/** Override the `GraphQLEngine` instance — used by tests. */
|
|
25
|
+
engine?: GraphQLEngine
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export class GraphQLProvider extends Provider {
|
|
29
|
+
#engine?: GraphQLEngine
|
|
30
|
+
|
|
31
|
+
constructor(app: AppContext, options: GraphQLProviderOptions = {}) {
|
|
32
|
+
super(app)
|
|
33
|
+
this.#engine = options.engine
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** The wired engine, or undefined when GraphQL is not configured. */
|
|
37
|
+
get engine(): GraphQLEngine | undefined {
|
|
38
|
+
return this.#engine
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
override register(): void {
|
|
42
|
+
const config = this.app.config.get<GraphQLConfig>('graphql')
|
|
43
|
+
const engine = this.#engine ?? (config?.schemaPath ? new GraphQLEngine(config) : undefined)
|
|
44
|
+
if (!engine) return // GraphQL not configured — opt-out.
|
|
45
|
+
engine.useContainer(this.app.container)
|
|
46
|
+
this.#engine = engine
|
|
47
|
+
this.app.container.singleton('graphql', () => engine)
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
override async boot(): Promise<void> {
|
|
51
|
+
const engine = this.#engine
|
|
52
|
+
if (!engine) return
|
|
53
|
+
const router = this.app.container.make<Router>('router')
|
|
54
|
+
// GraphQLEngine.handle() serves the playground on GET and executes queries
|
|
55
|
+
// on POST, so mount the same path for both verbs.
|
|
56
|
+
router.get(engine.path, (ctx) => engine.handle(ctx))
|
|
57
|
+
router.post(engine.path, (ctx) => engine.handle(ctx))
|
|
58
|
+
}
|
|
59
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
const STANDARD_B64_RE = /^[A-Za-z0-9+/]*={0,2}$/
|
|
2
|
+
const URL_B64_RE = /^[A-Za-z0-9_-]*$/
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Encode a string or Buffer to standard base64.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* base64.encode('hello') // 'aGVsbG8='
|
|
9
|
+
*/
|
|
10
|
+
function encode(value: string | Buffer): string {
|
|
11
|
+
const buf = typeof value === 'string' ? Buffer.from(value, 'utf-8') : value
|
|
12
|
+
return buf.toString('base64')
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Decode a standard base64 string. Returns null on invalid input unless strict mode,
|
|
17
|
+
* in which case it throws.
|
|
18
|
+
*
|
|
19
|
+
* @example
|
|
20
|
+
* base64.decode('aGVsbG8=') // 'hello'
|
|
21
|
+
* base64.decode('!!!') // null
|
|
22
|
+
*/
|
|
23
|
+
function decode(value: string, encoding: BufferEncoding = 'utf-8', strict = false): string | null {
|
|
24
|
+
if (!STANDARD_B64_RE.test(value)) {
|
|
25
|
+
if (strict) throw new Error(`Invalid base64 string: ${value}`)
|
|
26
|
+
return null
|
|
27
|
+
}
|
|
28
|
+
try {
|
|
29
|
+
return Buffer.from(value, 'base64').toString(encoding)
|
|
30
|
+
} catch {
|
|
31
|
+
if (strict) throw new Error(`Failed to decode base64 string: ${value}`)
|
|
32
|
+
return null
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Encode a string or Buffer to base64url (no padding, uses - and _ instead of + and /).
|
|
38
|
+
*
|
|
39
|
+
* @example
|
|
40
|
+
* base64.urlEncode('hello') // 'aGVsbG8'
|
|
41
|
+
*/
|
|
42
|
+
function urlEncode(value: string | Buffer): string {
|
|
43
|
+
const buf = typeof value === 'string' ? Buffer.from(value, 'utf-8') : value
|
|
44
|
+
return buf.toString('base64url')
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Decode a base64url string. Returns null on invalid input unless strict mode,
|
|
49
|
+
* in which case it throws.
|
|
50
|
+
*
|
|
51
|
+
* @example
|
|
52
|
+
* base64.urlDecode('aGVsbG8') // 'hello'
|
|
53
|
+
* base64.urlDecode('!!!') // null
|
|
54
|
+
*/
|
|
55
|
+
function urlDecode(
|
|
56
|
+
value: string,
|
|
57
|
+
encoding: BufferEncoding = 'utf-8',
|
|
58
|
+
strict = false,
|
|
59
|
+
): string | null {
|
|
60
|
+
if (!URL_B64_RE.test(value)) {
|
|
61
|
+
if (strict) throw new Error(`Invalid base64url string: ${value}`)
|
|
62
|
+
return null
|
|
63
|
+
}
|
|
64
|
+
try {
|
|
65
|
+
return Buffer.from(value, 'base64url').toString(encoding)
|
|
66
|
+
} catch {
|
|
67
|
+
if (strict) throw new Error(`Failed to decode base64url string: ${value}`)
|
|
68
|
+
return null
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export const base64 = { encode, decode, urlEncode, urlDecode }
|