imposters 0.1.0
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/Program/package.json +6 -0
- package/README.md +365 -0
- package/api/AdminApi/package.json +6 -0
- package/api/ApiErrors/package.json +6 -0
- package/api/ApiSchemas/package.json +6 -0
- package/api/Conversions/package.json +6 -0
- package/api/ImpostersGroup/package.json +6 -0
- package/api/ImpostersHandlers/package.json +6 -0
- package/api/SystemGroup/package.json +6 -0
- package/api/SystemHandlers/package.json +6 -0
- package/bin/imposters +47 -0
- package/cli/Commands/package.json +6 -0
- package/cli/ConfigLoader/package.json +6 -0
- package/client/HandlerHttpClient/package.json +6 -0
- package/client/ImpostersClient/package.json +6 -0
- package/client/testing/package.json +6 -0
- package/dist/cjs/Program.js +4 -0
- package/dist/cjs/Program.js.map +1 -0
- package/dist/cjs/api/AdminApi.js +11 -0
- package/dist/cjs/api/AdminApi.js.map +1 -0
- package/dist/cjs/api/ApiErrors.js +30 -0
- package/dist/cjs/api/ApiErrors.js.map +1 -0
- package/dist/cjs/api/ApiSchemas.js +36 -0
- package/dist/cjs/api/ApiSchemas.js.map +1 -0
- package/dist/cjs/api/Conversions.js +41 -0
- package/dist/cjs/api/Conversions.js.map +1 -0
- package/dist/cjs/api/ImpostersGroup.js +37 -0
- package/dist/cjs/api/ImpostersGroup.js.map +1 -0
- package/dist/cjs/api/ImpostersHandlers.js +361 -0
- package/dist/cjs/api/ImpostersHandlers.js.map +1 -0
- package/dist/cjs/api/SystemGroup.js +12 -0
- package/dist/cjs/api/SystemGroup.js.map +1 -0
- package/dist/cjs/api/SystemHandlers.js +74 -0
- package/dist/cjs/api/SystemHandlers.js.map +1 -0
- package/dist/cjs/cli/Commands.js +104 -0
- package/dist/cjs/cli/Commands.js.map +1 -0
- package/dist/cjs/cli/ConfigLoader.js +34 -0
- package/dist/cjs/cli/ConfigLoader.js.map +1 -0
- package/dist/cjs/client/HandlerHttpClient.js +50 -0
- package/dist/cjs/client/HandlerHttpClient.js.map +1 -0
- package/dist/cjs/client/ImpostersClient.js +20 -0
- package/dist/cjs/client/ImpostersClient.js.map +1 -0
- package/dist/cjs/client/index.js +57 -0
- package/dist/cjs/client/index.js.map +1 -0
- package/dist/cjs/client/testing.js +94 -0
- package/dist/cjs/client/testing.js.map +1 -0
- package/dist/cjs/domain/imposter.js +125 -0
- package/dist/cjs/domain/imposter.js.map +1 -0
- package/dist/cjs/domain/route.js +185 -0
- package/dist/cjs/domain/route.js.map +1 -0
- package/dist/cjs/index.js +106 -0
- package/dist/cjs/index.js.map +1 -0
- package/dist/cjs/layers/ApiLayer.js +18 -0
- package/dist/cjs/layers/ApiLayer.js.map +1 -0
- package/dist/cjs/layers/MainLayer.js +27 -0
- package/dist/cjs/layers/MainLayer.js.map +1 -0
- package/dist/cjs/matching/ExpressionEvaluator.js +103 -0
- package/dist/cjs/matching/ExpressionEvaluator.js.map +1 -0
- package/dist/cjs/matching/RequestMatcher.js +145 -0
- package/dist/cjs/matching/RequestMatcher.js.map +1 -0
- package/dist/cjs/matching/ResponseGenerator.js +80 -0
- package/dist/cjs/matching/ResponseGenerator.js.map +1 -0
- package/dist/cjs/matching/TemplateEngine.js +55 -0
- package/dist/cjs/matching/TemplateEngine.js.map +1 -0
- package/dist/cjs/repositories/ImposterRepository.js +118 -0
- package/dist/cjs/repositories/ImposterRepository.js.map +1 -0
- package/dist/cjs/schemas/ConfigFileSchema.js +44 -0
- package/dist/cjs/schemas/ConfigFileSchema.js.map +1 -0
- package/dist/cjs/schemas/ImposterSchema.js +202 -0
- package/dist/cjs/schemas/ImposterSchema.js.map +1 -0
- package/dist/cjs/schemas/RequestLogSchema.js +51 -0
- package/dist/cjs/schemas/RequestLogSchema.js.map +1 -0
- package/dist/cjs/schemas/StubSchema.js +84 -0
- package/dist/cjs/schemas/StubSchema.js.map +1 -0
- package/dist/cjs/schemas/common.js +67 -0
- package/dist/cjs/schemas/common.js.map +1 -0
- package/dist/cjs/server/AdminServer.js +36 -0
- package/dist/cjs/server/AdminServer.js.map +1 -0
- package/dist/cjs/server/BunServer.js +13 -0
- package/dist/cjs/server/BunServer.js.map +1 -0
- package/dist/cjs/server/FiberManager.js +21 -0
- package/dist/cjs/server/FiberManager.js.map +1 -0
- package/dist/cjs/server/ImposterServer.js +234 -0
- package/dist/cjs/server/ImposterServer.js.map +1 -0
- package/dist/cjs/services/AppConfig.js +18 -0
- package/dist/cjs/services/AppConfig.js.map +1 -0
- package/dist/cjs/services/MetricsService.js +113 -0
- package/dist/cjs/services/MetricsService.js.map +1 -0
- package/dist/cjs/services/PortAllocator.js +50 -0
- package/dist/cjs/services/PortAllocator.js.map +1 -0
- package/dist/cjs/services/ProxyService.js +109 -0
- package/dist/cjs/services/ProxyService.js.map +1 -0
- package/dist/cjs/services/RequestLogger.js +60 -0
- package/dist/cjs/services/RequestLogger.js.map +1 -0
- package/dist/cjs/services/Uuid.js +10 -0
- package/dist/cjs/services/Uuid.js.map +1 -0
- package/dist/cjs/services/UuidLive.js +16 -0
- package/dist/cjs/services/UuidLive.js.map +1 -0
- package/dist/cjs/ui/UiRouter.js +242 -0
- package/dist/cjs/ui/UiRouter.js.map +1 -0
- package/dist/cjs/ui/admin/AdminLayout.js +36 -0
- package/dist/cjs/ui/admin/AdminLayout.js.map +1 -0
- package/dist/cjs/ui/admin/AdminUiRouter.js +155 -0
- package/dist/cjs/ui/admin/AdminUiRouter.js.map +1 -0
- package/dist/cjs/ui/admin/pages/AdminDashboard.js +55 -0
- package/dist/cjs/ui/admin/pages/AdminDashboard.js.map +1 -0
- package/dist/cjs/ui/admin/partials.js +64 -0
- package/dist/cjs/ui/admin/partials.js.map +1 -0
- package/dist/cjs/ui/html.js +42 -0
- package/dist/cjs/ui/html.js.map +1 -0
- package/dist/cjs/ui/layout.js +39 -0
- package/dist/cjs/ui/layout.js.map +1 -0
- package/dist/cjs/ui/pages/dashboard.js +51 -0
- package/dist/cjs/ui/pages/dashboard.js.map +1 -0
- package/dist/cjs/ui/pages/request-detail.js +119 -0
- package/dist/cjs/ui/pages/request-detail.js.map +1 -0
- package/dist/cjs/ui/pages/requests.js +120 -0
- package/dist/cjs/ui/pages/requests.js.map +1 -0
- package/dist/cjs/ui/pages/stubs.js +46 -0
- package/dist/cjs/ui/pages/stubs.js.map +1 -0
- package/dist/cjs/ui/partials.js +104 -0
- package/dist/cjs/ui/partials.js.map +1 -0
- package/dist/dts/Program.d.ts +2 -0
- package/dist/dts/Program.d.ts.map +1 -0
- package/dist/dts/api/AdminApi.d.ts +490 -0
- package/dist/dts/api/AdminApi.d.ts.map +1 -0
- package/dist/dts/api/ApiErrors.d.ts +26 -0
- package/dist/dts/api/ApiErrors.d.ts.map +1 -0
- package/dist/dts/api/ApiSchemas.d.ts +36 -0
- package/dist/dts/api/ApiSchemas.d.ts.map +1 -0
- package/dist/dts/api/Conversions.d.ts +7 -0
- package/dist/dts/api/Conversions.d.ts.map +1 -0
- package/dist/dts/api/ImpostersGroup.d.ts +448 -0
- package/dist/dts/api/ImpostersGroup.d.ts.map +1 -0
- package/dist/dts/api/ImpostersHandlers.d.ts +9 -0
- package/dist/dts/api/ImpostersHandlers.d.ts.map +1 -0
- package/dist/dts/api/SystemGroup.d.ts +46 -0
- package/dist/dts/api/SystemGroup.d.ts.map +1 -0
- package/dist/dts/api/SystemHandlers.d.ts +4 -0
- package/dist/dts/api/SystemHandlers.d.ts.map +1 -0
- package/dist/dts/cli/Commands.d.ts +4 -0
- package/dist/dts/cli/Commands.d.ts.map +1 -0
- package/dist/dts/cli/ConfigLoader.d.ts +13 -0
- package/dist/dts/cli/ConfigLoader.d.ts.map +1 -0
- package/dist/dts/client/HandlerHttpClient.d.ts +5 -0
- package/dist/dts/client/HandlerHttpClient.d.ts.map +1 -0
- package/dist/dts/client/ImpostersClient.d.ts +1868 -0
- package/dist/dts/client/ImpostersClient.d.ts.map +1 -0
- package/dist/dts/client/index.d.ts +6 -0
- package/dist/dts/client/index.d.ts.map +1 -0
- package/dist/dts/client/testing.d.ts +35 -0
- package/dist/dts/client/testing.d.ts.map +1 -0
- package/dist/dts/domain/imposter.d.ts +123 -0
- package/dist/dts/domain/imposter.d.ts.map +1 -0
- package/dist/dts/domain/route.d.ts +128 -0
- package/dist/dts/domain/route.d.ts.map +1 -0
- package/dist/dts/index.d.ts +60 -0
- package/dist/dts/index.d.ts.map +1 -0
- package/dist/dts/layers/ApiLayer.d.ts +3 -0
- package/dist/dts/layers/ApiLayer.d.ts.map +1 -0
- package/dist/dts/layers/MainLayer.d.ts +3 -0
- package/dist/dts/layers/MainLayer.d.ts.map +1 -0
- package/dist/dts/matching/ExpressionEvaluator.d.ts +11 -0
- package/dist/dts/matching/ExpressionEvaluator.d.ts.map +1 -0
- package/dist/dts/matching/RequestMatcher.d.ts +13 -0
- package/dist/dts/matching/RequestMatcher.d.ts.map +1 -0
- package/dist/dts/matching/ResponseGenerator.d.ts +9 -0
- package/dist/dts/matching/ResponseGenerator.d.ts.map +1 -0
- package/dist/dts/matching/TemplateEngine.d.ts +4 -0
- package/dist/dts/matching/TemplateEngine.d.ts.map +1 -0
- package/dist/dts/repositories/ImposterRepository.d.ts +33 -0
- package/dist/dts/repositories/ImposterRepository.d.ts.map +1 -0
- package/dist/dts/schemas/ConfigFileSchema.d.ts +142 -0
- package/dist/dts/schemas/ConfigFileSchema.d.ts.map +1 -0
- package/dist/dts/schemas/ImposterSchema.d.ts +368 -0
- package/dist/dts/schemas/ImposterSchema.d.ts.map +1 -0
- package/dist/dts/schemas/RequestLogSchema.d.ts +36 -0
- package/dist/dts/schemas/RequestLogSchema.d.ts.map +1 -0
- package/dist/dts/schemas/StubSchema.d.ts +112 -0
- package/dist/dts/schemas/StubSchema.d.ts.map +1 -0
- package/dist/dts/schemas/common.d.ts +56 -0
- package/dist/dts/schemas/common.d.ts.map +1 -0
- package/dist/dts/server/AdminServer.d.ts +11 -0
- package/dist/dts/server/AdminServer.d.ts.map +1 -0
- package/dist/dts/server/BunServer.d.ts +17 -0
- package/dist/dts/server/BunServer.d.ts.map +1 -0
- package/dist/dts/server/FiberManager.d.ts +12 -0
- package/dist/dts/server/FiberManager.d.ts.map +1 -0
- package/dist/dts/server/ImposterServer.d.ts +29 -0
- package/dist/dts/server/ImposterServer.d.ts.map +1 -0
- package/dist/dts/services/AppConfig.d.ts +14 -0
- package/dist/dts/services/AppConfig.d.ts.map +1 -0
- package/dist/dts/services/MetricsService.d.ts +26 -0
- package/dist/dts/services/MetricsService.d.ts.map +1 -0
- package/dist/dts/services/PortAllocator.d.ts +29 -0
- package/dist/dts/services/PortAllocator.d.ts.map +1 -0
- package/dist/dts/services/ProxyService.d.ts +24 -0
- package/dist/dts/services/ProxyService.d.ts.map +1 -0
- package/dist/dts/services/RequestLogger.d.ts +23 -0
- package/dist/dts/services/RequestLogger.d.ts.map +1 -0
- package/dist/dts/services/Uuid.d.ts +9 -0
- package/dist/dts/services/Uuid.d.ts.map +1 -0
- package/dist/dts/services/UuidLive.d.ts +4 -0
- package/dist/dts/services/UuidLive.d.ts.map +1 -0
- package/dist/dts/ui/UiRouter.d.ts +15 -0
- package/dist/dts/ui/UiRouter.d.ts.map +1 -0
- package/dist/dts/ui/admin/AdminLayout.d.ts +7 -0
- package/dist/dts/ui/admin/AdminLayout.d.ts.map +1 -0
- package/dist/dts/ui/admin/AdminUiRouter.d.ts +6 -0
- package/dist/dts/ui/admin/AdminUiRouter.d.ts.map +1 -0
- package/dist/dts/ui/admin/pages/AdminDashboard.d.ts +7 -0
- package/dist/dts/ui/admin/pages/AdminDashboard.d.ts.map +1 -0
- package/dist/dts/ui/admin/partials.d.ts +14 -0
- package/dist/dts/ui/admin/partials.d.ts.map +1 -0
- package/dist/dts/ui/html.d.ts +12 -0
- package/dist/dts/ui/html.d.ts.map +1 -0
- package/dist/dts/ui/layout.d.ts +9 -0
- package/dist/dts/ui/layout.d.ts.map +1 -0
- package/dist/dts/ui/pages/dashboard.d.ts +10 -0
- package/dist/dts/ui/pages/dashboard.d.ts.map +1 -0
- package/dist/dts/ui/pages/request-detail.d.ts +11 -0
- package/dist/dts/ui/pages/request-detail.d.ts.map +1 -0
- package/dist/dts/ui/pages/requests.d.ts +15 -0
- package/dist/dts/ui/pages/requests.d.ts.map +1 -0
- package/dist/dts/ui/pages/stubs.d.ts +8 -0
- package/dist/dts/ui/pages/stubs.d.ts.map +1 -0
- package/dist/dts/ui/partials.d.ts +13 -0
- package/dist/dts/ui/partials.d.ts.map +1 -0
- package/dist/esm/Program.js +2 -0
- package/dist/esm/Program.js.map +1 -0
- package/dist/esm/api/AdminApi.js +5 -0
- package/dist/esm/api/AdminApi.js.map +1 -0
- package/dist/esm/api/ApiErrors.js +20 -0
- package/dist/esm/api/ApiErrors.js.map +1 -0
- package/dist/esm/api/ApiSchemas.js +29 -0
- package/dist/esm/api/ApiSchemas.js.map +1 -0
- package/dist/esm/api/Conversions.js +32 -0
- package/dist/esm/api/Conversions.js.map +1 -0
- package/dist/esm/api/ImpostersGroup.js +30 -0
- package/dist/esm/api/ImpostersGroup.js.map +1 -0
- package/dist/esm/api/ImpostersHandlers.js +354 -0
- package/dist/esm/api/ImpostersHandlers.js.map +1 -0
- package/dist/esm/api/SystemGroup.js +6 -0
- package/dist/esm/api/SystemGroup.js.map +1 -0
- package/dist/esm/api/SystemHandlers.js +67 -0
- package/dist/esm/api/SystemHandlers.js.map +1 -0
- package/dist/esm/cli/Commands.js +98 -0
- package/dist/esm/cli/Commands.js.map +1 -0
- package/dist/esm/cli/ConfigLoader.js +25 -0
- package/dist/esm/cli/ConfigLoader.js.map +1 -0
- package/dist/esm/client/HandlerHttpClient.js +42 -0
- package/dist/esm/client/HandlerHttpClient.js.map +1 -0
- package/dist/esm/client/ImpostersClient.js +10 -0
- package/dist/esm/client/ImpostersClient.js.map +1 -0
- package/dist/esm/client/index.js +4 -0
- package/dist/esm/client/index.js.map +1 -0
- package/dist/esm/client/testing.js +86 -0
- package/dist/esm/client/testing.js.map +1 -0
- package/dist/esm/domain/imposter.js +103 -0
- package/dist/esm/domain/imposter.js.map +1 -0
- package/dist/esm/domain/route.js +164 -0
- package/dist/esm/domain/route.js.map +1 -0
- package/dist/esm/index.js +60 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/layers/ApiLayer.js +11 -0
- package/dist/esm/layers/ApiLayer.js.map +1 -0
- package/dist/esm/layers/MainLayer.js +20 -0
- package/dist/esm/layers/MainLayer.js.map +1 -0
- package/dist/esm/matching/ExpressionEvaluator.js +94 -0
- package/dist/esm/matching/ExpressionEvaluator.js.map +1 -0
- package/dist/esm/matching/RequestMatcher.js +135 -0
- package/dist/esm/matching/RequestMatcher.js.map +1 -0
- package/dist/esm/matching/ResponseGenerator.js +71 -0
- package/dist/esm/matching/ResponseGenerator.js.map +1 -0
- package/dist/esm/matching/TemplateEngine.js +47 -0
- package/dist/esm/matching/TemplateEngine.js.map +1 -0
- package/dist/esm/package.json +4 -0
- package/dist/esm/repositories/ImposterRepository.js +110 -0
- package/dist/esm/repositories/ImposterRepository.js.map +1 -0
- package/dist/esm/schemas/ConfigFileSchema.js +37 -0
- package/dist/esm/schemas/ConfigFileSchema.js.map +1 -0
- package/dist/esm/schemas/ImposterSchema.js +195 -0
- package/dist/esm/schemas/ImposterSchema.js.map +1 -0
- package/dist/esm/schemas/RequestLogSchema.js +44 -0
- package/dist/esm/schemas/RequestLogSchema.js.map +1 -0
- package/dist/esm/schemas/StubSchema.js +77 -0
- package/dist/esm/schemas/StubSchema.js.map +1 -0
- package/dist/esm/schemas/common.js +59 -0
- package/dist/esm/schemas/common.js.map +1 -0
- package/dist/esm/server/AdminServer.js +27 -0
- package/dist/esm/server/AdminServer.js.map +1 -0
- package/dist/esm/server/BunServer.js +6 -0
- package/dist/esm/server/BunServer.js.map +1 -0
- package/dist/esm/server/FiberManager.js +14 -0
- package/dist/esm/server/FiberManager.js.map +1 -0
- package/dist/esm/server/ImposterServer.js +225 -0
- package/dist/esm/server/ImposterServer.js.map +1 -0
- package/dist/esm/services/AppConfig.js +11 -0
- package/dist/esm/services/AppConfig.js.map +1 -0
- package/dist/esm/services/MetricsService.js +105 -0
- package/dist/esm/services/MetricsService.js.map +1 -0
- package/dist/esm/services/PortAllocator.js +41 -0
- package/dist/esm/services/PortAllocator.js.map +1 -0
- package/dist/esm/services/ProxyService.js +101 -0
- package/dist/esm/services/ProxyService.js.map +1 -0
- package/dist/esm/services/RequestLogger.js +53 -0
- package/dist/esm/services/RequestLogger.js.map +1 -0
- package/dist/esm/services/Uuid.js +3 -0
- package/dist/esm/services/Uuid.js.map +1 -0
- package/dist/esm/services/UuidLive.js +9 -0
- package/dist/esm/services/UuidLive.js.map +1 -0
- package/dist/esm/ui/UiRouter.js +235 -0
- package/dist/esm/ui/UiRouter.js.map +1 -0
- package/dist/esm/ui/admin/AdminLayout.js +29 -0
- package/dist/esm/ui/admin/AdminLayout.js.map +1 -0
- package/dist/esm/ui/admin/AdminUiRouter.js +148 -0
- package/dist/esm/ui/admin/AdminUiRouter.js.map +1 -0
- package/dist/esm/ui/admin/pages/AdminDashboard.js +48 -0
- package/dist/esm/ui/admin/pages/AdminDashboard.js.map +1 -0
- package/dist/esm/ui/admin/partials.js +54 -0
- package/dist/esm/ui/admin/partials.js.map +1 -0
- package/dist/esm/ui/html.js +32 -0
- package/dist/esm/ui/html.js.map +1 -0
- package/dist/esm/ui/layout.js +32 -0
- package/dist/esm/ui/layout.js.map +1 -0
- package/dist/esm/ui/pages/dashboard.js +44 -0
- package/dist/esm/ui/pages/dashboard.js.map +1 -0
- package/dist/esm/ui/pages/request-detail.js +112 -0
- package/dist/esm/ui/pages/request-detail.js.map +1 -0
- package/dist/esm/ui/pages/requests.js +112 -0
- package/dist/esm/ui/pages/requests.js.map +1 -0
- package/dist/esm/ui/pages/stubs.js +39 -0
- package/dist/esm/ui/pages/stubs.js.map +1 -0
- package/dist/esm/ui/partials.js +91 -0
- package/dist/esm/ui/partials.js.map +1 -0
- package/domain/imposter/package.json +6 -0
- package/domain/route/package.json +6 -0
- package/layers/ApiLayer/package.json +6 -0
- package/layers/MainLayer/package.json +6 -0
- package/matching/ExpressionEvaluator/package.json +6 -0
- package/matching/RequestMatcher/package.json +6 -0
- package/matching/ResponseGenerator/package.json +6 -0
- package/matching/TemplateEngine/package.json +6 -0
- package/package.json +435 -0
- package/repositories/ImposterRepository/package.json +6 -0
- package/schemas/ConfigFileSchema/package.json +6 -0
- package/schemas/ImposterSchema/package.json +6 -0
- package/schemas/RequestLogSchema/package.json +6 -0
- package/schemas/StubSchema/package.json +6 -0
- package/schemas/common/package.json +6 -0
- package/server/AdminServer/package.json +6 -0
- package/server/BunServer/package.json +6 -0
- package/server/FiberManager/package.json +6 -0
- package/server/ImposterServer/package.json +6 -0
- package/services/AppConfig/package.json +6 -0
- package/services/MetricsService/package.json +6 -0
- package/services/PortAllocator/package.json +6 -0
- package/services/ProxyService/package.json +6 -0
- package/services/RequestLogger/package.json +6 -0
- package/services/Uuid/package.json +6 -0
- package/services/UuidLive/package.json +6 -0
- package/src/Program.ts +1 -0
- package/src/api/AdminApi.ts +7 -0
- package/src/api/ApiErrors.ts +20 -0
- package/src/api/ApiSchemas.ts +36 -0
- package/src/api/Conversions.ts +34 -0
- package/src/api/ImpostersGroup.ts +103 -0
- package/src/api/ImpostersHandlers.ts +387 -0
- package/src/api/SystemGroup.ts +12 -0
- package/src/api/SystemHandlers.ts +76 -0
- package/src/cli/Commands.ts +119 -0
- package/src/cli/ConfigLoader.ts +41 -0
- package/src/client/HandlerHttpClient.ts +50 -0
- package/src/client/ImpostersClient.ts +21 -0
- package/src/client/index.ts +9 -0
- package/src/client/testing.ts +105 -0
- package/src/domain/imposter.ts +186 -0
- package/src/domain/route.ts +255 -0
- package/src/index.ts +153 -0
- package/src/layers/ApiLayer.ts +21 -0
- package/src/layers/MainLayer.ts +43 -0
- package/src/matching/ExpressionEvaluator.ts +102 -0
- package/src/matching/RequestMatcher.ts +162 -0
- package/src/matching/ResponseGenerator.ts +86 -0
- package/src/matching/TemplateEngine.ts +54 -0
- package/src/repositories/ImposterRepository.ts +145 -0
- package/src/schemas/ConfigFileSchema.ts +32 -0
- package/src/schemas/ImposterSchema.ts +232 -0
- package/src/schemas/RequestLogSchema.ts +38 -0
- package/src/schemas/StubSchema.ts +90 -0
- package/src/schemas/common.ts +95 -0
- package/src/server/AdminServer.ts +22 -0
- package/src/server/BunServer.ts +19 -0
- package/src/server/FiberManager.ts +25 -0
- package/src/server/ImposterServer.ts +244 -0
- package/src/services/AppConfig.ts +22 -0
- package/src/services/MetricsService.ts +157 -0
- package/src/services/PortAllocator.ts +68 -0
- package/src/services/ProxyService.ts +139 -0
- package/src/services/RequestLogger.ts +87 -0
- package/src/services/Uuid.ts +9 -0
- package/src/services/UuidLive.ts +9 -0
- package/src/types/bun.d.ts +6 -0
- package/src/ui/UiRouter.ts +278 -0
- package/src/ui/admin/AdminLayout.ts +36 -0
- package/src/ui/admin/AdminUiRouter.ts +170 -0
- package/src/ui/admin/pages/AdminDashboard.ts +54 -0
- package/src/ui/admin/partials.ts +83 -0
- package/src/ui/html.ts +37 -0
- package/src/ui/layout.ts +44 -0
- package/src/ui/pages/dashboard.ts +64 -0
- package/src/ui/pages/request-detail.ts +142 -0
- package/src/ui/pages/requests.ts +141 -0
- package/src/ui/pages/stubs.ts +52 -0
- package/src/ui/partials.ts +133 -0
- package/ui/UiRouter/package.json +6 -0
- package/ui/admin/AdminLayout/package.json +6 -0
- package/ui/admin/AdminUiRouter/package.json +6 -0
- package/ui/admin/pages/AdminDashboard/package.json +6 -0
- package/ui/admin/partials/package.json +6 -0
- package/ui/html/package.json +6 -0
- package/ui/layout/package.json +6 -0
- package/ui/pages/dashboard/package.json +6 -0
- package/ui/pages/requests/package.json +6 -0
- package/ui/pages/stubs/package.json +6 -0
package/src/index.ts
ADDED
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
|
|
2
|
+
export * as Program from "./Program.js"
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
export * as AdminApi from "./api/AdminApi.js"
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
export * as ApiErrors from "./api/ApiErrors.js"
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
export * as ApiSchemas from "./api/ApiSchemas.js"
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
export * as Conversions from "./api/Conversions.js"
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
export * as ImpostersGroup from "./api/ImpostersGroup.js"
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
export * as ImpostersHandlers from "./api/ImpostersHandlers.js"
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
export * as SystemGroup from "./api/SystemGroup.js"
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
export * as SystemHandlers from "./api/SystemHandlers.js"
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
export * as Commands from "./cli/Commands.js"
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
export * as ConfigLoader from "./cli/ConfigLoader.js"
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
export * as HandlerHttpClient from "./client/HandlerHttpClient.js"
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
export * as ImpostersClient from "./client/ImpostersClient.js"
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
export * as testing from "./client/testing.js"
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Parses and validates imposter creation request
|
|
45
|
+
*/
|
|
46
|
+
export * as imposter from "./domain/imposter.js"
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Parses and validates route creation request
|
|
50
|
+
*/
|
|
51
|
+
export * as route from "./domain/route.js"
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
export * as ApiLayer from "./layers/ApiLayer.js"
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
export * as MainLayer from "./layers/MainLayer.js"
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Extract expression content from a ${...} pattern using brace-depth counting.
|
|
61
|
+
* Returns [expressionContent, endIndex] or null if no valid expression found.
|
|
62
|
+
*/
|
|
63
|
+
export * as ExpressionEvaluator from "./matching/ExpressionEvaluator.js"
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
export * as RequestMatcher from "./matching/RequestMatcher.js"
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
export * as ResponseGenerator from "./matching/ResponseGenerator.js"
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
export * as TemplateEngine from "./matching/TemplateEngine.js"
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
export * as ImposterRepository from "./repositories/ImposterRepository.js"
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
export * as ConfigFileSchema from "./schemas/ConfigFileSchema.js"
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
export * as ImposterSchema from "./schemas/ImposterSchema.js"
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
export * as RequestLogSchema from "./schemas/RequestLogSchema.js"
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
export * as StubSchema from "./schemas/StubSchema.js"
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
export * as common from "./schemas/common.js"
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
export * as AdminServer from "./server/AdminServer.js"
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
export * as BunServer from "./server/BunServer.js"
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
export * as FiberManager from "./server/FiberManager.js"
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
export * as ImposterServer from "./server/ImposterServer.js"
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
export * as AppConfig from "./services/AppConfig.js"
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
export * as MetricsService from "./services/MetricsService.js"
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
export * as PortAllocator from "./services/PortAllocator.js"
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
export * as ProxyService from "./services/ProxyService.js"
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
export * as RequestLogger from "./services/RequestLogger.js"
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
export * as Uuid from "./services/Uuid.js"
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
export * as UuidLive from "./services/UuidLive.js"
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
export * as UiRouter from "./ui/UiRouter.js"
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
export * as AdminLayout from "./ui/admin/AdminLayout.js"
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
export * as AdminUiRouter from "./ui/admin/AdminUiRouter.js"
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
export * as AdminDashboard from "./ui/admin/pages/AdminDashboard.js"
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
export * as partials from "./ui/admin/partials.js"
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
export * as html from "./ui/html.js"
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
export * as layout from "./ui/layout.js"
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
export * as dashboard from "./ui/pages/dashboard.js"
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
export * as requests from "./ui/pages/requests.js"
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
export * as stubs from "./ui/pages/stubs.js"
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { HttpApiBuilder, HttpApiSwagger, HttpServer } from "@effect/platform"
|
|
2
|
+
import * as Layer from "effect/Layer"
|
|
3
|
+
import { AdminApi } from "../api/AdminApi"
|
|
4
|
+
import { ImpostersHandlersLive } from "../api/ImpostersHandlers"
|
|
5
|
+
import { SystemHandlersLive } from "../api/SystemHandlers"
|
|
6
|
+
|
|
7
|
+
const HandlerLayers = Layer.mergeAll(ImpostersHandlersLive, SystemHandlersLive)
|
|
8
|
+
|
|
9
|
+
const ApiLive = HttpApiBuilder.api(AdminApi).pipe(Layer.provide(HandlerLayers))
|
|
10
|
+
|
|
11
|
+
// Middleware layers need Api — provide it from ApiLive
|
|
12
|
+
const MiddlewareLive = Layer.mergeAll(
|
|
13
|
+
HttpApiBuilder.middlewareOpenApi(),
|
|
14
|
+
HttpApiSwagger.layer()
|
|
15
|
+
).pipe(Layer.provide(ApiLive))
|
|
16
|
+
|
|
17
|
+
export const ApiLayer = Layer.mergeAll(
|
|
18
|
+
ApiLive,
|
|
19
|
+
MiddlewareLive,
|
|
20
|
+
HttpServer.layerContext
|
|
21
|
+
)
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import * as Layer from "effect/Layer"
|
|
2
|
+
import { ImposterRepositoryLive } from "../repositories/ImposterRepository"
|
|
3
|
+
import { BunServerFactoryLive } from "../server/BunServer"
|
|
4
|
+
import { FiberManagerLive } from "../server/FiberManager"
|
|
5
|
+
import { ImposterServerLive } from "../server/ImposterServer"
|
|
6
|
+
import { AppConfigLive } from "../services/AppConfig"
|
|
7
|
+
import { MetricsServiceLive } from "../services/MetricsService"
|
|
8
|
+
import { PortAllocatorLive } from "../services/PortAllocator"
|
|
9
|
+
import { ProxyServiceLive } from "../services/ProxyService"
|
|
10
|
+
import { RequestLoggerLive } from "../services/RequestLogger"
|
|
11
|
+
import { UuidLive } from "../services/UuidLive"
|
|
12
|
+
|
|
13
|
+
// PortAllocatorLive depends on AppConfig
|
|
14
|
+
const PortAllocatorWithDeps = PortAllocatorLive.pipe(Layer.provide(AppConfigLive))
|
|
15
|
+
|
|
16
|
+
// ProxyServiceLive depends on Uuid
|
|
17
|
+
const ProxyServiceWithDeps = ProxyServiceLive.pipe(Layer.provide(UuidLive))
|
|
18
|
+
|
|
19
|
+
// ImposterServerLive depends on FiberManager + ImposterRepository + ServerFactory + RequestLogger + Metrics + Proxy
|
|
20
|
+
const ImposterServerWithDeps = ImposterServerLive.pipe(
|
|
21
|
+
Layer.provide(
|
|
22
|
+
Layer.mergeAll(
|
|
23
|
+
FiberManagerLive,
|
|
24
|
+
ImposterRepositoryLive,
|
|
25
|
+
BunServerFactoryLive,
|
|
26
|
+
RequestLoggerLive,
|
|
27
|
+
MetricsServiceLive,
|
|
28
|
+
ProxyServiceWithDeps
|
|
29
|
+
)
|
|
30
|
+
)
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
// Compose all services
|
|
34
|
+
export const MainLayer = Layer.mergeAll(
|
|
35
|
+
UuidLive,
|
|
36
|
+
AppConfigLive,
|
|
37
|
+
PortAllocatorWithDeps,
|
|
38
|
+
ImposterRepositoryLive,
|
|
39
|
+
FiberManagerLive,
|
|
40
|
+
RequestLoggerLive,
|
|
41
|
+
MetricsServiceLive,
|
|
42
|
+
ImposterServerWithDeps
|
|
43
|
+
)
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import jsonata from "jsonata"
|
|
2
|
+
import type { RequestContext } from "./RequestMatcher"
|
|
3
|
+
|
|
4
|
+
const MAX_OUTPUT_SIZE = 1_048_576 // 1MB
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Extract expression content from a ${...} pattern using brace-depth counting.
|
|
8
|
+
* Returns [expressionContent, endIndex] or null if no valid expression found.
|
|
9
|
+
*/
|
|
10
|
+
const extractExpression = (str: string, startIndex: number): [string, number] | null => {
|
|
11
|
+
// startIndex should point to the '$' of '${'
|
|
12
|
+
if (str[startIndex] !== "$" || str[startIndex + 1] !== "{") return null
|
|
13
|
+
let depth = 1
|
|
14
|
+
let i = startIndex + 2
|
|
15
|
+
while (i < str.length && depth > 0) {
|
|
16
|
+
if (str[i] === "{") depth++
|
|
17
|
+
else if (str[i] === "}") depth--
|
|
18
|
+
i++
|
|
19
|
+
}
|
|
20
|
+
if (depth !== 0) return null
|
|
21
|
+
const content = str.slice(startIndex + 2, i - 1)
|
|
22
|
+
return [content, i]
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Evaluate a single JSONata expression against the request context.
|
|
27
|
+
* Returns the result or undefined on error.
|
|
28
|
+
*/
|
|
29
|
+
export const evaluateExpression = async (expr: string, ctx: RequestContext): Promise<unknown> => {
|
|
30
|
+
try {
|
|
31
|
+
const expression = jsonata(expr)
|
|
32
|
+
const context = { request: ctx }
|
|
33
|
+
return await expression.evaluate(context)
|
|
34
|
+
} catch {
|
|
35
|
+
return undefined
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Process a string, replacing all ${...} patterns with evaluated JSONata results.
|
|
41
|
+
*/
|
|
42
|
+
const processString = async (str: string, ctx: RequestContext): Promise<unknown> => {
|
|
43
|
+
// Quick check: if no ${, return as-is
|
|
44
|
+
if (!str.includes("${")) return str
|
|
45
|
+
|
|
46
|
+
// If the entire string is a single expression, return the raw result (preserving type)
|
|
47
|
+
const singleMatch = extractExpression(str, 0)
|
|
48
|
+
if (singleMatch && singleMatch[1] === str.length) {
|
|
49
|
+
const result = await evaluateExpression(singleMatch[0], ctx)
|
|
50
|
+
if (result === undefined) return str // Preserve raw on failure
|
|
51
|
+
return result
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Multiple expressions or mixed content: concatenate as string
|
|
55
|
+
let result = ""
|
|
56
|
+
let i = 0
|
|
57
|
+
while (i < str.length) {
|
|
58
|
+
if (str[i] === "$" && i + 1 < str.length && str[i + 1] === "{") {
|
|
59
|
+
const extracted = extractExpression(str, i)
|
|
60
|
+
if (extracted) {
|
|
61
|
+
const [exprContent, endIndex] = extracted
|
|
62
|
+
const evalResult = await evaluateExpression(exprContent, ctx)
|
|
63
|
+
if (evalResult === undefined) {
|
|
64
|
+
// Preserve raw expression on failure
|
|
65
|
+
result += str.slice(i, endIndex)
|
|
66
|
+
} else if (typeof evalResult === "object" && evalResult !== null) {
|
|
67
|
+
const jsonStr = JSON.stringify(evalResult)
|
|
68
|
+
result += jsonStr
|
|
69
|
+
} else {
|
|
70
|
+
result += String(evalResult)
|
|
71
|
+
}
|
|
72
|
+
i = endIndex
|
|
73
|
+
if (result.length > MAX_OUTPUT_SIZE) {
|
|
74
|
+
return result.slice(0, MAX_OUTPUT_SIZE)
|
|
75
|
+
}
|
|
76
|
+
continue
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
result += str[i]
|
|
80
|
+
i++
|
|
81
|
+
}
|
|
82
|
+
return result
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Recursively walk data structures, processing ${...} expressions in strings.
|
|
87
|
+
*/
|
|
88
|
+
export const processExpressions = async (ctx: RequestContext, data: unknown): Promise<unknown> => {
|
|
89
|
+
if (typeof data === "string") return processString(data, ctx)
|
|
90
|
+
if (Array.isArray(data)) {
|
|
91
|
+
const results = await Promise.all(data.map((item) => processExpressions(ctx, item)))
|
|
92
|
+
return results
|
|
93
|
+
}
|
|
94
|
+
if (data !== null && typeof data === "object") {
|
|
95
|
+
const entries = Object.entries(data as Record<string, unknown>)
|
|
96
|
+
const resolved = await Promise.all(
|
|
97
|
+
entries.map(async ([k, v]) => [k, await processExpressions(ctx, v)] as const)
|
|
98
|
+
)
|
|
99
|
+
return Object.fromEntries(resolved)
|
|
100
|
+
}
|
|
101
|
+
return data
|
|
102
|
+
}
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
import type { Predicate, Stub } from "../schemas/StubSchema"
|
|
2
|
+
|
|
3
|
+
export interface RequestContext {
|
|
4
|
+
readonly method: string
|
|
5
|
+
readonly path: string
|
|
6
|
+
readonly headers: Record<string, string>
|
|
7
|
+
readonly query: Record<string, string>
|
|
8
|
+
readonly body: unknown
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export const extractRequestContext = async (request: Request): Promise<RequestContext> => {
|
|
12
|
+
const url = new URL(request.url)
|
|
13
|
+
const method = request.method.toUpperCase()
|
|
14
|
+
const path = url.pathname
|
|
15
|
+
|
|
16
|
+
const headers: Record<string, string> = {}
|
|
17
|
+
request.headers.forEach((value, key) => {
|
|
18
|
+
headers[key.toLowerCase()] = value
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
const query: Record<string, string> = {}
|
|
22
|
+
url.searchParams.forEach((value, key) => {
|
|
23
|
+
query[key] = value
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
let body: unknown
|
|
27
|
+
if (request.body) {
|
|
28
|
+
const contentType = request.headers.get("content-type") ?? ""
|
|
29
|
+
const text = await request.text()
|
|
30
|
+
if (contentType.includes("application/json")) {
|
|
31
|
+
try {
|
|
32
|
+
body = JSON.parse(text)
|
|
33
|
+
} catch {
|
|
34
|
+
body = text
|
|
35
|
+
}
|
|
36
|
+
} else {
|
|
37
|
+
body = text === "" ? undefined : text
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return { method, path, headers, query, body }
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const normalize = (s: string, caseSensitive: boolean): string => caseSensitive ? s : s.toLowerCase()
|
|
45
|
+
|
|
46
|
+
const matchString = (
|
|
47
|
+
actual: string,
|
|
48
|
+
expected: unknown,
|
|
49
|
+
operator: Predicate["operator"],
|
|
50
|
+
caseSensitive: boolean
|
|
51
|
+
): boolean => {
|
|
52
|
+
if (operator === "exists") return true
|
|
53
|
+
if (typeof expected !== "string") return false
|
|
54
|
+
const a = normalize(actual, caseSensitive)
|
|
55
|
+
const e = normalize(expected, caseSensitive)
|
|
56
|
+
switch (operator) {
|
|
57
|
+
case "equals":
|
|
58
|
+
return a === e
|
|
59
|
+
case "contains":
|
|
60
|
+
return a.includes(e)
|
|
61
|
+
case "startsWith":
|
|
62
|
+
return a.startsWith(e)
|
|
63
|
+
case "matches": {
|
|
64
|
+
const flags = caseSensitive ? "" : "i"
|
|
65
|
+
return new RegExp(expected, flags).test(actual)
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const matchObject = (
|
|
71
|
+
actual: Record<string, string>,
|
|
72
|
+
expected: unknown,
|
|
73
|
+
operator: Predicate["operator"],
|
|
74
|
+
caseSensitive: boolean
|
|
75
|
+
): boolean => {
|
|
76
|
+
if (operator === "exists") {
|
|
77
|
+
if (typeof expected !== "object" || expected === null) return true
|
|
78
|
+
return Object.keys(expected as Record<string, unknown>).every((key) => key.toLowerCase() in actual || key in actual)
|
|
79
|
+
}
|
|
80
|
+
if (typeof expected !== "object" || expected === null) return false
|
|
81
|
+
const entries = Object.entries(expected as Record<string, unknown>)
|
|
82
|
+
return entries.every(([key, val]) => {
|
|
83
|
+
const actualKey = Object.keys(actual).find(
|
|
84
|
+
(k) => normalize(k, caseSensitive) === normalize(key, caseSensitive)
|
|
85
|
+
)
|
|
86
|
+
if (actualKey === undefined) return false
|
|
87
|
+
const actualVal = actual[actualKey]!
|
|
88
|
+
if (typeof val !== "string") return false
|
|
89
|
+
return matchString(actualVal, val, operator, caseSensitive)
|
|
90
|
+
})
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const deepSubsetMatch = (actual: unknown, expected: unknown, caseSensitive: boolean): boolean => {
|
|
94
|
+
if (expected === null || expected === undefined) return actual === expected
|
|
95
|
+
if (typeof expected === "string" && typeof actual === "string") {
|
|
96
|
+
return caseSensitive ? actual === expected : actual.toLowerCase() === expected.toLowerCase()
|
|
97
|
+
}
|
|
98
|
+
if (typeof expected === "number" || typeof expected === "boolean") return actual === expected
|
|
99
|
+
if (Array.isArray(expected)) {
|
|
100
|
+
if (!Array.isArray(actual)) return false
|
|
101
|
+
return expected.every((e, i) => i < actual.length && deepSubsetMatch(actual[i], e, caseSensitive))
|
|
102
|
+
}
|
|
103
|
+
if (typeof expected === "object" && expected !== null) {
|
|
104
|
+
if (typeof actual !== "object" || actual === null) return false
|
|
105
|
+
return Object.entries(expected as Record<string, unknown>).every(([key, val]) =>
|
|
106
|
+
deepSubsetMatch((actual as Record<string, unknown>)[key], val, caseSensitive)
|
|
107
|
+
)
|
|
108
|
+
}
|
|
109
|
+
return false
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const matchBody = (
|
|
113
|
+
actual: unknown,
|
|
114
|
+
expected: unknown,
|
|
115
|
+
operator: Predicate["operator"],
|
|
116
|
+
caseSensitive: boolean
|
|
117
|
+
): boolean => {
|
|
118
|
+
switch (operator) {
|
|
119
|
+
case "exists":
|
|
120
|
+
return actual !== null && actual !== undefined
|
|
121
|
+
case "equals":
|
|
122
|
+
return deepSubsetMatch(actual, expected, caseSensitive)
|
|
123
|
+
case "contains": {
|
|
124
|
+
const a = normalize(typeof actual === "string" ? actual : JSON.stringify(actual), caseSensitive)
|
|
125
|
+
const e = normalize(typeof expected === "string" ? expected : JSON.stringify(expected), caseSensitive)
|
|
126
|
+
return a.includes(e)
|
|
127
|
+
}
|
|
128
|
+
case "startsWith": {
|
|
129
|
+
const a = normalize(typeof actual === "string" ? actual : JSON.stringify(actual), caseSensitive)
|
|
130
|
+
const e = normalize(typeof expected === "string" ? expected : JSON.stringify(expected), caseSensitive)
|
|
131
|
+
return a.startsWith(e)
|
|
132
|
+
}
|
|
133
|
+
case "matches": {
|
|
134
|
+
const a = typeof actual === "string" ? actual : JSON.stringify(actual)
|
|
135
|
+
const pattern = typeof expected === "string" ? expected : JSON.stringify(expected)
|
|
136
|
+
const flags = caseSensitive ? "" : "i"
|
|
137
|
+
return new RegExp(pattern, flags).test(a)
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
export const evaluatePredicate = (ctx: RequestContext, predicate: Predicate): boolean => {
|
|
143
|
+
const { caseSensitive, field, operator, value } = predicate
|
|
144
|
+
switch (field) {
|
|
145
|
+
case "method":
|
|
146
|
+
return matchString(ctx.method, value, operator, caseSensitive)
|
|
147
|
+
case "path":
|
|
148
|
+
return matchString(ctx.path, value, operator, caseSensitive)
|
|
149
|
+
case "headers":
|
|
150
|
+
return matchObject(ctx.headers, value, operator, caseSensitive)
|
|
151
|
+
case "query":
|
|
152
|
+
return matchObject(ctx.query, value, operator, caseSensitive)
|
|
153
|
+
case "body":
|
|
154
|
+
return matchBody(ctx.body, value, operator, caseSensitive)
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
export const evaluatePredicates = (ctx: RequestContext, predicates: ReadonlyArray<Predicate>): boolean =>
|
|
159
|
+
predicates.length === 0 || predicates.every((p) => evaluatePredicate(ctx, p))
|
|
160
|
+
|
|
161
|
+
export const findMatchingStub = (ctx: RequestContext, stubs: ReadonlyArray<Stub>): Stub | undefined =>
|
|
162
|
+
stubs.find((stub) => evaluatePredicates(ctx, stub.predicates))
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import * as Effect from "effect/Effect"
|
|
2
|
+
import * as HashMap from "effect/HashMap"
|
|
3
|
+
import * as Ref from "effect/Ref"
|
|
4
|
+
import type { ResponseConfig, ResponseMode } from "../schemas/StubSchema"
|
|
5
|
+
import type { RequestContext } from "./RequestMatcher"
|
|
6
|
+
import { applyTemplates } from "./TemplateEngine"
|
|
7
|
+
|
|
8
|
+
type CounterMap = HashMap.HashMap<string, number>
|
|
9
|
+
type CounterResult = readonly [Effect.Effect<number, never>, CounterMap]
|
|
10
|
+
|
|
11
|
+
export const makeResponseState = () =>
|
|
12
|
+
Effect.gen(function*() {
|
|
13
|
+
const countersRef = yield* Ref.make<CounterMap>(HashMap.empty())
|
|
14
|
+
|
|
15
|
+
const getNextIndex = (
|
|
16
|
+
imposterId: string,
|
|
17
|
+
stubId: string,
|
|
18
|
+
count: number,
|
|
19
|
+
mode: ResponseMode
|
|
20
|
+
): Effect.Effect<number> => {
|
|
21
|
+
const key = `${imposterId}:${stubId}`
|
|
22
|
+
return Ref.modify(countersRef, (counters): CounterResult => {
|
|
23
|
+
const current = HashMap.get(counters, key)
|
|
24
|
+
const index = current._tag === "Some" ? current.value : 0
|
|
25
|
+
let result: number
|
|
26
|
+
switch (mode) {
|
|
27
|
+
case "sequential":
|
|
28
|
+
result = index % count
|
|
29
|
+
break
|
|
30
|
+
case "random":
|
|
31
|
+
result = Math.floor(Math.random() * count)
|
|
32
|
+
break
|
|
33
|
+
case "repeat":
|
|
34
|
+
result = Math.min(index, count - 1)
|
|
35
|
+
break
|
|
36
|
+
}
|
|
37
|
+
const nextIndex = mode === "random" ? index : index + 1
|
|
38
|
+
return [Effect.succeed(result), HashMap.set(counters, key, nextIndex)]
|
|
39
|
+
}).pipe(Effect.flatten)
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const reset = (imposterId: string): Effect.Effect<void> =>
|
|
43
|
+
Ref.update(countersRef, (counters) => {
|
|
44
|
+
let updated = counters
|
|
45
|
+
for (const key of HashMap.keys(counters)) {
|
|
46
|
+
if (key.startsWith(`${imposterId}:`)) {
|
|
47
|
+
updated = HashMap.remove(updated, key)
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
return updated
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
return { getNextIndex, reset }
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
export const buildResponse = async (config: ResponseConfig, ctx: RequestContext): Promise<Response> => {
|
|
57
|
+
const headers = new Headers()
|
|
58
|
+
const responseHeaders = config.headers
|
|
59
|
+
if (responseHeaders !== undefined) {
|
|
60
|
+
for (const [key, val] of Object.entries(responseHeaders)) {
|
|
61
|
+
const templated = await applyTemplates(ctx, val)
|
|
62
|
+
headers.set(key, typeof templated === "string" ? templated : String(templated))
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
let bodyStr: string | null = null
|
|
67
|
+
if (config.body !== undefined) {
|
|
68
|
+
const templated = await applyTemplates(ctx, config.body)
|
|
69
|
+
if (typeof templated === "string") {
|
|
70
|
+
bodyStr = templated
|
|
71
|
+
if (!headers.has("content-type")) {
|
|
72
|
+
headers.set("content-type", "text/plain")
|
|
73
|
+
}
|
|
74
|
+
} else {
|
|
75
|
+
bodyStr = JSON.stringify(templated)
|
|
76
|
+
if (!headers.has("content-type")) {
|
|
77
|
+
headers.set("content-type", "application/json")
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return new Response(bodyStr, {
|
|
83
|
+
status: config.status,
|
|
84
|
+
headers
|
|
85
|
+
})
|
|
86
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { substituteParams } from "../domain/route"
|
|
2
|
+
import { processExpressions } from "./ExpressionEvaluator"
|
|
3
|
+
import type { RequestContext } from "./RequestMatcher"
|
|
4
|
+
|
|
5
|
+
const flattenObject = (obj: unknown, prefix: string, result: Record<string, string>): void => {
|
|
6
|
+
if (obj === null || obj === undefined) return
|
|
7
|
+
if (typeof obj === "string") {
|
|
8
|
+
result[prefix] = obj
|
|
9
|
+
return
|
|
10
|
+
}
|
|
11
|
+
if (typeof obj === "number" || typeof obj === "boolean") {
|
|
12
|
+
result[prefix] = String(obj)
|
|
13
|
+
return
|
|
14
|
+
}
|
|
15
|
+
if (Array.isArray(obj)) {
|
|
16
|
+
result[prefix] = JSON.stringify(obj)
|
|
17
|
+
obj.forEach((item, i) => flattenObject(item, `${prefix}.${i}`, result))
|
|
18
|
+
return
|
|
19
|
+
}
|
|
20
|
+
if (typeof obj === "object") {
|
|
21
|
+
result[prefix] = JSON.stringify(obj)
|
|
22
|
+
for (const [key, val] of Object.entries(obj as Record<string, unknown>)) {
|
|
23
|
+
flattenObject(val, `${prefix}.${key}`, result)
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export const flattenRequestContext = (ctx: RequestContext): Record<string, string> => {
|
|
29
|
+
const result: Record<string, string> = {
|
|
30
|
+
"request.method": ctx.method,
|
|
31
|
+
"request.path": ctx.path
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
for (const [key, val] of Object.entries(ctx.headers)) {
|
|
35
|
+
result[`request.headers.${key}`] = val
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
for (const [key, val] of Object.entries(ctx.query)) {
|
|
39
|
+
result[`request.query.${key}`] = val
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
if (ctx.body !== undefined && ctx.body !== null) {
|
|
43
|
+
flattenObject(ctx.body, "request.body", result)
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return result
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export const applyTemplates = async (ctx: RequestContext, data: unknown): Promise<unknown> => {
|
|
50
|
+
// Step 1: Apply {{key}} substitution
|
|
51
|
+
const substituted = substituteParams(flattenRequestContext(ctx))(data)
|
|
52
|
+
// Step 2: Apply ${expr} JSONata evaluation
|
|
53
|
+
return processExpressions(ctx, substituted)
|
|
54
|
+
}
|