pi-cursor-sdk 0.1.50 → 0.1.52

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 (279) hide show
  1. package/CHANGELOG.md +31 -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-provider-turn-runner.ts +1 -1
  12. package/src/cursor-provider.ts +9 -2
  13. package/src/cursor-session-turn-queue.ts +52 -0
  14. package/src/cursor-tool-manifest.ts +1 -1
  15. package/node_modules/@connectrpc/connect-node/README.md +0 -141
  16. package/node_modules/@connectrpc/connect-node/dist/cjs/compression.d.ts +0 -15
  17. package/node_modules/@connectrpc/connect-node/dist/cjs/compression.js +0 -89
  18. package/node_modules/@connectrpc/connect-node/dist/cjs/connect-node-adapter.d.ts +0 -42
  19. package/node_modules/@connectrpc/connect-node/dist/cjs/connect-node-adapter.js +0 -61
  20. package/node_modules/@connectrpc/connect-node/dist/cjs/connect-transport.d.ts +0 -100
  21. package/node_modules/@connectrpc/connect-node/dist/cjs/connect-transport.js +0 -25
  22. package/node_modules/@connectrpc/connect-node/dist/cjs/grpc-transport.d.ts +0 -96
  23. package/node_modules/@connectrpc/connect-node/dist/cjs/grpc-transport.js +0 -25
  24. package/node_modules/@connectrpc/connect-node/dist/cjs/grpc-web-transport.d.ts +0 -96
  25. package/node_modules/@connectrpc/connect-node/dist/cjs/grpc-web-transport.js +0 -25
  26. package/node_modules/@connectrpc/connect-node/dist/cjs/http2-session-manager.d.ts +0 -146
  27. package/node_modules/@connectrpc/connect-node/dist/cjs/http2-session-manager.js +0 -536
  28. package/node_modules/@connectrpc/connect-node/dist/cjs/index.d.ts +0 -15
  29. package/node_modules/@connectrpc/connect-node/dist/cjs/index.js +0 -36
  30. package/node_modules/@connectrpc/connect-node/dist/cjs/node-error.d.ts +0 -45
  31. package/node_modules/@connectrpc/connect-node/dist/cjs/node-error.js +0 -154
  32. package/node_modules/@connectrpc/connect-node/dist/cjs/node-headers-polyfill.d.ts +0 -1
  33. package/node_modules/@connectrpc/connect-node/dist/cjs/node-headers-polyfill.js +0 -29
  34. package/node_modules/@connectrpc/connect-node/dist/cjs/node-transport-options.d.ts +0 -89
  35. package/node_modules/@connectrpc/connect-node/dist/cjs/node-transport-options.js +0 -58
  36. package/node_modules/@connectrpc/connect-node/dist/cjs/node-universal-client.d.ts +0 -54
  37. package/node_modules/@connectrpc/connect-node/dist/cjs/node-universal-client.js +0 -326
  38. package/node_modules/@connectrpc/connect-node/dist/cjs/node-universal-handler.d.ts +0 -41
  39. package/node_modules/@connectrpc/connect-node/dist/cjs/node-universal-handler.js +0 -234
  40. package/node_modules/@connectrpc/connect-node/dist/cjs/node-universal-header.d.ts +0 -19
  41. package/node_modules/@connectrpc/connect-node/dist/cjs/node-universal-header.js +0 -97
  42. package/node_modules/@connectrpc/connect-node/dist/cjs/package.json +0 -1
  43. package/node_modules/@connectrpc/connect-node/dist/esm/compression.d.ts +0 -15
  44. package/node_modules/@connectrpc/connect-node/dist/esm/compression.js +0 -86
  45. package/node_modules/@connectrpc/connect-node/dist/esm/connect-node-adapter.d.ts +0 -42
  46. package/node_modules/@connectrpc/connect-node/dist/esm/connect-node-adapter.js +0 -58
  47. package/node_modules/@connectrpc/connect-node/dist/esm/connect-transport.d.ts +0 -100
  48. package/node_modules/@connectrpc/connect-node/dist/esm/connect-transport.js +0 -22
  49. package/node_modules/@connectrpc/connect-node/dist/esm/grpc-transport.d.ts +0 -96
  50. package/node_modules/@connectrpc/connect-node/dist/esm/grpc-transport.js +0 -22
  51. package/node_modules/@connectrpc/connect-node/dist/esm/grpc-web-transport.d.ts +0 -96
  52. package/node_modules/@connectrpc/connect-node/dist/esm/grpc-web-transport.js +0 -22
  53. package/node_modules/@connectrpc/connect-node/dist/esm/http2-session-manager.d.ts +0 -146
  54. package/node_modules/@connectrpc/connect-node/dist/esm/http2-session-manager.js +0 -532
  55. package/node_modules/@connectrpc/connect-node/dist/esm/index.d.ts +0 -15
  56. package/node_modules/@connectrpc/connect-node/dist/esm/index.js +0 -23
  57. package/node_modules/@connectrpc/connect-node/dist/esm/node-error.d.ts +0 -45
  58. package/node_modules/@connectrpc/connect-node/dist/esm/node-error.js +0 -147
  59. package/node_modules/@connectrpc/connect-node/dist/esm/node-headers-polyfill.d.ts +0 -1
  60. package/node_modules/@connectrpc/connect-node/dist/esm/node-headers-polyfill.js +0 -27
  61. package/node_modules/@connectrpc/connect-node/dist/esm/node-transport-options.d.ts +0 -89
  62. package/node_modules/@connectrpc/connect-node/dist/esm/node-transport-options.js +0 -58
  63. package/node_modules/@connectrpc/connect-node/dist/esm/node-universal-client.d.ts +0 -54
  64. package/node_modules/@connectrpc/connect-node/dist/esm/node-universal-client.js +0 -324
  65. package/node_modules/@connectrpc/connect-node/dist/esm/node-universal-handler.d.ts +0 -41
  66. package/node_modules/@connectrpc/connect-node/dist/esm/node-universal-handler.js +0 -230
  67. package/node_modules/@connectrpc/connect-node/dist/esm/node-universal-header.d.ts +0 -19
  68. package/node_modules/@connectrpc/connect-node/dist/esm/node-universal-header.js +0 -93
  69. package/node_modules/@connectrpc/connect-node/package.json +0 -46
  70. package/node_modules/undici/LICENSE +0 -21
  71. package/node_modules/undici/README.md +0 -741
  72. package/node_modules/undici/docs/docs/api/Agent.md +0 -84
  73. package/node_modules/undici/docs/docs/api/BalancedPool.md +0 -99
  74. package/node_modules/undici/docs/docs/api/CacheStorage.md +0 -30
  75. package/node_modules/undici/docs/docs/api/CacheStore.md +0 -164
  76. package/node_modules/undici/docs/docs/api/Client.md +0 -288
  77. package/node_modules/undici/docs/docs/api/ClientStats.md +0 -27
  78. package/node_modules/undici/docs/docs/api/Connector.md +0 -115
  79. package/node_modules/undici/docs/docs/api/ContentType.md +0 -57
  80. package/node_modules/undici/docs/docs/api/Cookies.md +0 -128
  81. package/node_modules/undici/docs/docs/api/Debug.md +0 -62
  82. package/node_modules/undici/docs/docs/api/DiagnosticsChannel.md +0 -315
  83. package/node_modules/undici/docs/docs/api/Dispatcher.md +0 -1392
  84. package/node_modules/undici/docs/docs/api/EnvHttpProxyAgent.md +0 -159
  85. package/node_modules/undici/docs/docs/api/Errors.md +0 -49
  86. package/node_modules/undici/docs/docs/api/EventSource.md +0 -45
  87. package/node_modules/undici/docs/docs/api/Fetch.md +0 -60
  88. package/node_modules/undici/docs/docs/api/GlobalInstallation.md +0 -139
  89. package/node_modules/undici/docs/docs/api/H2CClient.md +0 -263
  90. package/node_modules/undici/docs/docs/api/MockAgent.md +0 -603
  91. package/node_modules/undici/docs/docs/api/MockCallHistory.md +0 -197
  92. package/node_modules/undici/docs/docs/api/MockCallHistoryLog.md +0 -43
  93. package/node_modules/undici/docs/docs/api/MockClient.md +0 -81
  94. package/node_modules/undici/docs/docs/api/MockErrors.md +0 -12
  95. package/node_modules/undici/docs/docs/api/MockPool.md +0 -555
  96. package/node_modules/undici/docs/docs/api/Pool.md +0 -84
  97. package/node_modules/undici/docs/docs/api/PoolStats.md +0 -35
  98. package/node_modules/undici/docs/docs/api/ProxyAgent.md +0 -229
  99. package/node_modules/undici/docs/docs/api/RedirectHandler.md +0 -93
  100. package/node_modules/undici/docs/docs/api/RetryAgent.md +0 -50
  101. package/node_modules/undici/docs/docs/api/RetryHandler.md +0 -118
  102. package/node_modules/undici/docs/docs/api/RoundRobinPool.md +0 -145
  103. package/node_modules/undici/docs/docs/api/SnapshotAgent.md +0 -616
  104. package/node_modules/undici/docs/docs/api/Socks5ProxyAgent.md +0 -275
  105. package/node_modules/undici/docs/docs/api/Util.md +0 -25
  106. package/node_modules/undici/docs/docs/api/WebSocket.md +0 -141
  107. package/node_modules/undici/docs/docs/api/api-lifecycle.md +0 -91
  108. package/node_modules/undici/docs/docs/best-practices/client-certificate.md +0 -64
  109. package/node_modules/undici/docs/docs/best-practices/crawling.md +0 -58
  110. package/node_modules/undici/docs/docs/best-practices/mocking-request.md +0 -190
  111. package/node_modules/undici/docs/docs/best-practices/proxy.md +0 -127
  112. package/node_modules/undici/docs/docs/best-practices/undici-vs-builtin-fetch.md +0 -224
  113. package/node_modules/undici/docs/docs/best-practices/writing-tests.md +0 -63
  114. package/node_modules/undici/index-fetch.js +0 -65
  115. package/node_modules/undici/index.d.ts +0 -3
  116. package/node_modules/undici/index.js +0 -234
  117. package/node_modules/undici/lib/api/abort-signal.js +0 -59
  118. package/node_modules/undici/lib/api/api-connect.js +0 -110
  119. package/node_modules/undici/lib/api/api-pipeline.js +0 -252
  120. package/node_modules/undici/lib/api/api-request.js +0 -214
  121. package/node_modules/undici/lib/api/api-stream.js +0 -209
  122. package/node_modules/undici/lib/api/api-upgrade.js +0 -111
  123. package/node_modules/undici/lib/api/index.js +0 -7
  124. package/node_modules/undici/lib/api/readable.js +0 -580
  125. package/node_modules/undici/lib/cache/memory-cache-store.js +0 -234
  126. package/node_modules/undici/lib/cache/sqlite-cache-store.js +0 -461
  127. package/node_modules/undici/lib/core/connect.js +0 -153
  128. package/node_modules/undici/lib/core/constants.js +0 -143
  129. package/node_modules/undici/lib/core/diagnostics.js +0 -227
  130. package/node_modules/undici/lib/core/errors.js +0 -477
  131. package/node_modules/undici/lib/core/request.js +0 -453
  132. package/node_modules/undici/lib/core/socks5-client.js +0 -422
  133. package/node_modules/undici/lib/core/socks5-utils.js +0 -212
  134. package/node_modules/undici/lib/core/symbols.js +0 -75
  135. package/node_modules/undici/lib/core/tree.js +0 -160
  136. package/node_modules/undici/lib/core/util.js +0 -992
  137. package/node_modules/undici/lib/dispatcher/agent.js +0 -158
  138. package/node_modules/undici/lib/dispatcher/balanced-pool.js +0 -219
  139. package/node_modules/undici/lib/dispatcher/client-h1.js +0 -1722
  140. package/node_modules/undici/lib/dispatcher/client-h2.js +0 -995
  141. package/node_modules/undici/lib/dispatcher/client.js +0 -664
  142. package/node_modules/undici/lib/dispatcher/dispatcher-base.js +0 -184
  143. package/node_modules/undici/lib/dispatcher/dispatcher.js +0 -48
  144. package/node_modules/undici/lib/dispatcher/env-http-proxy-agent.js +0 -146
  145. package/node_modules/undici/lib/dispatcher/fixed-queue.js +0 -135
  146. package/node_modules/undici/lib/dispatcher/h2c-client.js +0 -51
  147. package/node_modules/undici/lib/dispatcher/pool-base.js +0 -214
  148. package/node_modules/undici/lib/dispatcher/pool.js +0 -118
  149. package/node_modules/undici/lib/dispatcher/proxy-agent.js +0 -319
  150. package/node_modules/undici/lib/dispatcher/retry-agent.js +0 -35
  151. package/node_modules/undici/lib/dispatcher/round-robin-pool.js +0 -137
  152. package/node_modules/undici/lib/dispatcher/socks5-proxy-agent.js +0 -260
  153. package/node_modules/undici/lib/encoding/index.js +0 -33
  154. package/node_modules/undici/lib/global.js +0 -59
  155. package/node_modules/undici/lib/handler/cache-handler.js +0 -578
  156. package/node_modules/undici/lib/handler/cache-revalidation-handler.js +0 -124
  157. package/node_modules/undici/lib/handler/decorator-handler.js +0 -67
  158. package/node_modules/undici/lib/handler/deduplication-handler.js +0 -460
  159. package/node_modules/undici/lib/handler/redirect-handler.js +0 -238
  160. package/node_modules/undici/lib/handler/retry-handler.js +0 -394
  161. package/node_modules/undici/lib/handler/unwrap-handler.js +0 -106
  162. package/node_modules/undici/lib/handler/wrap-handler.js +0 -105
  163. package/node_modules/undici/lib/interceptor/cache.js +0 -495
  164. package/node_modules/undici/lib/interceptor/decompress.js +0 -259
  165. package/node_modules/undici/lib/interceptor/deduplicate.js +0 -117
  166. package/node_modules/undici/lib/interceptor/dns.js +0 -571
  167. package/node_modules/undici/lib/interceptor/dump.js +0 -112
  168. package/node_modules/undici/lib/interceptor/redirect.js +0 -21
  169. package/node_modules/undici/lib/interceptor/response-error.js +0 -95
  170. package/node_modules/undici/lib/interceptor/retry.js +0 -19
  171. package/node_modules/undici/lib/llhttp/.gitkeep +0 -0
  172. package/node_modules/undici/lib/llhttp/constants.d.ts +0 -195
  173. package/node_modules/undici/lib/llhttp/constants.js +0 -531
  174. package/node_modules/undici/lib/llhttp/llhttp-wasm.js +0 -15
  175. package/node_modules/undici/lib/llhttp/llhttp_simd-wasm.js +0 -15
  176. package/node_modules/undici/lib/llhttp/utils.d.ts +0 -2
  177. package/node_modules/undici/lib/llhttp/utils.js +0 -12
  178. package/node_modules/undici/lib/mock/mock-agent.js +0 -232
  179. package/node_modules/undici/lib/mock/mock-call-history.js +0 -248
  180. package/node_modules/undici/lib/mock/mock-client.js +0 -68
  181. package/node_modules/undici/lib/mock/mock-errors.js +0 -29
  182. package/node_modules/undici/lib/mock/mock-interceptor.js +0 -209
  183. package/node_modules/undici/lib/mock/mock-pool.js +0 -68
  184. package/node_modules/undici/lib/mock/mock-symbols.js +0 -32
  185. package/node_modules/undici/lib/mock/mock-utils.js +0 -486
  186. package/node_modules/undici/lib/mock/pending-interceptors-formatter.js +0 -43
  187. package/node_modules/undici/lib/mock/snapshot-agent.js +0 -353
  188. package/node_modules/undici/lib/mock/snapshot-recorder.js +0 -588
  189. package/node_modules/undici/lib/mock/snapshot-utils.js +0 -158
  190. package/node_modules/undici/lib/util/cache.js +0 -414
  191. package/node_modules/undici/lib/util/date.js +0 -653
  192. package/node_modules/undici/lib/util/promise.js +0 -28
  193. package/node_modules/undici/lib/util/runtime-features.js +0 -124
  194. package/node_modules/undici/lib/util/stats.js +0 -32
  195. package/node_modules/undici/lib/util/timers.js +0 -425
  196. package/node_modules/undici/lib/web/cache/cache.js +0 -864
  197. package/node_modules/undici/lib/web/cache/cachestorage.js +0 -152
  198. package/node_modules/undici/lib/web/cache/util.js +0 -45
  199. package/node_modules/undici/lib/web/cookies/constants.js +0 -12
  200. package/node_modules/undici/lib/web/cookies/index.js +0 -199
  201. package/node_modules/undici/lib/web/cookies/parse.js +0 -314
  202. package/node_modules/undici/lib/web/cookies/util.js +0 -282
  203. package/node_modules/undici/lib/web/eventsource/eventsource-stream.js +0 -399
  204. package/node_modules/undici/lib/web/eventsource/eventsource.js +0 -501
  205. package/node_modules/undici/lib/web/eventsource/util.js +0 -29
  206. package/node_modules/undici/lib/web/fetch/LICENSE +0 -21
  207. package/node_modules/undici/lib/web/fetch/body.js +0 -509
  208. package/node_modules/undici/lib/web/fetch/constants.js +0 -131
  209. package/node_modules/undici/lib/web/fetch/data-url.js +0 -596
  210. package/node_modules/undici/lib/web/fetch/formdata-parser.js +0 -586
  211. package/node_modules/undici/lib/web/fetch/formdata.js +0 -259
  212. package/node_modules/undici/lib/web/fetch/global.js +0 -40
  213. package/node_modules/undici/lib/web/fetch/headers.js +0 -719
  214. package/node_modules/undici/lib/web/fetch/index.js +0 -2413
  215. package/node_modules/undici/lib/web/fetch/request.js +0 -1115
  216. package/node_modules/undici/lib/web/fetch/response.js +0 -641
  217. package/node_modules/undici/lib/web/fetch/util.js +0 -1522
  218. package/node_modules/undici/lib/web/infra/index.js +0 -229
  219. package/node_modules/undici/lib/web/subresource-integrity/Readme.md +0 -9
  220. package/node_modules/undici/lib/web/subresource-integrity/subresource-integrity.js +0 -307
  221. package/node_modules/undici/lib/web/webidl/index.js +0 -1006
  222. package/node_modules/undici/lib/web/websocket/connection.js +0 -329
  223. package/node_modules/undici/lib/web/websocket/constants.js +0 -126
  224. package/node_modules/undici/lib/web/websocket/events.js +0 -331
  225. package/node_modules/undici/lib/web/websocket/frame.js +0 -133
  226. package/node_modules/undici/lib/web/websocket/permessage-deflate.js +0 -100
  227. package/node_modules/undici/lib/web/websocket/receiver.js +0 -507
  228. package/node_modules/undici/lib/web/websocket/sender.js +0 -109
  229. package/node_modules/undici/lib/web/websocket/stream/websocketerror.js +0 -104
  230. package/node_modules/undici/lib/web/websocket/stream/websocketstream.js +0 -498
  231. package/node_modules/undici/lib/web/websocket/util.js +0 -347
  232. package/node_modules/undici/lib/web/websocket/websocket.js +0 -758
  233. package/node_modules/undici/package.json +0 -152
  234. package/node_modules/undici/scripts/strip-comments.js +0 -10
  235. package/node_modules/undici/types/README.md +0 -6
  236. package/node_modules/undici/types/agent.d.ts +0 -32
  237. package/node_modules/undici/types/api.d.ts +0 -43
  238. package/node_modules/undici/types/balanced-pool.d.ts +0 -30
  239. package/node_modules/undici/types/cache-interceptor.d.ts +0 -179
  240. package/node_modules/undici/types/cache.d.ts +0 -36
  241. package/node_modules/undici/types/client-stats.d.ts +0 -15
  242. package/node_modules/undici/types/client.d.ts +0 -139
  243. package/node_modules/undici/types/connector.d.ts +0 -36
  244. package/node_modules/undici/types/content-type.d.ts +0 -21
  245. package/node_modules/undici/types/cookies.d.ts +0 -30
  246. package/node_modules/undici/types/diagnostics-channel.d.ts +0 -74
  247. package/node_modules/undici/types/dispatcher.d.ts +0 -275
  248. package/node_modules/undici/types/env-http-proxy-agent.d.ts +0 -22
  249. package/node_modules/undici/types/errors.d.ts +0 -177
  250. package/node_modules/undici/types/eventsource.d.ts +0 -66
  251. package/node_modules/undici/types/fetch.d.ts +0 -231
  252. package/node_modules/undici/types/formdata.d.ts +0 -114
  253. package/node_modules/undici/types/global-dispatcher.d.ts +0 -9
  254. package/node_modules/undici/types/global-origin.d.ts +0 -7
  255. package/node_modules/undici/types/h2c-client.d.ts +0 -73
  256. package/node_modules/undici/types/handlers.d.ts +0 -14
  257. package/node_modules/undici/types/header.d.ts +0 -160
  258. package/node_modules/undici/types/index.d.ts +0 -91
  259. package/node_modules/undici/types/interceptors.d.ts +0 -80
  260. package/node_modules/undici/types/mock-agent.d.ts +0 -68
  261. package/node_modules/undici/types/mock-call-history.d.ts +0 -111
  262. package/node_modules/undici/types/mock-client.d.ts +0 -27
  263. package/node_modules/undici/types/mock-errors.d.ts +0 -12
  264. package/node_modules/undici/types/mock-interceptor.d.ts +0 -94
  265. package/node_modules/undici/types/mock-pool.d.ts +0 -27
  266. package/node_modules/undici/types/patch.d.ts +0 -29
  267. package/node_modules/undici/types/pool-stats.d.ts +0 -19
  268. package/node_modules/undici/types/pool.d.ts +0 -41
  269. package/node_modules/undici/types/proxy-agent.d.ts +0 -29
  270. package/node_modules/undici/types/readable.d.ts +0 -68
  271. package/node_modules/undici/types/retry-agent.d.ts +0 -8
  272. package/node_modules/undici/types/retry-handler.d.ts +0 -125
  273. package/node_modules/undici/types/round-robin-pool.d.ts +0 -41
  274. package/node_modules/undici/types/snapshot-agent.d.ts +0 -109
  275. package/node_modules/undici/types/socks5-proxy-agent.d.ts +0 -25
  276. package/node_modules/undici/types/util.d.ts +0 -18
  277. package/node_modules/undici/types/utility.d.ts +0 -7
  278. package/node_modules/undici/types/webidl.d.ts +0 -347
  279. 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 }