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.
Files changed (426) hide show
  1. package/LICENSE +21 -0
  2. package/Program/package.json +6 -0
  3. package/README.md +365 -0
  4. package/api/AdminApi/package.json +6 -0
  5. package/api/ApiErrors/package.json +6 -0
  6. package/api/ApiSchemas/package.json +6 -0
  7. package/api/Conversions/package.json +6 -0
  8. package/api/ImpostersGroup/package.json +6 -0
  9. package/api/ImpostersHandlers/package.json +6 -0
  10. package/api/SystemGroup/package.json +6 -0
  11. package/api/SystemHandlers/package.json +6 -0
  12. package/bin/imposters +47 -0
  13. package/cli/Commands/package.json +6 -0
  14. package/cli/ConfigLoader/package.json +6 -0
  15. package/client/HandlerHttpClient/package.json +6 -0
  16. package/client/ImpostersClient/package.json +6 -0
  17. package/client/testing/package.json +6 -0
  18. package/dist/cjs/Program.js +4 -0
  19. package/dist/cjs/Program.js.map +1 -0
  20. package/dist/cjs/api/AdminApi.js +11 -0
  21. package/dist/cjs/api/AdminApi.js.map +1 -0
  22. package/dist/cjs/api/ApiErrors.js +30 -0
  23. package/dist/cjs/api/ApiErrors.js.map +1 -0
  24. package/dist/cjs/api/ApiSchemas.js +36 -0
  25. package/dist/cjs/api/ApiSchemas.js.map +1 -0
  26. package/dist/cjs/api/Conversions.js +41 -0
  27. package/dist/cjs/api/Conversions.js.map +1 -0
  28. package/dist/cjs/api/ImpostersGroup.js +37 -0
  29. package/dist/cjs/api/ImpostersGroup.js.map +1 -0
  30. package/dist/cjs/api/ImpostersHandlers.js +361 -0
  31. package/dist/cjs/api/ImpostersHandlers.js.map +1 -0
  32. package/dist/cjs/api/SystemGroup.js +12 -0
  33. package/dist/cjs/api/SystemGroup.js.map +1 -0
  34. package/dist/cjs/api/SystemHandlers.js +74 -0
  35. package/dist/cjs/api/SystemHandlers.js.map +1 -0
  36. package/dist/cjs/cli/Commands.js +104 -0
  37. package/dist/cjs/cli/Commands.js.map +1 -0
  38. package/dist/cjs/cli/ConfigLoader.js +34 -0
  39. package/dist/cjs/cli/ConfigLoader.js.map +1 -0
  40. package/dist/cjs/client/HandlerHttpClient.js +50 -0
  41. package/dist/cjs/client/HandlerHttpClient.js.map +1 -0
  42. package/dist/cjs/client/ImpostersClient.js +20 -0
  43. package/dist/cjs/client/ImpostersClient.js.map +1 -0
  44. package/dist/cjs/client/index.js +57 -0
  45. package/dist/cjs/client/index.js.map +1 -0
  46. package/dist/cjs/client/testing.js +94 -0
  47. package/dist/cjs/client/testing.js.map +1 -0
  48. package/dist/cjs/domain/imposter.js +125 -0
  49. package/dist/cjs/domain/imposter.js.map +1 -0
  50. package/dist/cjs/domain/route.js +185 -0
  51. package/dist/cjs/domain/route.js.map +1 -0
  52. package/dist/cjs/index.js +106 -0
  53. package/dist/cjs/index.js.map +1 -0
  54. package/dist/cjs/layers/ApiLayer.js +18 -0
  55. package/dist/cjs/layers/ApiLayer.js.map +1 -0
  56. package/dist/cjs/layers/MainLayer.js +27 -0
  57. package/dist/cjs/layers/MainLayer.js.map +1 -0
  58. package/dist/cjs/matching/ExpressionEvaluator.js +103 -0
  59. package/dist/cjs/matching/ExpressionEvaluator.js.map +1 -0
  60. package/dist/cjs/matching/RequestMatcher.js +145 -0
  61. package/dist/cjs/matching/RequestMatcher.js.map +1 -0
  62. package/dist/cjs/matching/ResponseGenerator.js +80 -0
  63. package/dist/cjs/matching/ResponseGenerator.js.map +1 -0
  64. package/dist/cjs/matching/TemplateEngine.js +55 -0
  65. package/dist/cjs/matching/TemplateEngine.js.map +1 -0
  66. package/dist/cjs/repositories/ImposterRepository.js +118 -0
  67. package/dist/cjs/repositories/ImposterRepository.js.map +1 -0
  68. package/dist/cjs/schemas/ConfigFileSchema.js +44 -0
  69. package/dist/cjs/schemas/ConfigFileSchema.js.map +1 -0
  70. package/dist/cjs/schemas/ImposterSchema.js +202 -0
  71. package/dist/cjs/schemas/ImposterSchema.js.map +1 -0
  72. package/dist/cjs/schemas/RequestLogSchema.js +51 -0
  73. package/dist/cjs/schemas/RequestLogSchema.js.map +1 -0
  74. package/dist/cjs/schemas/StubSchema.js +84 -0
  75. package/dist/cjs/schemas/StubSchema.js.map +1 -0
  76. package/dist/cjs/schemas/common.js +67 -0
  77. package/dist/cjs/schemas/common.js.map +1 -0
  78. package/dist/cjs/server/AdminServer.js +36 -0
  79. package/dist/cjs/server/AdminServer.js.map +1 -0
  80. package/dist/cjs/server/BunServer.js +13 -0
  81. package/dist/cjs/server/BunServer.js.map +1 -0
  82. package/dist/cjs/server/FiberManager.js +21 -0
  83. package/dist/cjs/server/FiberManager.js.map +1 -0
  84. package/dist/cjs/server/ImposterServer.js +234 -0
  85. package/dist/cjs/server/ImposterServer.js.map +1 -0
  86. package/dist/cjs/services/AppConfig.js +18 -0
  87. package/dist/cjs/services/AppConfig.js.map +1 -0
  88. package/dist/cjs/services/MetricsService.js +113 -0
  89. package/dist/cjs/services/MetricsService.js.map +1 -0
  90. package/dist/cjs/services/PortAllocator.js +50 -0
  91. package/dist/cjs/services/PortAllocator.js.map +1 -0
  92. package/dist/cjs/services/ProxyService.js +109 -0
  93. package/dist/cjs/services/ProxyService.js.map +1 -0
  94. package/dist/cjs/services/RequestLogger.js +60 -0
  95. package/dist/cjs/services/RequestLogger.js.map +1 -0
  96. package/dist/cjs/services/Uuid.js +10 -0
  97. package/dist/cjs/services/Uuid.js.map +1 -0
  98. package/dist/cjs/services/UuidLive.js +16 -0
  99. package/dist/cjs/services/UuidLive.js.map +1 -0
  100. package/dist/cjs/ui/UiRouter.js +242 -0
  101. package/dist/cjs/ui/UiRouter.js.map +1 -0
  102. package/dist/cjs/ui/admin/AdminLayout.js +36 -0
  103. package/dist/cjs/ui/admin/AdminLayout.js.map +1 -0
  104. package/dist/cjs/ui/admin/AdminUiRouter.js +155 -0
  105. package/dist/cjs/ui/admin/AdminUiRouter.js.map +1 -0
  106. package/dist/cjs/ui/admin/pages/AdminDashboard.js +55 -0
  107. package/dist/cjs/ui/admin/pages/AdminDashboard.js.map +1 -0
  108. package/dist/cjs/ui/admin/partials.js +64 -0
  109. package/dist/cjs/ui/admin/partials.js.map +1 -0
  110. package/dist/cjs/ui/html.js +42 -0
  111. package/dist/cjs/ui/html.js.map +1 -0
  112. package/dist/cjs/ui/layout.js +39 -0
  113. package/dist/cjs/ui/layout.js.map +1 -0
  114. package/dist/cjs/ui/pages/dashboard.js +51 -0
  115. package/dist/cjs/ui/pages/dashboard.js.map +1 -0
  116. package/dist/cjs/ui/pages/request-detail.js +119 -0
  117. package/dist/cjs/ui/pages/request-detail.js.map +1 -0
  118. package/dist/cjs/ui/pages/requests.js +120 -0
  119. package/dist/cjs/ui/pages/requests.js.map +1 -0
  120. package/dist/cjs/ui/pages/stubs.js +46 -0
  121. package/dist/cjs/ui/pages/stubs.js.map +1 -0
  122. package/dist/cjs/ui/partials.js +104 -0
  123. package/dist/cjs/ui/partials.js.map +1 -0
  124. package/dist/dts/Program.d.ts +2 -0
  125. package/dist/dts/Program.d.ts.map +1 -0
  126. package/dist/dts/api/AdminApi.d.ts +490 -0
  127. package/dist/dts/api/AdminApi.d.ts.map +1 -0
  128. package/dist/dts/api/ApiErrors.d.ts +26 -0
  129. package/dist/dts/api/ApiErrors.d.ts.map +1 -0
  130. package/dist/dts/api/ApiSchemas.d.ts +36 -0
  131. package/dist/dts/api/ApiSchemas.d.ts.map +1 -0
  132. package/dist/dts/api/Conversions.d.ts +7 -0
  133. package/dist/dts/api/Conversions.d.ts.map +1 -0
  134. package/dist/dts/api/ImpostersGroup.d.ts +448 -0
  135. package/dist/dts/api/ImpostersGroup.d.ts.map +1 -0
  136. package/dist/dts/api/ImpostersHandlers.d.ts +9 -0
  137. package/dist/dts/api/ImpostersHandlers.d.ts.map +1 -0
  138. package/dist/dts/api/SystemGroup.d.ts +46 -0
  139. package/dist/dts/api/SystemGroup.d.ts.map +1 -0
  140. package/dist/dts/api/SystemHandlers.d.ts +4 -0
  141. package/dist/dts/api/SystemHandlers.d.ts.map +1 -0
  142. package/dist/dts/cli/Commands.d.ts +4 -0
  143. package/dist/dts/cli/Commands.d.ts.map +1 -0
  144. package/dist/dts/cli/ConfigLoader.d.ts +13 -0
  145. package/dist/dts/cli/ConfigLoader.d.ts.map +1 -0
  146. package/dist/dts/client/HandlerHttpClient.d.ts +5 -0
  147. package/dist/dts/client/HandlerHttpClient.d.ts.map +1 -0
  148. package/dist/dts/client/ImpostersClient.d.ts +1868 -0
  149. package/dist/dts/client/ImpostersClient.d.ts.map +1 -0
  150. package/dist/dts/client/index.d.ts +6 -0
  151. package/dist/dts/client/index.d.ts.map +1 -0
  152. package/dist/dts/client/testing.d.ts +35 -0
  153. package/dist/dts/client/testing.d.ts.map +1 -0
  154. package/dist/dts/domain/imposter.d.ts +123 -0
  155. package/dist/dts/domain/imposter.d.ts.map +1 -0
  156. package/dist/dts/domain/route.d.ts +128 -0
  157. package/dist/dts/domain/route.d.ts.map +1 -0
  158. package/dist/dts/index.d.ts +60 -0
  159. package/dist/dts/index.d.ts.map +1 -0
  160. package/dist/dts/layers/ApiLayer.d.ts +3 -0
  161. package/dist/dts/layers/ApiLayer.d.ts.map +1 -0
  162. package/dist/dts/layers/MainLayer.d.ts +3 -0
  163. package/dist/dts/layers/MainLayer.d.ts.map +1 -0
  164. package/dist/dts/matching/ExpressionEvaluator.d.ts +11 -0
  165. package/dist/dts/matching/ExpressionEvaluator.d.ts.map +1 -0
  166. package/dist/dts/matching/RequestMatcher.d.ts +13 -0
  167. package/dist/dts/matching/RequestMatcher.d.ts.map +1 -0
  168. package/dist/dts/matching/ResponseGenerator.d.ts +9 -0
  169. package/dist/dts/matching/ResponseGenerator.d.ts.map +1 -0
  170. package/dist/dts/matching/TemplateEngine.d.ts +4 -0
  171. package/dist/dts/matching/TemplateEngine.d.ts.map +1 -0
  172. package/dist/dts/repositories/ImposterRepository.d.ts +33 -0
  173. package/dist/dts/repositories/ImposterRepository.d.ts.map +1 -0
  174. package/dist/dts/schemas/ConfigFileSchema.d.ts +142 -0
  175. package/dist/dts/schemas/ConfigFileSchema.d.ts.map +1 -0
  176. package/dist/dts/schemas/ImposterSchema.d.ts +368 -0
  177. package/dist/dts/schemas/ImposterSchema.d.ts.map +1 -0
  178. package/dist/dts/schemas/RequestLogSchema.d.ts +36 -0
  179. package/dist/dts/schemas/RequestLogSchema.d.ts.map +1 -0
  180. package/dist/dts/schemas/StubSchema.d.ts +112 -0
  181. package/dist/dts/schemas/StubSchema.d.ts.map +1 -0
  182. package/dist/dts/schemas/common.d.ts +56 -0
  183. package/dist/dts/schemas/common.d.ts.map +1 -0
  184. package/dist/dts/server/AdminServer.d.ts +11 -0
  185. package/dist/dts/server/AdminServer.d.ts.map +1 -0
  186. package/dist/dts/server/BunServer.d.ts +17 -0
  187. package/dist/dts/server/BunServer.d.ts.map +1 -0
  188. package/dist/dts/server/FiberManager.d.ts +12 -0
  189. package/dist/dts/server/FiberManager.d.ts.map +1 -0
  190. package/dist/dts/server/ImposterServer.d.ts +29 -0
  191. package/dist/dts/server/ImposterServer.d.ts.map +1 -0
  192. package/dist/dts/services/AppConfig.d.ts +14 -0
  193. package/dist/dts/services/AppConfig.d.ts.map +1 -0
  194. package/dist/dts/services/MetricsService.d.ts +26 -0
  195. package/dist/dts/services/MetricsService.d.ts.map +1 -0
  196. package/dist/dts/services/PortAllocator.d.ts +29 -0
  197. package/dist/dts/services/PortAllocator.d.ts.map +1 -0
  198. package/dist/dts/services/ProxyService.d.ts +24 -0
  199. package/dist/dts/services/ProxyService.d.ts.map +1 -0
  200. package/dist/dts/services/RequestLogger.d.ts +23 -0
  201. package/dist/dts/services/RequestLogger.d.ts.map +1 -0
  202. package/dist/dts/services/Uuid.d.ts +9 -0
  203. package/dist/dts/services/Uuid.d.ts.map +1 -0
  204. package/dist/dts/services/UuidLive.d.ts +4 -0
  205. package/dist/dts/services/UuidLive.d.ts.map +1 -0
  206. package/dist/dts/ui/UiRouter.d.ts +15 -0
  207. package/dist/dts/ui/UiRouter.d.ts.map +1 -0
  208. package/dist/dts/ui/admin/AdminLayout.d.ts +7 -0
  209. package/dist/dts/ui/admin/AdminLayout.d.ts.map +1 -0
  210. package/dist/dts/ui/admin/AdminUiRouter.d.ts +6 -0
  211. package/dist/dts/ui/admin/AdminUiRouter.d.ts.map +1 -0
  212. package/dist/dts/ui/admin/pages/AdminDashboard.d.ts +7 -0
  213. package/dist/dts/ui/admin/pages/AdminDashboard.d.ts.map +1 -0
  214. package/dist/dts/ui/admin/partials.d.ts +14 -0
  215. package/dist/dts/ui/admin/partials.d.ts.map +1 -0
  216. package/dist/dts/ui/html.d.ts +12 -0
  217. package/dist/dts/ui/html.d.ts.map +1 -0
  218. package/dist/dts/ui/layout.d.ts +9 -0
  219. package/dist/dts/ui/layout.d.ts.map +1 -0
  220. package/dist/dts/ui/pages/dashboard.d.ts +10 -0
  221. package/dist/dts/ui/pages/dashboard.d.ts.map +1 -0
  222. package/dist/dts/ui/pages/request-detail.d.ts +11 -0
  223. package/dist/dts/ui/pages/request-detail.d.ts.map +1 -0
  224. package/dist/dts/ui/pages/requests.d.ts +15 -0
  225. package/dist/dts/ui/pages/requests.d.ts.map +1 -0
  226. package/dist/dts/ui/pages/stubs.d.ts +8 -0
  227. package/dist/dts/ui/pages/stubs.d.ts.map +1 -0
  228. package/dist/dts/ui/partials.d.ts +13 -0
  229. package/dist/dts/ui/partials.d.ts.map +1 -0
  230. package/dist/esm/Program.js +2 -0
  231. package/dist/esm/Program.js.map +1 -0
  232. package/dist/esm/api/AdminApi.js +5 -0
  233. package/dist/esm/api/AdminApi.js.map +1 -0
  234. package/dist/esm/api/ApiErrors.js +20 -0
  235. package/dist/esm/api/ApiErrors.js.map +1 -0
  236. package/dist/esm/api/ApiSchemas.js +29 -0
  237. package/dist/esm/api/ApiSchemas.js.map +1 -0
  238. package/dist/esm/api/Conversions.js +32 -0
  239. package/dist/esm/api/Conversions.js.map +1 -0
  240. package/dist/esm/api/ImpostersGroup.js +30 -0
  241. package/dist/esm/api/ImpostersGroup.js.map +1 -0
  242. package/dist/esm/api/ImpostersHandlers.js +354 -0
  243. package/dist/esm/api/ImpostersHandlers.js.map +1 -0
  244. package/dist/esm/api/SystemGroup.js +6 -0
  245. package/dist/esm/api/SystemGroup.js.map +1 -0
  246. package/dist/esm/api/SystemHandlers.js +67 -0
  247. package/dist/esm/api/SystemHandlers.js.map +1 -0
  248. package/dist/esm/cli/Commands.js +98 -0
  249. package/dist/esm/cli/Commands.js.map +1 -0
  250. package/dist/esm/cli/ConfigLoader.js +25 -0
  251. package/dist/esm/cli/ConfigLoader.js.map +1 -0
  252. package/dist/esm/client/HandlerHttpClient.js +42 -0
  253. package/dist/esm/client/HandlerHttpClient.js.map +1 -0
  254. package/dist/esm/client/ImpostersClient.js +10 -0
  255. package/dist/esm/client/ImpostersClient.js.map +1 -0
  256. package/dist/esm/client/index.js +4 -0
  257. package/dist/esm/client/index.js.map +1 -0
  258. package/dist/esm/client/testing.js +86 -0
  259. package/dist/esm/client/testing.js.map +1 -0
  260. package/dist/esm/domain/imposter.js +103 -0
  261. package/dist/esm/domain/imposter.js.map +1 -0
  262. package/dist/esm/domain/route.js +164 -0
  263. package/dist/esm/domain/route.js.map +1 -0
  264. package/dist/esm/index.js +60 -0
  265. package/dist/esm/index.js.map +1 -0
  266. package/dist/esm/layers/ApiLayer.js +11 -0
  267. package/dist/esm/layers/ApiLayer.js.map +1 -0
  268. package/dist/esm/layers/MainLayer.js +20 -0
  269. package/dist/esm/layers/MainLayer.js.map +1 -0
  270. package/dist/esm/matching/ExpressionEvaluator.js +94 -0
  271. package/dist/esm/matching/ExpressionEvaluator.js.map +1 -0
  272. package/dist/esm/matching/RequestMatcher.js +135 -0
  273. package/dist/esm/matching/RequestMatcher.js.map +1 -0
  274. package/dist/esm/matching/ResponseGenerator.js +71 -0
  275. package/dist/esm/matching/ResponseGenerator.js.map +1 -0
  276. package/dist/esm/matching/TemplateEngine.js +47 -0
  277. package/dist/esm/matching/TemplateEngine.js.map +1 -0
  278. package/dist/esm/package.json +4 -0
  279. package/dist/esm/repositories/ImposterRepository.js +110 -0
  280. package/dist/esm/repositories/ImposterRepository.js.map +1 -0
  281. package/dist/esm/schemas/ConfigFileSchema.js +37 -0
  282. package/dist/esm/schemas/ConfigFileSchema.js.map +1 -0
  283. package/dist/esm/schemas/ImposterSchema.js +195 -0
  284. package/dist/esm/schemas/ImposterSchema.js.map +1 -0
  285. package/dist/esm/schemas/RequestLogSchema.js +44 -0
  286. package/dist/esm/schemas/RequestLogSchema.js.map +1 -0
  287. package/dist/esm/schemas/StubSchema.js +77 -0
  288. package/dist/esm/schemas/StubSchema.js.map +1 -0
  289. package/dist/esm/schemas/common.js +59 -0
  290. package/dist/esm/schemas/common.js.map +1 -0
  291. package/dist/esm/server/AdminServer.js +27 -0
  292. package/dist/esm/server/AdminServer.js.map +1 -0
  293. package/dist/esm/server/BunServer.js +6 -0
  294. package/dist/esm/server/BunServer.js.map +1 -0
  295. package/dist/esm/server/FiberManager.js +14 -0
  296. package/dist/esm/server/FiberManager.js.map +1 -0
  297. package/dist/esm/server/ImposterServer.js +225 -0
  298. package/dist/esm/server/ImposterServer.js.map +1 -0
  299. package/dist/esm/services/AppConfig.js +11 -0
  300. package/dist/esm/services/AppConfig.js.map +1 -0
  301. package/dist/esm/services/MetricsService.js +105 -0
  302. package/dist/esm/services/MetricsService.js.map +1 -0
  303. package/dist/esm/services/PortAllocator.js +41 -0
  304. package/dist/esm/services/PortAllocator.js.map +1 -0
  305. package/dist/esm/services/ProxyService.js +101 -0
  306. package/dist/esm/services/ProxyService.js.map +1 -0
  307. package/dist/esm/services/RequestLogger.js +53 -0
  308. package/dist/esm/services/RequestLogger.js.map +1 -0
  309. package/dist/esm/services/Uuid.js +3 -0
  310. package/dist/esm/services/Uuid.js.map +1 -0
  311. package/dist/esm/services/UuidLive.js +9 -0
  312. package/dist/esm/services/UuidLive.js.map +1 -0
  313. package/dist/esm/ui/UiRouter.js +235 -0
  314. package/dist/esm/ui/UiRouter.js.map +1 -0
  315. package/dist/esm/ui/admin/AdminLayout.js +29 -0
  316. package/dist/esm/ui/admin/AdminLayout.js.map +1 -0
  317. package/dist/esm/ui/admin/AdminUiRouter.js +148 -0
  318. package/dist/esm/ui/admin/AdminUiRouter.js.map +1 -0
  319. package/dist/esm/ui/admin/pages/AdminDashboard.js +48 -0
  320. package/dist/esm/ui/admin/pages/AdminDashboard.js.map +1 -0
  321. package/dist/esm/ui/admin/partials.js +54 -0
  322. package/dist/esm/ui/admin/partials.js.map +1 -0
  323. package/dist/esm/ui/html.js +32 -0
  324. package/dist/esm/ui/html.js.map +1 -0
  325. package/dist/esm/ui/layout.js +32 -0
  326. package/dist/esm/ui/layout.js.map +1 -0
  327. package/dist/esm/ui/pages/dashboard.js +44 -0
  328. package/dist/esm/ui/pages/dashboard.js.map +1 -0
  329. package/dist/esm/ui/pages/request-detail.js +112 -0
  330. package/dist/esm/ui/pages/request-detail.js.map +1 -0
  331. package/dist/esm/ui/pages/requests.js +112 -0
  332. package/dist/esm/ui/pages/requests.js.map +1 -0
  333. package/dist/esm/ui/pages/stubs.js +39 -0
  334. package/dist/esm/ui/pages/stubs.js.map +1 -0
  335. package/dist/esm/ui/partials.js +91 -0
  336. package/dist/esm/ui/partials.js.map +1 -0
  337. package/domain/imposter/package.json +6 -0
  338. package/domain/route/package.json +6 -0
  339. package/layers/ApiLayer/package.json +6 -0
  340. package/layers/MainLayer/package.json +6 -0
  341. package/matching/ExpressionEvaluator/package.json +6 -0
  342. package/matching/RequestMatcher/package.json +6 -0
  343. package/matching/ResponseGenerator/package.json +6 -0
  344. package/matching/TemplateEngine/package.json +6 -0
  345. package/package.json +435 -0
  346. package/repositories/ImposterRepository/package.json +6 -0
  347. package/schemas/ConfigFileSchema/package.json +6 -0
  348. package/schemas/ImposterSchema/package.json +6 -0
  349. package/schemas/RequestLogSchema/package.json +6 -0
  350. package/schemas/StubSchema/package.json +6 -0
  351. package/schemas/common/package.json +6 -0
  352. package/server/AdminServer/package.json +6 -0
  353. package/server/BunServer/package.json +6 -0
  354. package/server/FiberManager/package.json +6 -0
  355. package/server/ImposterServer/package.json +6 -0
  356. package/services/AppConfig/package.json +6 -0
  357. package/services/MetricsService/package.json +6 -0
  358. package/services/PortAllocator/package.json +6 -0
  359. package/services/ProxyService/package.json +6 -0
  360. package/services/RequestLogger/package.json +6 -0
  361. package/services/Uuid/package.json +6 -0
  362. package/services/UuidLive/package.json +6 -0
  363. package/src/Program.ts +1 -0
  364. package/src/api/AdminApi.ts +7 -0
  365. package/src/api/ApiErrors.ts +20 -0
  366. package/src/api/ApiSchemas.ts +36 -0
  367. package/src/api/Conversions.ts +34 -0
  368. package/src/api/ImpostersGroup.ts +103 -0
  369. package/src/api/ImpostersHandlers.ts +387 -0
  370. package/src/api/SystemGroup.ts +12 -0
  371. package/src/api/SystemHandlers.ts +76 -0
  372. package/src/cli/Commands.ts +119 -0
  373. package/src/cli/ConfigLoader.ts +41 -0
  374. package/src/client/HandlerHttpClient.ts +50 -0
  375. package/src/client/ImpostersClient.ts +21 -0
  376. package/src/client/index.ts +9 -0
  377. package/src/client/testing.ts +105 -0
  378. package/src/domain/imposter.ts +186 -0
  379. package/src/domain/route.ts +255 -0
  380. package/src/index.ts +153 -0
  381. package/src/layers/ApiLayer.ts +21 -0
  382. package/src/layers/MainLayer.ts +43 -0
  383. package/src/matching/ExpressionEvaluator.ts +102 -0
  384. package/src/matching/RequestMatcher.ts +162 -0
  385. package/src/matching/ResponseGenerator.ts +86 -0
  386. package/src/matching/TemplateEngine.ts +54 -0
  387. package/src/repositories/ImposterRepository.ts +145 -0
  388. package/src/schemas/ConfigFileSchema.ts +32 -0
  389. package/src/schemas/ImposterSchema.ts +232 -0
  390. package/src/schemas/RequestLogSchema.ts +38 -0
  391. package/src/schemas/StubSchema.ts +90 -0
  392. package/src/schemas/common.ts +95 -0
  393. package/src/server/AdminServer.ts +22 -0
  394. package/src/server/BunServer.ts +19 -0
  395. package/src/server/FiberManager.ts +25 -0
  396. package/src/server/ImposterServer.ts +244 -0
  397. package/src/services/AppConfig.ts +22 -0
  398. package/src/services/MetricsService.ts +157 -0
  399. package/src/services/PortAllocator.ts +68 -0
  400. package/src/services/ProxyService.ts +139 -0
  401. package/src/services/RequestLogger.ts +87 -0
  402. package/src/services/Uuid.ts +9 -0
  403. package/src/services/UuidLive.ts +9 -0
  404. package/src/types/bun.d.ts +6 -0
  405. package/src/ui/UiRouter.ts +278 -0
  406. package/src/ui/admin/AdminLayout.ts +36 -0
  407. package/src/ui/admin/AdminUiRouter.ts +170 -0
  408. package/src/ui/admin/pages/AdminDashboard.ts +54 -0
  409. package/src/ui/admin/partials.ts +83 -0
  410. package/src/ui/html.ts +37 -0
  411. package/src/ui/layout.ts +44 -0
  412. package/src/ui/pages/dashboard.ts +64 -0
  413. package/src/ui/pages/request-detail.ts +142 -0
  414. package/src/ui/pages/requests.ts +141 -0
  415. package/src/ui/pages/stubs.ts +52 -0
  416. package/src/ui/partials.ts +133 -0
  417. package/ui/UiRouter/package.json +6 -0
  418. package/ui/admin/AdminLayout/package.json +6 -0
  419. package/ui/admin/AdminUiRouter/package.json +6 -0
  420. package/ui/admin/pages/AdminDashboard/package.json +6 -0
  421. package/ui/admin/partials/package.json +6 -0
  422. package/ui/html/package.json +6 -0
  423. package/ui/layout/package.json +6 -0
  424. package/ui/pages/dashboard/package.json +6 -0
  425. package/ui/pages/requests/package.json +6 -0
  426. package/ui/pages/stubs/package.json +6 -0
