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,83 @@
1
+ import { html } from "../html"
2
+ import type { SafeHtml } from "../html"
3
+
4
+ export interface AdminImposterData {
5
+ readonly id: string
6
+ readonly name: string
7
+ readonly port: number
8
+ readonly status: string
9
+ readonly protocol: string
10
+ readonly stubCount: number
11
+ }
12
+
13
+ const statusBadge = (status: string): SafeHtml => {
14
+ if (status === "running") {
15
+ return html`<span class="px-2 py-0.5 rounded text-xs font-semibold bg-green-100 text-green-700">running</span>`
16
+ }
17
+ if (status === "stopped") {
18
+ return html`<span class="px-2 py-0.5 rounded text-xs font-semibold bg-gray-100 text-gray-600">stopped</span>`
19
+ }
20
+ return html`<span class="px-2 py-0.5 rounded text-xs font-semibold bg-yellow-100 text-yellow-700">${status}</span>`
21
+ }
22
+
23
+ export const imposterRowPartial = (imp: AdminImposterData): SafeHtml => {
24
+ const isRunning = imp.status === "running"
25
+ return html`<tr id="row-${imp.id}" class="border-t hover:bg-gray-50">
26
+ <td class="py-3 px-4 font-medium">${imp.name}</td>
27
+ <td class="py-3 px-4 font-mono text-sm">${String(imp.port)}</td>
28
+ <td class="py-3 px-4">${statusBadge(imp.status)}</td>
29
+ <td class="py-3 px-4 text-sm text-gray-600">${imp.protocol}</td>
30
+ <td class="py-3 px-4 text-sm">${String(imp.stubCount)}</td>
31
+ <td class="py-3 px-4">
32
+ <div class="flex gap-2 items-center">
33
+ ${
34
+ isRunning
35
+ ? html`<button hx-post="/_ui/imposters/${imp.id}/stop" hx-target="#row-${imp.id}" hx-swap="outerHTML" class="text-orange-600 hover:text-orange-800 text-sm font-medium">Stop</button>`
36
+ : html`<button hx-post="/_ui/imposters/${imp.id}/start" hx-target="#row-${imp.id}" hx-swap="outerHTML" class="text-green-600 hover:text-green-800 text-sm font-medium">Start</button>`
37
+ }
38
+ <button hx-delete="/_ui/imposters/${imp.id}" hx-target="#imposter-list" hx-swap="innerHTML" hx-confirm="Delete this imposter?" class="text-red-500 hover:text-red-700 text-sm">Delete</button>
39
+ ${
40
+ isRunning
41
+ ? html`<a href="http://localhost:${
42
+ String(imp.port)
43
+ }/_admin" target="_blank" class="text-indigo-600 hover:text-indigo-800 text-sm">Open UI</a>`
44
+ : html``
45
+ }
46
+ </div>
47
+ </td>
48
+ </tr>`
49
+ }
50
+
51
+ export const imposterListPartial = (imposters: ReadonlyArray<AdminImposterData>): SafeHtml => {
52
+ if (imposters.length === 0) {
53
+ return html`<tr><td colspan="6" class="text-center py-8 text-gray-400">No imposters. Create one above.</td></tr>`
54
+ }
55
+ return imposters.reduce(
56
+ (acc, imp) => html`${acc}${imposterRowPartial(imp)}`,
57
+ html``
58
+ )
59
+ }
60
+
61
+ export const createFormPartial = (error?: string): SafeHtml =>
62
+ html`<div class="bg-white rounded-lg shadow p-4 mb-6">
63
+ <h2 class="text-lg font-semibold mb-3">Create Imposter</h2>
64
+ ${error ? html`<div class="bg-red-50 border border-red-200 text-red-700 rounded p-3 mb-3">${error}</div>` : html``}
65
+ <form hx-post="/_ui/imposters" hx-target="#imposter-list" hx-swap="innerHTML" class="flex flex-wrap gap-3 items-end">
66
+ <div>
67
+ <label class="block text-xs text-gray-500 mb-1">Name (optional)</label>
68
+ <input name="name" type="text" class="border rounded p-2 text-sm w-40" placeholder="My Service" />
69
+ </div>
70
+ <div>
71
+ <label class="block text-xs text-gray-500 mb-1">Port (optional)</label>
72
+ <input name="port" type="number" class="border rounded p-2 text-sm w-24" placeholder="auto" />
73
+ </div>
74
+ <div class="flex items-center gap-2">
75
+ <input name="autoStart" type="checkbox" checked class="rounded" id="auto-start" />
76
+ <label for="auto-start" class="text-sm text-gray-700">Auto-start</label>
77
+ </div>
78
+ <button type="submit" class="bg-gray-800 text-white px-4 py-2 rounded hover:bg-gray-700 text-sm">Create</button>
79
+ </form>
80
+ </div>`
81
+
82
+ export const adminErrorPartial = (message: string): SafeHtml =>
83
+ html`<div class="bg-red-50 border border-red-200 text-red-700 rounded p-3 mb-3">${message}</div>`
package/src/ui/html.ts ADDED
@@ -0,0 +1,37 @@
1
+ /** Escapes &, <, >, ", ' → HTML entities */
2
+ export const escapeHtml = (s: string): string =>
3
+ s
4
+ .replaceAll("&", "&amp;")
5
+ .replaceAll("<", "&lt;")
6
+ .replaceAll(">", "&gt;")
7
+ .replaceAll("\"", "&quot;")
8
+ .replaceAll("'", "&#39;")
9
+
10
+ /** Marker class for pre-escaped HTML */
11
+ export class SafeHtml {
12
+ constructor(readonly value: string) {}
13
+ }
14
+
15
+ /** Wrap a raw HTML string to skip auto-escaping */
16
+ export const raw = (s: string): SafeHtml => new SafeHtml(s)
17
+
18
+ /** Tagged template: auto-escapes interpolations, passes SafeHtml through */
19
+ export const html = (strings: TemplateStringsArray, ...values: Array<unknown>): SafeHtml => {
20
+ let result = ""
21
+ for (let i = 0; i < strings.length; i++) {
22
+ result += strings[i]
23
+ if (i < values.length) {
24
+ const v = values[i]
25
+ if (v instanceof SafeHtml) {
26
+ result += v.value
27
+ } else if (v == null) {
28
+ result += ""
29
+ } else if (typeof v === "string") {
30
+ result += escapeHtml(v)
31
+ } else {
32
+ result += escapeHtml(String(v))
33
+ }
34
+ }
35
+ }
36
+ return new SafeHtml(result)
37
+ }
@@ -0,0 +1,44 @@
1
+ import { html, raw, type SafeHtml } from "./html"
2
+
3
+ export interface LayoutOpts {
4
+ readonly title: string
5
+ readonly imposterName: string
6
+ readonly port: number
7
+ readonly activeTab: "dashboard" | "stubs" | "requests"
8
+ }
9
+
10
+ const navTab = (label: string, href: string, active: boolean): SafeHtml =>
11
+ active
12
+ ? html`<a href="${href}" class="px-4 py-2 bg-white text-indigo-700 rounded-t font-semibold border-b-2 border-indigo-600">${label}</a>`
13
+ : html`<a href="${href}" class="px-4 py-2 text-gray-300 hover:text-white hover:bg-indigo-500 rounded-t">${label}</a>`
14
+
15
+ export const layout = (opts: LayoutOpts, content: SafeHtml): SafeHtml =>
16
+ html`<!DOCTYPE html>
17
+ <html lang="en">
18
+ <head>
19
+ <meta charset="utf-8">
20
+ <meta name="viewport" content="width=device-width, initial-scale=1">
21
+ <title>${opts.title}</title>
22
+ <script src="https://cdn.tailwindcss.com"></script>
23
+ <script src="https://unpkg.com/htmx.org@2.0.4"></script>
24
+ </head>
25
+ <body class="bg-gray-50 min-h-screen">
26
+ <nav class="bg-indigo-700 text-white shadow-md">
27
+ <div class="max-w-5xl mx-auto px-4 py-3 flex items-center justify-between">
28
+ <div>
29
+ <span class="text-indigo-200 text-sm font-medium mr-2">Imposters</span>
30
+ <span class="text-lg font-bold">${opts.imposterName}</span>
31
+ <span class="ml-2 text-indigo-200 text-sm">port ${String(opts.port)}</span>
32
+ </div>
33
+ <div class="flex gap-1">
34
+ ${navTab("Dashboard", "/_admin", opts.activeTab === "dashboard")}
35
+ ${navTab("Stubs", "/_admin/stubs", opts.activeTab === "stubs")}
36
+ ${navTab("Requests", "/_admin/requests", opts.activeTab === "requests")}
37
+ </div>
38
+ </div>
39
+ </nav>
40
+ <main class="max-w-5xl mx-auto px-4 py-6">
41
+ ${raw(content.value)}
42
+ </main>
43
+ </body>
44
+ </html>`
@@ -0,0 +1,64 @@
1
+ import type { ImposterConfig } from "../../domain/imposter"
2
+ import type { RequestLogEntry } from "../../schemas/RequestLogSchema"
3
+ import { html, raw } from "../html"
4
+ import { layout } from "../layout"
5
+ import { requestTablePartial } from "../partials"
6
+
7
+ export interface DashboardData {
8
+ readonly config: ImposterConfig
9
+ readonly stubCount: number
10
+ readonly requestCount: number
11
+ readonly recentRequests: ReadonlyArray<RequestLogEntry>
12
+ }
13
+
14
+ const statCard = (label: string, value: string, color: string) =>
15
+ html`<div class="bg-white rounded-lg shadow p-4">
16
+ <div class="text-sm text-gray-500">${label}</div>
17
+ <div class="text-2xl font-bold ${color}">${value}</div>
18
+ </div>`
19
+
20
+ export const dashboardPage = (data: DashboardData) => {
21
+ const statusColor = data.config.status === "running" ? "text-green-600" : "text-gray-500"
22
+
23
+ const content = html`
24
+ <div class="grid grid-cols-2 md:grid-cols-4 gap-4 mb-6">
25
+ ${statCard("Status", data.config.status, statusColor)}
26
+ ${statCard("Port", String(data.config.port), "text-gray-800")}
27
+ ${statCard("Stubs", String(data.stubCount), "text-indigo-600")}
28
+ ${statCard("Requests", String(data.requestCount), "text-indigo-600")}
29
+ </div>
30
+
31
+ <div class="bg-white rounded-lg shadow">
32
+ <h2 class="text-lg font-semibold px-4 pt-4 pb-2">
33
+ <a href="/_admin/requests" class="hover:text-indigo-600">Recent Requests &rarr;</a>
34
+ </h2>
35
+ ${
36
+ data.recentRequests.length === 0
37
+ ? html`<p class="px-4 pb-4 text-gray-400">No requests yet.</p>`
38
+ : html`<table class="w-full text-left">
39
+ <thead>
40
+ <tr class="text-xs text-gray-500 uppercase border-b">
41
+ <th class="py-2 px-3">Time</th>
42
+ <th class="py-2 px-3">Method</th>
43
+ <th class="py-2 px-3">Path</th>
44
+ <th class="py-2 px-3">Status</th>
45
+ <th class="py-2 px-3">Stub</th>
46
+ <th class="py-2 px-3">Duration</th>
47
+ <th class="py-2 px-3"></th>
48
+ </tr>
49
+ </thead>
50
+ <tbody>${raw(requestTablePartial(data.recentRequests).value)}</tbody>
51
+ </table>`
52
+ }
53
+ </div>`
54
+
55
+ return layout(
56
+ {
57
+ title: `${data.config.name} — Dashboard`,
58
+ imposterName: data.config.name,
59
+ port: data.config.port,
60
+ activeTab: "dashboard"
61
+ },
62
+ content
63
+ )
64
+ }
@@ -0,0 +1,142 @@
1
+ import type { ImposterConfig } from "../../domain/imposter"
2
+ import type { RequestLogEntry } from "../../schemas/RequestLogSchema"
3
+ import type { Stub } from "../../schemas/StubSchema"
4
+ import { html, raw } from "../html"
5
+ import type { SafeHtml } from "../html"
6
+ import { layout } from "../layout"
7
+ import { methodBadge, statusBadge, stubCardPartial } from "../partials"
8
+
9
+ export interface RequestDetailData {
10
+ readonly config: ImposterConfig
11
+ readonly entry: RequestLogEntry
12
+ readonly matchedStub: Stub | null
13
+ }
14
+
15
+ const formatTimestamp = (ts: unknown): string => {
16
+ try {
17
+ const d = typeof (ts as { epochMillis: bigint }).epochMillis === "bigint"
18
+ ? new Date(Number((ts as { epochMillis: bigint }).epochMillis))
19
+ : new Date(String(ts))
20
+ return d.toISOString()
21
+ } catch {
22
+ return String(ts)
23
+ }
24
+ }
25
+
26
+ const formatBody = (body: unknown): string => {
27
+ if (body === undefined || body === null) return "(empty)"
28
+ if (typeof body === "string") {
29
+ try {
30
+ return JSON.stringify(JSON.parse(body), null, 2)
31
+ } catch {
32
+ return body
33
+ }
34
+ }
35
+ return JSON.stringify(body, null, 2)
36
+ }
37
+
38
+ const headersTable = (headers: Record<string, string>): SafeHtml => {
39
+ const entries = Object.entries(headers)
40
+ if (entries.length === 0) return html`<p class="text-gray-400 text-sm italic">No headers</p>`
41
+ const rows = entries.map(([k, v]) =>
42
+ html`<tr class="border-t"><td class="py-1 px-2 text-xs font-mono font-semibold text-gray-600">${k}</td><td class="py-1 px-2 text-xs font-mono">${v}</td></tr>`
43
+ )
44
+ return html`<table class="w-full"><tbody>${rows.reduce((a, r) => html`${a}${r}`, html``)}</tbody></table>`
45
+ }
46
+
47
+ const queryTable = (query: Record<string, string>): SafeHtml => {
48
+ const entries = Object.entries(query)
49
+ if (entries.length === 0) return html`<p class="text-gray-400 text-sm italic">No query parameters</p>`
50
+ const rows = entries.map(([k, v]) =>
51
+ html`<tr class="border-t"><td class="py-1 px-2 text-xs font-mono font-semibold text-gray-600">${k}</td><td class="py-1 px-2 text-xs font-mono">${v}</td></tr>`
52
+ )
53
+ return html`<table class="w-full"><tbody>${rows.reduce((a, r) => html`${a}${r}`, html``)}</tbody></table>`
54
+ }
55
+
56
+ export const requestDetailPage = (data: RequestDetailData): SafeHtml => {
57
+ const { entry, matchedStub } = data
58
+ const timestamp = formatTimestamp(entry.timestamp)
59
+ const reqBody = formatBody(entry.request.body)
60
+ const respBody = formatBody(entry.response.body)
61
+ const respHeaders = entry.response.headers ?? {}
62
+
63
+ const content = html`
64
+ <div class="mb-4">
65
+ <a href="/_admin/requests" class="text-indigo-600 hover:underline text-sm">&larr; Back to Requests</a>
66
+ <span class="text-gray-400 mx-2">/</span>
67
+ <span class="text-sm text-gray-500">Request ${entry.id}</span>
68
+ </div>
69
+
70
+ <div class="bg-white rounded-lg shadow p-4 mb-6">
71
+ <div class="flex items-center gap-3 mb-3">
72
+ ${methodBadge(entry.request.method)}
73
+ <span class="font-mono text-lg">${entry.request.path}</span>
74
+ ${statusBadge(entry.response.status)}
75
+ <span class="text-sm text-gray-500">${String(entry.duration)}ms</span>
76
+ </div>
77
+ <div class="text-sm text-gray-500">
78
+ <span>Timestamp: ${timestamp}</span>
79
+ ${
80
+ entry.response.matchedStubId
81
+ ? html`<span class="ml-4">Matched Stub: <span class="font-mono text-indigo-600">${entry.response.matchedStubId}</span></span>`
82
+ : html`<span class="ml-4 text-orange-500">No matching stub</span>`
83
+ }
84
+ </div>
85
+ </div>
86
+
87
+ <div class="grid grid-cols-1 md:grid-cols-2 gap-6 mb-6">
88
+ <div>
89
+ <h3 class="text-lg font-semibold mb-3">Request</h3>
90
+ <div class="bg-white rounded-lg shadow p-4 space-y-3">
91
+ <div>
92
+ <h4 class="text-sm font-medium text-gray-700 mb-1">Headers</h4>
93
+ ${headersTable(entry.request.headers as Record<string, string>)}
94
+ </div>
95
+ <div>
96
+ <h4 class="text-sm font-medium text-gray-700 mb-1">Query Parameters</h4>
97
+ ${queryTable(entry.request.query as Record<string, string>)}
98
+ </div>
99
+ <div>
100
+ <h4 class="text-sm font-medium text-gray-700 mb-1">Body</h4>
101
+ <pre class="bg-gray-50 border rounded p-2 text-xs font-mono overflow-x-auto whitespace-pre-wrap max-h-64 overflow-y-auto">${reqBody}</pre>
102
+ </div>
103
+ </div>
104
+ </div>
105
+
106
+ <div>
107
+ <h3 class="text-lg font-semibold mb-3">Response</h3>
108
+ <div class="bg-white rounded-lg shadow p-4 space-y-3">
109
+ <div class="flex items-center gap-2">
110
+ ${statusBadge(entry.response.status)}
111
+ </div>
112
+ <div>
113
+ <h4 class="text-sm font-medium text-gray-700 mb-1">Headers</h4>
114
+ ${headersTable(respHeaders as Record<string, string>)}
115
+ </div>
116
+ <div>
117
+ <h4 class="text-sm font-medium text-gray-700 mb-1">Body</h4>
118
+ <pre class="bg-gray-50 border rounded p-2 text-xs font-mono overflow-x-auto whitespace-pre-wrap max-h-64 overflow-y-auto">${respBody}</pre>
119
+ </div>
120
+ </div>
121
+ </div>
122
+ </div>
123
+
124
+ ${
125
+ matchedStub !== null
126
+ ? html`<div>
127
+ <h3 class="text-lg font-semibold mb-3">Matched Stub</h3>
128
+ ${raw(stubCardPartial(matchedStub).value)}
129
+ </div>`
130
+ : html``
131
+ }`
132
+
133
+ return layout(
134
+ {
135
+ title: `${data.config.name} — Request ${entry.id}`,
136
+ imposterName: data.config.name,
137
+ port: data.config.port,
138
+ activeTab: "requests"
139
+ },
140
+ content
141
+ )
142
+ }
@@ -0,0 +1,141 @@
1
+ import type { ImposterConfig } from "../../domain/imposter"
2
+ import type { RequestLogEntry } from "../../schemas/RequestLogSchema"
3
+ import { html, raw } from "../html"
4
+ import type { SafeHtml } from "../html"
5
+ import { layout } from "../layout"
6
+ import { requestTablePartial, statusBadge } from "../partials"
7
+
8
+ export interface RequestsPageData {
9
+ readonly config: ImposterConfig
10
+ readonly entries: ReadonlyArray<RequestLogEntry>
11
+ }
12
+
13
+ const testRequestForm = (_port: number): SafeHtml =>
14
+ html`<details class="bg-white rounded-lg shadow p-4 mb-6">
15
+ <summary class="text-lg font-semibold cursor-pointer">Send Test Request</summary>
16
+ <form hx-post="/_admin/requests/test" hx-target="#test-result" hx-swap="innerHTML" class="mt-3 space-y-3">
17
+ <div class="grid grid-cols-2 gap-3">
18
+ <div>
19
+ <label class="block text-sm font-medium text-gray-700 mb-1">Method</label>
20
+ <select name="method" class="w-full border rounded p-2 text-sm">
21
+ <option value="GET">GET</option>
22
+ <option value="POST">POST</option>
23
+ <option value="PUT">PUT</option>
24
+ <option value="PATCH">PATCH</option>
25
+ <option value="DELETE">DELETE</option>
26
+ <option value="HEAD">HEAD</option>
27
+ <option value="OPTIONS">OPTIONS</option>
28
+ </select>
29
+ </div>
30
+ <div>
31
+ <label class="block text-sm font-medium text-gray-700 mb-1">Path</label>
32
+ <input name="path" type="text" value="/" class="w-full border rounded p-2 text-sm font-mono" placeholder="/api/test" />
33
+ </div>
34
+ </div>
35
+ <div>
36
+ <label class="block text-sm font-medium text-gray-700 mb-1">Content-Type</label>
37
+ <select name="contentType" class="w-full border rounded p-2 text-sm">
38
+ <option value="application/json">application/json</option>
39
+ <option value="text/plain">text/plain</option>
40
+ <option value="application/x-www-form-urlencoded">application/x-www-form-urlencoded</option>
41
+ <option value="application/xml">application/xml</option>
42
+ </select>
43
+ </div>
44
+ <div>
45
+ <label class="block text-sm font-medium text-gray-700 mb-1">Headers (one per line: Key: Value)</label>
46
+ <textarea name="headers" rows="2" class="w-full border rounded p-2 text-sm font-mono" placeholder="Authorization: Bearer token123"></textarea>
47
+ </div>
48
+ <div>
49
+ <label class="block text-sm font-medium text-gray-700 mb-1">Body</label>
50
+ <textarea name="body" rows="3" class="w-full border rounded p-2 text-sm font-mono" placeholder='{"key": "value"}'></textarea>
51
+ </div>
52
+ <button type="submit" class="bg-indigo-600 text-white px-4 py-2 rounded hover:bg-indigo-700 text-sm">Send Request</button>
53
+ </form>
54
+ <div id="test-result" class="mt-3"></div>
55
+ </details>`
56
+
57
+ const filterBar = (): SafeHtml =>
58
+ html`<div class="bg-white rounded-lg shadow p-4 mb-6">
59
+ <form hx-get="/_admin/requests/list" hx-target="#request-table-body" hx-swap="innerHTML" class="flex flex-wrap gap-3 items-end">
60
+ <div>
61
+ <label class="block text-xs text-gray-500 mb-1">Method</label>
62
+ <select name="method" class="border rounded p-1.5 text-sm">
63
+ <option value="">All</option>
64
+ <option value="GET">GET</option>
65
+ <option value="POST">POST</option>
66
+ <option value="PUT">PUT</option>
67
+ <option value="PATCH">PATCH</option>
68
+ <option value="DELETE">DELETE</option>
69
+ </select>
70
+ </div>
71
+ <div>
72
+ <label class="block text-xs text-gray-500 mb-1">Path</label>
73
+ <input name="path" type="text" class="border rounded p-1.5 text-sm font-mono w-40" placeholder="/api/..." />
74
+ </div>
75
+ <div>
76
+ <label class="block text-xs text-gray-500 mb-1">Status</label>
77
+ <input name="status" type="text" class="border rounded p-1.5 text-sm font-mono w-20" placeholder="200" />
78
+ </div>
79
+ <button type="submit" class="bg-indigo-600 text-white px-3 py-1.5 rounded hover:bg-indigo-700 text-sm">Filter</button>
80
+ <button type="button" hx-delete="/_admin/requests" hx-target="#request-table-body" hx-swap="innerHTML" hx-confirm="Clear all request logs?" class="bg-red-50 text-red-600 border border-red-200 px-3 py-1.5 rounded hover:bg-red-100 text-sm ml-auto">Clear Log</button>
81
+ </form>
82
+ </div>`
83
+
84
+ export const requestsPage = (data: RequestsPageData): SafeHtml => {
85
+ const content = html`
86
+ ${testRequestForm(data.config.port)}
87
+ ${filterBar()}
88
+ <div class="bg-white rounded-lg shadow overflow-x-auto">
89
+ <table class="w-full text-left">
90
+ <thead>
91
+ <tr class="text-xs text-gray-500 uppercase border-b">
92
+ <th class="py-2 px-3">Time</th>
93
+ <th class="py-2 px-3">Method</th>
94
+ <th class="py-2 px-3">Path</th>
95
+ <th class="py-2 px-3">Status</th>
96
+ <th class="py-2 px-3">Stub</th>
97
+ <th class="py-2 px-3">Duration</th>
98
+ <th class="py-2 px-3"></th>
99
+ </tr>
100
+ </thead>
101
+ <tbody id="request-table-body">
102
+ ${raw(requestTablePartial(data.entries.slice().reverse()).value)}
103
+ </tbody>
104
+ </table>
105
+ </div>`
106
+
107
+ return layout(
108
+ {
109
+ title: `${data.config.name} — Requests`,
110
+ imposterName: data.config.name,
111
+ port: data.config.port,
112
+ activeTab: "requests"
113
+ },
114
+ content
115
+ )
116
+ }
117
+
118
+ export const testResultPartial = (
119
+ result: { status: number; headers: Record<string, string>; body: string; duration: number }
120
+ ): SafeHtml => {
121
+ const headerRows = Object.entries(result.headers).map(([k, v]) =>
122
+ html`<tr class="border-t"><td class="py-1 px-2 text-xs font-mono text-gray-600">${k}</td><td class="py-1 px-2 text-xs font-mono">${v}</td></tr>`
123
+ )
124
+ return html`<div class="bg-gray-50 rounded p-3 border">
125
+ <div class="flex items-center gap-3 mb-2">
126
+ <span class="font-semibold text-sm">Response</span>
127
+ ${statusBadge(result.status)}
128
+ <span class="text-xs text-gray-500">${String(result.duration)}ms</span>
129
+ </div>
130
+ ${
131
+ headerRows.length > 0
132
+ ? html`<table class="w-full mb-2"><thead><tr class="text-xs text-gray-500"><th class="text-left px-2">Header</th><th class="text-left px-2">Value</th></tr></thead><tbody>${
133
+ headerRows.reduce((a, r) => html`${a}${r}`, html``)
134
+ }</tbody></table>`
135
+ : html``
136
+ }
137
+ <pre class="bg-white border rounded p-2 text-xs font-mono overflow-x-auto whitespace-pre-wrap max-h-64 overflow-y-auto">${
138
+ result.body || "(empty body)"
139
+ }</pre>
140
+ </div>`
141
+ }
@@ -0,0 +1,52 @@
1
+ import type { ImposterConfig } from "../../domain/imposter"
2
+ import type { Stub } from "../../schemas/StubSchema"
3
+ import { html, raw } from "../html"
4
+ import { layout } from "../layout"
5
+ import { stubListPartial } from "../partials"
6
+
7
+ export interface StubsPageData {
8
+ readonly config: ImposterConfig
9
+ readonly stubs: ReadonlyArray<Stub>
10
+ }
11
+
12
+ const addStubForm = () =>
13
+ html`<div class="bg-white rounded-lg shadow p-4 mb-6">
14
+ <h2 class="text-lg font-semibold mb-3">Add Stub</h2>
15
+ <form hx-post="/_admin/stubs" hx-target="#stub-list" hx-swap="innerHTML" hx-on::after-request="if(event.detail.successful) this.reset()">
16
+ <div class="mb-3">
17
+ <label class="block text-sm font-medium text-gray-700 mb-1">Predicates (JSON array)</label>
18
+ <textarea name="predicates" rows="3" class="w-full border rounded p-2 font-mono text-sm" placeholder="[]">[]</textarea>
19
+ </div>
20
+ <div class="mb-3">
21
+ <label class="block text-sm font-medium text-gray-700 mb-1">Responses (JSON array, at least 1)</label>
22
+ <textarea name="responses" rows="4" class="w-full border rounded p-2 font-mono text-sm" placeholder='[{"status": 200, "body": {}}]'></textarea>
23
+ </div>
24
+ <div class="mb-3">
25
+ <label class="block text-sm font-medium text-gray-700 mb-1">Response Mode</label>
26
+ <select name="responseMode" class="border rounded p-2 text-sm">
27
+ <option value="sequential">sequential</option>
28
+ <option value="random">random</option>
29
+ <option value="repeat">repeat</option>
30
+ </select>
31
+ </div>
32
+ <button type="submit" class="bg-indigo-600 text-white px-4 py-2 rounded hover:bg-indigo-700 text-sm">Add Stub</button>
33
+ </form>
34
+ </div>`
35
+
36
+ export const stubsPage = (data: StubsPageData) => {
37
+ const content = html`
38
+ ${addStubForm()}
39
+ <div id="stub-list">
40
+ ${raw(stubListPartial(data.stubs).value)}
41
+ </div>`
42
+
43
+ return layout(
44
+ {
45
+ title: `${data.config.name} — Stubs`,
46
+ imposterName: data.config.name,
47
+ port: data.config.port,
48
+ activeTab: "stubs"
49
+ },
50
+ content
51
+ )
52
+ }