procsi 0.2.6

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 (296) hide show
  1. package/LICENSE +665 -0
  2. package/README.md +587 -0
  3. package/dist/cli/commands/clear.d.ts +3 -0
  4. package/dist/cli/commands/clear.d.ts.map +1 -0
  5. package/dist/cli/commands/clear.js +30 -0
  6. package/dist/cli/commands/clear.js.map +1 -0
  7. package/dist/cli/commands/daemon.d.ts +3 -0
  8. package/dist/cli/commands/daemon.d.ts.map +1 -0
  9. package/dist/cli/commands/daemon.js +59 -0
  10. package/dist/cli/commands/daemon.js.map +1 -0
  11. package/dist/cli/commands/debug-dump.d.ts +27 -0
  12. package/dist/cli/commands/debug-dump.d.ts.map +1 -0
  13. package/dist/cli/commands/debug-dump.js +102 -0
  14. package/dist/cli/commands/debug-dump.js.map +1 -0
  15. package/dist/cli/commands/helpers.d.ts +18 -0
  16. package/dist/cli/commands/helpers.d.ts.map +1 -0
  17. package/dist/cli/commands/helpers.js +34 -0
  18. package/dist/cli/commands/helpers.js.map +1 -0
  19. package/dist/cli/commands/init.d.ts +9 -0
  20. package/dist/cli/commands/init.d.ts.map +1 -0
  21. package/dist/cli/commands/init.js +28 -0
  22. package/dist/cli/commands/init.js.map +1 -0
  23. package/dist/cli/commands/intercept.d.ts +9 -0
  24. package/dist/cli/commands/intercept.d.ts.map +1 -0
  25. package/dist/cli/commands/intercept.js +121 -0
  26. package/dist/cli/commands/intercept.js.map +1 -0
  27. package/dist/cli/commands/interceptors.d.ts +3 -0
  28. package/dist/cli/commands/interceptors.d.ts.map +1 -0
  29. package/dist/cli/commands/interceptors.js +163 -0
  30. package/dist/cli/commands/interceptors.js.map +1 -0
  31. package/dist/cli/commands/mcp.d.ts +3 -0
  32. package/dist/cli/commands/mcp.d.ts.map +1 -0
  33. package/dist/cli/commands/mcp.js +24 -0
  34. package/dist/cli/commands/mcp.js.map +1 -0
  35. package/dist/cli/commands/off.d.ts +8 -0
  36. package/dist/cli/commands/off.d.ts.map +1 -0
  37. package/dist/cli/commands/off.js +34 -0
  38. package/dist/cli/commands/off.js.map +1 -0
  39. package/dist/cli/commands/on.d.ts +9 -0
  40. package/dist/cli/commands/on.d.ts.map +1 -0
  41. package/dist/cli/commands/on.js +121 -0
  42. package/dist/cli/commands/on.js.map +1 -0
  43. package/dist/cli/commands/project.d.ts +3 -0
  44. package/dist/cli/commands/project.d.ts.map +1 -0
  45. package/dist/cli/commands/project.js +15 -0
  46. package/dist/cli/commands/project.js.map +1 -0
  47. package/dist/cli/commands/restart.d.ts +3 -0
  48. package/dist/cli/commands/restart.d.ts.map +1 -0
  49. package/dist/cli/commands/restart.js +35 -0
  50. package/dist/cli/commands/restart.js.map +1 -0
  51. package/dist/cli/commands/status.d.ts +3 -0
  52. package/dist/cli/commands/status.d.ts.map +1 -0
  53. package/dist/cli/commands/status.js +66 -0
  54. package/dist/cli/commands/status.js.map +1 -0
  55. package/dist/cli/commands/stop.d.ts +3 -0
  56. package/dist/cli/commands/stop.d.ts.map +1 -0
  57. package/dist/cli/commands/stop.js +24 -0
  58. package/dist/cli/commands/stop.js.map +1 -0
  59. package/dist/cli/commands/tui.d.ts +3 -0
  60. package/dist/cli/commands/tui.d.ts.map +1 -0
  61. package/dist/cli/commands/tui.js +36 -0
  62. package/dist/cli/commands/tui.js.map +1 -0
  63. package/dist/cli/commands/vars.d.ts +36 -0
  64. package/dist/cli/commands/vars.d.ts.map +1 -0
  65. package/dist/cli/commands/vars.js +207 -0
  66. package/dist/cli/commands/vars.js.map +1 -0
  67. package/dist/cli/index.d.ts +3 -0
  68. package/dist/cli/index.d.ts.map +1 -0
  69. package/dist/cli/index.js +37 -0
  70. package/dist/cli/index.js.map +1 -0
  71. package/dist/cli/tui/App.d.ts +15 -0
  72. package/dist/cli/tui/App.d.ts.map +1 -0
  73. package/dist/cli/tui/App.js +544 -0
  74. package/dist/cli/tui/App.js.map +1 -0
  75. package/dist/cli/tui/components/AccordionContent.d.ts +28 -0
  76. package/dist/cli/tui/components/AccordionContent.d.ts.map +1 -0
  77. package/dist/cli/tui/components/AccordionContent.js +87 -0
  78. package/dist/cli/tui/components/AccordionContent.js.map +1 -0
  79. package/dist/cli/tui/components/AccordionPanel.d.ts +38 -0
  80. package/dist/cli/tui/components/AccordionPanel.d.ts.map +1 -0
  81. package/dist/cli/tui/components/AccordionPanel.js +110 -0
  82. package/dist/cli/tui/components/AccordionPanel.js.map +1 -0
  83. package/dist/cli/tui/components/AccordionSection.d.ts +32 -0
  84. package/dist/cli/tui/components/AccordionSection.d.ts.map +1 -0
  85. package/dist/cli/tui/components/AccordionSection.js +41 -0
  86. package/dist/cli/tui/components/AccordionSection.js.map +1 -0
  87. package/dist/cli/tui/components/BodyView.d.ts +14 -0
  88. package/dist/cli/tui/components/BodyView.d.ts.map +1 -0
  89. package/dist/cli/tui/components/BodyView.js +39 -0
  90. package/dist/cli/tui/components/BodyView.js.map +1 -0
  91. package/dist/cli/tui/components/ExportModal.d.ts +34 -0
  92. package/dist/cli/tui/components/ExportModal.d.ts.map +1 -0
  93. package/dist/cli/tui/components/ExportModal.js +109 -0
  94. package/dist/cli/tui/components/ExportModal.js.map +1 -0
  95. package/dist/cli/tui/components/FilterBar.d.ts +21 -0
  96. package/dist/cli/tui/components/FilterBar.d.ts.map +1 -0
  97. package/dist/cli/tui/components/FilterBar.js +155 -0
  98. package/dist/cli/tui/components/FilterBar.js.map +1 -0
  99. package/dist/cli/tui/components/HeadersView.d.ts +13 -0
  100. package/dist/cli/tui/components/HeadersView.d.ts.map +1 -0
  101. package/dist/cli/tui/components/HeadersView.js +8 -0
  102. package/dist/cli/tui/components/HeadersView.js.map +1 -0
  103. package/dist/cli/tui/components/HelpModal.d.ts +13 -0
  104. package/dist/cli/tui/components/HelpModal.d.ts.map +1 -0
  105. package/dist/cli/tui/components/HelpModal.js +78 -0
  106. package/dist/cli/tui/components/HelpModal.js.map +1 -0
  107. package/dist/cli/tui/components/HintContent.d.ts +25 -0
  108. package/dist/cli/tui/components/HintContent.d.ts.map +1 -0
  109. package/dist/cli/tui/components/HintContent.js +44 -0
  110. package/dist/cli/tui/components/HintContent.js.map +1 -0
  111. package/dist/cli/tui/components/InfoModal.d.ts +15 -0
  112. package/dist/cli/tui/components/InfoModal.d.ts.map +1 -0
  113. package/dist/cli/tui/components/InfoModal.js +17 -0
  114. package/dist/cli/tui/components/InfoModal.js.map +1 -0
  115. package/dist/cli/tui/components/JsonExplorerModal.d.ts +24 -0
  116. package/dist/cli/tui/components/JsonExplorerModal.d.ts.map +1 -0
  117. package/dist/cli/tui/components/JsonExplorerModal.js +311 -0
  118. package/dist/cli/tui/components/JsonExplorerModal.js.map +1 -0
  119. package/dist/cli/tui/components/Modal.d.ts +26 -0
  120. package/dist/cli/tui/components/Modal.d.ts.map +1 -0
  121. package/dist/cli/tui/components/Modal.js +15 -0
  122. package/dist/cli/tui/components/Modal.js.map +1 -0
  123. package/dist/cli/tui/components/Panel.d.ts +19 -0
  124. package/dist/cli/tui/components/Panel.d.ts.map +1 -0
  125. package/dist/cli/tui/components/Panel.js +37 -0
  126. package/dist/cli/tui/components/Panel.js.map +1 -0
  127. package/dist/cli/tui/components/RequestDetails.d.ts +16 -0
  128. package/dist/cli/tui/components/RequestDetails.d.ts.map +1 -0
  129. package/dist/cli/tui/components/RequestDetails.js +23 -0
  130. package/dist/cli/tui/components/RequestDetails.js.map +1 -0
  131. package/dist/cli/tui/components/RequestList.d.ts +21 -0
  132. package/dist/cli/tui/components/RequestList.d.ts.map +1 -0
  133. package/dist/cli/tui/components/RequestList.js +30 -0
  134. package/dist/cli/tui/components/RequestList.js.map +1 -0
  135. package/dist/cli/tui/components/RequestListItem.d.ts +36 -0
  136. package/dist/cli/tui/components/RequestListItem.d.ts.map +1 -0
  137. package/dist/cli/tui/components/RequestListItem.js +130 -0
  138. package/dist/cli/tui/components/RequestListItem.js.map +1 -0
  139. package/dist/cli/tui/components/SaveModal.d.ts +30 -0
  140. package/dist/cli/tui/components/SaveModal.d.ts.map +1 -0
  141. package/dist/cli/tui/components/SaveModal.js +95 -0
  142. package/dist/cli/tui/components/SaveModal.js.map +1 -0
  143. package/dist/cli/tui/components/StatusBar.d.ts +39 -0
  144. package/dist/cli/tui/components/StatusBar.d.ts.map +1 -0
  145. package/dist/cli/tui/components/StatusBar.js +53 -0
  146. package/dist/cli/tui/components/StatusBar.js.map +1 -0
  147. package/dist/cli/tui/components/TextViewerModal.d.ts +19 -0
  148. package/dist/cli/tui/components/TextViewerModal.d.ts.map +1 -0
  149. package/dist/cli/tui/components/TextViewerModal.js +227 -0
  150. package/dist/cli/tui/components/TextViewerModal.js.map +1 -0
  151. package/dist/cli/tui/hooks/useBodyExport.d.ts +26 -0
  152. package/dist/cli/tui/hooks/useBodyExport.d.ts.map +1 -0
  153. package/dist/cli/tui/hooks/useBodyExport.js +173 -0
  154. package/dist/cli/tui/hooks/useBodyExport.js.map +1 -0
  155. package/dist/cli/tui/hooks/useExport.d.ts +29 -0
  156. package/dist/cli/tui/hooks/useExport.d.ts.map +1 -0
  157. package/dist/cli/tui/hooks/useExport.js +64 -0
  158. package/dist/cli/tui/hooks/useExport.js.map +1 -0
  159. package/dist/cli/tui/hooks/useRequests.d.ts +26 -0
  160. package/dist/cli/tui/hooks/useRequests.d.ts.map +1 -0
  161. package/dist/cli/tui/hooks/useRequests.js +131 -0
  162. package/dist/cli/tui/hooks/useRequests.js.map +1 -0
  163. package/dist/cli/tui/hooks/useSaveBinary.d.ts +26 -0
  164. package/dist/cli/tui/hooks/useSaveBinary.d.ts.map +1 -0
  165. package/dist/cli/tui/hooks/useSaveBinary.js +165 -0
  166. package/dist/cli/tui/hooks/useSaveBinary.js.map +1 -0
  167. package/dist/cli/tui/hooks/useSpinner.d.ts +5 -0
  168. package/dist/cli/tui/hooks/useSpinner.d.ts.map +1 -0
  169. package/dist/cli/tui/hooks/useSpinner.js +25 -0
  170. package/dist/cli/tui/hooks/useSpinner.js.map +1 -0
  171. package/dist/cli/tui/hooks/useStdoutDimensions.d.ts +11 -0
  172. package/dist/cli/tui/hooks/useStdoutDimensions.d.ts.map +1 -0
  173. package/dist/cli/tui/hooks/useStdoutDimensions.js +29 -0
  174. package/dist/cli/tui/hooks/useStdoutDimensions.js.map +1 -0
  175. package/dist/cli/tui/utils/binary.d.ts +24 -0
  176. package/dist/cli/tui/utils/binary.d.ts.map +1 -0
  177. package/dist/cli/tui/utils/binary.js +152 -0
  178. package/dist/cli/tui/utils/binary.js.map +1 -0
  179. package/dist/cli/tui/utils/clipboard.d.ts +9 -0
  180. package/dist/cli/tui/utils/clipboard.d.ts.map +1 -0
  181. package/dist/cli/tui/utils/clipboard.js +58 -0
  182. package/dist/cli/tui/utils/clipboard.js.map +1 -0
  183. package/dist/cli/tui/utils/content-type.d.ts +8 -0
  184. package/dist/cli/tui/utils/content-type.d.ts.map +1 -0
  185. package/dist/cli/tui/utils/content-type.js +10 -0
  186. package/dist/cli/tui/utils/content-type.js.map +1 -0
  187. package/dist/cli/tui/utils/curl.d.ts +9 -0
  188. package/dist/cli/tui/utils/curl.d.ts.map +1 -0
  189. package/dist/cli/tui/utils/curl.js +54 -0
  190. package/dist/cli/tui/utils/curl.js.map +1 -0
  191. package/dist/cli/tui/utils/filters.d.ts +6 -0
  192. package/dist/cli/tui/utils/filters.d.ts.map +1 -0
  193. package/dist/cli/tui/utils/filters.js +13 -0
  194. package/dist/cli/tui/utils/filters.js.map +1 -0
  195. package/dist/cli/tui/utils/formatters.d.ts +49 -0
  196. package/dist/cli/tui/utils/formatters.d.ts.map +1 -0
  197. package/dist/cli/tui/utils/formatters.js +200 -0
  198. package/dist/cli/tui/utils/formatters.js.map +1 -0
  199. package/dist/cli/tui/utils/har.d.ts +75 -0
  200. package/dist/cli/tui/utils/har.d.ts.map +1 -0
  201. package/dist/cli/tui/utils/har.js +117 -0
  202. package/dist/cli/tui/utils/har.js.map +1 -0
  203. package/dist/cli/tui/utils/json-tree.d.ts +69 -0
  204. package/dist/cli/tui/utils/json-tree.d.ts.map +1 -0
  205. package/dist/cli/tui/utils/json-tree.js +339 -0
  206. package/dist/cli/tui/utils/json-tree.js.map +1 -0
  207. package/dist/cli/tui/utils/open-external.d.ts +17 -0
  208. package/dist/cli/tui/utils/open-external.d.ts.map +1 -0
  209. package/dist/cli/tui/utils/open-external.js +57 -0
  210. package/dist/cli/tui/utils/open-external.js.map +1 -0
  211. package/dist/cli/tui/utils/syntax-highlight.d.ts +16 -0
  212. package/dist/cli/tui/utils/syntax-highlight.d.ts.map +1 -0
  213. package/dist/cli/tui/utils/syntax-highlight.js +64 -0
  214. package/dist/cli/tui/utils/syntax-highlight.js.map +1 -0
  215. package/dist/daemon/control.d.ts +21 -0
  216. package/dist/daemon/control.d.ts.map +1 -0
  217. package/dist/daemon/control.js +311 -0
  218. package/dist/daemon/control.js.map +1 -0
  219. package/dist/daemon/htpx-client.d.ts +8 -0
  220. package/dist/daemon/htpx-client.d.ts.map +1 -0
  221. package/dist/daemon/htpx-client.js +25 -0
  222. package/dist/daemon/htpx-client.js.map +1 -0
  223. package/dist/daemon/index.d.ts +3 -0
  224. package/dist/daemon/index.d.ts.map +1 -0
  225. package/dist/daemon/index.js +178 -0
  226. package/dist/daemon/index.js.map +1 -0
  227. package/dist/daemon/interceptor-loader.d.ts +30 -0
  228. package/dist/daemon/interceptor-loader.d.ts.map +1 -0
  229. package/dist/daemon/interceptor-loader.js +249 -0
  230. package/dist/daemon/interceptor-loader.js.map +1 -0
  231. package/dist/daemon/interceptor-runner.d.ts +39 -0
  232. package/dist/daemon/interceptor-runner.d.ts.map +1 -0
  233. package/dist/daemon/interceptor-runner.js +312 -0
  234. package/dist/daemon/interceptor-runner.js.map +1 -0
  235. package/dist/daemon/procsi-client.d.ts +8 -0
  236. package/dist/daemon/procsi-client.d.ts.map +1 -0
  237. package/dist/daemon/procsi-client.js +25 -0
  238. package/dist/daemon/procsi-client.js.map +1 -0
  239. package/dist/daemon/proxy.d.ts +34 -0
  240. package/dist/daemon/proxy.d.ts.map +1 -0
  241. package/dist/daemon/proxy.js +213 -0
  242. package/dist/daemon/proxy.js.map +1 -0
  243. package/dist/daemon/storage.d.ts +130 -0
  244. package/dist/daemon/storage.d.ts.map +1 -0
  245. package/dist/daemon/storage.js +761 -0
  246. package/dist/daemon/storage.js.map +1 -0
  247. package/dist/interceptors.d.ts +2 -0
  248. package/dist/interceptors.d.ts.map +1 -0
  249. package/dist/interceptors.js +2 -0
  250. package/dist/interceptors.js.map +1 -0
  251. package/dist/mcp/server.d.ts +110 -0
  252. package/dist/mcp/server.d.ts.map +1 -0
  253. package/dist/mcp/server.js +806 -0
  254. package/dist/mcp/server.js.map +1 -0
  255. package/dist/overrides/node.d.ts +30 -0
  256. package/dist/overrides/node.d.ts.map +1 -0
  257. package/dist/overrides/node.js +66 -0
  258. package/dist/overrides/node.js.map +1 -0
  259. package/dist/shared/config.d.ts +21 -0
  260. package/dist/shared/config.d.ts.map +1 -0
  261. package/dist/shared/config.js +83 -0
  262. package/dist/shared/config.js.map +1 -0
  263. package/dist/shared/content-type.d.ts +64 -0
  264. package/dist/shared/content-type.d.ts.map +1 -0
  265. package/dist/shared/content-type.js +145 -0
  266. package/dist/shared/content-type.js.map +1 -0
  267. package/dist/shared/control-client.d.ts +144 -0
  268. package/dist/shared/control-client.d.ts.map +1 -0
  269. package/dist/shared/control-client.js +272 -0
  270. package/dist/shared/control-client.js.map +1 -0
  271. package/dist/shared/daemon.d.ts +33 -0
  272. package/dist/shared/daemon.d.ts.map +1 -0
  273. package/dist/shared/daemon.js +231 -0
  274. package/dist/shared/daemon.js.map +1 -0
  275. package/dist/shared/logger.d.ts +47 -0
  276. package/dist/shared/logger.d.ts.map +1 -0
  277. package/dist/shared/logger.js +200 -0
  278. package/dist/shared/logger.js.map +1 -0
  279. package/dist/shared/project.d.ts +76 -0
  280. package/dist/shared/project.d.ts.map +1 -0
  281. package/dist/shared/project.js +185 -0
  282. package/dist/shared/project.js.map +1 -0
  283. package/dist/shared/proxy-info.d.ts +10 -0
  284. package/dist/shared/proxy-info.d.ts.map +1 -0
  285. package/dist/shared/proxy-info.js +15 -0
  286. package/dist/shared/proxy-info.js.map +1 -0
  287. package/dist/shared/types.d.ts +128 -0
  288. package/dist/shared/types.d.ts.map +1 -0
  289. package/dist/shared/types.js +5 -0
  290. package/dist/shared/types.js.map +1 -0
  291. package/dist/shared/version.d.ts +5 -0
  292. package/dist/shared/version.d.ts.map +1 -0
  293. package/dist/shared/version.js +21 -0
  294. package/dist/shared/version.js.map +1 -0
  295. package/package.json +113 -0
  296. package/skills/procsi/SKILL.md +228 -0
