pi-cursor-sdk 0.1.50 → 0.1.51

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 (276) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/README.md +1 -1
  3. package/docs/cursor-live-smoke-checklist.md +4 -4
  4. package/docs/cursor-model-ux-spec.md +5 -5
  5. package/docs/cursor-native-tool-replay.md +2 -2
  6. package/docs/cursor-native-tool-visual-audit.md +1 -1
  7. package/docs/cursor-testing-lessons.md +1 -1
  8. package/package.json +3 -12
  9. package/src/cursor-fallback-models.generated.ts +536 -302
  10. package/src/cursor-provider-errors.ts +29 -0
  11. package/src/cursor-tool-manifest.ts +1 -1
  12. package/node_modules/@connectrpc/connect-node/README.md +0 -141
  13. package/node_modules/@connectrpc/connect-node/dist/cjs/compression.d.ts +0 -15
  14. package/node_modules/@connectrpc/connect-node/dist/cjs/compression.js +0 -89
  15. package/node_modules/@connectrpc/connect-node/dist/cjs/connect-node-adapter.d.ts +0 -42
  16. package/node_modules/@connectrpc/connect-node/dist/cjs/connect-node-adapter.js +0 -61
  17. package/node_modules/@connectrpc/connect-node/dist/cjs/connect-transport.d.ts +0 -100
  18. package/node_modules/@connectrpc/connect-node/dist/cjs/connect-transport.js +0 -25
  19. package/node_modules/@connectrpc/connect-node/dist/cjs/grpc-transport.d.ts +0 -96
  20. package/node_modules/@connectrpc/connect-node/dist/cjs/grpc-transport.js +0 -25
  21. package/node_modules/@connectrpc/connect-node/dist/cjs/grpc-web-transport.d.ts +0 -96
  22. package/node_modules/@connectrpc/connect-node/dist/cjs/grpc-web-transport.js +0 -25
  23. package/node_modules/@connectrpc/connect-node/dist/cjs/http2-session-manager.d.ts +0 -146
  24. package/node_modules/@connectrpc/connect-node/dist/cjs/http2-session-manager.js +0 -536
  25. package/node_modules/@connectrpc/connect-node/dist/cjs/index.d.ts +0 -15
  26. package/node_modules/@connectrpc/connect-node/dist/cjs/index.js +0 -36
  27. package/node_modules/@connectrpc/connect-node/dist/cjs/node-error.d.ts +0 -45
  28. package/node_modules/@connectrpc/connect-node/dist/cjs/node-error.js +0 -154
  29. package/node_modules/@connectrpc/connect-node/dist/cjs/node-headers-polyfill.d.ts +0 -1
  30. package/node_modules/@connectrpc/connect-node/dist/cjs/node-headers-polyfill.js +0 -29
  31. package/node_modules/@connectrpc/connect-node/dist/cjs/node-transport-options.d.ts +0 -89
  32. package/node_modules/@connectrpc/connect-node/dist/cjs/node-transport-options.js +0 -58
  33. package/node_modules/@connectrpc/connect-node/dist/cjs/node-universal-client.d.ts +0 -54
  34. package/node_modules/@connectrpc/connect-node/dist/cjs/node-universal-client.js +0 -326
  35. package/node_modules/@connectrpc/connect-node/dist/cjs/node-universal-handler.d.ts +0 -41
  36. package/node_modules/@connectrpc/connect-node/dist/cjs/node-universal-handler.js +0 -234
  37. package/node_modules/@connectrpc/connect-node/dist/cjs/node-universal-header.d.ts +0 -19
  38. package/node_modules/@connectrpc/connect-node/dist/cjs/node-universal-header.js +0 -97
  39. package/node_modules/@connectrpc/connect-node/dist/cjs/package.json +0 -1
  40. package/node_modules/@connectrpc/connect-node/dist/esm/compression.d.ts +0 -15
  41. package/node_modules/@connectrpc/connect-node/dist/esm/compression.js +0 -86
  42. package/node_modules/@connectrpc/connect-node/dist/esm/connect-node-adapter.d.ts +0 -42
  43. package/node_modules/@connectrpc/connect-node/dist/esm/connect-node-adapter.js +0 -58
  44. package/node_modules/@connectrpc/connect-node/dist/esm/connect-transport.d.ts +0 -100
  45. package/node_modules/@connectrpc/connect-node/dist/esm/connect-transport.js +0 -22
  46. package/node_modules/@connectrpc/connect-node/dist/esm/grpc-transport.d.ts +0 -96
  47. package/node_modules/@connectrpc/connect-node/dist/esm/grpc-transport.js +0 -22
  48. package/node_modules/@connectrpc/connect-node/dist/esm/grpc-web-transport.d.ts +0 -96
  49. package/node_modules/@connectrpc/connect-node/dist/esm/grpc-web-transport.js +0 -22
  50. package/node_modules/@connectrpc/connect-node/dist/esm/http2-session-manager.d.ts +0 -146
  51. package/node_modules/@connectrpc/connect-node/dist/esm/http2-session-manager.js +0 -532
  52. package/node_modules/@connectrpc/connect-node/dist/esm/index.d.ts +0 -15
  53. package/node_modules/@connectrpc/connect-node/dist/esm/index.js +0 -23
  54. package/node_modules/@connectrpc/connect-node/dist/esm/node-error.d.ts +0 -45
  55. package/node_modules/@connectrpc/connect-node/dist/esm/node-error.js +0 -147
  56. package/node_modules/@connectrpc/connect-node/dist/esm/node-headers-polyfill.d.ts +0 -1
  57. package/node_modules/@connectrpc/connect-node/dist/esm/node-headers-polyfill.js +0 -27
  58. package/node_modules/@connectrpc/connect-node/dist/esm/node-transport-options.d.ts +0 -89
  59. package/node_modules/@connectrpc/connect-node/dist/esm/node-transport-options.js +0 -58
  60. package/node_modules/@connectrpc/connect-node/dist/esm/node-universal-client.d.ts +0 -54
  61. package/node_modules/@connectrpc/connect-node/dist/esm/node-universal-client.js +0 -324
  62. package/node_modules/@connectrpc/connect-node/dist/esm/node-universal-handler.d.ts +0 -41
  63. package/node_modules/@connectrpc/connect-node/dist/esm/node-universal-handler.js +0 -230
  64. package/node_modules/@connectrpc/connect-node/dist/esm/node-universal-header.d.ts +0 -19
  65. package/node_modules/@connectrpc/connect-node/dist/esm/node-universal-header.js +0 -93
  66. package/node_modules/@connectrpc/connect-node/package.json +0 -46
  67. package/node_modules/undici/LICENSE +0 -21
  68. package/node_modules/undici/README.md +0 -741
  69. package/node_modules/undici/docs/docs/api/Agent.md +0 -84
  70. package/node_modules/undici/docs/docs/api/BalancedPool.md +0 -99
  71. package/node_modules/undici/docs/docs/api/CacheStorage.md +0 -30
  72. package/node_modules/undici/docs/docs/api/CacheStore.md +0 -164
  73. package/node_modules/undici/docs/docs/api/Client.md +0 -288
  74. package/node_modules/undici/docs/docs/api/ClientStats.md +0 -27
  75. package/node_modules/undici/docs/docs/api/Connector.md +0 -115
  76. package/node_modules/undici/docs/docs/api/ContentType.md +0 -57
  77. package/node_modules/undici/docs/docs/api/Cookies.md +0 -128
  78. package/node_modules/undici/docs/docs/api/Debug.md +0 -62
  79. package/node_modules/undici/docs/docs/api/DiagnosticsChannel.md +0 -315
  80. package/node_modules/undici/docs/docs/api/Dispatcher.md +0 -1392
  81. package/node_modules/undici/docs/docs/api/EnvHttpProxyAgent.md +0 -159
  82. package/node_modules/undici/docs/docs/api/Errors.md +0 -49
  83. package/node_modules/undici/docs/docs/api/EventSource.md +0 -45
  84. package/node_modules/undici/docs/docs/api/Fetch.md +0 -60
  85. package/node_modules/undici/docs/docs/api/GlobalInstallation.md +0 -139
  86. package/node_modules/undici/docs/docs/api/H2CClient.md +0 -263
  87. package/node_modules/undici/docs/docs/api/MockAgent.md +0 -603
  88. package/node_modules/undici/docs/docs/api/MockCallHistory.md +0 -197
  89. package/node_modules/undici/docs/docs/api/MockCallHistoryLog.md +0 -43
  90. package/node_modules/undici/docs/docs/api/MockClient.md +0 -81
  91. package/node_modules/undici/docs/docs/api/MockErrors.md +0 -12
  92. package/node_modules/undici/docs/docs/api/MockPool.md +0 -555
  93. package/node_modules/undici/docs/docs/api/Pool.md +0 -84
  94. package/node_modules/undici/docs/docs/api/PoolStats.md +0 -35
  95. package/node_modules/undici/docs/docs/api/ProxyAgent.md +0 -229
  96. package/node_modules/undici/docs/docs/api/RedirectHandler.md +0 -93
  97. package/node_modules/undici/docs/docs/api/RetryAgent.md +0 -50
  98. package/node_modules/undici/docs/docs/api/RetryHandler.md +0 -118
  99. package/node_modules/undici/docs/docs/api/RoundRobinPool.md +0 -145
  100. package/node_modules/undici/docs/docs/api/SnapshotAgent.md +0 -616
  101. package/node_modules/undici/docs/docs/api/Socks5ProxyAgent.md +0 -275
  102. package/node_modules/undici/docs/docs/api/Util.md +0 -25
  103. package/node_modules/undici/docs/docs/api/WebSocket.md +0 -141
  104. package/node_modules/undici/docs/docs/api/api-lifecycle.md +0 -91
  105. package/node_modules/undici/docs/docs/best-practices/client-certificate.md +0 -64
  106. package/node_modules/undici/docs/docs/best-practices/crawling.md +0 -58
  107. package/node_modules/undici/docs/docs/best-practices/mocking-request.md +0 -190
  108. package/node_modules/undici/docs/docs/best-practices/proxy.md +0 -127
  109. package/node_modules/undici/docs/docs/best-practices/undici-vs-builtin-fetch.md +0 -224
  110. package/node_modules/undici/docs/docs/best-practices/writing-tests.md +0 -63
  111. package/node_modules/undici/index-fetch.js +0 -65
  112. package/node_modules/undici/index.d.ts +0 -3
  113. package/node_modules/undici/index.js +0 -234
  114. package/node_modules/undici/lib/api/abort-signal.js +0 -59
  115. package/node_modules/undici/lib/api/api-connect.js +0 -110
  116. package/node_modules/undici/lib/api/api-pipeline.js +0 -252
  117. package/node_modules/undici/lib/api/api-request.js +0 -214
  118. package/node_modules/undici/lib/api/api-stream.js +0 -209
  119. package/node_modules/undici/lib/api/api-upgrade.js +0 -111
  120. package/node_modules/undici/lib/api/index.js +0 -7
  121. package/node_modules/undici/lib/api/readable.js +0 -580
  122. package/node_modules/undici/lib/cache/memory-cache-store.js +0 -234
  123. package/node_modules/undici/lib/cache/sqlite-cache-store.js +0 -461
  124. package/node_modules/undici/lib/core/connect.js +0 -153
  125. package/node_modules/undici/lib/core/constants.js +0 -143
  126. package/node_modules/undici/lib/core/diagnostics.js +0 -227
  127. package/node_modules/undici/lib/core/errors.js +0 -477
  128. package/node_modules/undici/lib/core/request.js +0 -453
  129. package/node_modules/undici/lib/core/socks5-client.js +0 -422
  130. package/node_modules/undici/lib/core/socks5-utils.js +0 -212
  131. package/node_modules/undici/lib/core/symbols.js +0 -75
  132. package/node_modules/undici/lib/core/tree.js +0 -160
  133. package/node_modules/undici/lib/core/util.js +0 -992
  134. package/node_modules/undici/lib/dispatcher/agent.js +0 -158
  135. package/node_modules/undici/lib/dispatcher/balanced-pool.js +0 -219
  136. package/node_modules/undici/lib/dispatcher/client-h1.js +0 -1722
  137. package/node_modules/undici/lib/dispatcher/client-h2.js +0 -995
  138. package/node_modules/undici/lib/dispatcher/client.js +0 -664
  139. package/node_modules/undici/lib/dispatcher/dispatcher-base.js +0 -184
  140. package/node_modules/undici/lib/dispatcher/dispatcher.js +0 -48
  141. package/node_modules/undici/lib/dispatcher/env-http-proxy-agent.js +0 -146
  142. package/node_modules/undici/lib/dispatcher/fixed-queue.js +0 -135
  143. package/node_modules/undici/lib/dispatcher/h2c-client.js +0 -51
  144. package/node_modules/undici/lib/dispatcher/pool-base.js +0 -214
  145. package/node_modules/undici/lib/dispatcher/pool.js +0 -118
  146. package/node_modules/undici/lib/dispatcher/proxy-agent.js +0 -319
  147. package/node_modules/undici/lib/dispatcher/retry-agent.js +0 -35
  148. package/node_modules/undici/lib/dispatcher/round-robin-pool.js +0 -137
  149. package/node_modules/undici/lib/dispatcher/socks5-proxy-agent.js +0 -260
  150. package/node_modules/undici/lib/encoding/index.js +0 -33
  151. package/node_modules/undici/lib/global.js +0 -59
  152. package/node_modules/undici/lib/handler/cache-handler.js +0 -578
  153. package/node_modules/undici/lib/handler/cache-revalidation-handler.js +0 -124
  154. package/node_modules/undici/lib/handler/decorator-handler.js +0 -67
  155. package/node_modules/undici/lib/handler/deduplication-handler.js +0 -460
  156. package/node_modules/undici/lib/handler/redirect-handler.js +0 -238
  157. package/node_modules/undici/lib/handler/retry-handler.js +0 -394
  158. package/node_modules/undici/lib/handler/unwrap-handler.js +0 -106
  159. package/node_modules/undici/lib/handler/wrap-handler.js +0 -105
  160. package/node_modules/undici/lib/interceptor/cache.js +0 -495
  161. package/node_modules/undici/lib/interceptor/decompress.js +0 -259
  162. package/node_modules/undici/lib/interceptor/deduplicate.js +0 -117
  163. package/node_modules/undici/lib/interceptor/dns.js +0 -571
  164. package/node_modules/undici/lib/interceptor/dump.js +0 -112
  165. package/node_modules/undici/lib/interceptor/redirect.js +0 -21
  166. package/node_modules/undici/lib/interceptor/response-error.js +0 -95
  167. package/node_modules/undici/lib/interceptor/retry.js +0 -19
  168. package/node_modules/undici/lib/llhttp/.gitkeep +0 -0
  169. package/node_modules/undici/lib/llhttp/constants.d.ts +0 -195
  170. package/node_modules/undici/lib/llhttp/constants.js +0 -531
  171. package/node_modules/undici/lib/llhttp/llhttp-wasm.js +0 -15
  172. package/node_modules/undici/lib/llhttp/llhttp_simd-wasm.js +0 -15
  173. package/node_modules/undici/lib/llhttp/utils.d.ts +0 -2
  174. package/node_modules/undici/lib/llhttp/utils.js +0 -12
  175. package/node_modules/undici/lib/mock/mock-agent.js +0 -232
  176. package/node_modules/undici/lib/mock/mock-call-history.js +0 -248
  177. package/node_modules/undici/lib/mock/mock-client.js +0 -68
  178. package/node_modules/undici/lib/mock/mock-errors.js +0 -29
  179. package/node_modules/undici/lib/mock/mock-interceptor.js +0 -209
  180. package/node_modules/undici/lib/mock/mock-pool.js +0 -68
  181. package/node_modules/undici/lib/mock/mock-symbols.js +0 -32
  182. package/node_modules/undici/lib/mock/mock-utils.js +0 -486
  183. package/node_modules/undici/lib/mock/pending-interceptors-formatter.js +0 -43
  184. package/node_modules/undici/lib/mock/snapshot-agent.js +0 -353
  185. package/node_modules/undici/lib/mock/snapshot-recorder.js +0 -588
  186. package/node_modules/undici/lib/mock/snapshot-utils.js +0 -158
  187. package/node_modules/undici/lib/util/cache.js +0 -414
  188. package/node_modules/undici/lib/util/date.js +0 -653
  189. package/node_modules/undici/lib/util/promise.js +0 -28
  190. package/node_modules/undici/lib/util/runtime-features.js +0 -124
  191. package/node_modules/undici/lib/util/stats.js +0 -32
  192. package/node_modules/undici/lib/util/timers.js +0 -425
  193. package/node_modules/undici/lib/web/cache/cache.js +0 -864
  194. package/node_modules/undici/lib/web/cache/cachestorage.js +0 -152
  195. package/node_modules/undici/lib/web/cache/util.js +0 -45
  196. package/node_modules/undici/lib/web/cookies/constants.js +0 -12
  197. package/node_modules/undici/lib/web/cookies/index.js +0 -199
  198. package/node_modules/undici/lib/web/cookies/parse.js +0 -314
  199. package/node_modules/undici/lib/web/cookies/util.js +0 -282
  200. package/node_modules/undici/lib/web/eventsource/eventsource-stream.js +0 -399
  201. package/node_modules/undici/lib/web/eventsource/eventsource.js +0 -501
  202. package/node_modules/undici/lib/web/eventsource/util.js +0 -29
  203. package/node_modules/undici/lib/web/fetch/LICENSE +0 -21
  204. package/node_modules/undici/lib/web/fetch/body.js +0 -509
  205. package/node_modules/undici/lib/web/fetch/constants.js +0 -131
  206. package/node_modules/undici/lib/web/fetch/data-url.js +0 -596
  207. package/node_modules/undici/lib/web/fetch/formdata-parser.js +0 -586
  208. package/node_modules/undici/lib/web/fetch/formdata.js +0 -259
  209. package/node_modules/undici/lib/web/fetch/global.js +0 -40
  210. package/node_modules/undici/lib/web/fetch/headers.js +0 -719
  211. package/node_modules/undici/lib/web/fetch/index.js +0 -2413
  212. package/node_modules/undici/lib/web/fetch/request.js +0 -1115
  213. package/node_modules/undici/lib/web/fetch/response.js +0 -641
  214. package/node_modules/undici/lib/web/fetch/util.js +0 -1522
  215. package/node_modules/undici/lib/web/infra/index.js +0 -229
  216. package/node_modules/undici/lib/web/subresource-integrity/Readme.md +0 -9
  217. package/node_modules/undici/lib/web/subresource-integrity/subresource-integrity.js +0 -307
  218. package/node_modules/undici/lib/web/webidl/index.js +0 -1006
  219. package/node_modules/undici/lib/web/websocket/connection.js +0 -329
  220. package/node_modules/undici/lib/web/websocket/constants.js +0 -126
  221. package/node_modules/undici/lib/web/websocket/events.js +0 -331
  222. package/node_modules/undici/lib/web/websocket/frame.js +0 -133
  223. package/node_modules/undici/lib/web/websocket/permessage-deflate.js +0 -100
  224. package/node_modules/undici/lib/web/websocket/receiver.js +0 -507
  225. package/node_modules/undici/lib/web/websocket/sender.js +0 -109
  226. package/node_modules/undici/lib/web/websocket/stream/websocketerror.js +0 -104
  227. package/node_modules/undici/lib/web/websocket/stream/websocketstream.js +0 -498
  228. package/node_modules/undici/lib/web/websocket/util.js +0 -347
  229. package/node_modules/undici/lib/web/websocket/websocket.js +0 -758
  230. package/node_modules/undici/package.json +0 -152
  231. package/node_modules/undici/scripts/strip-comments.js +0 -10
  232. package/node_modules/undici/types/README.md +0 -6
  233. package/node_modules/undici/types/agent.d.ts +0 -32
  234. package/node_modules/undici/types/api.d.ts +0 -43
  235. package/node_modules/undici/types/balanced-pool.d.ts +0 -30
  236. package/node_modules/undici/types/cache-interceptor.d.ts +0 -179
  237. package/node_modules/undici/types/cache.d.ts +0 -36
  238. package/node_modules/undici/types/client-stats.d.ts +0 -15
  239. package/node_modules/undici/types/client.d.ts +0 -139
  240. package/node_modules/undici/types/connector.d.ts +0 -36
  241. package/node_modules/undici/types/content-type.d.ts +0 -21
  242. package/node_modules/undici/types/cookies.d.ts +0 -30
  243. package/node_modules/undici/types/diagnostics-channel.d.ts +0 -74
  244. package/node_modules/undici/types/dispatcher.d.ts +0 -275
  245. package/node_modules/undici/types/env-http-proxy-agent.d.ts +0 -22
  246. package/node_modules/undici/types/errors.d.ts +0 -177
  247. package/node_modules/undici/types/eventsource.d.ts +0 -66
  248. package/node_modules/undici/types/fetch.d.ts +0 -231
  249. package/node_modules/undici/types/formdata.d.ts +0 -114
  250. package/node_modules/undici/types/global-dispatcher.d.ts +0 -9
  251. package/node_modules/undici/types/global-origin.d.ts +0 -7
  252. package/node_modules/undici/types/h2c-client.d.ts +0 -73
  253. package/node_modules/undici/types/handlers.d.ts +0 -14
  254. package/node_modules/undici/types/header.d.ts +0 -160
  255. package/node_modules/undici/types/index.d.ts +0 -91
  256. package/node_modules/undici/types/interceptors.d.ts +0 -80
  257. package/node_modules/undici/types/mock-agent.d.ts +0 -68
  258. package/node_modules/undici/types/mock-call-history.d.ts +0 -111
  259. package/node_modules/undici/types/mock-client.d.ts +0 -27
  260. package/node_modules/undici/types/mock-errors.d.ts +0 -12
  261. package/node_modules/undici/types/mock-interceptor.d.ts +0 -94
  262. package/node_modules/undici/types/mock-pool.d.ts +0 -27
  263. package/node_modules/undici/types/patch.d.ts +0 -29
  264. package/node_modules/undici/types/pool-stats.d.ts +0 -19
  265. package/node_modules/undici/types/pool.d.ts +0 -41
  266. package/node_modules/undici/types/proxy-agent.d.ts +0 -29
  267. package/node_modules/undici/types/readable.d.ts +0 -68
  268. package/node_modules/undici/types/retry-agent.d.ts +0 -8
  269. package/node_modules/undici/types/retry-handler.d.ts +0 -125
  270. package/node_modules/undici/types/round-robin-pool.d.ts +0 -41
  271. package/node_modules/undici/types/snapshot-agent.d.ts +0 -109
  272. package/node_modules/undici/types/socks5-proxy-agent.d.ts +0 -25
  273. package/node_modules/undici/types/util.d.ts +0 -18
  274. package/node_modules/undici/types/utility.d.ts +0 -7
  275. package/node_modules/undici/types/webidl.d.ts +0 -347
  276. package/node_modules/undici/types/websocket.d.ts +0 -188