@@ -0,0 +1,387 @@
1
+ import { HttpApiBuilder } from "@effect/platform"
2
+ import * as Clock from "effect/Clock"
3
+ import * as DateTime from "effect/DateTime"
4
+ import * as Effect from "effect/Effect"
5
+ import { ImposterConfig, type ProxyConfigDomain } from "../domain/imposter"
6
+ import { ImposterRepository } from "../repositories/ImposterRepository"
7
+ import { NonEmptyString } from "../schemas/common"
8
+ import { ImposterServer } from "../server/ImposterServer"
9
+ import { AppConfig } from "../services/AppConfig"
10
+ import { MetricsService } from "../services/MetricsService"
11
+ import { PortAllocator } from "../services/PortAllocator"
12
+ import { RequestLogger } from "../services/RequestLogger"
13
+ import { Uuid } from "../services/Uuid"
14
+ import { AdminApi } from "./AdminApi"
15
+ import { ApiConflictError, ApiNotFoundError, ApiServiceError } from "./ApiErrors"
16
+ import { buildPaginationMeta, toImposterResponse } from "./Conversions"
17
+
18
+ export const ImpostersHandlersLive = HttpApiBuilder.group(AdminApi, "imposters", (handlers) =>
19
+ handlers
20
+ .handle("createImposter", ({ payload }) =>
21
+ Effect.gen(function*() {
22
+ const repo = yield* ImposterRepository
23
+ const uuid = yield* Uuid
24
+ const allocator = yield* PortAllocator
25
+ const config = yield* AppConfig
26
+
27
+ const all = yield* repo.getAll
28
+ if (all.length >= config.maxImposters) {
29
+ return yield* Effect.fail(
30
+ new ApiServiceError({ message: `Maximum number of imposters (${config.maxImposters}) reached` })
31
+ )
32
+ }
33
+
34
+ const id = yield* uuid.generateShort
35
+ const name = payload.name ?? NonEmptyString.make(id)
36
+
37
+ const port = yield* allocator.allocate(payload.port).pipe(
38
+ Effect.catchTags({
39
+ PortAllocatorError: (e) => Effect.fail(new ApiConflictError({ message: e.reason })),
40
+ PortExhaustedError: (e) =>
41
+ Effect.fail(new ApiServiceError({ message: `No available ports in range ${e.rangeMin}-${e.rangeMax}` }))
42
+ })
43
+ )
44
+
45
+ const imposterConfig = ImposterConfig({
46
+ id,
47
+ name,
48
+ port,
49
+ status: "stopped",
50
+ createdAt: DateTime.unsafeNow(),
51
+ ...(payload.proxy !== undefined ? { proxy: payload.proxy } : {})
52
+ })
53
+
54
+ const record = yield* repo.create(imposterConfig)
55
+ return yield* toImposterResponse(record)
56
+ }))
57
+ .handle("listImposters", ({ urlParams }) =>
58
+ Effect.gen(function*() {
59
+ const repo = yield* ImposterRepository
60
+ const all = yield* repo.getAll
61
+
62
+ // All imposters are HTTP in Phase 2; protocol filter is for forward compatibility
63
+ const filtered = all
64
+ .filter((r) => urlParams.status === undefined || r.config.status === urlParams.status)
65
+ .filter(() => urlParams.protocol === undefined || urlParams.protocol === "HTTP")
66
+
67
+ filtered.sort((a, b) => DateTime.toEpochMillis(a.config.createdAt) - DateTime.toEpochMillis(b.config.createdAt))
68
+
69
+ const total = filtered.length
70
+ const paged = filtered.slice(urlParams.offset, urlParams.offset + urlParams.limit)
71
+ const imposters = yield* Effect.all(paged.map(toImposterResponse))
72
+
73
+ return {
74
+ imposters,
75
+ pagination: buildPaginationMeta(total, urlParams.limit, urlParams.offset)
76
+ }
77
+ }))
78
+ .handle("getImposter", ({ path }) =>
79
+ Effect.gen(function*() {
80
+ const repo = yield* ImposterRepository
81
+ const record = yield* repo.get(path.id).pipe(
82
+ Effect.catchTag("ImposterNotFoundError", (e) =>
83
+ Effect.fail(
84
+ new ApiNotFoundError({ message: "Imposter not found", resourceType: "imposter", resourceId: e.id })
85
+ ))
86
+ )
87
+ return yield* toImposterResponse(record)
88
+ }))
89
+ .handle("updateImposter", ({ path, payload }) =>
90
+ Effect.gen(function*() {
91
+ const repo = yield* ImposterRepository
92
+ const allocator = yield* PortAllocator
93
+ const imposterServer = yield* ImposterServer
94
+
95
+ const existing = yield* repo.get(path.id).pipe(
96
+ Effect.catchTag("ImposterNotFoundError", (e) =>
97
+ Effect.fail(
98
+ new ApiNotFoundError({ message: "Imposter not found", resourceType: "imposter", resourceId: e.id })
99
+ ))
100
+ )
101
+
102
+ const wasRunning = yield* imposterServer.isRunning(path.id)
103
+ const wantsRunning = payload.status === "running"
104
+ const wantsStopped = payload.status === "stopped"
105
+ const portChanging = payload.port !== undefined && payload.port !== existing.config.port
106
+
107
+ // If port is changing while running, stop first
108
+ if (portChanging && wasRunning) {
109
+ yield* imposterServer.stop(path.id)
110
+ }
111
+
112
+ let newPort: number | undefined
113
+ if (portChanging) {
114
+ newPort = yield* allocator.allocate(payload.port).pipe(
115
+ Effect.catchTags({
116
+ PortAllocatorError: (e) => Effect.fail(new ApiConflictError({ message: e.reason })),
117
+ PortExhaustedError: (e) =>
118
+ Effect.fail(new ApiServiceError({ message: `No available ports in range ${e.rangeMin}-${e.rangeMax}` }))
119
+ })
120
+ )
121
+ }
122
+
123
+ // Compute proxy update: undefined = no change, null = remove, object = set
124
+ const proxyUpdate: { proxy?: ProxyConfigDomain | undefined } = payload.proxy === undefined
125
+ ? {}
126
+ : payload.proxy === null
127
+ ? { proxy: undefined }
128
+ : { proxy: payload.proxy }
129
+
130
+ yield* repo.update(path.id, (r) => ({
131
+ ...r,
132
+ config: ImposterConfig({
133
+ ...r.config,
134
+ ...(payload.name !== undefined ? { name: payload.name as string } : {}),
135
+ ...(payload.status !== undefined ? { status: payload.status } : {}),
136
+ ...(newPort !== undefined ? { port: newPort } : {}),
137
+ ...proxyUpdate
138
+ })
139
+ })).pipe(
140
+ Effect.catchTag("ImposterNotFoundError", (e) =>
141
+ Effect.fail(
142
+ new ApiNotFoundError({ message: "Imposter not found", resourceType: "imposter", resourceId: e.id })
143
+ )),
144
+ Effect.tapError(() => newPort !== undefined ? allocator.release(newPort) : Effect.void)
145
+ )
146
+
147
+ if (newPort !== undefined) {
148
+ yield* allocator.release(existing.config.port)
149
+ }
150
+
151
+ // Hot-reload proxy config if it changed
152
+ if (payload.proxy !== undefined) {
153
+ yield* imposterServer.updateProxyConfig(path.id)
154
+ }
155
+
156
+ // Handle start/stop transitions
157
+ if (wantsRunning && !wasRunning) {
158
+ yield* imposterServer.start(path.id).pipe(
159
+ Effect.catchTag("ImposterServerError", (e) => Effect.fail(new ApiServiceError({ message: e.reason }))),
160
+ Effect.catchTag("ImposterNotFoundError", (e) =>
161
+ Effect.fail(
162
+ new ApiNotFoundError({ message: "Imposter not found", resourceType: "imposter", resourceId: e.id })
163
+ ))
164
+ )
165
+ } else if (wantsStopped && wasRunning && !portChanging) {
166
+ yield* imposterServer.stop(path.id)
167
+ } else if (portChanging && wasRunning) {
168
+ // Port changed while running — restart
169
+ yield* imposterServer.start(path.id).pipe(
170
+ Effect.catchTag("ImposterServerError", (e) => Effect.fail(new ApiServiceError({ message: e.reason }))),
171
+ Effect.catchTag("ImposterNotFoundError", (e) =>
172
+ Effect.fail(
173
+ new ApiNotFoundError({ message: "Imposter not found", resourceType: "imposter", resourceId: e.id })
174
+ ))
175
+ )
176
+ }
177
+
178
+ // Re-read to get final status
179
+ const final = yield* repo.get(path.id).pipe(
180
+ Effect.catchTag("ImposterNotFoundError", (e) =>
181
+ Effect.fail(
182
+ new ApiNotFoundError({ message: "Imposter not found", resourceType: "imposter", resourceId: e.id })
183
+ ))
184
+ )
185
+ return yield* toImposterResponse(final)
186
+ }))
187
+ .handle("deleteImposter", ({ path, urlParams }) =>
188
+ Effect.gen(function*() {
189
+ const repo = yield* ImposterRepository
190
+ const allocator = yield* PortAllocator
191
+ const imposterServer = yield* ImposterServer
192
+ const metricsService = yield* MetricsService
193
+
194
+ const existing = yield* repo.get(path.id).pipe(
195
+ Effect.catchTag("ImposterNotFoundError", (e) =>
196
+ Effect.fail(
197
+ new ApiNotFoundError({ message: "Imposter not found", resourceType: "imposter", resourceId: e.id })
198
+ ))
199
+ )
200
+
201
+ if (!urlParams.force && existing.config.status !== "stopped") {
202
+ return yield* Effect.fail(
203
+ new ApiConflictError({
204
+ message: `Imposter is ${existing.config.status}, use force=true to delete`
205
+ })
206
+ )
207
+ }
208
+
209
+ // Stop if running
210
+ const running = yield* imposterServer.isRunning(path.id)
211
+ if (running) {
212
+ yield* imposterServer.stop(path.id)
213
+ }
214
+
215
+ yield* repo.remove(path.id).pipe(
216
+ Effect.catchTag("ImposterNotFoundError", (e) =>
217
+ Effect.fail(
218
+ new ApiNotFoundError({ message: "Imposter not found", resourceType: "imposter", resourceId: e.id })
219
+ ))
220
+ )
221
+ yield* allocator.release(existing.config.port)
222
+ yield* metricsService.resetStats(path.id)
223
+
224
+ const now = yield* Effect.map(Clock.currentTimeMillis, (ms) => DateTime.unsafeMake(ms))
225
+
226
+ return {
227
+ message: NonEmptyString.make(`Imposter ${path.id} deleted`),
228
+ id: NonEmptyString.make(path.id),
229
+ deletedAt: now
230
+ }
231
+ }))
232
+ .handle("addStub", ({ path, payload }) =>
233
+ Effect.gen(function*() {
234
+ const repo = yield* ImposterRepository
235
+ const uuid = yield* Uuid
236
+ const imposterServer = yield* ImposterServer
237
+
238
+ const id = yield* uuid.generateShort
239
+ const stub = {
240
+ id: NonEmptyString.make(id),
241
+ predicates: payload.predicates,
242
+ responses: payload.responses,
243
+ responseMode: payload.responseMode
244
+ }
245
+
246
+ const result = yield* repo.addStub(path.imposterId, stub).pipe(
247
+ Effect.catchTag("ImposterNotFoundError", (e) =>
248
+ Effect.fail(
249
+ new ApiNotFoundError({ message: "Imposter not found", resourceType: "imposter", resourceId: e.id })
250
+ ))
251
+ )
252
+
253
+ // Hot-reload if running
254
+ const running = yield* imposterServer.isRunning(path.imposterId)
255
+ if (running) {
256
+ yield* imposterServer.updateStubs(path.imposterId)
257
+ }
258
+
259
+ return result
260
+ }))
261
+ .handle("listStubs", ({ path }) =>
262
+ Effect.gen(function*() {
263
+ const repo = yield* ImposterRepository
264
+ return yield* repo.getStubs(path.imposterId).pipe(
265
+ Effect.catchTag("ImposterNotFoundError", (e) =>
266
+ Effect.fail(
267
+ new ApiNotFoundError({ message: "Imposter not found", resourceType: "imposter", resourceId: e.id })
268
+ ))
269
+ )
270
+ }))
271
+ .handle("updateStub", ({ path, payload }) =>
272
+ Effect.gen(function*() {
273
+ const repo = yield* ImposterRepository
274
+ const imposterServer = yield* ImposterServer
275
+
276
+ const result = yield* repo.updateStub(path.imposterId, path.stubId, (s) => ({
277
+ ...s,
278
+ ...(payload.predicates !== undefined ? { predicates: payload.predicates } : {}),
279
+ ...(payload.responses !== undefined ? { responses: payload.responses } : {}),
280
+ ...(payload.responseMode !== undefined ? { responseMode: payload.responseMode } : {})
281
+ })).pipe(
282
+ Effect.catchTag("ImposterNotFoundError", (e) =>
283
+ Effect.fail(
284
+ new ApiNotFoundError({ message: "Imposter not found", resourceType: "imposter", resourceId: e.id })
285
+ )),
286
+ Effect.catchTag("StubNotFoundError", (e) =>
287
+ Effect.fail(
288
+ new ApiNotFoundError({ message: "Stub not found", resourceType: "stub", resourceId: e.stubId })
289
+ ))
290
+ )
291
+
292
+ // Hot-reload if running
293
+ const running = yield* imposterServer.isRunning(path.imposterId)
294
+ if (running) {
295
+ yield* imposterServer.updateStubs(path.imposterId)
296
+ }
297
+
298
+ return result
299
+ }))
300
+ .handle("deleteStub", ({ path }) =>
301
+ Effect.gen(function*() {
302
+ const repo = yield* ImposterRepository
303
+ const imposterServer = yield* ImposterServer
304
+
305
+ const result = yield* repo.removeStub(path.imposterId, path.stubId).pipe(
306
+ Effect.catchTag("ImposterNotFoundError", (e) =>
307
+ Effect.fail(
308
+ new ApiNotFoundError({ message: "Imposter not found", resourceType: "imposter", resourceId: e.id })
309
+ )),
310
+ Effect.catchTag(
311
+ "StubNotFoundError",
312
+ (e) =>
313
+ Effect.fail(
314
+ new ApiNotFoundError({ message: "Stub not found", resourceType: "stub", resourceId: e.stubId })
315
+ )
316
+ )
317
+ )
318
+
319
+ // Hot-reload if running
320
+ const running = yield* imposterServer.isRunning(path.imposterId)
321
+ if (running) {
322
+ yield* imposterServer.updateStubs(path.imposterId)
323
+ }
324
+
325
+ return result
326
+ }))
327
+ .handle("listRequests", ({ path, urlParams }) =>
328
+ Effect.gen(function*() {
329
+ const repo = yield* ImposterRepository
330
+ const requestLogger = yield* RequestLogger
331
+ yield* repo.get(path.id).pipe(
332
+ Effect.catchTag(
333
+ "ImposterNotFoundError",
334
+ (e) =>
335
+ Effect.fail(
336
+ new ApiNotFoundError({ message: "Imposter not found", resourceType: "imposter", resourceId: e.id })
337
+ )
338
+ )
339
+ )
340
+ return yield* requestLogger.getEntries(path.id, {
341
+ limit: urlParams.limit,
342
+ ...(urlParams.method !== undefined ? { method: urlParams.method } : {}),
343
+ ...(urlParams.path !== undefined ? { path: urlParams.path } : {}),
344
+ ...(urlParams.status !== undefined ? { status: urlParams.status } : {})
345
+ })
346
+ }))
347
+ .handle("clearRequests", ({ path }) =>
348
+ Effect.gen(function*() {
349
+ const repo = yield* ImposterRepository
350
+ const requestLogger = yield* RequestLogger
351
+ yield* repo.get(path.id).pipe(
352
+ Effect.catchTag(
353
+ "ImposterNotFoundError",
354
+ (e) =>
355
+ Effect.fail(
356
+ new ApiNotFoundError({ message: "Imposter not found", resourceType: "imposter", resourceId: e.id })
357
+ )
358
+ )
359
+ )
360
+ yield* requestLogger.clear(path.id)
361
+ return { message: `Request log cleared for imposter ${path.id}` }
362
+ }))
363
+ .handle("getImposterStats", ({ path }) =>
364
+ Effect.gen(function*() {
365
+ const repo = yield* ImposterRepository
366
+ const metricsService = yield* MetricsService
367
+ yield* repo.get(path.id).pipe(
368
+ Effect.catchTag("ImposterNotFoundError", (e) =>
369
+ Effect.fail(
370
+ new ApiNotFoundError({ message: "Imposter not found", resourceType: "imposter", resourceId: e.id })
371
+ ))
372
+ )
373
+ return yield* metricsService.getStats(path.id)
374
+ }))
375
+ .handle("resetImposterStats", ({ path }) =>
376
+ Effect.gen(function*() {
377
+ const repo = yield* ImposterRepository
378
+ const metricsService = yield* MetricsService
379
+ yield* repo.get(path.id).pipe(
380
+ Effect.catchTag("ImposterNotFoundError", (e) =>
381
+ Effect.fail(
382
+ new ApiNotFoundError({ message: "Imposter not found", resourceType: "imposter", resourceId: e.id })
383
+ ))
384
+ )
385
+ yield* metricsService.resetStats(path.id)
386
+ return { message: `Statistics reset for imposter ${path.id}` }
387
+ })))
@@ -0,0 +1,12 @@
1
+ import { HttpApiEndpoint, HttpApiGroup } from "@effect/platform"
2
+ import { HealthResponse, ServerInfoResponse } from "../schemas/ImposterSchema"
3
+
4
+ export const SystemGroup = HttpApiGroup.make("system", { topLevel: true })
5
+ .add(
6
+ HttpApiEndpoint.get("healthCheck", "/health")
7
+ .addSuccess(HealthResponse)
8
+ )
9
+ .add(
10
+ HttpApiEndpoint.get("serverInfo", "/info")
11
+ .addSuccess(ServerInfoResponse)
12
+ )
@@ -0,0 +1,76 @@
1
+ import { HttpApiBuilder } from "@effect/platform"
2
+ import * as Clock from "effect/Clock"
3
+ import * as DateTime from "effect/DateTime"
4
+ import * as Duration from "effect/Duration"
5
+ import * as Effect from "effect/Effect"
6
+ import { ImposterRepository } from "../repositories/ImposterRepository"
7
+ import { NonEmptyString, PortNumber } from "../schemas/common"
8
+ import { AppConfig } from "../services/AppConfig"
9
+ import { AdminApi } from "./AdminApi"
10
+
11
+ export const SystemHandlersLive = HttpApiBuilder.group(AdminApi, "system", (handlers) =>
12
+ handlers
13
+ .handle("healthCheck", () =>
14
+ Effect.gen(function*() {
15
+ const config = yield* AppConfig
16
+ const repo = yield* ImposterRepository
17
+ const all = yield* repo.getAll
18
+
19
+ const now = yield* Effect.map(Clock.currentTimeMillis, (ms) => DateTime.unsafeMake(ms))
20
+ const memUsage = process.memoryUsage()
21
+
22
+ const running = all.filter((r) => r.config.status === "running").length
23
+ const stopped = all.filter((r) => r.config.status === "stopped").length
24
+
25
+ return {
26
+ status: "healthy" as const,
27
+ timestamp: now,
28
+ version: NonEmptyString.make("0.0.0"),
29
+ uptime: Duration.format(Duration.millis(process.uptime() * 1000)),
30
+ system: {
31
+ memory: {
32
+ used: NonEmptyString.make(`${Math.round(memUsage.heapUsed / 1024 / 1024)}MB`),
33
+ free: NonEmptyString.make(`${Math.round((memUsage.heapTotal - memUsage.heapUsed) / 1024 / 1024)}MB`)
34
+ },
35
+ imposters: {
36
+ total: all.length,
37
+ running,
38
+ stopped
39
+ },
40
+ ports: {
41
+ available: config.portRangeMax - config.portRangeMin + 1 - all.length,
42
+ allocated: all.length
43
+ }
44
+ }
45
+ }
46
+ }))
47
+ .handle("serverInfo", () =>
48
+ Effect.gen(function*() {
49
+ const config = yield* AppConfig
50
+ const now = yield* Effect.map(Clock.currentTimeMillis, (ms) => DateTime.unsafeMake(ms))
51
+
52
+ return {
53
+ server: {
54
+ name: NonEmptyString.make("imposters"),
55
+ version: NonEmptyString.make("0.0.0"),
56
+ buildTime: now,
57
+ platform: NonEmptyString.make(process.platform),
58
+ protocols: ["HTTP" as const]
59
+ },
60
+ configuration: {
61
+ maxImposters: config.maxImposters,
62
+ portRange: {
63
+ min: PortNumber.make(config.portRangeMin),
64
+ max: PortNumber.make(config.portRangeMax)
65
+ },
66
+ defaultTimeout: 30000,
67
+ logLevel: config.logLevel
68
+ },
69
+ features: {
70
+ openApiGeneration: true,
71
+ clientGeneration: false,
72
+ authentication: false,
73
+ clustering: false
74
+ }
75
+ }
76
+ })))
@@ -0,0 +1,119 @@
1
+ import { Command, Options } from "@effect/cli"
2
+ import { NodeContext, NodeRuntime } from "@effect/platform-node"
3
+ import { Effect, Layer, Option } from "effect"
4
+ import { HandlerHttpClientLive } from "../client/HandlerHttpClient"
5
+ import { ImpostersClient, ImpostersClientLive } from "../client/ImpostersClient"
6
+ import { makeCompositeHandler } from "../server/AdminServer"
7
+ import { loadConfigFile } from "./ConfigLoader"
8
+
9
+ const configOption = Options.file("config").pipe(
10
+ Options.withAlias("c"),
11
+ Options.withDescription("Path to JSON config file"),
12
+ Options.optional
13
+ )
14
+
15
+ const portOption = Options.integer("port").pipe(
16
+ Options.withAlias("p"),
17
+ Options.withDescription("Admin server port (default: 2525)"),
18
+ Options.optional
19
+ )
20
+
21
+ const startCommand = Command.make(
22
+ "start",
23
+ { config: configOption, port: portOption },
24
+ ({ config, port }) =>
25
+ Effect.gen(function*() {
26
+ const adminPort = Option.isSome(port) ? port.value : Number(process.env.ADMIN_PORT ?? 2525)
27
+
28
+ const { dispose, handler } = makeCompositeHandler(adminPort)
29
+ const server = Bun.serve({ port: adminPort, fetch: handler })
30
+
31
+ console.log(`Imposters admin server running on http://localhost:${server.port}`)
32
+ console.log(`Admin UI: http://localhost:${server.port}/_ui`)
33
+
34
+ // Load config and create imposters if config file provided
35
+ if (Option.isSome(config)) {
36
+ const configData = yield* loadConfigFile(config.value).pipe(
37
+ Effect.catchTag("ConfigLoadError", (e) =>
38
+ Effect.sync(() => {
39
+ console.error(`Warning: ${e.message}`)
40
+ return null
41
+ }))
42
+ )
43
+
44
+ if (configData !== null && configData.imposters.length > 0) {
45
+ const clientLayer = ImpostersClientLive(`http://localhost:${server.port}`).pipe(
46
+ Layer.provide(HandlerHttpClientLive(handler))
47
+ )
48
+
49
+ yield* Effect.provide(
50
+ Effect.gen(function*() {
51
+ const client = yield* ImpostersClient
52
+ for (const imp of configData.imposters) {
53
+ const created = yield* client.imposters.createImposter({
54
+ payload: {
55
+ port: imp.port,
56
+ ...(imp.name !== undefined ? { name: imp.name } : {}),
57
+ protocol: "HTTP" as const,
58
+ adminPath: "/_admin"
59
+ }
60
+ }).pipe(Effect.catchAll((e) => {
61
+ console.error(`Failed to create imposter on port ${imp.port}: ${e}`)
62
+ return Effect.succeed(null)
63
+ }))
64
+
65
+ if (created === null) continue
66
+
67
+ for (const stub of imp.stubs) {
68
+ yield* client.imposters.addStub({
69
+ path: { imposterId: created.id },
70
+ payload: stub
71
+ }).pipe(Effect.catchAll((e) => {
72
+ console.error(`Failed to add stub: ${e}`)
73
+ return Effect.void
74
+ }))
75
+ }
76
+
77
+ yield* client.imposters.updateImposter({
78
+ path: { id: created.id },
79
+ payload: { status: "running" as const }
80
+ }).pipe(Effect.catchAll((e) => {
81
+ console.error(`Failed to start imposter ${created.id}: ${e}`)
82
+ return Effect.void
83
+ }))
84
+
85
+ console.log(`Created imposter "${imp.name ?? created.id}" on port ${imp.port}`)
86
+ }
87
+ }),
88
+ clientLayer
89
+ )
90
+ }
91
+ }
92
+
93
+ // Keep running until interrupted
94
+ yield* Effect.async<never, never>(() => {
95
+ const shutdown = () => {
96
+ console.log("Shutting down...")
97
+ server.stop(true)
98
+ dispose()
99
+ process.exit(0)
100
+ }
101
+ process.on("SIGINT", shutdown)
102
+ process.on("SIGTERM", shutdown)
103
+ })
104
+ })
105
+ )
106
+
107
+ const command = Command.make("imposters").pipe(
108
+ Command.withSubcommands([startCommand])
109
+ )
110
+
111
+ export const run = Command.run(command, {
112
+ name: "imposters",
113
+ version: "0.1.0"
114
+ })
115
+
116
+ export const main = run(process.argv).pipe(
117
+ Effect.provide(NodeContext.layer),
118
+ NodeRuntime.runMain
119
+ )
@@ -0,0 +1,41 @@
1
+ import { Data, Effect, Schema } from "effect"
2
+ import * as fs from "node:fs"
3
+ import { ConfigFile } from "../schemas/ConfigFileSchema"
4
+
5
+ export class ConfigLoadError extends Data.TaggedError("ConfigLoadError")<{
6
+ readonly message: string
7
+ readonly cause?: unknown
8
+ }> {}
9
+
10
+ export const loadConfigFile = (
11
+ filePath: string
12
+ ): Effect.Effect<Schema.Schema.Type<typeof ConfigFile>, ConfigLoadError> =>
13
+ Effect.gen(function*() {
14
+ const content = yield* Effect.try({
15
+ try: () => fs.readFileSync(filePath, "utf-8"),
16
+ catch: (error) =>
17
+ new ConfigLoadError({
18
+ message: `Failed to read config file: ${filePath}`,
19
+ cause: error
20
+ })
21
+ })
22
+
23
+ const json = yield* Effect.try({
24
+ try: () => JSON.parse(content) as unknown,
25
+ catch: (error) =>
26
+ new ConfigLoadError({
27
+ message: `Invalid JSON in config file: ${filePath}`,
28
+ cause: error
29
+ })
30
+ })
31
+
32
+ return yield* Schema.decodeUnknown(ConfigFile)(json).pipe(
33
+ Effect.mapError(
34
+ (error) =>
35
+ new ConfigLoadError({
36
+ message: `Config validation failed: ${String(error)}`,
37
+ cause: error
38
+ })
39
+ )
40
+ )
41
+ })
@@ -0,0 +1,50 @@
1
+ import { HttpClient, HttpClientResponse } from "@effect/platform"
2
+ import { Effect, Layer } from "effect"
3
+
4
+ export const makeHandlerHttpClient = (
5
+ handler: (request: Request) => Promise<Response>
6
+ ): HttpClient.HttpClient =>
7
+ HttpClient.make((request, url) =>
8
+ Effect.tryPromise({
9
+ try: async () => {
10
+ const method = request.method
11
+ const headers: Record<string, string> = {}
12
+ for (const key of Object.keys(request.headers)) {
13
+ headers[key] = request.headers[key]!
14
+ }
15
+
16
+ let body: BodyInit | undefined
17
+ switch (request.body._tag) {
18
+ case "Uint8Array":
19
+ body = new globalThis.Uint8Array(request.body.body)
20
+ break
21
+ case "Raw": {
22
+ const raw = request.body.body
23
+ body = typeof raw === "string" ? raw : JSON.stringify(raw)
24
+ break
25
+ }
26
+ case "Empty":
27
+ body = undefined
28
+ break
29
+ default:
30
+ body = undefined
31
+ }
32
+
33
+ const webRequest = new Request(url.toString(), {
34
+ method,
35
+ headers,
36
+ ...(body !== undefined ? { body } : {})
37
+ })
38
+
39
+ const webResponse = await handler(webRequest)
40
+ return HttpClientResponse.fromWeb(request, webResponse)
41
+ },
42
+ catch: (error) => {
43
+ throw error
44
+ }
45
+ })
46
+ )
47
+
48
+ export const HandlerHttpClientLive = (
49
+ handler: (request: Request) => Promise<Response>
50
+ ): Layer.Layer<HttpClient.HttpClient> => Layer.succeed(HttpClient.HttpClient, makeHandlerHttpClient(handler))