@@ -0,0 +1,761 @@
1
+ import Database from "better-sqlite3";
2
+ import { v4 as uuidv4 } from "uuid";
3
+ import { createLogger } from "../shared/logger.js";
4
+ import { normaliseContentType, buildTextContentTypeSqlCondition, buildJsonContentTypeSqlCondition, } from "../shared/content-type.js";
5
+ import { DEFAULT_MAX_STORED_REQUESTS } from "../shared/config.js";
6
+ const DEFAULT_QUERY_LIMIT = 1000;
7
+ const EVICTION_CHECK_INTERVAL = 100;
8
+ const SCHEMA = `
9
+ CREATE TABLE IF NOT EXISTS sessions (
10
+ id TEXT PRIMARY KEY,
11
+ label TEXT,
12
+ pid INTEGER NOT NULL,
13
+ started_at INTEGER NOT NULL
14
+ );
15
+
16
+ CREATE TABLE IF NOT EXISTS requests (
17
+ id TEXT PRIMARY KEY,
18
+ session_id TEXT NOT NULL,
19
+ label TEXT,
20
+ timestamp INTEGER NOT NULL,
21
+ method TEXT NOT NULL,
22
+ url TEXT NOT NULL,
23
+ host TEXT NOT NULL,
24
+ path TEXT NOT NULL,
25
+ request_headers TEXT,
26
+ request_body BLOB,
27
+ request_body_truncated INTEGER DEFAULT 0,
28
+ response_status INTEGER,
29
+ response_headers TEXT,
30
+ response_body BLOB,
31
+ response_body_truncated INTEGER DEFAULT 0,
32
+ duration_ms INTEGER,
33
+ request_content_type TEXT,
34
+ response_content_type TEXT,
35
+ intercepted_by TEXT,
36
+ interception_type TEXT CHECK(interception_type IN ('modified', 'mocked')),
37
+ created_at INTEGER DEFAULT (unixepoch()),
38
+ FOREIGN KEY (session_id) REFERENCES sessions(id)
39
+ );
40
+
41
+ CREATE INDEX IF NOT EXISTS idx_requests_timestamp ON requests(timestamp DESC);
42
+ CREATE INDEX IF NOT EXISTS idx_requests_session ON requests(session_id);
43
+ CREATE INDEX IF NOT EXISTS idx_requests_label ON requests(label);
44
+ CREATE INDEX IF NOT EXISTS idx_requests_method ON requests(method);
45
+ CREATE INDEX IF NOT EXISTS idx_requests_status ON requests(response_status);
46
+ CREATE INDEX IF NOT EXISTS idx_requests_host ON requests(host);
47
+ `;
48
+ const MIGRATIONS = [
49
+ {
50
+ version: 1,
51
+ description: "Add body truncation tracking columns",
52
+ sql: `
53
+ ALTER TABLE requests ADD COLUMN request_body_truncated INTEGER DEFAULT 0;
54
+ ALTER TABLE requests ADD COLUMN response_body_truncated INTEGER DEFAULT 0;
55
+ `,
56
+ },
57
+ {
58
+ version: 2,
59
+ description: "Add indices for method and status filtering",
60
+ sql: `
61
+ CREATE INDEX IF NOT EXISTS idx_requests_method ON requests(method);
62
+ CREATE INDEX IF NOT EXISTS idx_requests_status ON requests(response_status);
63
+ `,
64
+ },
65
+ {
66
+ version: 3,
67
+ description: "Add content-type columns for efficient body searching",
68
+ sql: `
69
+ ALTER TABLE requests ADD COLUMN request_content_type TEXT;
70
+ ALTER TABLE requests ADD COLUMN response_content_type TEXT;
71
+ `,
72
+ },
73
+ {
74
+ version: 4,
75
+ description: "Add index on host for host-based filtering",
76
+ sql: `CREATE INDEX IF NOT EXISTS idx_requests_host ON requests(host);`,
77
+ },
78
+ {
79
+ version: 5,
80
+ description: "Add interceptor tracking columns",
81
+ sql: `
82
+ ALTER TABLE requests ADD COLUMN intercepted_by TEXT;
83
+ ALTER TABLE requests ADD COLUMN interception_type TEXT;
84
+ `,
85
+ },
86
+ ];
87
+ const STATUS_RANGE_MULTIPLIER = 100;
88
+ const MIN_HTTP_STATUS = 100;
89
+ const MAX_HTTP_STATUS = 599;
90
+ /**
91
+ * Escape SQL LIKE wildcards in user input to prevent unintended pattern matching.
92
+ */
93
+ function escapeLikeWildcards(input) {
94
+ return input.replace(/\\/g, "\\\\").replace(/%/g, "\\%").replace(/_/g, "\\_");
95
+ }
96
+ /**
97
+ * Apply status range filter condition. Supports three formats:
98
+ * - Nxx pattern (e.g. "2xx") → range from N00 to (N+1)00
99
+ * - Exact code (e.g. "401") → exact match
100
+ * - Numeric range (e.g. "500-503") → inclusive range
101
+ */
102
+ function applyStatusCondition(conditions, params, statusRange) {
103
+ // Nxx pattern — e.g. "2xx", "4xx"
104
+ if (/^[1-5]xx$/.test(statusRange)) {
105
+ const firstDigit = parseInt(statusRange.charAt(0), 10);
106
+ const lower = firstDigit * STATUS_RANGE_MULTIPLIER;
107
+ const upper = (firstDigit + 1) * STATUS_RANGE_MULTIPLIER;
108
+ conditions.push("response_status >= ? AND response_status < ?");
109
+ params.push(lower, upper);
110
+ return;
111
+ }
112
+ // Numeric range — e.g. "500-503"
113
+ const rangeMatch = statusRange.match(/^(\d{3})-(\d{3})$/);
114
+ if (rangeMatch && rangeMatch[1] && rangeMatch[2]) {
115
+ const low = parseInt(rangeMatch[1], 10);
116
+ const high = parseInt(rangeMatch[2], 10);
117
+ if (low >= MIN_HTTP_STATUS && high <= MAX_HTTP_STATUS && low <= high) {
118
+ conditions.push("response_status >= ? AND response_status <= ?");
119
+ params.push(low, high);
120
+ return;
121
+ }
122
+ }
123
+ // Exact code — e.g. "401"
124
+ if (/^\d{3}$/.test(statusRange)) {
125
+ const code = parseInt(statusRange, 10);
126
+ if (code >= MIN_HTTP_STATUS && code <= MAX_HTTP_STATUS) {
127
+ conditions.push("response_status = ?");
128
+ params.push(code);
129
+ return;
130
+ }
131
+ }
132
+ // Unrecognised format — silently ignored at the storage layer
133
+ // (validation should happen upstream in MCP/control server)
134
+ }
135
+ /**
136
+ * Apply RequestFilter conditions to an existing SQL conditions/params array.
137
+ * Mutates both arrays in place.
138
+ */
139
+ function applyFilterConditions(conditions, params, filter) {
140
+ if (!filter)
141
+ return;
142
+ if (filter.methods && filter.methods.length > 0) {
143
+ const placeholders = filter.methods.map(() => "?").join(", ");
144
+ conditions.push(`method IN (${placeholders})`);
145
+ params.push(...filter.methods);
146
+ }
147
+ if (filter.statusRange) {
148
+ applyStatusCondition(conditions, params, filter.statusRange);
149
+ }
150
+ if (filter.search) {
151
+ const escaped = escapeLikeWildcards(filter.search);
152
+ const pattern = `%${escaped}%`;
153
+ conditions.push("(url LIKE ? ESCAPE '\\' OR path LIKE ? ESCAPE '\\')");
154
+ params.push(pattern, pattern);
155
+ }
156
+ if (filter.host) {
157
+ if (filter.host.startsWith(".")) {
158
+ // Suffix match — e.g. ".example.com" matches "api.example.com"
159
+ const escaped = escapeLikeWildcards(filter.host);
160
+ conditions.push("host LIKE ? ESCAPE '\\'");
161
+ params.push(`%${escaped}`);
162
+ }
163
+ else {
164
+ // Exact match
165
+ conditions.push("host = ?");
166
+ params.push(filter.host);
167
+ }
168
+ }
169
+ if (filter.pathPrefix) {
170
+ const escaped = escapeLikeWildcards(filter.pathPrefix);
171
+ conditions.push("path LIKE ? ESCAPE '\\'");
172
+ params.push(`${escaped}%`);
173
+ }
174
+ if (filter.since !== undefined) {
175
+ conditions.push("timestamp >= ?");
176
+ params.push(filter.since);
177
+ }
178
+ if (filter.before !== undefined) {
179
+ conditions.push("timestamp < ?");
180
+ params.push(filter.before);
181
+ }
182
+ if (filter.interceptedBy) {
183
+ conditions.push("intercepted_by = ?");
184
+ params.push(filter.interceptedBy);
185
+ }
186
+ if (filter.headerName) {
187
+ const name = filter.headerName.toLowerCase();
188
+ const jsonPath = `$."${name}"`;
189
+ const target = filter.headerTarget ?? "both";
190
+ if (filter.headerValue !== undefined) {
191
+ // Name + value match
192
+ if (target === "request") {
193
+ conditions.push("json_extract(request_headers, ?) = ?");
194
+ params.push(jsonPath, filter.headerValue);
195
+ }
196
+ else if (target === "response") {
197
+ conditions.push("json_extract(response_headers, ?) = ?");
198
+ params.push(jsonPath, filter.headerValue);
199
+ }
200
+ else {
201
+ conditions.push("(json_extract(request_headers, ?) = ? OR json_extract(response_headers, ?) = ?)");
202
+ params.push(jsonPath, filter.headerValue, jsonPath, filter.headerValue);
203
+ }
204
+ }
205
+ else {
206
+ // Name-only existence check
207
+ if (target === "request") {
208
+ conditions.push("json_extract(request_headers, ?) IS NOT NULL");
209
+ params.push(jsonPath);
210
+ }
211
+ else if (target === "response") {
212
+ conditions.push("json_extract(response_headers, ?) IS NOT NULL");
213
+ params.push(jsonPath);
214
+ }
215
+ else {
216
+ conditions.push("(json_extract(request_headers, ?) IS NOT NULL OR json_extract(response_headers, ?) IS NOT NULL)");
217
+ params.push(jsonPath, jsonPath);
218
+ }
219
+ }
220
+ }
221
+ }
222
+ export class RequestRepository {
223
+ db;
224
+ logger;
225
+ maxStoredRequests;
226
+ insertsSinceLastEvictionCheck = 0;
227
+ constructor(dbPath, projectRoot, logLevel, options) {
228
+ this.db = new Database(dbPath);
229
+ this.db.pragma("journal_mode = WAL");
230
+ this.db.exec(SCHEMA);
231
+ this.maxStoredRequests = options?.maxStoredRequests ?? DEFAULT_MAX_STORED_REQUESTS;
232
+ // Fresh databases already have the latest schema — stamp to latest version
233
+ // so migrations don't try to re-apply what's already in the CREATE TABLE.
234
+ const currentVersion = this.db.pragma("user_version", { simple: true });
235
+ if (currentVersion === 0) {
236
+ const hasData = this.db.prepare("SELECT COUNT(*) as count FROM requests").get().count > 0;
237
+ if (!hasData) {
238
+ const lastMigration = MIGRATIONS[MIGRATIONS.length - 1];
239
+ const latestVersion = lastMigration ? lastMigration.version : 0;
240
+ this.db.pragma(`user_version = ${latestVersion}`);
241
+ }
242
+ }
243
+ this.applyMigrations();
244
+ if (projectRoot) {
245
+ this.logger = createLogger("storage", projectRoot, logLevel);
246
+ }
247
+ }
248
+ /**
249
+ * Apply pending database migrations using SQLite's user_version pragma for tracking.
250
+ */
251
+ applyMigrations() {
252
+ const currentVersion = this.db.pragma("user_version", { simple: true });
253
+ const pending = MIGRATIONS.filter((m) => m.version > currentVersion);
254
+ if (pending.length === 0)
255
+ return;
256
+ const applyAll = this.db.transaction(() => {
257
+ for (const migration of pending) {
258
+ this.db.exec(migration.sql);
259
+ this.db.pragma(`user_version = ${migration.version}`);
260
+ }
261
+ });
262
+ applyAll();
263
+ }
264
+ /**
265
+ * Register a new session.
266
+ */
267
+ registerSession(label, pid = process.pid) {
268
+ const session = {
269
+ id: uuidv4(),
270
+ label,
271
+ pid,
272
+ startedAt: Date.now(),
273
+ };
274
+ const stmt = this.db.prepare(`
275
+ INSERT INTO sessions (id, label, pid, started_at)
276
+ VALUES (?, ?, ?, ?)
277
+ `);
278
+ stmt.run(session.id, session.label ?? null, session.pid, session.startedAt);
279
+ return session;
280
+ }
281
+ /**
282
+ * Ensure a session exists with a specific ID.
283
+ * If the session already exists, returns it unchanged.
284
+ * If not, creates a new session with the given ID.
285
+ */
286
+ ensureSession(id, label, pid = process.pid) {
287
+ const startedAt = Date.now();
288
+ const stmt = this.db.prepare(`
289
+ INSERT OR IGNORE INTO sessions (id, label, pid, started_at)
290
+ VALUES (?, ?, ?, ?)
291
+ `);
292
+ stmt.run(id, label ?? null, pid, startedAt);
293
+ // Return the session (either newly created or existing)
294
+ const existing = this.getSession(id);
295
+ if (existing) {
296
+ return existing;
297
+ }
298
+ // This should never happen since we just inserted, but satisfies the type checker
299
+ return { id, label, pid, startedAt };
300
+ }
301
+ /**
302
+ * Get a session by ID.
303
+ */
304
+ getSession(id) {
305
+ const stmt = this.db.prepare(`
306
+ SELECT id, label, pid, started_at as startedAt
307
+ FROM sessions
308
+ WHERE id = ?
309
+ `);
310
+ const row = stmt.get(id);
311
+ if (!row) {
312
+ return undefined;
313
+ }
314
+ return {
315
+ id: row.id,
316
+ label: row.label ?? undefined,
317
+ pid: row.pid,
318
+ startedAt: row.startedAt,
319
+ };
320
+ }
321
+ /**
322
+ * List all sessions.
323
+ */
324
+ listSessions() {
325
+ const stmt = this.db.prepare(`
326
+ SELECT id, label, pid, started_at as startedAt
327
+ FROM sessions
328
+ ORDER BY started_at DESC
329
+ `);
330
+ const rows = stmt.all();
331
+ return rows.map((row) => ({
332
+ id: row.id,
333
+ label: row.label ?? undefined,
334
+ pid: row.pid,
335
+ startedAt: row.startedAt,
336
+ }));
337
+ }
338
+ /**
339
+ * Save a captured request. Returns the generated ID.
340
+ */
341
+ saveRequest(request) {
342
+ const id = uuidv4();
343
+ const requestContentType = request.requestHeaders
344
+ ? normaliseContentType(request.requestHeaders["content-type"])
345
+ : null;
346
+ const stmt = this.db.prepare(`
347
+ INSERT INTO requests (
348
+ id, session_id, label, timestamp, method, url, host, path,
349
+ request_headers, request_body, request_body_truncated, response_status, response_headers,
350
+ response_body, response_body_truncated, duration_ms, request_content_type
351
+ )
352
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
353
+ `);
354
+ stmt.run(id, request.sessionId, request.label ?? null, request.timestamp, request.method, request.url, request.host, request.path, request.requestHeaders ? JSON.stringify(request.requestHeaders) : null, request.requestBody ?? null, request.requestBodyTruncated ? 1 : 0, request.responseStatus ?? null, request.responseHeaders ? JSON.stringify(request.responseHeaders) : null, request.responseBody ?? null, request.responseBodyTruncated ? 1 : 0, request.durationMs ?? null, requestContentType);
355
+ this.logger?.debug("Request saved", {
356
+ id,
357
+ sessionId: request.sessionId,
358
+ method: request.method,
359
+ url: request.url,
360
+ });
361
+ this.evictIfNeeded();
362
+ return id;
363
+ }
364
+ /**
365
+ * Update a request with response data.
366
+ */
367
+ updateRequestResponse(id, response) {
368
+ const responseContentType = normaliseContentType(response.headers["content-type"]);
369
+ const stmt = this.db.prepare(`
370
+ UPDATE requests
371
+ SET response_status = ?, response_headers = ?, response_body = ?, response_body_truncated = ?, duration_ms = ?, response_content_type = ?
372
+ WHERE id = ?
373
+ `);
374
+ stmt.run(response.status, JSON.stringify(response.headers), response.body ?? null, response.responseBodyTruncated ? 1 : 0, response.durationMs, responseContentType, id);
375
+ }
376
+ /**
377
+ * Update a request with interceptor metadata.
378
+ */
379
+ updateRequestInterception(id, interceptedBy, interceptionType) {
380
+ const stmt = this.db.prepare(`
381
+ UPDATE requests
382
+ SET intercepted_by = ?, interception_type = ?
383
+ WHERE id = ?
384
+ `);
385
+ stmt.run(interceptedBy, interceptionType, id);
386
+ }
387
+ /**
388
+ * Get a request by ID.
389
+ */
390
+ getRequest(id) {
391
+ const stmt = this.db.prepare(`
392
+ SELECT * FROM requests WHERE id = ?
393
+ `);
394
+ const row = stmt.get(id);
395
+ return row ? this.rowToRequest(row) : undefined;
396
+ }
397
+ /**
398
+ * List requests, optionally filtered by session or label.
399
+ */
400
+ listRequests(options = {}) {
401
+ const conditions = [];
402
+ const params = [];
403
+ if (options.sessionId) {
404
+ conditions.push("session_id = ?");
405
+ params.push(options.sessionId);
406
+ }
407
+ if (options.label) {
408
+ conditions.push("label = ?");
409
+ params.push(options.label);
410
+ }
411
+ applyFilterConditions(conditions, params, options.filter);
412
+ const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
413
+ const limit = options.limit ?? DEFAULT_QUERY_LIMIT;
414
+ const offset = options.offset ?? 0;
415
+ const stmt = this.db.prepare(`
416
+ SELECT * FROM requests
417
+ ${whereClause}
418
+ ORDER BY timestamp DESC
419
+ LIMIT ? OFFSET ?
420
+ `);
421
+ params.push(limit, offset);
422
+ const rows = stmt.all(...params);
423
+ return rows.map((row) => this.rowToRequest(row));
424
+ }
425
+ /**
426
+ * List request summaries (excludes body/header data for performance).
427
+ * Use this for list views where full request data isn't needed.
428
+ */
429
+ listRequestsSummary(options = {}) {
430
+ const conditions = [];
431
+ const params = [];
432
+ if (options.sessionId) {
433
+ conditions.push("session_id = ?");
434
+ params.push(options.sessionId);
435
+ }
436
+ if (options.label) {
437
+ conditions.push("label = ?");
438
+ params.push(options.label);
439
+ }
440
+ applyFilterConditions(conditions, params, options.filter);
441
+ const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
442
+ const limit = options.limit ?? DEFAULT_QUERY_LIMIT;
443
+ const offset = options.offset ?? 0;
444
+ const stmt = this.db.prepare(`
445
+ SELECT
446
+ id,
447
+ session_id,
448
+ label,
449
+ timestamp,
450
+ method,
451
+ url,
452
+ host,
453
+ path,
454
+ response_status,
455
+ duration_ms,
456
+ COALESCE(LENGTH(request_body), 0) as request_body_size,
457
+ COALESCE(LENGTH(response_body), 0) as response_body_size,
458
+ intercepted_by,
459
+ interception_type
460
+ FROM requests
461
+ ${whereClause}
462
+ ORDER BY timestamp DESC
463
+ LIMIT ? OFFSET ?
464
+ `);
465
+ params.push(limit, offset);
466
+ const rows = stmt.all(...params);
467
+ return rows.map((row) => this.rowToSummary(row));
468
+ }
469
+ /**
470
+ * Count requests, optionally filtered by session or label.
471
+ */
472
+ countRequests(options = {}) {
473
+ const conditions = [];
474
+ const params = [];
475
+ if (options.sessionId) {
476
+ conditions.push("session_id = ?");
477
+ params.push(options.sessionId);
478
+ }
479
+ if (options.label) {
480
+ conditions.push("label = ?");
481
+ params.push(options.label);
482
+ }
483
+ applyFilterConditions(conditions, params, options.filter);
484
+ const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
485
+ const stmt = this.db.prepare(`
486
+ SELECT COUNT(*) as count FROM requests ${whereClause}
487
+ `);
488
+ const result = stmt.get(...params);
489
+ return result.count;
490
+ }
491
+ /**
492
+ * Search through request/response body content for a text pattern.
493
+ * Only searches text-based bodies (not binary).
494
+ */
495
+ searchBodies(options) {
496
+ const conditions = [];
497
+ const params = [];
498
+ const escaped = escapeLikeWildcards(options.query);
499
+ const pattern = `%${escaped}%`;
500
+ // Build content-type conditions — only search text-based bodies
501
+ const reqCt = buildTextContentTypeSqlCondition("request_content_type");
502
+ const resCt = buildTextContentTypeSqlCondition("response_content_type");
503
+ // Search in both request and response bodies, but only where the content type is text-based
504
+ conditions.push(`((${reqCt.clause} AND CAST(request_body AS TEXT) LIKE ? ESCAPE '\\') OR (${resCt.clause} AND CAST(response_body AS TEXT) LIKE ? ESCAPE '\\'))`);
505
+ params.push(...reqCt.params, pattern, ...resCt.params, pattern);
506
+ applyFilterConditions(conditions, params, options.filter);
507
+ const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
508
+ const limit = options.limit ?? DEFAULT_QUERY_LIMIT;
509
+ const offset = options.offset ?? 0;
510
+ const stmt = this.db.prepare(`
511
+ SELECT
512
+ id,
513
+ session_id,
514
+ label,
515
+ timestamp,
516
+ method,
517
+ url,
518
+ host,
519
+ path,
520
+ response_status,
521
+ duration_ms,
522
+ COALESCE(LENGTH(request_body), 0) as request_body_size,
523
+ COALESCE(LENGTH(response_body), 0) as response_body_size,
524
+ intercepted_by,
525
+ interception_type
526
+ FROM requests
527
+ ${whereClause}
528
+ ORDER BY timestamp DESC
529
+ LIMIT ? OFFSET ?
530
+ `);
531
+ params.push(limit, offset);
532
+ const rows = stmt.all(...params);
533
+ return rows.map((row) => this.rowToSummary(row));
534
+ }
535
+ /**
536
+ * Query JSON bodies using SQLite's json_extract.
537
+ * Only queries rows with JSON content types.
538
+ */
539
+ queryJsonBodies(options) {
540
+ const target = options.target ?? "both";
541
+ const conditions = [];
542
+ const params = [];
543
+ // Build the JSON extraction expressions per target
544
+ const extractParts = [];
545
+ if (target === "request" || target === "both") {
546
+ const reqCt = buildJsonContentTypeSqlCondition("request_content_type");
547
+ const reqExtract = `CASE WHEN ${reqCt.clause} THEN json_extract(CAST(request_body AS TEXT), ?) ELSE NULL END`;
548
+ extractParts.push({ sql: reqExtract, ctParams: reqCt.params, column: "request" });
549
+ }
550
+ if (target === "response" || target === "both") {
551
+ const resCt = buildJsonContentTypeSqlCondition("response_content_type");
552
+ const resExtract = `CASE WHEN ${resCt.clause} THEN json_extract(CAST(response_body AS TEXT), ?) ELSE NULL END`;
553
+ extractParts.push({ sql: resExtract, ctParams: resCt.params, column: "response" });
554
+ }
555
+ // Build the select with extracted value, preferring request over response for "both"
556
+ const extracts = extractParts;
557
+ const extractSelectParts = [];
558
+ const extractSelectParams = [];
559
+ for (const part of extracts) {
560
+ extractSelectParts.push(part.sql);
561
+ extractSelectParams.push(...part.ctParams, options.jsonPath);
562
+ }
563
+ // COALESCE so "both" returns the first non-null value
564
+ const extractedValueExpr = extractSelectParts.length > 1
565
+ ? `COALESCE(${extractSelectParts.join(", ")})`
566
+ : (extractSelectParts[0] ?? "NULL");
567
+ // Content-type restriction: at least one target must have a JSON content type
568
+ const ctConditions = [];
569
+ const ctParams = [];
570
+ if (target === "request" || target === "both") {
571
+ const reqCt = buildJsonContentTypeSqlCondition("request_content_type");
572
+ ctConditions.push(reqCt.clause);
573
+ ctParams.push(...reqCt.params);
574
+ }
575
+ if (target === "response" || target === "both") {
576
+ const resCt = buildJsonContentTypeSqlCondition("response_content_type");
577
+ ctConditions.push(resCt.clause);
578
+ ctParams.push(...resCt.params);
579
+ }
580
+ conditions.push(`(${ctConditions.join(" OR ")})`);
581
+ params.push(...ctParams);
582
+ applyFilterConditions(conditions, params, options.filter);
583
+ const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
584
+ const limit = options.limit ?? DEFAULT_QUERY_LIMIT;
585
+ const offset = options.offset ?? 0;
586
+ // Build the full query — we use a subquery to compute extracted_value,
587
+ // then filter on it in the outer query
588
+ let sql;
589
+ const allParams = [];
590
+ if (options.value !== undefined) {
591
+ sql = `
592
+ SELECT * FROM (
593
+ SELECT
594
+ id,
595
+ session_id,
596
+ label,
597
+ timestamp,
598
+ method,
599
+ url,
600
+ host,
601
+ path,
602
+ response_status,
603
+ duration_ms,
604
+ COALESCE(LENGTH(request_body), 0) as request_body_size,
605
+ COALESCE(LENGTH(response_body), 0) as response_body_size,
606
+ ${extractedValueExpr} as extracted_value
607
+ FROM requests
608
+ ${whereClause}
609
+ ) sub
610
+ WHERE extracted_value = ?
611
+ ORDER BY timestamp DESC
612
+ LIMIT ? OFFSET ?
613
+ `;
614
+ allParams.push(...extractSelectParams, ...params, options.value, limit, offset);
615
+ }
616
+ else {
617
+ sql = `
618
+ SELECT * FROM (
619
+ SELECT
620
+ id,
621
+ session_id,
622
+ label,
623
+ timestamp,
624
+ method,
625
+ url,
626
+ host,
627
+ path,
628
+ response_status,
629
+ duration_ms,
630
+ COALESCE(LENGTH(request_body), 0) as request_body_size,
631
+ COALESCE(LENGTH(response_body), 0) as response_body_size,
632
+ ${extractedValueExpr} as extracted_value
633
+ FROM requests
634
+ ${whereClause}
635
+ ) sub
636
+ WHERE extracted_value IS NOT NULL
637
+ ORDER BY timestamp DESC
638
+ LIMIT ? OFFSET ?
639
+ `;
640
+ allParams.push(...extractSelectParams, ...params, limit, offset);
641
+ }
642
+ const stmt = this.db.prepare(sql);
643
+ const rows = stmt.all(...allParams);
644
+ return rows.map((row) => ({
645
+ id: row.id,
646
+ sessionId: row.session_id,
647
+ label: row.label ?? undefined,
648
+ timestamp: row.timestamp,
649
+ method: row.method,
650
+ url: row.url,
651
+ host: row.host,
652
+ path: row.path,
653
+ responseStatus: row.response_status ?? undefined,
654
+ durationMs: row.duration_ms ?? undefined,
655
+ requestBodySize: row.request_body_size,
656
+ responseBodySize: row.response_body_size,
657
+ extractedValue: row.extracted_value,
658
+ }));
659
+ }
660
+ /**
661
+ * Delete all requests (useful for cleanup).
662
+ */
663
+ clearRequests() {
664
+ this.db.exec("DELETE FROM requests");
665
+ }
666
+ /**
667
+ * Reclaim disk space by checkpointing the WAL and vacuuming.
668
+ * Intended for use during shutdown — not suitable for the hot path.
669
+ */
670
+ compactDatabase() {
671
+ this.db.pragma("wal_checkpoint(TRUNCATE)");
672
+ this.db.exec("VACUUM");
673
+ }
674
+ /**
675
+ * Close the database connection.
676
+ */
677
+ close() {
678
+ this.db.close();
679
+ }
680
+ /**
681
+ * Check whether the request count exceeds the cap and evict oldest rows.
682
+ * Only runs the actual COUNT query every EVICTION_CHECK_INTERVAL inserts
683
+ * to keep the hot path cheap.
684
+ */
685
+ evictIfNeeded() {
686
+ this.insertsSinceLastEvictionCheck++;
687
+ if (this.insertsSinceLastEvictionCheck < EVICTION_CHECK_INTERVAL) {
688
+ return;
689
+ }
690
+ this.insertsSinceLastEvictionCheck = 0;
691
+ const { count } = this.db.prepare("SELECT COUNT(*) as count FROM requests").get();
692
+ if (count <= this.maxStoredRequests) {
693
+ return;
694
+ }
695
+ const excess = count - this.maxStoredRequests;
696
+ this.db
697
+ .prepare(`DELETE FROM requests WHERE id IN (
698
+ SELECT id FROM requests ORDER BY timestamp ASC LIMIT ?
699
+ )`)
700
+ .run(excess);
701
+ this.logger?.debug("Evicted old requests", {
702
+ evicted: excess,
703
+ remaining: this.maxStoredRequests,
704
+ });
705
+ }
706
+ rowToSummary(row) {
707
+ return {
708
+ id: row.id,
709
+ sessionId: row.session_id,
710
+ label: row.label ?? undefined,
711
+ timestamp: row.timestamp,
712
+ method: row.method,
713
+ url: row.url,
714
+ host: row.host,
715
+ path: row.path,
716
+ responseStatus: row.response_status ?? undefined,
717
+ durationMs: row.duration_ms ?? undefined,
718
+ requestBodySize: row.request_body_size,
719
+ responseBodySize: row.response_body_size,
720
+ interceptedBy: row.intercepted_by ?? undefined,
721
+ interceptionType: row.interception_type === "modified" || row.interception_type === "mocked"
722
+ ? row.interception_type
723
+ : undefined,
724
+ };
725
+ }
726
+ safeParseHeaders(json) {
727
+ try {
728
+ return JSON.parse(json);
729
+ }
730
+ catch {
731
+ return {};
732
+ }
733
+ }
734
+ rowToRequest(row) {
735
+ return {
736
+ id: row.id,
737
+ sessionId: row.session_id,
738
+ label: row.label ?? undefined,
739
+ timestamp: row.timestamp,
740
+ method: row.method,
741
+ url: row.url,
742
+ host: row.host,
743
+ path: row.path,
744
+ requestHeaders: row.request_headers ? this.safeParseHeaders(row.request_headers) : {},
745
+ requestBody: row.request_body ?? undefined,
746
+ requestBodyTruncated: row.request_body_truncated === 1,
747
+ responseStatus: row.response_status ?? undefined,
748
+ responseHeaders: row.response_headers
749
+ ? this.safeParseHeaders(row.response_headers)
750
+ : undefined,
751
+ responseBody: row.response_body ?? undefined,
752
+ responseBodyTruncated: row.response_body_truncated === 1,
753
+ durationMs: row.duration_ms ?? undefined,
754
+ interceptedBy: row.intercepted_by ?? undefined,
755
+ interceptionType: row.interception_type === "modified" || row.interception_type === "mocked"
756
+ ? row.interception_type
757
+ : undefined,
758
+ };
759
+ }
760
+ }
761
+ //# sourceMappingURL=storage.js.map