@@ -1,588 +0,0 @@
1
- 'use strict'
2
-
3
- const { writeFile, readFile, mkdir } = require('node:fs/promises')
4
- const { dirname, resolve } = require('node:path')
5
- const { setTimeout, clearTimeout } = require('node:timers')
6
- const { InvalidArgumentError, UndiciError } = require('../core/errors')
7
- const { hashId, isUrlExcludedFactory, normalizeHeaders, createHeaderFilters } = require('./snapshot-utils')
8
-
9
- /**
10
- * @typedef {Object} SnapshotRequestOptions
11
- * @property {string} method - HTTP method (e.g. 'GET', 'POST', etc.)
12
- * @property {string} path - Request path
13
- * @property {string} origin - Request origin (base URL)
14
- * @property {import('./snapshot-utils').Headers|import('./snapshot-utils').UndiciHeaders} headers - Request headers
15
- * @property {import('./snapshot-utils').NormalizedHeaders} _normalizedHeaders - Request headers as a lowercase object
16
- * @property {string|Buffer} [body] - Request body (optional)
17
- */
18
-
19
- /**
20
- * @typedef {Object} SnapshotEntryRequest
21
- * @property {string} method - HTTP method (e.g. 'GET', 'POST', etc.)
22
- * @property {string} url - Full URL of the request
23
- * @property {import('./snapshot-utils').NormalizedHeaders} headers - Normalized headers as a lowercase object
24
- * @property {string|Buffer} [body] - Request body (optional)
25
- */
26
-
27
- /**
28
- * @typedef {Object} SnapshotEntryResponse
29
- * @property {number} statusCode - HTTP status code of the response
30
- * @property {import('./snapshot-utils').NormalizedHeaders} headers - Normalized response headers as a lowercase object
31
- * @property {string} body - Response body as a base64url encoded string
32
- * @property {Object} [trailers] - Optional response trailers
33
- */
34
-
35
- /**
36
- * @typedef {Object} SnapshotEntry
37
- * @property {SnapshotEntryRequest} request - The request object
38
- * @property {Array<SnapshotEntryResponse>} responses - Array of response objects
39
- * @property {number} callCount - Number of times this snapshot has been called
40
- * @property {string} timestamp - ISO timestamp of when the snapshot was created
41
- */
42
-
43
- /**
44
- * @typedef {Object} SnapshotRecorderMatchOptions
45
- * @property {Array<string>} [matchHeaders=[]] - Headers to match (empty array means match all headers)
46
- * @property {Array<string>} [ignoreHeaders=[]] - Headers to ignore for matching
47
- * @property {Array<string>} [excludeHeaders=[]] - Headers to exclude from matching
48
- * @property {boolean} [matchBody=true] - Whether to match request body
49
- * @property {boolean} [matchQuery=true] - Whether to match query properties
50
- * @property {boolean} [caseSensitive=false] - Whether header matching is case-sensitive
51
- */
52
-
53
- /**
54
- * @typedef {Object} SnapshotRecorderOptions
55
- * @property {string} [snapshotPath] - Path to save/load snapshots
56
- * @property {import('./snapshot-utils').SnapshotMode} [mode='record'] - Mode: 'record' or 'playback'
57
- * @property {number} [maxSnapshots=Infinity] - Maximum number of snapshots to keep
58
- * @property {boolean} [autoFlush=false] - Whether to automatically flush snapshots to disk
59
- * @property {number} [flushInterval=30000] - Auto-flush interval in milliseconds (default: 30 seconds)
60
- * @property {Array<string|RegExp>} [excludeUrls=[]] - URLs to exclude from recording
61
- * @property {function} [shouldRecord=null] - Function to filter requests for recording
62
- * @property {function} [shouldPlayback=null] - Function to filter requests
63
- */
64
-
65
- /**
66
- * @typedef {Object} SnapshotFormattedRequest
67
- * @property {string} method - HTTP method (e.g. 'GET', 'POST', etc.)
68
- * @property {string} url - Full URL of the request (with query parameters if matchQuery is true)
69
- * @property {import('./snapshot-utils').NormalizedHeaders} headers - Normalized headers as a lowercase object
70
- * @property {string} body - Request body (optional, only if matchBody is true)
71
- */
72
-
73
- /**
74
- * @typedef {Object} SnapshotInfo
75
- * @property {string} hash - Hash key for the snapshot
76
- * @property {SnapshotEntryRequest} request - The request object
77
- * @property {number} responseCount - Number of responses recorded for this request
78
- * @property {number} callCount - Number of times this snapshot has been called
79
- * @property {string} timestamp - ISO timestamp of when the snapshot was created
80
- */
81
-
82
- /**
83
- * Formats a request for consistent snapshot storage
84
- * Caches normalized headers to avoid repeated processing
85
- *
86
- * @param {SnapshotRequestOptions} opts - Request options
87
- * @param {import('./snapshot-utils').HeaderFilters} headerFilters - Cached header sets for performance
88
- * @param {SnapshotRecorderMatchOptions} [matchOptions] - Matching options for headers and body
89
- * @returns {SnapshotFormattedRequest} - Formatted request object
90
- */
91
- function formatRequestKey (opts, headerFilters, matchOptions = {}) {
92
- const url = new URL(opts.path, opts.origin)
93
-
94
- // Cache normalized headers if not already done
95
- const normalized = opts._normalizedHeaders || normalizeHeaders(opts.headers)
96
- if (!opts._normalizedHeaders) {
97
- opts._normalizedHeaders = normalized
98
- }
99
-
100
- return {
101
- method: opts.method || 'GET',
102
- url: matchOptions.matchQuery !== false ? url.toString() : `${url.origin}${url.pathname}`,
103
- headers: filterHeadersForMatching(normalized, headerFilters, matchOptions),
104
- body: matchOptions.matchBody !== false && opts.body ? String(opts.body) : ''
105
- }
106
- }
107
-
108
- /**
109
- * Filters headers based on matching configuration
110
- *
111
- * @param {import('./snapshot-utils').Headers} headers - Headers to filter
112
- * @param {import('./snapshot-utils').HeaderFilters} headerFilters - Cached sets for ignore, exclude, and match headers
113
- * @param {SnapshotRecorderMatchOptions} [matchOptions] - Matching options for headers
114
- */
115
- function filterHeadersForMatching (headers, headerFilters, matchOptions = {}) {
116
- if (!headers || typeof headers !== 'object') return {}
117
-
118
- const {
119
- caseSensitive = false
120
- } = matchOptions
121
-
122
- const filtered = {}
123
- const { ignore, exclude, match } = headerFilters
124
-
125
- for (const [key, value] of Object.entries(headers)) {
126
- const headerKey = caseSensitive ? key : key.toLowerCase()
127
-
128
- // Skip if in exclude list (for security)
129
- if (exclude.has(headerKey)) continue
130
-
131
- // Skip if in ignore list (for matching)
132
- if (ignore.has(headerKey)) continue
133
-
134
- // If matchHeaders is specified, only include those headers
135
- if (match.size !== 0) {
136
- if (!match.has(headerKey)) continue
137
- }
138
-
139
- filtered[headerKey] = value
140
- }
141
-
142
- return filtered
143
- }
144
-
145
- /**
146
- * Filters headers for storage (only excludes sensitive headers)
147
- *
148
- * @param {import('./snapshot-utils').Headers} headers - Headers to filter
149
- * @param {import('./snapshot-utils').HeaderFilters} headerFilters - Cached sets for ignore, exclude, and match headers
150
- * @param {SnapshotRecorderMatchOptions} [matchOptions] - Matching options for headers
151
- */
152
- function filterHeadersForStorage (headers, headerFilters, matchOptions = {}) {
153
- if (!headers || typeof headers !== 'object') return {}
154
-
155
- const {
156
- caseSensitive = false
157
- } = matchOptions
158
-
159
- const filtered = {}
160
- const { exclude: excludeSet } = headerFilters
161
-
162
- for (const [key, value] of Object.entries(headers)) {
163
- const headerKey = caseSensitive ? key : key.toLowerCase()
164
-
165
- // Skip if in exclude list (for security)
166
- if (excludeSet.has(headerKey)) continue
167
-
168
- filtered[headerKey] = value
169
- }
170
-
171
- return filtered
172
- }
173
-
174
- /**
175
- * Creates a hash key for request matching
176
- * Properly orders headers to avoid conflicts and uses crypto hashing when available
177
- *
178
- * @param {SnapshotFormattedRequest} formattedRequest - Request object
179
- * @returns {string} - Base64url encoded hash of the request
180
- */
181
- function createRequestHash (formattedRequest) {
182
- const parts = [
183
- formattedRequest.method,
184
- formattedRequest.url
185
- ]
186
-
187
- // Process headers in a deterministic way to avoid conflicts
188
- if (formattedRequest.headers && typeof formattedRequest.headers === 'object') {
189
- const headerKeys = Object.keys(formattedRequest.headers).sort()
190
- for (const key of headerKeys) {
191
- const values = Array.isArray(formattedRequest.headers[key])
192
- ? formattedRequest.headers[key]
193
- : [formattedRequest.headers[key]]
194
-
195
- // Add header name
196
- parts.push(key)
197
-
198
- // Add all values for this header, sorted for consistency
199
- for (const value of values.sort()) {
200
- parts.push(String(value))
201
- }
202
- }
203
- }
204
-
205
- // Add body
206
- parts.push(formattedRequest.body)
207
-
208
- const content = parts.join('|')
209
-
210
- return hashId(content)
211
- }
212
-
213
- class SnapshotRecorder {
214
- /** @type {NodeJS.Timeout | null} */
215
- #flushTimeout
216
-
217
- /** @type {import('./snapshot-utils').IsUrlExcluded} */
218
- #isUrlExcluded
219
-
220
- /** @type {Map<string, SnapshotEntry>} */
221
- #snapshots = new Map()
222
-
223
- /** @type {string|undefined} */
224
- #snapshotPath
225
-
226
- /** @type {number} */
227
- #maxSnapshots = Infinity
228
-
229
- /** @type {boolean} */
230
- #autoFlush = false
231
-
232
- /** @type {import('./snapshot-utils').HeaderFilters} */
233
- #headerFilters
234
-
235
- /**
236
- * Creates a new SnapshotRecorder instance
237
- * @param {SnapshotRecorderOptions&SnapshotRecorderMatchOptions} [options={}] - Configuration options for the recorder
238
- */
239
- constructor (options = {}) {
240
- this.#snapshotPath = options.snapshotPath
241
- this.#maxSnapshots = options.maxSnapshots || Infinity
242
- this.#autoFlush = options.autoFlush || false
243
- this.flushInterval = options.flushInterval || 30000 // 30 seconds default
244
- this._flushTimer = null
245
-
246
- // Matching configuration
247
- /** @type {Required<SnapshotRecorderMatchOptions>} */
248
- this.matchOptions = {
249
- matchHeaders: options.matchHeaders || [], // empty means match all headers
250
- ignoreHeaders: options.ignoreHeaders || [],
251
- excludeHeaders: options.excludeHeaders || [],
252
- matchBody: options.matchBody !== false, // default: true
253
- matchQuery: options.matchQuery !== false, // default: true
254
- caseSensitive: options.caseSensitive || false
255
- }
256
-
257
- // Cache processed header sets to avoid recreating them on every request
258
- this.#headerFilters = createHeaderFilters(this.matchOptions)
259
-
260
- // Request filtering callbacks
261
- this.shouldRecord = options.shouldRecord || (() => true) // function(requestOpts) -> boolean
262
- this.shouldPlayback = options.shouldPlayback || (() => true) // function(requestOpts) -> boolean
263
-
264
- // URL pattern filtering
265
- this.#isUrlExcluded = isUrlExcludedFactory(options.excludeUrls) // Array of regex patterns or strings
266
-
267
- // Start auto-flush timer if enabled
268
- if (this.#autoFlush && this.#snapshotPath) {
269
- this.#startAutoFlush()
270
- }
271
- }
272
-
273
- /**
274
- * Records a request-response interaction
275
- * @param {SnapshotRequestOptions} requestOpts - Request options
276
- * @param {SnapshotEntryResponse} response - Response data to record
277
- * @return {Promise<void>} - Resolves when the recording is complete
278
- */
279
- async record (requestOpts, response) {
280
- // Check if recording should be filtered out
281
- if (!this.shouldRecord(requestOpts)) {
282
- return // Skip recording
283
- }
284
-
285
- // Check URL exclusion patterns
286
- if (this.isUrlExcluded(requestOpts)) {
287
- return // Skip recording
288
- }
289
-
290
- const request = formatRequestKey(requestOpts, this.#headerFilters, this.matchOptions)
291
- const hash = createRequestHash(request)
292
-
293
- // Extract response data - always store body as base64
294
- const normalizedHeaders = normalizeHeaders(response.headers)
295
-
296
- /** @type {SnapshotEntryResponse} */
297
- const responseData = {
298
- statusCode: response.statusCode,
299
- headers: filterHeadersForStorage(normalizedHeaders, this.#headerFilters, this.matchOptions),
300
- body: Buffer.isBuffer(response.body)
301
- ? response.body.toString('base64')
302
- : Buffer.from(String(response.body || '')).toString('base64'),
303
- trailers: response.trailers
304
- }
305
-
306
- // Remove oldest snapshot if we exceed maxSnapshots limit
307
- if (this.#snapshots.size >= this.#maxSnapshots && !this.#snapshots.has(hash)) {
308
- const oldestKey = this.#snapshots.keys().next().value
309
- this.#snapshots.delete(oldestKey)
310
- }
311
-
312
- // Support sequential responses - if snapshot exists, add to responses array
313
- const existingSnapshot = this.#snapshots.get(hash)
314
- if (existingSnapshot && existingSnapshot.responses) {
315
- existingSnapshot.responses.push(responseData)
316
- existingSnapshot.timestamp = new Date().toISOString()
317
- } else {
318
- this.#snapshots.set(hash, {
319
- request,
320
- responses: [responseData], // Always store as array for consistency
321
- callCount: 0,
322
- timestamp: new Date().toISOString()
323
- })
324
- }
325
-
326
- // Auto-flush if enabled
327
- if (this.#autoFlush && this.#snapshotPath) {
328
- this.#scheduleFlush()
329
- }
330
- }
331
-
332
- /**
333
- * Checks if a URL should be excluded from recording/playback
334
- * @param {SnapshotRequestOptions} requestOpts - Request options to check
335
- * @returns {boolean} - True if URL is excluded
336
- */
337
- isUrlExcluded (requestOpts) {
338
- const url = new URL(requestOpts.path, requestOpts.origin).toString()
339
- return this.#isUrlExcluded(url)
340
- }
341
-
342
- /**
343
- * Finds a matching snapshot for the given request
344
- * Returns the appropriate response based on call count for sequential responses
345
- *
346
- * @param {SnapshotRequestOptions} requestOpts - Request options to match
347
- * @returns {SnapshotEntry&Record<'response', SnapshotEntryResponse>|undefined} - Matching snapshot response or undefined if not found
348
- */
349
- findSnapshot (requestOpts) {
350
- // Check if playback should be filtered out
351
- if (!this.shouldPlayback(requestOpts)) {
352
- return undefined // Skip playback
353
- }
354
-
355
- // Check URL exclusion patterns
356
- if (this.isUrlExcluded(requestOpts)) {
357
- return undefined // Skip playback
358
- }
359
-
360
- const request = formatRequestKey(requestOpts, this.#headerFilters, this.matchOptions)
361
- const hash = createRequestHash(request)
362
- const snapshot = this.#snapshots.get(hash)
363
-
364
- if (!snapshot) return undefined
365
-
366
- // Handle sequential responses
367
- const currentCallCount = snapshot.callCount || 0
368
- const responseIndex = Math.min(currentCallCount, snapshot.responses.length - 1)
369
- snapshot.callCount = currentCallCount + 1
370
-
371
- return {
372
- ...snapshot,
373
- response: snapshot.responses[responseIndex]
374
- }
375
- }
376
-
377
- /**
378
- * Loads snapshots from file
379
- * @param {string} [filePath] - Optional file path to load snapshots from
380
- * @return {Promise<void>} - Resolves when snapshots are loaded
381
- */
382
- async loadSnapshots (filePath) {
383
- const path = filePath || this.#snapshotPath
384
- if (!path) {
385
- throw new InvalidArgumentError('Snapshot path is required')
386
- }
387
-
388
- try {
389
- const data = await readFile(resolve(path), 'utf8')
390
- const parsed = JSON.parse(data)
391
-
392
- // Convert array format back to Map
393
- if (Array.isArray(parsed)) {
394
- this.#snapshots.clear()
395
- for (const { hash, snapshot } of parsed) {
396
- this.#snapshots.set(hash, snapshot)
397
- }
398
- } else {
399
- // Legacy object format
400
- this.#snapshots = new Map(Object.entries(parsed))
401
- }
402
- } catch (error) {
403
- if (error.code === 'ENOENT') {
404
- // File doesn't exist yet - that's ok for recording mode
405
- this.#snapshots.clear()
406
- } else {
407
- throw new UndiciError(`Failed to load snapshots from ${path}`, { cause: error })
408
- }
409
- }
410
- }
411
-
412
- /**
413
- * Saves snapshots to file
414
- *
415
- * @param {string} [filePath] - Optional file path to save snapshots
416
- * @returns {Promise<void>} - Resolves when snapshots are saved
417
- */
418
- async saveSnapshots (filePath) {
419
- const path = filePath || this.#snapshotPath
420
- if (!path) {
421
- throw new InvalidArgumentError('Snapshot path is required')
422
- }
423
-
424
- const resolvedPath = resolve(path)
425
-
426
- // Ensure directory exists
427
- await mkdir(dirname(resolvedPath), { recursive: true })
428
-
429
- // Convert Map to serializable format
430
- const data = Array.from(this.#snapshots.entries()).map(([hash, snapshot]) => ({
431
- hash,
432
- snapshot
433
- }))
434
-
435
- await writeFile(resolvedPath, JSON.stringify(data, null, 2), { flush: true })
436
- }
437
-
438
- /**
439
- * Clears all recorded snapshots
440
- * @returns {void}
441
- */
442
- clear () {
443
- this.#snapshots.clear()
444
- }
445
-
446
- /**
447
- * Gets all recorded snapshots
448
- * @return {Array<SnapshotEntry>} - Array of all recorded snapshots
449
- */
450
- getSnapshots () {
451
- return Array.from(this.#snapshots.values())
452
- }
453
-
454
- /**
455
- * Gets snapshot count
456
- * @return {number} - Number of recorded snapshots
457
- */
458
- size () {
459
- return this.#snapshots.size
460
- }
461
-
462
- /**
463
- * Resets call counts for all snapshots (useful for test cleanup)
464
- * @returns {void}
465
- */
466
- resetCallCounts () {
467
- for (const snapshot of this.#snapshots.values()) {
468
- snapshot.callCount = 0
469
- }
470
- }
471
-
472
- /**
473
- * Deletes a specific snapshot by request options
474
- * @param {SnapshotRequestOptions} requestOpts - Request options to match
475
- * @returns {boolean} - True if snapshot was deleted, false if not found
476
- */
477
- deleteSnapshot (requestOpts) {
478
- const request = formatRequestKey(requestOpts, this.#headerFilters, this.matchOptions)
479
- const hash = createRequestHash(request)
480
- return this.#snapshots.delete(hash)
481
- }
482
-
483
- /**
484
- * Gets information about a specific snapshot
485
- * @param {SnapshotRequestOptions} requestOpts - Request options to match
486
- * @returns {SnapshotInfo|null} - Snapshot information or null if not found
487
- */
488
- getSnapshotInfo (requestOpts) {
489
- const request = formatRequestKey(requestOpts, this.#headerFilters, this.matchOptions)
490
- const hash = createRequestHash(request)
491
- const snapshot = this.#snapshots.get(hash)
492
-
493
- if (!snapshot) return null
494
-
495
- return {
496
- hash,
497
- request: snapshot.request,
498
- responseCount: snapshot.responses ? snapshot.responses.length : (snapshot.response ? 1 : 0), // .response for legacy snapshots
499
- callCount: snapshot.callCount || 0,
500
- timestamp: snapshot.timestamp
501
- }
502
- }
503
-
504
- /**
505
- * Replaces all snapshots with new data (full replacement)
506
- * @param {Array<{hash: string; snapshot: SnapshotEntry}>|Record<string, SnapshotEntry>} snapshotData - New snapshot data to replace existing ones
507
- * @returns {void}
508
- */
509
- replaceSnapshots (snapshotData) {
510
- this.#snapshots.clear()
511
-
512
- if (Array.isArray(snapshotData)) {
513
- for (const { hash, snapshot } of snapshotData) {
514
- this.#snapshots.set(hash, snapshot)
515
- }
516
- } else if (snapshotData && typeof snapshotData === 'object') {
517
- // Legacy object format
518
- this.#snapshots = new Map(Object.entries(snapshotData))
519
- }
520
- }
521
-
522
- /**
523
- * Starts the auto-flush timer
524
- * @returns {void}
525
- */
526
- #startAutoFlush () {
527
- return this.#scheduleFlush()
528
- }
529
-
530
- /**
531
- * Stops the auto-flush timer
532
- * @returns {void}
533
- */
534
- #stopAutoFlush () {
535
- if (this.#flushTimeout) {
536
- clearTimeout(this.#flushTimeout)
537
- // Ensure any pending flush is completed
538
- this.saveSnapshots().catch(() => {
539
- // Ignore flush errors
540
- })
541
- this.#flushTimeout = null
542
- }
543
- }
544
-
545
- /**
546
- * Schedules a flush (debounced to avoid excessive writes)
547
- */
548
- #scheduleFlush () {
549
- this.#flushTimeout = setTimeout(() => {
550
- this.saveSnapshots().catch(() => {
551
- // Ignore flush errors
552
- })
553
- if (this.#autoFlush) {
554
- this.#flushTimeout?.refresh()
555
- } else {
556
- this.#flushTimeout = null
557
- }
558
- }, 1000) // 1 second debounce
559
- }
560
-
561
- /**
562
- * Cleanup method to stop timers
563
- * @returns {void}
564
- */
565
- destroy () {
566
- this.#stopAutoFlush()
567
- if (this.#flushTimeout) {
568
- clearTimeout(this.#flushTimeout)
569
- this.#flushTimeout = null
570
- }
571
- }
572
-
573
- /**
574
- * Async close method that saves all recordings and performs cleanup
575
- * @returns {Promise<void>}
576
- */
577
- async close () {
578
- // Save any pending recordings if we have a snapshot path
579
- if (this.#snapshotPath && this.#snapshots.size !== 0) {
580
- await this.saveSnapshots()
581
- }
582
-
583
- // Perform cleanup
584
- this.destroy()
585
- }
586
- }
587
-
588
- module.exports = { SnapshotRecorder, formatRequestKey, createRequestHash, filterHeadersForMatching, filterHeadersForStorage, createHeaderFilters }