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
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { HttpClient } from "@effect/platform"
|
|
2
|
+
import { FetchHttpClient, HttpApiClient } from "@effect/platform"
|
|
3
|
+
import type { Effect } from "effect"
|
|
4
|
+
import { Context, Layer } from "effect"
|
|
5
|
+
import { AdminApi } from "../api/AdminApi"
|
|
6
|
+
|
|
7
|
+
export const makeImpostersClient = (baseUrl?: string) =>
|
|
8
|
+
HttpApiClient.make(AdminApi, { baseUrl: baseUrl ?? "http://localhost:2525" })
|
|
9
|
+
|
|
10
|
+
export type ImpostersClientShape = Effect.Effect.Success<ReturnType<typeof makeImpostersClient>>
|
|
11
|
+
|
|
12
|
+
export class ImpostersClient extends Context.Tag("ImpostersClient")<
|
|
13
|
+
ImpostersClient,
|
|
14
|
+
ImpostersClientShape
|
|
15
|
+
>() {}
|
|
16
|
+
|
|
17
|
+
export const ImpostersClientLive = (baseUrl?: string): Layer.Layer<ImpostersClient, never, HttpClient.HttpClient> =>
|
|
18
|
+
Layer.effect(ImpostersClient, makeImpostersClient(baseUrl))
|
|
19
|
+
|
|
20
|
+
export const ImpostersClientFetchLive = (baseUrl?: string): Layer.Layer<ImpostersClient> =>
|
|
21
|
+
ImpostersClientLive(baseUrl).pipe(Layer.provide(FetchHttpClient.layer))
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export { HandlerHttpClientLive, makeHandlerHttpClient } from "./HandlerHttpClient"
|
|
2
|
+
|
|
3
|
+
export { ImpostersClient, ImpostersClientFetchLive, ImpostersClientLive, makeImpostersClient } from "./ImpostersClient"
|
|
4
|
+
|
|
5
|
+
export type { ImpostersClientShape } from "./ImpostersClient"
|
|
6
|
+
|
|
7
|
+
export { makeTestServer, withImposter } from "./testing"
|
|
8
|
+
|
|
9
|
+
export type { ImposterTestContext, StubConfig, WithImposterConfig } from "./testing"
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { HttpApiBuilder } from "@effect/platform"
|
|
2
|
+
import { Effect, Layer } from "effect"
|
|
3
|
+
import type { NonEmptyString, PortNumber } from "../schemas/common"
|
|
4
|
+
import type { CreateStubRequest } from "../schemas/StubSchema"
|
|
5
|
+
import { HandlerHttpClientLive } from "./HandlerHttpClient"
|
|
6
|
+
import { ImpostersClient, ImpostersClientLive } from "./ImpostersClient"
|
|
7
|
+
|
|
8
|
+
export interface StubConfig {
|
|
9
|
+
readonly predicates?: ReadonlyArray<{
|
|
10
|
+
readonly field: "method" | "path" | "headers" | "query" | "body"
|
|
11
|
+
readonly operator: "equals" | "contains" | "startsWith" | "matches" | "exists"
|
|
12
|
+
readonly value: unknown
|
|
13
|
+
readonly caseSensitive?: boolean
|
|
14
|
+
}>
|
|
15
|
+
readonly responses: readonly [ResponseConfigInput, ...ReadonlyArray<ResponseConfigInput>]
|
|
16
|
+
readonly responseMode?: "sequential" | "random" | "repeat"
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
interface ResponseConfigInput {
|
|
20
|
+
readonly status?: number
|
|
21
|
+
readonly headers?: Record<string, string>
|
|
22
|
+
readonly body?: unknown
|
|
23
|
+
readonly delay?: number
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface WithImposterConfig {
|
|
27
|
+
readonly port?: number
|
|
28
|
+
readonly name?: string
|
|
29
|
+
readonly stubs?: ReadonlyArray<StubConfig>
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export interface ImposterTestContext {
|
|
33
|
+
readonly id: string
|
|
34
|
+
readonly port: number
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const asPort = (n: number) => n as PortNumber
|
|
38
|
+
const asNes = (s: string) => s as NonEmptyString
|
|
39
|
+
|
|
40
|
+
const toStubPayload = (stub: StubConfig): CreateStubRequest => ({
|
|
41
|
+
predicates: (stub.predicates ?? []).map((p) => ({
|
|
42
|
+
field: p.field,
|
|
43
|
+
operator: p.operator,
|
|
44
|
+
value: p.value,
|
|
45
|
+
caseSensitive: p.caseSensitive ?? true
|
|
46
|
+
})),
|
|
47
|
+
responses: stub.responses.map((r) => ({
|
|
48
|
+
status: r.status ?? 200,
|
|
49
|
+
...(r.headers !== undefined ? { headers: r.headers } : {}),
|
|
50
|
+
...(r.body !== undefined ? { body: r.body } : {}),
|
|
51
|
+
...(r.delay !== undefined ? { delay: r.delay } : {})
|
|
52
|
+
})) as unknown as CreateStubRequest["responses"],
|
|
53
|
+
responseMode: stub.responseMode ?? "sequential"
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
export const withImposter = <A, E>(
|
|
57
|
+
config: WithImposterConfig,
|
|
58
|
+
testFn: (ctx: ImposterTestContext) => Effect.Effect<A, E, ImpostersClient>
|
|
59
|
+
): Effect.Effect<A, E | Error, ImpostersClient> =>
|
|
60
|
+
Effect.acquireUseRelease(
|
|
61
|
+
Effect.gen(function*() {
|
|
62
|
+
const client = yield* ImpostersClient
|
|
63
|
+
const imp = yield* client.imposters.createImposter({
|
|
64
|
+
payload: {
|
|
65
|
+
...(config.port !== undefined ? { port: asPort(config.port) } : {}),
|
|
66
|
+
...(config.name !== undefined ? { name: asNes(config.name) } : {}),
|
|
67
|
+
protocol: "HTTP" as const,
|
|
68
|
+
adminPath: "/_admin"
|
|
69
|
+
}
|
|
70
|
+
})
|
|
71
|
+
|
|
72
|
+
for (const stub of config.stubs ?? []) {
|
|
73
|
+
yield* client.imposters.addStub({
|
|
74
|
+
path: { imposterId: imp.id },
|
|
75
|
+
payload: toStubPayload(stub)
|
|
76
|
+
})
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
yield* client.imposters.updateImposter({
|
|
80
|
+
path: { id: imp.id },
|
|
81
|
+
payload: { status: "running" }
|
|
82
|
+
})
|
|
83
|
+
|
|
84
|
+
yield* Effect.sleep("150 millis")
|
|
85
|
+
|
|
86
|
+
return { id: imp.id as string, port: imp.port as number }
|
|
87
|
+
}),
|
|
88
|
+
(ctx) => testFn(ctx),
|
|
89
|
+
(ctx) =>
|
|
90
|
+
Effect.gen(function*() {
|
|
91
|
+
const client = yield* ImpostersClient
|
|
92
|
+
yield* client.imposters.deleteImposter({
|
|
93
|
+
path: { id: ctx.id as typeof ctx.id & NonEmptyString },
|
|
94
|
+
urlParams: { force: true }
|
|
95
|
+
}).pipe(Effect.catchAll(() => Effect.void))
|
|
96
|
+
}).pipe(Effect.catchAll(() => Effect.void))
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
export const makeTestServer = (fullLayer: Layer.Layer<any, any, never>) => {
|
|
100
|
+
const { dispose, handler } = HttpApiBuilder.toWebHandler(fullLayer as any)
|
|
101
|
+
const clientLayer = ImpostersClientLive().pipe(
|
|
102
|
+
Layer.provide(HandlerHttpClientLive(handler))
|
|
103
|
+
)
|
|
104
|
+
return { handler, dispose, clientLayer }
|
|
105
|
+
}
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
import * as Clock from "effect/Clock"
|
|
2
|
+
import * as Data from "effect/Data"
|
|
3
|
+
import * as DateTime from "effect/DateTime"
|
|
4
|
+
import * as Duration from "effect/Duration"
|
|
5
|
+
import * as Effect from "effect/Effect"
|
|
6
|
+
import type * as ParseResult from "effect/ParseResult"
|
|
7
|
+
import * as Schema from "effect/Schema"
|
|
8
|
+
import { Uuid } from "../services/Uuid"
|
|
9
|
+
|
|
10
|
+
// Schemas for validation
|
|
11
|
+
const ImposterNameSchema = Schema.String.pipe(
|
|
12
|
+
Schema.minLength(1),
|
|
13
|
+
Schema.maxLength(100),
|
|
14
|
+
Schema.pattern(/^[a-zA-Z0-9-_]+$/)
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
const PortSchema = Schema.Number.pipe(
|
|
18
|
+
Schema.int(),
|
|
19
|
+
Schema.between(1024, 65535)
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
type ImposterStatus = "running" | "stopped" | "starting" | "stopping"
|
|
23
|
+
|
|
24
|
+
const CreateImposterRequestSchema = Schema.Struct({
|
|
25
|
+
name: Schema.optional(ImposterNameSchema),
|
|
26
|
+
port: Schema.optional(PortSchema)
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
export interface ProxyConfigDomain {
|
|
30
|
+
readonly targetUrl: string
|
|
31
|
+
readonly mode: "passthrough" | "record"
|
|
32
|
+
readonly addHeaders?: Record<string, string> | undefined
|
|
33
|
+
readonly removeHeaders: ReadonlyArray<string>
|
|
34
|
+
readonly followRedirects: boolean
|
|
35
|
+
readonly timeout: number
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Domain types using tagged interfaces
|
|
39
|
+
export interface ImposterConfig {
|
|
40
|
+
readonly _tag: "ImposterConfig"
|
|
41
|
+
readonly id: string
|
|
42
|
+
readonly name: string
|
|
43
|
+
readonly port: number
|
|
44
|
+
readonly status: ImposterStatus
|
|
45
|
+
readonly createdAt: DateTime.Utc
|
|
46
|
+
readonly proxy?: ProxyConfigDomain | undefined
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export const ImposterConfig = Data.tagged<ImposterConfig>("ImposterConfig")
|
|
50
|
+
|
|
51
|
+
export interface CreateImposterRequest {
|
|
52
|
+
readonly _tag: "CreateImposterRequest"
|
|
53
|
+
readonly name?: string
|
|
54
|
+
readonly port?: number
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export const CreateImposterRequest = Data.tagged<CreateImposterRequest>("CreateImposterRequest")
|
|
58
|
+
|
|
59
|
+
export interface ImposterRef {
|
|
60
|
+
readonly _tag: "ImposterRef"
|
|
61
|
+
readonly config: ImposterConfig
|
|
62
|
+
readonly startTime: DateTime.Utc
|
|
63
|
+
readonly endpointCount: number
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export const ImposterRef = Data.tagged<ImposterRef>("ImposterRef")
|
|
67
|
+
|
|
68
|
+
// Tagged errors
|
|
69
|
+
export class ImposterError extends Data.TaggedError("ImposterError")<{
|
|
70
|
+
readonly reason: string
|
|
71
|
+
readonly cause?: unknown
|
|
72
|
+
}> {}
|
|
73
|
+
|
|
74
|
+
export class PortInUseError extends Data.TaggedError("PortInUseError")<{
|
|
75
|
+
readonly port: number
|
|
76
|
+
}> {}
|
|
77
|
+
|
|
78
|
+
export class ImposterNotFoundError extends Data.TaggedError("ImposterNotFoundError")<{
|
|
79
|
+
readonly id: string
|
|
80
|
+
}> {}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Parses and validates imposter creation request
|
|
84
|
+
*/
|
|
85
|
+
export const parseCreateImposterRequest = (
|
|
86
|
+
input: unknown
|
|
87
|
+
): Effect.Effect<typeof CreateImposterRequestSchema.Type, ParseResult.ParseError> =>
|
|
88
|
+
Schema.decodeUnknown(CreateImposterRequestSchema)(input)
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Creates a new imposter configuration from validated input
|
|
92
|
+
*/
|
|
93
|
+
export const createImposterConfig = (
|
|
94
|
+
validatedInput: typeof CreateImposterRequestSchema.Type
|
|
95
|
+
) =>
|
|
96
|
+
Effect.gen(function*() {
|
|
97
|
+
const uuid = yield* Uuid
|
|
98
|
+
const id = yield* uuid.generateShort
|
|
99
|
+
const name = validatedInput.name ?? id
|
|
100
|
+
|
|
101
|
+
return ImposterConfig({
|
|
102
|
+
id,
|
|
103
|
+
name,
|
|
104
|
+
port: validatedInput.port ?? 0, // Will be assigned by port allocator if 0
|
|
105
|
+
status: "starting",
|
|
106
|
+
createdAt: DateTime.unsafeNow()
|
|
107
|
+
})
|
|
108
|
+
})
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Creates a new imposter from raw input (parse + create)
|
|
112
|
+
*/
|
|
113
|
+
export const newImposterConfig = (input: unknown) =>
|
|
114
|
+
Effect.gen(function*() {
|
|
115
|
+
const validated = yield* parseCreateImposterRequest(input)
|
|
116
|
+
return yield* createImposterConfig(validated)
|
|
117
|
+
})
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Updates imposter status
|
|
121
|
+
*/
|
|
122
|
+
export const updateImposterStatus = (status: ImposterStatus) => (config: ImposterConfig): ImposterConfig =>
|
|
123
|
+
ImposterConfig({
|
|
124
|
+
...config,
|
|
125
|
+
status
|
|
126
|
+
})
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Updates imposter port
|
|
130
|
+
*/
|
|
131
|
+
export const updateImposterPort = (port: number) => (config: ImposterConfig): ImposterConfig =>
|
|
132
|
+
ImposterConfig({
|
|
133
|
+
...config,
|
|
134
|
+
port
|
|
135
|
+
})
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Calculates imposter uptime using Effect's Duration
|
|
139
|
+
*/
|
|
140
|
+
export const calculateUptime = (startTime: DateTime.Utc) =>
|
|
141
|
+
Effect.map(Clock.currentTimeMillis, (now) => Duration.millis(now - DateTime.toEpochMillis(startTime)))
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Creates an ImposterRef from config and runtime info
|
|
145
|
+
*/
|
|
146
|
+
export const createImposterRef =
|
|
147
|
+
(startTime: DateTime.Utc) => (endpointCount: number) => (config: ImposterConfig): ImposterRef =>
|
|
148
|
+
ImposterRef({
|
|
149
|
+
config,
|
|
150
|
+
startTime,
|
|
151
|
+
endpointCount
|
|
152
|
+
})
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Extracts imposter summary for API responses
|
|
156
|
+
*/
|
|
157
|
+
export const toImposterSummary = (ref: ImposterRef) =>
|
|
158
|
+
Effect.map(calculateUptime(ref.startTime), (uptime) => ({
|
|
159
|
+
id: ref.config.id,
|
|
160
|
+
name: ref.config.name,
|
|
161
|
+
port: ref.config.port,
|
|
162
|
+
status: ref.config.status,
|
|
163
|
+
endpointCount: ref.endpointCount,
|
|
164
|
+
createdAt: DateTime.formatIso(ref.config.createdAt),
|
|
165
|
+
uptime: Duration.format(uptime)
|
|
166
|
+
}))
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Gets uptime in human readable format
|
|
170
|
+
*/
|
|
171
|
+
export const getUptimeFormatted = (ref: ImposterRef) => Effect.map(calculateUptime(ref.startTime), Duration.format)
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Checks if imposter is running
|
|
175
|
+
*/
|
|
176
|
+
export const isRunning = (config: ImposterConfig): boolean => config.status === "running"
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Checks if imposter can be started
|
|
180
|
+
*/
|
|
181
|
+
export const canStart = (config: ImposterConfig): boolean => config.status === "stopped" || config.status === "starting"
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Checks if imposter can be stopped
|
|
185
|
+
*/
|
|
186
|
+
export const canStop = (config: ImposterConfig): boolean => config.status === "running" || config.status === "stopping"
|
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
import * as Data from "effect/Data"
|
|
2
|
+
import * as DateTime from "effect/DateTime"
|
|
3
|
+
import * as Duration from "effect/Duration"
|
|
4
|
+
import * as Effect from "effect/Effect"
|
|
5
|
+
import { pipe } from "effect/Function"
|
|
6
|
+
import * as Option from "effect/Option"
|
|
7
|
+
import type * as ParseResult from "effect/ParseResult"
|
|
8
|
+
import * as Schema from "effect/Schema"
|
|
9
|
+
import { Uuid } from "../services/Uuid"
|
|
10
|
+
|
|
11
|
+
const HttpMethodSchema = Schema.Literal("GET", "POST", "PUT", "DELETE", "PATCH", "HEAD", "OPTIONS")
|
|
12
|
+
|
|
13
|
+
const StatusCodeSchema = Schema.Number.pipe(Schema.int(), Schema.between(100, 599))
|
|
14
|
+
|
|
15
|
+
const DelaySchema = Schema.Number.pipe(Schema.int(), Schema.between(0, 60000))
|
|
16
|
+
|
|
17
|
+
const PathSchema = Schema.String.pipe(
|
|
18
|
+
Schema.startsWith("/"),
|
|
19
|
+
Schema.pattern(/^\/[a-zA-Z0-9\-._~!$&'()*+,;=:@/{}[\]]*$/)
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
const ResponseSchema = Schema.Struct({
|
|
23
|
+
status: Schema.optionalWith(StatusCodeSchema, { default: () => 200 }),
|
|
24
|
+
headers: Schema.optional(
|
|
25
|
+
Schema.Record({ key: Schema.String, value: Schema.String })
|
|
26
|
+
),
|
|
27
|
+
body: Schema.Unknown
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
const CreateRouteRequestSchema = Schema.Struct({
|
|
31
|
+
id: Schema.optional(Schema.String),
|
|
32
|
+
path: PathSchema,
|
|
33
|
+
method: Schema.optionalWith(HttpMethodSchema, { default: () => "GET" }),
|
|
34
|
+
response: ResponseSchema,
|
|
35
|
+
delay: Schema.optional(DelaySchema)
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
export interface Route {
|
|
39
|
+
readonly _tag: "Route"
|
|
40
|
+
readonly id: string
|
|
41
|
+
readonly path: string
|
|
42
|
+
readonly method: typeof HttpMethodSchema.Type
|
|
43
|
+
readonly response: Response
|
|
44
|
+
readonly delay: Option.Option<Duration.Duration>
|
|
45
|
+
readonly createdAt: DateTime.Utc
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export const Route = Data.tagged<Route>("Route")
|
|
49
|
+
|
|
50
|
+
export interface Response {
|
|
51
|
+
readonly _tag: "Response"
|
|
52
|
+
readonly status: number
|
|
53
|
+
readonly headers: Option.Option<Record<string, string>>
|
|
54
|
+
readonly body: unknown
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export const Response = Data.tagged<Response>("Response")
|
|
58
|
+
|
|
59
|
+
export interface CreateRouteRequest {
|
|
60
|
+
readonly _tag: "CreateRouteRequest"
|
|
61
|
+
readonly id?: string
|
|
62
|
+
readonly path: string
|
|
63
|
+
readonly method?: string
|
|
64
|
+
readonly response: {
|
|
65
|
+
readonly status?: number
|
|
66
|
+
readonly headers?: Record<string, string>
|
|
67
|
+
readonly body: unknown
|
|
68
|
+
}
|
|
69
|
+
readonly delay?: number
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export const CreateRouteRequest = Data.tagged<CreateRouteRequest>("CreateRouteRequest")
|
|
73
|
+
|
|
74
|
+
// Tagged errors
|
|
75
|
+
export class RouteError extends Data.TaggedError("RouteError")<{
|
|
76
|
+
readonly reason: string
|
|
77
|
+
readonly field?: string
|
|
78
|
+
readonly value?: unknown
|
|
79
|
+
}> {}
|
|
80
|
+
|
|
81
|
+
export class RouteNotFoundError extends Data.TaggedError("RouteNotFoundError")<{
|
|
82
|
+
readonly id: string
|
|
83
|
+
}> {}
|
|
84
|
+
|
|
85
|
+
// Pure functions following Effect patterns
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Parses and validates route creation request
|
|
89
|
+
*/
|
|
90
|
+
export const parseCreateRouteRequest = (
|
|
91
|
+
input: unknown
|
|
92
|
+
): Effect.Effect<typeof CreateRouteRequestSchema.Type, ParseResult.ParseError> =>
|
|
93
|
+
Schema.decodeUnknown(CreateRouteRequestSchema)(input)
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Creates a new route from validated input
|
|
97
|
+
*/
|
|
98
|
+
export const createRoute = (
|
|
99
|
+
validatedInput: typeof CreateRouteRequestSchema.Type
|
|
100
|
+
) =>
|
|
101
|
+
Effect.gen(function*() {
|
|
102
|
+
const uuid = yield* Uuid
|
|
103
|
+
const id = validatedInput.id ?? (yield* uuid.generate)
|
|
104
|
+
|
|
105
|
+
const response = Response({
|
|
106
|
+
status: validatedInput.response.status,
|
|
107
|
+
headers: Option.fromNullable(validatedInput.response.headers),
|
|
108
|
+
body: validatedInput.response.body
|
|
109
|
+
})
|
|
110
|
+
|
|
111
|
+
return Route({
|
|
112
|
+
id,
|
|
113
|
+
path: validatedInput.path,
|
|
114
|
+
method: validatedInput.method,
|
|
115
|
+
response,
|
|
116
|
+
delay: pipe(
|
|
117
|
+
validatedInput.delay,
|
|
118
|
+
Option.fromNullable,
|
|
119
|
+
Option.map(Duration.millis)
|
|
120
|
+
),
|
|
121
|
+
createdAt: DateTime.unsafeNow()
|
|
122
|
+
})
|
|
123
|
+
})
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Creates a new route from raw input (parse + create)
|
|
127
|
+
*/
|
|
128
|
+
export const newRoute = (input: unknown) =>
|
|
129
|
+
Effect.gen(function*() {
|
|
130
|
+
const validated = yield* parseCreateRouteRequest(input)
|
|
131
|
+
return yield* createRoute(validated)
|
|
132
|
+
})
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Updates a route preserving creation time and ID
|
|
136
|
+
*/
|
|
137
|
+
export const updateRoute = (updates: Partial<typeof CreateRouteRequestSchema.Type>) => (existingRoute: Route) =>
|
|
138
|
+
Effect.gen(function*() {
|
|
139
|
+
const updateRequest = {
|
|
140
|
+
id: existingRoute.id,
|
|
141
|
+
path: updates.path ?? existingRoute.path,
|
|
142
|
+
method: updates.method ?? existingRoute.method,
|
|
143
|
+
response: updates.response ?? {
|
|
144
|
+
status: existingRoute.response.status,
|
|
145
|
+
headers: Option.getOrUndefined(existingRoute.response.headers),
|
|
146
|
+
body: existingRoute.response.body
|
|
147
|
+
},
|
|
148
|
+
delay: updates.delay ?? pipe(
|
|
149
|
+
existingRoute.delay,
|
|
150
|
+
Option.map(Duration.toMillis),
|
|
151
|
+
Option.getOrUndefined
|
|
152
|
+
)
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
const updated = yield* createRoute(updateRequest)
|
|
156
|
+
|
|
157
|
+
return Route({
|
|
158
|
+
...updated,
|
|
159
|
+
createdAt: existingRoute.createdAt
|
|
160
|
+
})
|
|
161
|
+
})
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Substitutes parameters in a string using Effect's String utilities
|
|
165
|
+
*/
|
|
166
|
+
const substituteInString = (params: Record<string, string>) => (str: string): string =>
|
|
167
|
+
Object.entries(params).reduce((acc, [key, value]) => acc.replaceAll(`{{${key}}}`, value), str)
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Recursively substitutes parameters in unknown data structure
|
|
171
|
+
*/
|
|
172
|
+
export const substituteParams = (params: Record<string, string>) => (body: unknown): unknown => {
|
|
173
|
+
if (typeof body === "string") return substituteInString(params)(body)
|
|
174
|
+
if (Array.isArray(body)) return body.map(substituteParams(params))
|
|
175
|
+
// TypeScript narrows `typeof body === "object"` to `object | null`, but we've excluded null and Array above.
|
|
176
|
+
// The cast to Record<string, unknown> is necessary since TS cannot narrow `object` to an indexable type.
|
|
177
|
+
if (body !== null && typeof body === "object") {
|
|
178
|
+
return Object.fromEntries(
|
|
179
|
+
Object.entries(body as Record<string, unknown>).map(([k, v]) => [k, substituteParams(params)(v)])
|
|
180
|
+
)
|
|
181
|
+
}
|
|
182
|
+
return body
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Creates a response with substituted parameters
|
|
187
|
+
*/
|
|
188
|
+
export const createResponseWithParams = (params: Record<string, string>) => (response: Response): Response =>
|
|
189
|
+
Response({
|
|
190
|
+
...response,
|
|
191
|
+
body: substituteParams(params)(response.body)
|
|
192
|
+
})
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Extracts route summary for API responses
|
|
196
|
+
*/
|
|
197
|
+
export const toRouteSummary = (route: Route) => ({
|
|
198
|
+
id: route.id,
|
|
199
|
+
path: route.path,
|
|
200
|
+
method: route.method,
|
|
201
|
+
status: route.response.status,
|
|
202
|
+
hasDelay: Option.isSome(route.delay),
|
|
203
|
+
delayMs: pipe(
|
|
204
|
+
route.delay,
|
|
205
|
+
Option.map(Duration.toMillis),
|
|
206
|
+
Option.getOrUndefined
|
|
207
|
+
),
|
|
208
|
+
createdAt: DateTime.formatIso(route.createdAt)
|
|
209
|
+
})
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* Gets delay in milliseconds for compatibility
|
|
213
|
+
*/
|
|
214
|
+
export const getDelayMillis = (route: Route): Option.Option<number> => Option.map(route.delay, Duration.toMillis)
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* Checks if route has custom headers
|
|
218
|
+
*/
|
|
219
|
+
export const hasCustomHeaders = (route: Route): boolean => Option.isSome(route.response.headers)
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* Gets headers as record or empty object
|
|
223
|
+
*/
|
|
224
|
+
export const getHeaders = (route: Route): Record<string, string> =>
|
|
225
|
+
pipe(
|
|
226
|
+
route.response.headers,
|
|
227
|
+
Option.getOrElse(() => ({}))
|
|
228
|
+
)
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* Checks if route has delay configured
|
|
232
|
+
*/
|
|
233
|
+
export const hasDelay = (route: Route): boolean => Option.isSome(route.delay)
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* Creates a minimal route for testing
|
|
237
|
+
*/
|
|
238
|
+
export const createMinimalRoute = (path: string) => (method: typeof HttpMethodSchema.Type = "GET") =>
|
|
239
|
+
Effect.gen(function*() {
|
|
240
|
+
const uuid = yield* Uuid
|
|
241
|
+
const id = yield* uuid.generateShort
|
|
242
|
+
|
|
243
|
+
return Route({
|
|
244
|
+
id,
|
|
245
|
+
path,
|
|
246
|
+
method,
|
|
247
|
+
response: Response({
|
|
248
|
+
status: 200,
|
|
249
|
+
headers: Option.none(),
|
|
250
|
+
body: { message: "OK" }
|
|
251
|
+
}),
|
|
252
|
+
delay: Option.none(),
|
|
253
|
+
createdAt: DateTime.unsafeNow()
|
|
254
|
+
})
|
|
255
|
+
})
|