@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,733 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Fluent Router — AdonisJS v6 compatible routing with controllers, resources, and groups.
|
|
3
|
+
*
|
|
4
|
+
* @implements FR24, FR25, FR26, FR27, FR28
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { HttpContext } from '../http/HttpContext.js'
|
|
8
|
+
import type { MiddlewareFunction } from '../middleware/Pipeline.js'
|
|
9
|
+
import type { MiddlewareEntry } from '../server/Server.js'
|
|
10
|
+
import { resolveMiddlewareEntry } from '../server/Server.js'
|
|
11
|
+
|
|
12
|
+
// ─── Types ──────────────────────────────────────────────────
|
|
13
|
+
|
|
14
|
+
export type RouteHandlerFunction = (ctx: HttpContext) => Promise<void> | void
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Controller tuple: [ControllerClass, 'methodName'].
|
|
18
|
+
* Constructor params are resolved by the IoC container, not by TypeScript —
|
|
19
|
+
* same pattern as AdonisJS (@poppinss/utils Constructor type).
|
|
20
|
+
*/
|
|
21
|
+
// biome-ignore lint/suspicious/noExplicitAny: required — TypeScript contravariance makes it impossible to type "any constructor" without `any`
|
|
22
|
+
export type ControllerAction = [target: new (...args: any[]) => any, method: string]
|
|
23
|
+
|
|
24
|
+
/** A route handler is either a closure or a controller tuple. */
|
|
25
|
+
export type RouteHandler = RouteHandlerFunction | ControllerAction
|
|
26
|
+
|
|
27
|
+
/** Param matcher — regex or predefined matcher. */
|
|
28
|
+
export type ParamMatcher = RegExp | { pattern: RegExp }
|
|
29
|
+
|
|
30
|
+
export interface RouteDefinition {
|
|
31
|
+
method: string
|
|
32
|
+
path: string
|
|
33
|
+
handler: RouteHandlerFunction | null
|
|
34
|
+
// biome-ignore lint/suspicious/noExplicitAny: see ControllerAction
|
|
35
|
+
controller?: { target: new (...args: any[]) => any; method: string }
|
|
36
|
+
middleware: string[]
|
|
37
|
+
inlineMiddleware: MiddlewareFunction[]
|
|
38
|
+
guards: string[]
|
|
39
|
+
roles: string[]
|
|
40
|
+
permissions: string[]
|
|
41
|
+
validators: string[]
|
|
42
|
+
name?: string
|
|
43
|
+
version?: string
|
|
44
|
+
domain?: string
|
|
45
|
+
matchers: Record<string, ParamMatcher>
|
|
46
|
+
deprecates?: { version: string; sunset?: string }
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export interface MatchResult {
|
|
50
|
+
route: RouteDefinition
|
|
51
|
+
params: Record<string, string>
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// ─── Matchers ───────────────────────────────────────────────
|
|
55
|
+
|
|
56
|
+
export const matchers = {
|
|
57
|
+
/** Match numeric params only. */
|
|
58
|
+
number(): ParamMatcher {
|
|
59
|
+
return { pattern: /^\d+$/ }
|
|
60
|
+
},
|
|
61
|
+
/** Match UUID v4 params only. */
|
|
62
|
+
uuid(): ParamMatcher {
|
|
63
|
+
return { pattern: /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i }
|
|
64
|
+
},
|
|
65
|
+
/** Match slug params only (lowercase alphanumeric + hyphens). */
|
|
66
|
+
slug(): ParamMatcher {
|
|
67
|
+
return { pattern: /^[a-z0-9]+(?:-[a-z0-9]+)*$/ }
|
|
68
|
+
},
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// ─── RouteBuilder ───────────────────────────────────────────
|
|
72
|
+
|
|
73
|
+
/** Fluent route builder — mutates the already-registered route definition. */
|
|
74
|
+
export class RouteBuilder {
|
|
75
|
+
#route: RouteDefinition
|
|
76
|
+
|
|
77
|
+
constructor(route: RouteDefinition) {
|
|
78
|
+
this.#route = route
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/** Name this route (for URL generation and redirects). */
|
|
82
|
+
as(name: string): this {
|
|
83
|
+
this.#route.name = name
|
|
84
|
+
return this
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/** Add named middleware. */
|
|
88
|
+
middleware(...names: string[]): this {
|
|
89
|
+
this.#route.middleware.push(...names)
|
|
90
|
+
return this
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/** Add inline middleware functions. */
|
|
94
|
+
use(...mw: MiddlewareFunction[]): this {
|
|
95
|
+
this.#route.inlineMiddleware.push(...mw)
|
|
96
|
+
return this
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/** Add authentication guards. */
|
|
100
|
+
guard(...guards: string[]): this {
|
|
101
|
+
this.#route.guards.push(...guards)
|
|
102
|
+
return this
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/** Require specific roles. */
|
|
106
|
+
role(...roles: string[]): this {
|
|
107
|
+
this.#route.roles.push(...roles)
|
|
108
|
+
return this
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/** Require specific permissions. */
|
|
112
|
+
permission(...permissions: string[]): this {
|
|
113
|
+
this.#route.permissions.push(...permissions)
|
|
114
|
+
return this
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/** Add a param constraint. */
|
|
118
|
+
where(param: string, matcher: ParamMatcher | RegExp): this {
|
|
119
|
+
this.#route.matchers[param] = matcher instanceof RegExp ? { pattern: matcher } : matcher
|
|
120
|
+
return this
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/** Attach a validator. */
|
|
124
|
+
validate(validator: string): this {
|
|
125
|
+
this.#route.validators.push(validator)
|
|
126
|
+
return this
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/** Set API version. */
|
|
130
|
+
version(v: string): this {
|
|
131
|
+
this.#route.version = v
|
|
132
|
+
return this
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/** Restrict to a specific domain. */
|
|
136
|
+
domain(d: string): this {
|
|
137
|
+
this.#route.domain = d
|
|
138
|
+
return this
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/** Mark as deprecated. */
|
|
142
|
+
deprecates(version: string, options?: { sunset?: string }): this {
|
|
143
|
+
this.#route.deprecates = { version, sunset: options?.sunset }
|
|
144
|
+
return this
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/** @internal Get the underlying definition. */
|
|
148
|
+
getDefinition(): RouteDefinition {
|
|
149
|
+
return this.#route
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// ─── GroupBuilder ───────────────────────────────────────────
|
|
154
|
+
|
|
155
|
+
/** Fluent group builder — returned by router.group(callback). */
|
|
156
|
+
export class GroupBuilder {
|
|
157
|
+
#routes: RouteDefinition[]
|
|
158
|
+
|
|
159
|
+
constructor(routes: RouteDefinition[]) {
|
|
160
|
+
this.#routes = routes
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/** Set URL prefix for all routes in the group. */
|
|
164
|
+
prefix(p: string): this {
|
|
165
|
+
for (const route of this.#routes) {
|
|
166
|
+
route.path = p + route.path
|
|
167
|
+
}
|
|
168
|
+
return this
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/** Add middleware to all routes in the group. */
|
|
172
|
+
middleware(...names: string[]): this {
|
|
173
|
+
for (const route of this.#routes) {
|
|
174
|
+
route.middleware = [...names, ...route.middleware]
|
|
175
|
+
}
|
|
176
|
+
return this
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/** Add inline middleware to all routes in the group. */
|
|
180
|
+
use(...mw: MiddlewareFunction[]): this {
|
|
181
|
+
for (const route of this.#routes) {
|
|
182
|
+
route.inlineMiddleware = [...mw, ...route.inlineMiddleware]
|
|
183
|
+
}
|
|
184
|
+
return this
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/** Add guards to all routes in the group. */
|
|
188
|
+
guard(...guards: string[]): this {
|
|
189
|
+
for (const route of this.#routes) {
|
|
190
|
+
route.guards = [...guards, ...route.guards]
|
|
191
|
+
}
|
|
192
|
+
return this
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/** Prefix route names for all routes in the group. */
|
|
196
|
+
as(namePrefix: string): this {
|
|
197
|
+
for (const route of this.#routes) {
|
|
198
|
+
if (route.name) {
|
|
199
|
+
route.name = `${namePrefix}.${route.name}`
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
return this
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/** Set domain for all routes in the group. */
|
|
206
|
+
domain(d: string): this {
|
|
207
|
+
for (const route of this.#routes) {
|
|
208
|
+
route.domain = d
|
|
209
|
+
}
|
|
210
|
+
return this
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/** Add param matcher to all routes in the group. */
|
|
214
|
+
where(param: string, matcher: ParamMatcher | RegExp): this {
|
|
215
|
+
const m = matcher instanceof RegExp ? { pattern: matcher } : matcher
|
|
216
|
+
for (const route of this.#routes) {
|
|
217
|
+
if (!(param in route.matchers)) {
|
|
218
|
+
route.matchers[param] = m
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
return this
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// ─── View engine helper ───────────────────────────────────────
|
|
226
|
+
|
|
227
|
+
interface ViewEngine {
|
|
228
|
+
render(name: string, data?: Record<string, unknown>): Promise<string>
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
function isViewEngine(value: unknown): value is ViewEngine {
|
|
232
|
+
return (
|
|
233
|
+
value !== null &&
|
|
234
|
+
typeof value === 'object' &&
|
|
235
|
+
'render' in value &&
|
|
236
|
+
typeof value.render === 'function'
|
|
237
|
+
)
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
// ─── OnRouteBuilder ─────────────────────────────────────────
|
|
241
|
+
|
|
242
|
+
/** Builder for on(path).render(view) and on(path).redirect(target). */
|
|
243
|
+
export class OnRouteBuilder {
|
|
244
|
+
#router: Router
|
|
245
|
+
#path: string
|
|
246
|
+
|
|
247
|
+
constructor(router: Router, path: string) {
|
|
248
|
+
this.#router = router
|
|
249
|
+
this.#path = path
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/** Render a view (requires Photon/view provider). */
|
|
253
|
+
render(view: string, data?: Record<string, unknown>): RouteBuilder {
|
|
254
|
+
return this.#router.get(this.#path, async (ctx) => {
|
|
255
|
+
const raw = ctx.store.get('view')
|
|
256
|
+
if (!isViewEngine(raw)) {
|
|
257
|
+
throw new Error('View engine not configured. Register a view provider (Photon) first.')
|
|
258
|
+
}
|
|
259
|
+
const viewEngine = raw
|
|
260
|
+
const html = await viewEngine.render(view, data)
|
|
261
|
+
ctx.response.type('text/html; charset=utf-8').send(html)
|
|
262
|
+
})
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/** Redirect to a path. */
|
|
266
|
+
redirect(target: string, status = 302): RouteBuilder {
|
|
267
|
+
return this.#router.get(this.#path, async (ctx) => {
|
|
268
|
+
ctx.response.redirect().status(status).toPath(target)
|
|
269
|
+
})
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
/** Redirect to a named route. */
|
|
273
|
+
redirectToRoute(name: string, params?: Record<string, string>, status = 302): RouteBuilder {
|
|
274
|
+
return this.#router.get(this.#path, async (ctx) => {
|
|
275
|
+
ctx.response.redirect().status(status).toRoute(name, params)
|
|
276
|
+
})
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
// ─── Router ─────────────────────────────────────────────────
|
|
281
|
+
|
|
282
|
+
/** Main Router. */
|
|
283
|
+
export class Router {
|
|
284
|
+
#routes: RouteDefinition[] = []
|
|
285
|
+
#globalMatchers: Record<string, ParamMatcher> = {}
|
|
286
|
+
#routerMiddleware: MiddlewareFunction[] = []
|
|
287
|
+
#namedMiddleware: Map<string, MiddlewareFunction> = new Map()
|
|
288
|
+
|
|
289
|
+
/** Index: static routes by "METHOD:path" for O(1) exact match. */
|
|
290
|
+
#staticIndex: Map<string, RouteDefinition> = new Map()
|
|
291
|
+
/** Index: parametric routes grouped by method. */
|
|
292
|
+
#paramIndex: Map<string, RouteDefinition[]> = new Map()
|
|
293
|
+
/** Index: named routes for O(1) URL generation. */
|
|
294
|
+
#nameIndex: Map<string, RouteDefinition> = new Map()
|
|
295
|
+
/** Whether the index needs rebuilding. */
|
|
296
|
+
#indexDirty = true
|
|
297
|
+
|
|
298
|
+
/** Predefined param matchers. */
|
|
299
|
+
readonly matchers = matchers
|
|
300
|
+
|
|
301
|
+
// ─── Router-level middleware (like AdonisJS) ──────────────
|
|
302
|
+
|
|
303
|
+
/**
|
|
304
|
+
* Register router-level middleware (runs on requests with a matched route).
|
|
305
|
+
* router.use([() => import('#middleware/auth_middleware')])
|
|
306
|
+
*/
|
|
307
|
+
use(middleware: MiddlewareEntry[]): this {
|
|
308
|
+
for (const mw of middleware) {
|
|
309
|
+
this.#routerMiddleware.push(resolveMiddlewareEntry(mw))
|
|
310
|
+
}
|
|
311
|
+
return this
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
/**
|
|
315
|
+
* Register named middleware collection.
|
|
316
|
+
* export const middleware = router.named({
|
|
317
|
+
* auth: () => import('#middleware/auth_middleware'),
|
|
318
|
+
* })
|
|
319
|
+
*/
|
|
320
|
+
named(collection: Record<string, MiddlewareEntry>): Record<string, MiddlewareFunction> {
|
|
321
|
+
const resolved: Record<string, MiddlewareFunction> = {}
|
|
322
|
+
for (const [name, mw] of Object.entries(collection)) {
|
|
323
|
+
const fn = resolveMiddlewareEntry(mw)
|
|
324
|
+
this.#namedMiddleware.set(name, fn)
|
|
325
|
+
resolved[name] = fn
|
|
326
|
+
}
|
|
327
|
+
return resolved
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
/** Get the router-level middleware stack. */
|
|
331
|
+
getRouterMiddleware(): MiddlewareFunction[] {
|
|
332
|
+
return [...this.#routerMiddleware]
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
/** Get a named middleware by name. */
|
|
336
|
+
getNamedMiddleware(name: string): MiddlewareFunction | undefined {
|
|
337
|
+
return this.#namedMiddleware.get(name)
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
// ─── Route registration ───────────────────────────────────
|
|
341
|
+
|
|
342
|
+
/** Register a route with any HTTP method. */
|
|
343
|
+
route(method: string, path: string, handler: RouteHandler): RouteBuilder {
|
|
344
|
+
const def = this.#createDefinition(method, path, handler)
|
|
345
|
+
this.#routes.push(def)
|
|
346
|
+
this.#indexDirty = true
|
|
347
|
+
return new RouteBuilder(def)
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
get(path: string, handler: RouteHandler): RouteBuilder {
|
|
351
|
+
return this.route('GET', path, handler)
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
post(path: string, handler: RouteHandler): RouteBuilder {
|
|
355
|
+
return this.route('POST', path, handler)
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
put(path: string, handler: RouteHandler): RouteBuilder {
|
|
359
|
+
return this.route('PUT', path, handler)
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
patch(path: string, handler: RouteHandler): RouteBuilder {
|
|
363
|
+
return this.route('PATCH', path, handler)
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
delete(path: string, handler: RouteHandler): RouteBuilder {
|
|
367
|
+
return this.route('DELETE', path, handler)
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
head(path: string, handler: RouteHandler): RouteBuilder {
|
|
371
|
+
return this.route('HEAD', path, handler)
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
options(path: string, handler: RouteHandler): RouteBuilder {
|
|
375
|
+
return this.route('OPTIONS', path, handler)
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
/** Register a route for all HTTP methods. */
|
|
379
|
+
any(path: string, handler: RouteHandler): RouteBuilder {
|
|
380
|
+
return this.route('*', path, handler)
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
// ─── Resource routes ──────────────────────────────────────
|
|
384
|
+
|
|
385
|
+
/**
|
|
386
|
+
* Register resourceful routes for a controller.
|
|
387
|
+
* Generates: index, store, show, update, destroy
|
|
388
|
+
*
|
|
389
|
+
* Usage:
|
|
390
|
+
* router.resource('posts', PostsController)
|
|
391
|
+
* // GET /posts → PostsController.index
|
|
392
|
+
* // POST /posts → PostsController.store
|
|
393
|
+
* // GET /posts/:id → PostsController.show
|
|
394
|
+
* // PUT /posts/:id → PostsController.update
|
|
395
|
+
* // DELETE /posts/:id → PostsController.destroy
|
|
396
|
+
*/
|
|
397
|
+
// biome-ignore lint/suspicious/noExplicitAny: see ControllerAction
|
|
398
|
+
resource(path: string, controller: new (...args: any[]) => any): GroupBuilder {
|
|
399
|
+
const baseName = path.replace(/\//g, '.')
|
|
400
|
+
const routes: RouteDefinition[] = []
|
|
401
|
+
|
|
402
|
+
const actions: Array<{ method: string; suffix: string; action: string; nameSuffix: string }> = [
|
|
403
|
+
{ method: 'GET', suffix: '', action: 'index', nameSuffix: 'index' },
|
|
404
|
+
{ method: 'POST', suffix: '', action: 'store', nameSuffix: 'store' },
|
|
405
|
+
{ method: 'GET', suffix: '/:id', action: 'show', nameSuffix: 'show' },
|
|
406
|
+
{ method: 'PUT', suffix: '/:id', action: 'update', nameSuffix: 'update' },
|
|
407
|
+
{ method: 'PATCH', suffix: '/:id', action: 'update', nameSuffix: 'update' },
|
|
408
|
+
{ method: 'DELETE', suffix: '/:id', action: 'destroy', nameSuffix: 'destroy' },
|
|
409
|
+
]
|
|
410
|
+
|
|
411
|
+
for (const { method, suffix, action, nameSuffix } of actions) {
|
|
412
|
+
const def = this.#createDefinition(method, `/${path}${suffix}`, [controller, action])
|
|
413
|
+
def.name = `${baseName}.${nameSuffix}`
|
|
414
|
+
routes.push(def)
|
|
415
|
+
this.#routes.push(def)
|
|
416
|
+
}
|
|
417
|
+
this.#indexDirty = true
|
|
418
|
+
|
|
419
|
+
return new GroupBuilder(routes)
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
// ─── Groups ───────────────────────────────────────────────
|
|
423
|
+
|
|
424
|
+
/**
|
|
425
|
+
* Create a route group with shared configuration.
|
|
426
|
+
*
|
|
427
|
+
* AdonisJS-style (chainable):
|
|
428
|
+
* router.group(() => {
|
|
429
|
+
* router.get('/users', [UsersController, 'index'])
|
|
430
|
+
* }).prefix('/api').middleware('auth')
|
|
431
|
+
*
|
|
432
|
+
* Legacy-style (config object):
|
|
433
|
+
* router.group({ prefix: '/api', guards: ['jwt'] }, (r) => {
|
|
434
|
+
* r.get('/users', handler)
|
|
435
|
+
* })
|
|
436
|
+
*/
|
|
437
|
+
group(callback: () => void): GroupBuilder
|
|
438
|
+
group(
|
|
439
|
+
config: {
|
|
440
|
+
prefix?: string
|
|
441
|
+
middleware?: string[]
|
|
442
|
+
guards?: string[]
|
|
443
|
+
roles?: string[]
|
|
444
|
+
permissions?: string[]
|
|
445
|
+
},
|
|
446
|
+
callback: (router: Router) => void,
|
|
447
|
+
): void
|
|
448
|
+
group(
|
|
449
|
+
callbackOrConfig:
|
|
450
|
+
| (() => void)
|
|
451
|
+
| {
|
|
452
|
+
prefix?: string
|
|
453
|
+
middleware?: string[]
|
|
454
|
+
guards?: string[]
|
|
455
|
+
roles?: string[]
|
|
456
|
+
permissions?: string[]
|
|
457
|
+
},
|
|
458
|
+
legacyCallback?: (router: Router) => void,
|
|
459
|
+
): GroupBuilder | undefined {
|
|
460
|
+
if (typeof callbackOrConfig === 'function') {
|
|
461
|
+
// AdonisJS-style: group(() => { ... }).prefix().middleware()
|
|
462
|
+
const snapshot = this.#routes.length
|
|
463
|
+
callbackOrConfig()
|
|
464
|
+
const newRoutes = this.#routes.slice(snapshot)
|
|
465
|
+
return new GroupBuilder(newRoutes)
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
// Legacy-style: group({ prefix, guards }, (r) => { ... })
|
|
469
|
+
const config = callbackOrConfig
|
|
470
|
+
const childRouter = new Router()
|
|
471
|
+
legacyCallback?.(childRouter)
|
|
472
|
+
|
|
473
|
+
for (const route of childRouter.#routes) {
|
|
474
|
+
this.#routes.push({
|
|
475
|
+
...route,
|
|
476
|
+
path: (config.prefix ?? '') + route.path,
|
|
477
|
+
middleware: [...(config.middleware ?? []), ...route.middleware],
|
|
478
|
+
guards: [...(config.guards ?? []), ...route.guards],
|
|
479
|
+
roles: [...(config.roles ?? []), ...route.roles],
|
|
480
|
+
permissions: [...(config.permissions ?? []), ...route.permissions],
|
|
481
|
+
})
|
|
482
|
+
}
|
|
483
|
+
this.#indexDirty = true
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
// ─── On (view/redirect shortcuts) ─────────────────────────
|
|
487
|
+
|
|
488
|
+
/** Create a view/redirect route shortcut. */
|
|
489
|
+
on(path: string): OnRouteBuilder {
|
|
490
|
+
return new OnRouteBuilder(this, path)
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
// ─── Global matchers ──────────────────────────────────────
|
|
494
|
+
|
|
495
|
+
/** Set a global param matcher (applied to all routes). */
|
|
496
|
+
where(param: string, matcher: ParamMatcher | RegExp): this {
|
|
497
|
+
this.#globalMatchers[param] = matcher instanceof RegExp ? { pattern: matcher } : matcher
|
|
498
|
+
return this
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
// ─── Route matching ───────────────────────────────────────
|
|
502
|
+
|
|
503
|
+
/** Build route indexes for fast matching. Called lazily on first match. */
|
|
504
|
+
#buildIndex(): void {
|
|
505
|
+
this.#staticIndex.clear()
|
|
506
|
+
this.#paramIndex.clear()
|
|
507
|
+
this.#nameIndex.clear()
|
|
508
|
+
|
|
509
|
+
for (const route of this.#routes) {
|
|
510
|
+
// Named route index
|
|
511
|
+
if (route.name) {
|
|
512
|
+
this.#nameIndex.set(route.name, route)
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
const isParam = route.path.includes(':') || route.path.includes('*')
|
|
516
|
+
|
|
517
|
+
if (!isParam && !route.domain) {
|
|
518
|
+
// Static route — O(1) exact match
|
|
519
|
+
const key = route.method === '*' ? `*:${route.path}` : `${route.method}:${route.path}`
|
|
520
|
+
this.#staticIndex.set(key, route)
|
|
521
|
+
} else {
|
|
522
|
+
// Parametric route — indexed by method
|
|
523
|
+
const methods =
|
|
524
|
+
route.method === '*'
|
|
525
|
+
? ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'HEAD', 'OPTIONS']
|
|
526
|
+
: [route.method]
|
|
527
|
+
for (const m of methods) {
|
|
528
|
+
if (!this.#paramIndex.has(m)) this.#paramIndex.set(m, [])
|
|
529
|
+
this.#paramIndex.get(m)?.push(route)
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
this.#indexDirty = false
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
/** Find a matching route, extracting :param values and validating matchers. */
|
|
538
|
+
match(method: string, path: string, host?: string): MatchResult | undefined {
|
|
539
|
+
if (this.#indexDirty) this.#buildIndex()
|
|
540
|
+
|
|
541
|
+
const candidates = [path]
|
|
542
|
+
// Trailing-slash normalisation: `/users/` should match `/users`. Done as
|
|
543
|
+
// a fallback (not a primary rewrite) so a route declared as `/users/`
|
|
544
|
+
// still wins its exact match before we try the trimmed form.
|
|
545
|
+
if (path.length > 1 && path.endsWith('/')) {
|
|
546
|
+
candidates.push(path.slice(0, -1))
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
for (const candidatePath of candidates) {
|
|
550
|
+
// 1. Try static exact match (O(1))
|
|
551
|
+
const staticRoute =
|
|
552
|
+
this.#staticIndex.get(`${method}:${candidatePath}`) ??
|
|
553
|
+
this.#staticIndex.get(`*:${candidatePath}`)
|
|
554
|
+
if (staticRoute) {
|
|
555
|
+
if (!staticRoute.domain || !host || matchDomain(staticRoute.domain, host)) {
|
|
556
|
+
return { route: staticRoute, params: {} }
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
// 2. Try parametric routes (only same-method candidates)
|
|
561
|
+
const paramCandidates = this.#paramIndex.get(method) ?? []
|
|
562
|
+
for (const route of paramCandidates) {
|
|
563
|
+
if (route.domain && host && !matchDomain(route.domain, host)) continue
|
|
564
|
+
|
|
565
|
+
const params = matchPath(route.path, candidatePath)
|
|
566
|
+
if (params === null) continue
|
|
567
|
+
|
|
568
|
+
if (!this.#validateMatchers(params, route.matchers)) continue
|
|
569
|
+
if (!this.#validateMatchers(params, this.#globalMatchers)) continue
|
|
570
|
+
|
|
571
|
+
return { route, params }
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
return undefined
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
// ─── URL generation ───────────────────────────────────────
|
|
579
|
+
|
|
580
|
+
/** Generate a URL from a named route. */
|
|
581
|
+
makeUrl(name: string, params?: Record<string, string>): string {
|
|
582
|
+
if (this.#indexDirty) this.#buildIndex()
|
|
583
|
+
const route = this.#nameIndex.get(name)
|
|
584
|
+
if (!route) {
|
|
585
|
+
throw new Error(
|
|
586
|
+
`Route '${name}' not found. Available: ${this.#routes
|
|
587
|
+
.filter((r) => r.name)
|
|
588
|
+
.map((r) => r.name)
|
|
589
|
+
.join(', ')}`,
|
|
590
|
+
)
|
|
591
|
+
}
|
|
592
|
+
let url = route.path
|
|
593
|
+
if (params) {
|
|
594
|
+
for (const [key, value] of Object.entries(params)) {
|
|
595
|
+
url = url
|
|
596
|
+
.replace(`:${key}?`, encodeURIComponent(value))
|
|
597
|
+
.replace(`:${key}`, encodeURIComponent(value))
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
// Strip remaining optional placeholders (`:name?` segments not provided).
|
|
601
|
+
url = url.replace(/\/:[A-Za-z_][\w]*\?/g, '')
|
|
602
|
+
// Any remaining `:foo` is a required param the caller forgot. Surface it
|
|
603
|
+
// as a configuration error rather than emitting a broken URL.
|
|
604
|
+
const missing = url.match(/:[A-Za-z_][\w]*/g)
|
|
605
|
+
if (missing && missing.length > 0) {
|
|
606
|
+
throw new Error(
|
|
607
|
+
`Cannot generate URL for route '${name}': missing params ${missing.join(', ')}`,
|
|
608
|
+
)
|
|
609
|
+
}
|
|
610
|
+
return url
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
// ─── Accessors ────────────────────────────────────────────
|
|
614
|
+
|
|
615
|
+
/** Get all registered routes (for OpenAPI generation, introspection). */
|
|
616
|
+
getRoutes(): RouteDefinition[] {
|
|
617
|
+
return [...this.#routes]
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
/** Clear all registered routes (used by hot-reload). */
|
|
621
|
+
clear(): void {
|
|
622
|
+
this.#routes = []
|
|
623
|
+
this.#indexDirty = true
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
get routeCount(): number {
|
|
627
|
+
return this.#routes.length
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
// ─── Internals ────────────────────────────────────────────
|
|
631
|
+
|
|
632
|
+
#createDefinition(method: string, path: string, handler: RouteHandler): RouteDefinition {
|
|
633
|
+
const def: RouteDefinition = {
|
|
634
|
+
method: method.toUpperCase(),
|
|
635
|
+
path,
|
|
636
|
+
handler: null,
|
|
637
|
+
middleware: [],
|
|
638
|
+
inlineMiddleware: [],
|
|
639
|
+
guards: [],
|
|
640
|
+
roles: [],
|
|
641
|
+
permissions: [],
|
|
642
|
+
validators: [],
|
|
643
|
+
matchers: {},
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
if (Array.isArray(handler)) {
|
|
647
|
+
def.controller = { target: handler[0], method: handler[1] }
|
|
648
|
+
} else {
|
|
649
|
+
def.handler = handler
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
return def
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
#validateMatchers(
|
|
656
|
+
params: Record<string, string>,
|
|
657
|
+
matcherMap: Record<string, ParamMatcher>,
|
|
658
|
+
): boolean {
|
|
659
|
+
for (const [param, value] of Object.entries(params)) {
|
|
660
|
+
const matcher = matcherMap[param]
|
|
661
|
+
if (!matcher) continue
|
|
662
|
+
const regex = matcher instanceof RegExp ? matcher : matcher.pattern
|
|
663
|
+
if (!regex.test(value)) return false
|
|
664
|
+
}
|
|
665
|
+
return true
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
// ─── Path matching ──────────────────────────────────────────
|
|
670
|
+
|
|
671
|
+
/** Match path pattern against actual path, extracting params. Returns null on no match. */
|
|
672
|
+
function matchPath(pattern: string, actual: string): Record<string, string> | null {
|
|
673
|
+
const patternParts = pattern.split('/')
|
|
674
|
+
const actualParts = actual.split('/')
|
|
675
|
+
|
|
676
|
+
// Catch-all wildcard: `/assets/*` greedily swallows the remainder of the
|
|
677
|
+
// path into a `*` param. The wildcard must be the final segment — any
|
|
678
|
+
// pattern after it would never match.
|
|
679
|
+
const wildcardIdx = patternParts.indexOf('*')
|
|
680
|
+
if (wildcardIdx !== -1) {
|
|
681
|
+
if (wildcardIdx !== patternParts.length - 1) return null
|
|
682
|
+
if (actualParts.length < wildcardIdx + 1) return null
|
|
683
|
+
const params: Record<string, string> = {}
|
|
684
|
+
for (let i = 0; i < wildcardIdx; i++) {
|
|
685
|
+
const part = patternParts[i]
|
|
686
|
+
if (part.startsWith(':')) {
|
|
687
|
+
params[part.substring(1)] = actualParts[i]
|
|
688
|
+
} else if (part !== actualParts[i]) {
|
|
689
|
+
return null
|
|
690
|
+
}
|
|
691
|
+
}
|
|
692
|
+
params['*'] = actualParts.slice(wildcardIdx).join('/')
|
|
693
|
+
return params
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
// Count required (non-optional) pattern parts
|
|
697
|
+
const requiredCount = patternParts.filter((p) => !p.endsWith('?')).length
|
|
698
|
+
const maxCount = patternParts.length
|
|
699
|
+
|
|
700
|
+
if (actualParts.length < requiredCount || actualParts.length > maxCount) return null
|
|
701
|
+
|
|
702
|
+
const params: Record<string, string> = {}
|
|
703
|
+
|
|
704
|
+
for (let i = 0; i < patternParts.length; i++) {
|
|
705
|
+
const part = patternParts[i]
|
|
706
|
+
const isOptional = part.endsWith('?')
|
|
707
|
+
|
|
708
|
+
if (part.startsWith(':')) {
|
|
709
|
+
const paramName = isOptional ? part.slice(1, -1) : part.substring(1)
|
|
710
|
+
if (i < actualParts.length) {
|
|
711
|
+
params[paramName] = actualParts[i]
|
|
712
|
+
}
|
|
713
|
+
// Optional param with no actual part — skip (param not set)
|
|
714
|
+
} else if (i < actualParts.length && part !== actualParts[i]) {
|
|
715
|
+
return null
|
|
716
|
+
} else if (i >= actualParts.length && !isOptional) {
|
|
717
|
+
return null
|
|
718
|
+
}
|
|
719
|
+
}
|
|
720
|
+
|
|
721
|
+
return params
|
|
722
|
+
}
|
|
723
|
+
|
|
724
|
+
/** Match domain pattern against actual host. Supports wildcards like *.example.com */
|
|
725
|
+
function matchDomain(pattern: string, host: string): boolean {
|
|
726
|
+
const actualHost = host.split(':')[0] // strip port
|
|
727
|
+
if (pattern === actualHost) return true
|
|
728
|
+
if (pattern.startsWith('*.')) {
|
|
729
|
+
const suffix = pattern.slice(1) // ".example.com"
|
|
730
|
+
return actualHost.endsWith(suffix) && actualHost.length > suffix.length
|
|
731
|
+
}
|
|
732
|
+
return false
|
|
733
|
+
}
|