@apifuse/provider-sdk 2.2.0-beta.5 → 2.2.0-beta.50

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 (365) hide show
  1. package/AUTHORING.md +537 -33
  2. package/CHANGELOG.md +201 -1
  3. package/README.md +83 -22
  4. package/SUBMISSION.md +2 -2
  5. package/bin/apifuse-check.ts +165 -11
  6. package/bin/apifuse-dev.ts +57 -13
  7. package/bin/apifuse-migrate-operation-declaration.ts +55 -0
  8. package/bin/apifuse-migrate-shape.ts +202 -0
  9. package/bin/apifuse-pack-check.ts +22 -2
  10. package/bin/apifuse-pack-smoke.ts +57 -2
  11. package/bin/apifuse-pack-types.ts +357 -38
  12. package/bin/apifuse-perf.ts +14 -13
  13. package/bin/apifuse-record.ts +709 -72
  14. package/bin/apifuse-submit-check.ts +2344 -324
  15. package/bin/apifuse-sync-assets.ts +117 -0
  16. package/bin/submit-check-delimited-text.ts +50 -0
  17. package/dist/auth-turn/index.d.ts +3 -3
  18. package/dist/auth-turn/index.js +1 -1
  19. package/dist/auth.d.ts +14 -0
  20. package/dist/auth.js +67 -0
  21. package/dist/ceremonies/index.d.ts +16 -0
  22. package/dist/ceremonies/index.js +141 -36
  23. package/dist/cli/commands.d.ts +1 -1
  24. package/dist/cli/commands.js +27 -0
  25. package/dist/cli/create.d.ts +4 -1
  26. package/dist/cli/create.js +42 -37
  27. package/dist/cli/migrate-operation-declaration.d.ts +59 -0
  28. package/dist/cli/migrate-operation-declaration.js +1178 -0
  29. package/dist/cli/migrate-operation-shape.d.ts +44 -0
  30. package/dist/cli/migrate-operation-shape.js +113 -0
  31. package/dist/cli/migrate-provider-shape.d.ts +52 -0
  32. package/dist/cli/migrate-provider-shape.js +578 -0
  33. package/dist/cli/prompt-assets.d.ts +80 -0
  34. package/dist/cli/prompt-assets.js +743 -0
  35. package/dist/cli/templates/provider/AGENTS.md.tpl +17 -8
  36. package/dist/cli/templates/provider/Dockerfile.tpl +1 -1
  37. package/dist/cli/templates/provider/README.md.tpl +5 -5
  38. package/dist/cli/templates/provider/index.ts.tpl +6 -3
  39. package/dist/cli/templates/provider/operations/ping.ts.tpl +4 -1
  40. package/dist/cli/templates/provider/provider.json.tpl +6 -0
  41. package/dist/config/loader.d.ts +179 -16
  42. package/dist/config/loader.js +435 -127
  43. package/dist/contract-serialization.js +4 -8
  44. package/dist/contract-types.d.ts +12 -5
  45. package/dist/contract.js +24 -10
  46. package/dist/declaration-validation.d.ts +33 -0
  47. package/dist/declaration-validation.js +266 -0
  48. package/dist/define.d.ts +66 -29
  49. package/dist/define.js +778 -69
  50. package/dist/dev.d.ts +3 -0
  51. package/dist/dev.js +1 -1
  52. package/dist/engine.d.ts +78 -0
  53. package/dist/engine.js +133 -0
  54. package/dist/error-observability.d.ts +7 -0
  55. package/dist/error-observability.js +61 -0
  56. package/dist/error-resolution.d.ts +4 -0
  57. package/dist/error-resolution.js +122 -0
  58. package/dist/errors.d.ts +33 -0
  59. package/dist/errors.js +40 -0
  60. package/dist/fixture-sanitization.d.ts +28 -0
  61. package/dist/fixture-sanitization.js +227 -0
  62. package/dist/health-scenario.d.ts +1842 -0
  63. package/dist/health-scenario.js +624 -0
  64. package/dist/index.d.ts +21 -10
  65. package/dist/index.js +12 -7
  66. package/dist/lint.d.ts +23 -11
  67. package/dist/lint.js +572 -69
  68. package/dist/native-address.d.ts +43 -0
  69. package/dist/native-address.js +281 -0
  70. package/dist/native-egress-policy.d.ts +31 -0
  71. package/dist/native-egress-policy.js +288 -0
  72. package/dist/observability.d.ts +5 -2
  73. package/dist/observability.js +48 -1
  74. package/dist/provider.d.ts +10 -2
  75. package/dist/provider.js +4 -1
  76. package/dist/runtime/auth-flow.d.ts +5 -1
  77. package/dist/runtime/auth-flow.js +6 -0
  78. package/dist/runtime/browser.d.ts +1 -0
  79. package/dist/runtime/browser.js +492 -49
  80. package/dist/runtime/cache.d.ts +1 -0
  81. package/dist/runtime/cache.js +169 -15
  82. package/dist/runtime/choice-wordlist.d.ts +9 -0
  83. package/dist/runtime/choice-wordlist.js +138 -0
  84. package/dist/runtime/choice.d.ts +13 -1
  85. package/dist/runtime/choice.js +490 -102
  86. package/dist/runtime/chrome149-header-order.d.ts +58 -0
  87. package/dist/runtime/chrome149-header-order.js +289 -0
  88. package/dist/runtime/env.js +12 -0
  89. package/dist/runtime/executor.d.ts +3 -2
  90. package/dist/runtime/executor.js +26 -25
  91. package/dist/runtime/http.d.ts +1 -0
  92. package/dist/runtime/http.js +515 -53
  93. package/dist/runtime/insights.js +2 -2
  94. package/dist/runtime/instrumentation.d.ts +2 -2
  95. package/dist/runtime/instrumentation.js +366 -8
  96. package/dist/runtime/native-network-errors.d.ts +33 -0
  97. package/dist/runtime/native-network-errors.js +69 -0
  98. package/dist/runtime/native-network.d.ts +96 -0
  99. package/dist/runtime/native-network.js +1232 -0
  100. package/dist/runtime/ocr.d.ts +29 -0
  101. package/dist/runtime/ocr.js +440 -0
  102. package/dist/runtime/otlp.d.ts +71 -2
  103. package/dist/runtime/otlp.js +397 -16
  104. package/dist/runtime/proxy-errors.js +6 -2
  105. package/dist/runtime/proxy-nodemaven.d.ts +56 -0
  106. package/dist/runtime/proxy-nodemaven.js +146 -0
  107. package/dist/runtime/proxy-telemetry.d.ts +80 -1
  108. package/dist/runtime/proxy-telemetry.js +154 -47
  109. package/dist/runtime/redirects.d.ts +29 -0
  110. package/dist/runtime/redirects.js +36 -0
  111. package/dist/runtime/redis.d.ts +1 -1
  112. package/dist/runtime/redis.js +4 -2
  113. package/dist/runtime/request-options.d.ts +68 -1
  114. package/dist/runtime/request-options.js +548 -0
  115. package/dist/runtime/resolver-config.d.ts +6 -0
  116. package/dist/runtime/resolver-config.js +6 -0
  117. package/dist/runtime/resolver-public.d.ts +1 -0
  118. package/dist/runtime/resolver-public.js +1 -0
  119. package/dist/runtime/resolver-shared.d.ts +14 -0
  120. package/dist/runtime/resolver-shared.js +12 -0
  121. package/dist/runtime/resolver-vendors/bindings.d.ts +48 -0
  122. package/dist/runtime/resolver-vendors/bindings.js +40 -0
  123. package/dist/runtime/resolver-vendors/browser.d.ts +22 -0
  124. package/dist/runtime/resolver-vendors/browser.js +377 -0
  125. package/dist/runtime/resolver-vendors/capsolver.d.ts +22 -0
  126. package/dist/runtime/resolver-vendors/capsolver.js +531 -0
  127. package/dist/runtime/resolver-vendors/hosts.d.ts +2 -0
  128. package/dist/runtime/resolver-vendors/hosts.js +33 -0
  129. package/dist/runtime/resolver-vendors/twocaptcha.d.ts +24 -0
  130. package/dist/runtime/resolver-vendors/twocaptcha.js +408 -0
  131. package/dist/runtime/resolver-vendors/types.d.ts +94 -0
  132. package/dist/runtime/resolver-vendors/types.js +96 -0
  133. package/dist/runtime/resolver.d.ts +72 -0
  134. package/dist/runtime/resolver.js +771 -0
  135. package/dist/runtime/secrets.d.ts +27 -0
  136. package/dist/runtime/secrets.js +51 -0
  137. package/dist/runtime/state.d.ts +3 -0
  138. package/dist/runtime/state.js +277 -71
  139. package/dist/runtime/stealth-cookies.d.ts +20 -0
  140. package/dist/runtime/stealth-cookies.js +111 -0
  141. package/dist/runtime/stealth.d.ts +41 -5
  142. package/dist/runtime/stealth.js +997 -291
  143. package/dist/runtime/stt.js +1 -12
  144. package/dist/runtime/timeout.d.ts +5 -0
  145. package/dist/runtime/timeout.js +12 -0
  146. package/dist/runtime/trace-config.d.ts +12 -0
  147. package/dist/runtime/trace-config.js +62 -0
  148. package/dist/runtime/trace.d.ts +5 -0
  149. package/dist/runtime/trace.js +43 -10
  150. package/dist/serve.d.ts +1 -1
  151. package/dist/serve.js +1 -1
  152. package/dist/server/error-observability.d.ts +1 -0
  153. package/dist/server/error-observability.js +1 -0
  154. package/dist/server/index.d.ts +5 -2
  155. package/dist/server/index.js +2 -2
  156. package/dist/server/self-test-input-tokens.d.ts +2 -1
  157. package/dist/server/self-test-input-tokens.js +18 -14
  158. package/dist/server/self-test.d.ts +14 -3
  159. package/dist/server/self-test.js +129 -58
  160. package/dist/server/serve-implementation.d.ts +230 -0
  161. package/dist/server/serve-implementation.js +2265 -0
  162. package/dist/server/serve.d.ts +1 -70
  163. package/dist/server/serve.js +1 -1131
  164. package/dist/server/trace-output.d.ts +6 -0
  165. package/dist/server/trace-output.js +79 -0
  166. package/dist/server/types.d.ts +30 -5
  167. package/dist/server/types.js +13 -1
  168. package/dist/stateful/errors.d.ts +19 -0
  169. package/dist/stateful/errors.js +24 -0
  170. package/dist/stateful/http-provider-event-emitter.d.ts +40 -0
  171. package/dist/stateful/http-provider-event-emitter.js +237 -0
  172. package/dist/stateful/http-session-owner-registry.d.ts +44 -0
  173. package/dist/stateful/http-session-owner-registry.js +210 -0
  174. package/dist/stateful/index.d.ts +18 -0
  175. package/dist/stateful/index.js +18 -0
  176. package/dist/stateful/provider-event-delivery-failures.d.ts +32 -0
  177. package/dist/stateful/provider-event-delivery-failures.js +43 -0
  178. package/dist/stateful/provider-event-pipeline-metrics.d.ts +46 -0
  179. package/dist/stateful/provider-event-pipeline-metrics.js +48 -0
  180. package/dist/stateful/provider-event-pipeline.d.ts +50 -0
  181. package/dist/stateful/provider-event-pipeline.js +1 -0
  182. package/dist/stateful/provider-events.d.ts +101 -0
  183. package/dist/stateful/provider-events.js +289 -0
  184. package/dist/stateful/session-key.d.ts +15 -0
  185. package/dist/stateful/session-key.js +86 -0
  186. package/dist/stateful/stateful-provider-adapter-context.d.ts +5 -0
  187. package/dist/stateful/stateful-provider-adapter-context.js +42 -0
  188. package/dist/stateful/stateful-provider-adapter-metrics.d.ts +15 -0
  189. package/dist/stateful/stateful-provider-adapter-metrics.js +21 -0
  190. package/dist/stateful/stateful-provider-adapter.d.ts +98 -0
  191. package/dist/stateful/stateful-provider-adapter.js +287 -0
  192. package/dist/stateful/stateful-provider-observability.d.ts +62 -0
  193. package/dist/stateful/stateful-provider-observability.js +161 -0
  194. package/dist/stateful/stateful-provider-owner-forwarder.d.ts +41 -0
  195. package/dist/stateful/stateful-provider-owner-forwarder.js +215 -0
  196. package/dist/stateful/stateful-provider-runtime-context.d.ts +32 -0
  197. package/dist/stateful/stateful-provider-runtime-context.js +60 -0
  198. package/dist/stateful/stateful-provider-runtime-executor.d.ts +34 -0
  199. package/dist/stateful/stateful-provider-runtime-executor.js +52 -0
  200. package/dist/stateful/stateful-provider-session-routing.d.ts +67 -0
  201. package/dist/stateful/stateful-provider-session-routing.js +345 -0
  202. package/dist/stateful/stateful-provider-session-runtime.d.ts +98 -0
  203. package/dist/stateful/stateful-provider-session-runtime.js +245 -0
  204. package/dist/stateful-signing.d.ts +18 -0
  205. package/dist/stateful-signing.js +27 -0
  206. package/dist/stealth/profiles.d.ts +9 -4
  207. package/dist/stealth/profiles.js +160 -211
  208. package/dist/stream-evidence.d.ts +74 -0
  209. package/dist/stream-evidence.js +785 -0
  210. package/dist/stream.js +7 -1
  211. package/dist/testing/index.d.ts +2 -1
  212. package/dist/testing/index.js +2 -1
  213. package/dist/testing/run.d.ts +32 -2
  214. package/dist/testing/run.js +489 -21
  215. package/dist/trace-sanitization.d.ts +5 -0
  216. package/dist/trace-sanitization.js +45 -0
  217. package/dist/types.d.ts +673 -131
  218. package/dist/types.js +1 -0
  219. package/package.json +45 -5
  220. package/src/auth-turn/index.ts +1 -1
  221. package/src/auth.ts +118 -0
  222. package/src/ceremonies/index.ts +189 -46
  223. package/src/cli/__tests__/fixtures/migrate-operation-declaration/approval-override.ts.txt +6 -0
  224. package/src/cli/__tests__/fixtures/migrate-operation-declaration/codemod-syntax.ts.txt +3 -0
  225. package/src/cli/__tests__/fixtures/migrate-operation-declaration/connection-precedence.ts.txt +10 -0
  226. package/src/cli/__tests__/fixtures/migrate-operation-declaration/docs-conflict.ts.txt +8 -0
  227. package/src/cli/__tests__/fixtures/migrate-operation-declaration/examples-map.ts.txt +5 -0
  228. package/src/cli/__tests__/fixtures/migrate-operation-declaration/examples-operation.ts.txt +16 -0
  229. package/src/cli/__tests__/fixtures/migrate-operation-declaration/factory-map.ts.txt +3 -0
  230. package/src/cli/__tests__/fixtures/migrate-operation-declaration/hoist-all.ts.txt +31 -0
  231. package/src/cli/__tests__/fixtures/migrate-operation-declaration/hoisted-const.ts.txt +11 -0
  232. package/src/cli/__tests__/fixtures/migrate-operation-declaration/imported-spread.ts.txt +11 -0
  233. package/src/cli/__tests__/fixtures/migrate-operation-declaration/inline-map.ts.txt +11 -0
  234. package/src/cli/__tests__/fixtures/migrate-operation-declaration/inline-spread-cast-tail.ts.txt +21 -0
  235. package/src/cli/__tests__/fixtures/migrate-operation-declaration/inline-spread-ekitan.ts.txt +11 -0
  236. package/src/cli/__tests__/fixtures/migrate-operation-declaration/inline-spread-override.ts.txt +14 -0
  237. package/src/cli/__tests__/fixtures/migrate-operation-declaration/missing-english-locale.ts.txt +7 -0
  238. package/src/cli/__tests__/fixtures/migrate-operation-declaration/no-safety.ts.txt +6 -0
  239. package/src/cli/__tests__/fixtures/migrate-operation-declaration/non-literal.ts.txt +7 -0
  240. package/src/cli/__tests__/fixtures/migrate-operation-declaration/redundant-approval.ts.txt +6 -0
  241. package/src/cli/__tests__/fixtures/migrate-operation-declaration/safety-conflict.ts.txt +7 -0
  242. package/src/cli/__tests__/fixtures/migrate-operation-declaration/stream.ts.txt +7 -0
  243. package/src/cli/__tests__/fixtures/migrate-operation-declaration/tool-router-spread.ts.txt +15 -0
  244. package/src/cli/__tests__/fixtures/migrate-operation-declaration/unparseable.ts.txt +4 -0
  245. package/src/cli/__tests__/fixtures/migrate-operation-declaration/verbatim-template.ts.txt +12 -0
  246. package/src/cli/commands.ts +33 -0
  247. package/src/cli/create.ts +50 -37
  248. package/src/cli/migrate-operation-declaration.ts +1654 -0
  249. package/src/cli/migrate-operation-shape.ts +184 -0
  250. package/src/cli/migrate-provider-shape.ts +772 -0
  251. package/src/cli/prompt-assets.ts +865 -0
  252. package/src/cli/templates/provider/AGENTS.md.tpl +17 -8
  253. package/src/cli/templates/provider/Dockerfile.tpl +1 -1
  254. package/src/cli/templates/provider/README.md.tpl +5 -5
  255. package/src/cli/templates/provider/index.ts.tpl +6 -3
  256. package/src/cli/templates/provider/operations/ping.ts.tpl +4 -1
  257. package/src/cli/templates/provider/provider.json.tpl +6 -0
  258. package/src/config/loader.ts +690 -163
  259. package/src/contract-serialization.ts +5 -7
  260. package/src/contract-types.ts +12 -5
  261. package/src/contract.ts +24 -10
  262. package/src/declaration-validation.ts +330 -0
  263. package/src/define.ts +1048 -131
  264. package/src/dev.ts +4 -1
  265. package/src/engine.ts +279 -0
  266. package/src/error-observability.ts +64 -0
  267. package/src/error-resolution.ts +127 -0
  268. package/src/errors.ts +68 -0
  269. package/src/fixture-sanitization.ts +264 -0
  270. package/src/health-scenario.ts +875 -0
  271. package/src/index.ts +220 -13
  272. package/src/lint.ts +698 -90
  273. package/src/native-address.ts +340 -0
  274. package/src/native-egress-policy.ts +358 -0
  275. package/src/observability.ts +51 -1
  276. package/src/provider.ts +161 -3
  277. package/src/runtime/auth-flow.ts +12 -0
  278. package/src/runtime/browser.ts +661 -63
  279. package/src/runtime/cache.ts +189 -14
  280. package/src/runtime/choice-wordlist.ts +145 -0
  281. package/src/runtime/choice.ts +631 -120
  282. package/src/runtime/chrome149-header-order.ts +330 -0
  283. package/src/runtime/env.ts +13 -0
  284. package/src/runtime/executor.ts +43 -31
  285. package/src/runtime/http.ts +641 -61
  286. package/src/runtime/insights.ts +2 -2
  287. package/src/runtime/instrumentation.ts +520 -15
  288. package/src/runtime/native-network-errors.ts +99 -0
  289. package/src/runtime/native-network.ts +1605 -0
  290. package/src/runtime/ocr.ts +523 -0
  291. package/src/runtime/otlp.ts +467 -21
  292. package/src/runtime/proxy-errors.ts +12 -4
  293. package/src/runtime/proxy-nodemaven.ts +221 -0
  294. package/src/runtime/proxy-telemetry.ts +244 -75
  295. package/src/runtime/redirects.ts +66 -0
  296. package/src/runtime/redis.ts +7 -2
  297. package/src/runtime/request-options.ts +680 -1
  298. package/src/runtime/resolver-config.ts +6 -0
  299. package/src/runtime/resolver-public.ts +23 -0
  300. package/src/runtime/resolver-shared.ts +33 -0
  301. package/src/runtime/resolver-vendors/bindings.ts +56 -0
  302. package/src/runtime/resolver-vendors/browser.ts +533 -0
  303. package/src/runtime/resolver-vendors/capsolver.ts +707 -0
  304. package/src/runtime/resolver-vendors/hosts.ts +38 -0
  305. package/src/runtime/resolver-vendors/twocaptcha.ts +540 -0
  306. package/src/runtime/resolver-vendors/types.ts +212 -0
  307. package/src/runtime/resolver.ts +1157 -0
  308. package/src/runtime/secrets.ts +64 -0
  309. package/src/runtime/state.ts +394 -77
  310. package/src/runtime/stealth-cookies.ts +132 -0
  311. package/src/runtime/stealth.ts +1280 -336
  312. package/src/runtime/stt.ts +1 -19
  313. package/src/runtime/timeout.ts +18 -0
  314. package/src/runtime/trace-config.ts +78 -0
  315. package/src/runtime/trace.ts +57 -17
  316. package/src/serve.ts +6 -1
  317. package/src/server/error-observability.ts +1 -0
  318. package/src/server/index.ts +34 -2
  319. package/src/server/self-test-input-tokens.ts +29 -14
  320. package/src/server/self-test.ts +191 -68
  321. package/src/server/serve-implementation.ts +3378 -0
  322. package/src/server/serve.ts +1 -1632
  323. package/src/server/trace-output.ts +129 -0
  324. package/src/server/types.ts +13 -1
  325. package/src/stateful/README.md +146 -0
  326. package/src/stateful/errors.ts +35 -0
  327. package/src/stateful/http-provider-event-emitter.ts +314 -0
  328. package/src/stateful/http-session-owner-registry.ts +306 -0
  329. package/src/stateful/index.ts +18 -0
  330. package/src/stateful/provider-event-delivery-failures.ts +80 -0
  331. package/src/stateful/provider-event-pipeline-metrics.ts +95 -0
  332. package/src/stateful/provider-event-pipeline.ts +61 -0
  333. package/src/stateful/provider-events.ts +462 -0
  334. package/src/stateful/session-key.ts +111 -0
  335. package/src/stateful/stateful-provider-adapter-context.ts +59 -0
  336. package/src/stateful/stateful-provider-adapter-metrics.ts +48 -0
  337. package/src/stateful/stateful-provider-adapter.ts +562 -0
  338. package/src/stateful/stateful-provider-observability.ts +261 -0
  339. package/src/stateful/stateful-provider-owner-forwarder.ts +287 -0
  340. package/src/stateful/stateful-provider-runtime-context.ts +92 -0
  341. package/src/stateful/stateful-provider-runtime-executor.ts +96 -0
  342. package/src/stateful/stateful-provider-session-routing.ts +546 -0
  343. package/src/stateful/stateful-provider-session-runtime.ts +403 -0
  344. package/src/stateful-signing.ts +46 -0
  345. package/src/stealth/profiles.ts +202 -231
  346. package/src/stream-evidence.ts +988 -0
  347. package/src/stream.ts +8 -1
  348. package/src/testing/index.ts +10 -1
  349. package/src/testing/run.ts +658 -15
  350. package/src/trace-sanitization.ts +63 -0
  351. package/src/types.ts +788 -174
  352. package/dist/cli/templates/provider/CLAUDE.md.tpl +0 -1
  353. package/src/cli/templates/provider/CLAUDE.md.tpl +0 -1
  354. /package/dist/cli/templates/provider/{skills → .agents/skills}/fixtures-and-recording/SKILL.md.tpl +0 -0
  355. /package/dist/cli/templates/provider/{skills → .agents/skills}/health-checks-and-fail-closed/SKILL.md.tpl +0 -0
  356. /package/dist/cli/templates/provider/{skills → .agents/skills}/normalization-standards/SKILL.md.tpl +0 -0
  357. /package/dist/cli/templates/provider/{skills → .agents/skills}/pagination-and-counts/SKILL.md.tpl +0 -0
  358. /package/dist/cli/templates/provider/{skills → .agents/skills}/upstream-contract-verification/SKILL.md.tpl +0 -0
  359. /package/dist/cli/templates/provider/{skills → .agents/skills}/upstream-notes/README.md.tpl +0 -0
  360. /package/src/cli/templates/provider/{skills → .agents/skills}/fixtures-and-recording/SKILL.md.tpl +0 -0
  361. /package/src/cli/templates/provider/{skills → .agents/skills}/health-checks-and-fail-closed/SKILL.md.tpl +0 -0
  362. /package/src/cli/templates/provider/{skills → .agents/skills}/normalization-standards/SKILL.md.tpl +0 -0
  363. /package/src/cli/templates/provider/{skills → .agents/skills}/pagination-and-counts/SKILL.md.tpl +0 -0
  364. /package/src/cli/templates/provider/{skills → .agents/skills}/upstream-contract-verification/SKILL.md.tpl +0 -0
  365. /package/src/cli/templates/provider/{skills → .agents/skills}/upstream-notes/README.md.tpl +0 -0
@@ -0,0 +1,785 @@
1
+ import { createHash } from "node:crypto";
2
+ import { isSensitiveFixtureKey, isSensitiveFixtureValue, REDACTED_FIXTURE_VALUE, sanitizeDiagnosticText, sanitizeFixtureString, sanitizeUrlForLogs, } from "./fixture-sanitization.js";
3
+ import { toJsonValue } from "./contract-json.js";
4
+ import { readableBytes, readableLines, readableTextChunks } from "./stream.js";
5
+ export const STREAM_PREVIEW_BYTES = 4096;
6
+ export const STREAM_FINALIZE_TIMEOUT_MS = 30_000;
7
+ const STREAM_EVIDENCE_HEADER_NAMES = [
8
+ "content-disposition",
9
+ "content-length",
10
+ "content-type",
11
+ ];
12
+ const STREAM_EVIDENCE_HEADERS = new Set(STREAM_EVIDENCE_HEADER_NAMES);
13
+ const STREAM_EVIDENCE_REDACTION_REASONS = [
14
+ "high-entropy-token",
15
+ "malformed-json",
16
+ "pem-private-key",
17
+ "sanitized-preview-too-large",
18
+ "sanitizer-output-invalid",
19
+ "sensitive-delimited-column",
20
+ "textual-xml",
21
+ "truncated-form",
22
+ "truncated-json",
23
+ "undecodable-text",
24
+ ];
25
+ const STREAM_EVIDENCE_REDACTION_REASON_SET = new Set(STREAM_EVIDENCE_REDACTION_REASONS);
26
+ export function parseStreamEvidenceRecord(value) {
27
+ if (value === null || typeof value !== "object" || Array.isArray(value)) {
28
+ throw new Error("Stream evidence must be an object.");
29
+ }
30
+ const record = value;
31
+ if (record.__apifuse_stream__ !== true) {
32
+ throw new Error('Stream evidence field "__apifuse_stream__" must be true.');
33
+ }
34
+ if (!Number.isInteger(record.status) ||
35
+ record.status < 100 ||
36
+ record.status > 599) {
37
+ throw new Error('Stream evidence field "status" must be an integer from 100 through 599.');
38
+ }
39
+ if (typeof record.ok !== "boolean") {
40
+ throw new Error('Stream evidence field "ok" must be a boolean.');
41
+ }
42
+ if (record.ok !== (record.status >= 200 && record.status < 300)) {
43
+ throw new Error('Stream evidence field "ok" is inconsistent with "status".');
44
+ }
45
+ if (!isStringRecord(record.headers)) {
46
+ throw new Error('Stream evidence field "headers" must contain only string values.');
47
+ }
48
+ for (const name of Object.keys(record.headers)) {
49
+ if (!STREAM_EVIDENCE_HEADERS.has(name)) {
50
+ throw new Error(`Stream evidence header "${sanitizeDiagnosticText(name)}" is not allowlisted.`);
51
+ }
52
+ }
53
+ if (typeof record.body_sha256 !== "string" || !/^[a-f0-9]{64}$/.test(record.body_sha256)) {
54
+ throw new Error('Stream evidence field "body_sha256" must be a lowercase SHA-256 hex digest.');
55
+ }
56
+ if (!Number.isSafeInteger(record.body_bytes) || record.body_bytes < 0) {
57
+ throw new Error('Stream evidence field "body_bytes" must be a non-negative safe integer.');
58
+ }
59
+ if (typeof record.body_preview_base64 !== "string") {
60
+ throw new Error('Stream evidence field "body_preview_base64" must be a string.');
61
+ }
62
+ if (record.request !== undefined)
63
+ validateStreamEvidenceRequest(record.request);
64
+ if (record.preview_sanitized !== undefined && record.preview_sanitized !== true) {
65
+ throw new Error('Stream evidence field "preview_sanitized" must be true when present.');
66
+ }
67
+ if (record.preview_redaction_reason !== undefined &&
68
+ !isStreamEvidenceRedactionReason(record.preview_redaction_reason)) {
69
+ throw new Error('Stream evidence field "preview_redaction_reason" is invalid.');
70
+ }
71
+ if (record.preview_redaction_reason !== undefined && record.preview_sanitized !== true) {
72
+ throw new Error('Stream evidence field "preview_redaction_reason" requires "preview_sanitized".');
73
+ }
74
+ const preview = Buffer.from(record.body_preview_base64, "base64");
75
+ if (preview.toString("base64") !== record.body_preview_base64) {
76
+ throw new Error('Stream evidence field "body_preview_base64" must be canonical base64.');
77
+ }
78
+ const expectedPreviewBytes = Math.min(record.body_bytes, STREAM_PREVIEW_BYTES);
79
+ if (preview.byteLength !== expectedPreviewBytes) {
80
+ throw new Error(`Stream evidence preview must decode to ${expectedPreviewBytes} bytes for body_bytes=${String(record.body_bytes)}.`);
81
+ }
82
+ if (record.preview_sanitized !== true &&
83
+ record.body_bytes <= STREAM_PREVIEW_BYTES &&
84
+ createHash("sha256").update(preview).digest("hex") !== record.body_sha256) {
85
+ throw new Error('Stream evidence field "body_sha256" must match a complete unsanitized preview.');
86
+ }
87
+ const headers = streamEvidenceHeaders(record.headers);
88
+ return {
89
+ __apifuse_stream__: true,
90
+ status: record.status,
91
+ ok: record.ok,
92
+ headers,
93
+ body_sha256: record.body_sha256,
94
+ body_bytes: record.body_bytes,
95
+ body_preview_base64: record.body_preview_base64,
96
+ ...(record.request === undefined ? {} : { request: streamEvidenceRequest(record.request) }),
97
+ ...(record.preview_sanitized === true ? { preview_sanitized: true } : {}),
98
+ ...(record.preview_redaction_reason === undefined
99
+ ? {}
100
+ : { preview_redaction_reason: record.preview_redaction_reason }),
101
+ };
102
+ }
103
+ export function isStreamEvidenceRecord(value) {
104
+ try {
105
+ parseStreamEvidenceRecord(value);
106
+ return true;
107
+ }
108
+ catch {
109
+ return false;
110
+ }
111
+ }
112
+ export function findStreamEvidenceRecord(value) {
113
+ return findStreamEvidenceRecords(value).at(-1);
114
+ }
115
+ /**
116
+ * Selects every stream captured by the latest recording invocation, in stream call order.
117
+ * Evidence without request ordinals uses the legacy latest-record-only behavior.
118
+ */
119
+ export function findStreamEvidenceRecords(value) {
120
+ if (isStreamCaptureEnvelope(value)) {
121
+ return parseStreamCaptureEnvelope(value).items.flatMap((item) => item.kind === "stream" ? [item.evidence] : []);
122
+ }
123
+ if (Array.isArray(value) && isStreamCaptureEnvelope(value.at(-1))) {
124
+ return parseStreamCaptureEnvelope(value.at(-1)).items.flatMap((item) => (item.kind === "stream" ? [item.evidence] : []));
125
+ }
126
+ const group = findLatestStreamCaptureGroup(value);
127
+ if (!group)
128
+ return [];
129
+ return group
130
+ .filter(hasStreamEvidenceMarker)
131
+ .map((candidate) => parseStreamEvidenceRecord(candidate));
132
+ }
133
+ /** Returns a tagged latest mixed response group used by evidence-only snapshot replay. */
134
+ export function findStreamCaptureGroup(value) {
135
+ if (isStreamCaptureEnvelope(value))
136
+ return parseStreamCaptureEnvelope(value);
137
+ if (Array.isArray(value)) {
138
+ const latest = value.at(-1);
139
+ if (isStreamCaptureEnvelope(latest))
140
+ return parseStreamCaptureEnvelope(latest);
141
+ if (Array.isArray(latest)) {
142
+ const nested = findStreamCaptureGroup(latest);
143
+ if (nested)
144
+ return nested;
145
+ }
146
+ }
147
+ const group = findLatestStreamCaptureGroup(value);
148
+ if (!group)
149
+ return undefined;
150
+ return {
151
+ items: group.map((item) => {
152
+ if (hasStreamEvidenceMarker(item)) {
153
+ return { kind: "stream", evidence: parseStreamEvidenceRecord(item) };
154
+ }
155
+ const json = toJsonValue(item);
156
+ if (json === undefined) {
157
+ throw new Error("Stream capture group contains a non-JSON response value.");
158
+ }
159
+ return { kind: "response", value: json };
160
+ }),
161
+ };
162
+ }
163
+ /** Creates a discriminated invocation envelope so ordinary arrays cannot mimic capture timelines. */
164
+ export function createStreamCaptureEnvelope(items) {
165
+ if (!items.some((item) => item.kind === "stream")) {
166
+ throw new Error("A stream capture envelope must contain at least one stream item.");
167
+ }
168
+ return { __apifuse_capture__: true, items };
169
+ }
170
+ function isStreamCaptureEnvelope(value) {
171
+ return (value !== null &&
172
+ typeof value === "object" &&
173
+ !Array.isArray(value) &&
174
+ value.__apifuse_capture__ === true);
175
+ }
176
+ function parseStreamCaptureEnvelope(value) {
177
+ if (!Array.isArray(value.items) || value.items.length === 0) {
178
+ throw new Error("Stream capture envelope items must be a non-empty array.");
179
+ }
180
+ const items = value.items.map((item) => {
181
+ if (item === null || typeof item !== "object" || Array.isArray(item)) {
182
+ throw new Error("Stream capture envelope item must be an object.");
183
+ }
184
+ if (item.kind === "stream") {
185
+ return { kind: "stream", evidence: parseStreamEvidenceRecord(item.evidence) };
186
+ }
187
+ if (item.kind === "response") {
188
+ const json = toJsonValue(item.value);
189
+ if (json === undefined) {
190
+ throw new Error("Stream capture response item must contain a JSON value.");
191
+ }
192
+ return { kind: "response", value: json };
193
+ }
194
+ throw new Error('Stream capture envelope item kind must be "stream" or "response".');
195
+ });
196
+ if (!items.some((item) => item.kind === "stream")) {
197
+ throw new Error("Stream capture envelope must contain at least one stream item.");
198
+ }
199
+ return { items };
200
+ }
201
+ export function isStreamEvidenceReplayResponse(response) {
202
+ const candidate = response;
203
+ return (candidate.evidence_only === true &&
204
+ typeof candidate.body_sha256 === "string" &&
205
+ Number.isSafeInteger(candidate.body_bytes));
206
+ }
207
+ export function captureStreamEvidence(response, options) {
208
+ const reader = response.body.getReader();
209
+ const hash = createHash("sha256");
210
+ const preview = new Uint8Array(STREAM_PREVIEW_BYTES);
211
+ let bodyBytes = 0;
212
+ let previewBytes = 0;
213
+ let evidence;
214
+ let terminalError;
215
+ let handlerPull;
216
+ let handlerCanceled = false;
217
+ let finalizing = false;
218
+ let evidencePromise;
219
+ function retainChunk(chunk) {
220
+ hash.update(chunk);
221
+ bodyBytes += chunk.byteLength;
222
+ if (previewBytes < STREAM_PREVIEW_BYTES) {
223
+ const retained = chunk.subarray(0, STREAM_PREVIEW_BYTES - previewBytes);
224
+ preview.set(retained, previewBytes);
225
+ previewBytes += retained.byteLength;
226
+ }
227
+ }
228
+ function finishCapture() {
229
+ evidence ??= createEvidenceRecord(response, hash.digest("hex"), bodyBytes, preview.subarray(0, previewBytes), options.sanitizeFixture, options.request);
230
+ return evidence;
231
+ }
232
+ function captureReadFailure(error) {
233
+ terminalError ??= streamCaptureError("read failed", options.requestUrl, response, bodyBytes, error);
234
+ return terminalError;
235
+ }
236
+ const body = new ReadableStream({
237
+ async pull(controller) {
238
+ if (finalizing)
239
+ return;
240
+ const currentPull = (async () => {
241
+ try {
242
+ const result = await reader.read();
243
+ if (terminalError)
244
+ throw terminalError;
245
+ if (result.done) {
246
+ finishCapture();
247
+ if (!handlerCanceled)
248
+ controller.close();
249
+ return;
250
+ }
251
+ retainChunk(result.value);
252
+ if (!handlerCanceled)
253
+ controller.enqueue(result.value);
254
+ }
255
+ catch (error) {
256
+ controller.error(captureReadFailure(error));
257
+ }
258
+ })();
259
+ handlerPull = currentPull;
260
+ try {
261
+ await currentPull;
262
+ }
263
+ finally {
264
+ if (handlerPull === currentPull)
265
+ handlerPull = undefined;
266
+ }
267
+ },
268
+ cancel() {
269
+ handlerCanceled = true;
270
+ // Keep the upstream reader alive so finalization can capture the complete body.
271
+ },
272
+ });
273
+ return {
274
+ response: streamResponseFromBody(response.status, response.ok, response.headers, body),
275
+ getEvidence() {
276
+ if (terminalError)
277
+ return Promise.reject(terminalError);
278
+ if (evidence)
279
+ return Promise.resolve(evidence);
280
+ if (evidencePromise)
281
+ return evidencePromise;
282
+ finalizing = true;
283
+ const drainPromise = (async () => {
284
+ await handlerPull;
285
+ if (terminalError)
286
+ throw terminalError;
287
+ if (evidence)
288
+ return evidence;
289
+ for (;;) {
290
+ let result;
291
+ try {
292
+ result = await reader.read();
293
+ }
294
+ catch (error) {
295
+ throw captureReadFailure(error);
296
+ }
297
+ if (terminalError)
298
+ throw terminalError;
299
+ if (result.done)
300
+ return finishCapture();
301
+ retainChunk(result.value);
302
+ }
303
+ })();
304
+ const timeoutMs = options.finalizeTimeoutMs ?? STREAM_FINALIZE_TIMEOUT_MS;
305
+ evidencePromise = withFinalizationTimeout(drainPromise, timeoutMs, () => {
306
+ const timeoutError = streamCaptureError(`finalization timed out after ${timeoutMs}ms`, options.requestUrl, response, bodyBytes);
307
+ terminalError ??= timeoutError;
308
+ void reader.cancel(timeoutError).catch((cancelError) => {
309
+ timeoutError.cause = cancelError;
310
+ });
311
+ return timeoutError;
312
+ });
313
+ return evidencePromise;
314
+ },
315
+ };
316
+ }
317
+ export function replayStreamEvidence(evidence) {
318
+ const validEvidence = parseStreamEvidenceRecord(evidence);
319
+ const preview = new Uint8Array(Buffer.from(validEvidence.body_preview_base64, "base64"));
320
+ const replayHeaders = Object.fromEntries(Object.entries(validEvidence.headers).filter(([name]) => name !== "content-length"));
321
+ return {
322
+ ...streamResponseFromBytes(validEvidence.status, validEvidence.ok, replayHeaders, preview),
323
+ evidence_only: true,
324
+ body_sha256: validEvidence.body_sha256,
325
+ body_bytes: validEvidence.body_bytes,
326
+ ...(validEvidence.preview_sanitized ? { preview_sanitized: true } : {}),
327
+ ...(validEvidence.preview_redaction_reason
328
+ ? { preview_redaction_reason: validEvidence.preview_redaction_reason }
329
+ : {}),
330
+ };
331
+ }
332
+ function createEvidenceRecord(response, bodySha256, bodyBytes, preview, sanitizeFixture, request) {
333
+ const contentType = headerValue(response.headers, "content-type");
334
+ const sanitized = sanitizeFixture
335
+ ? sanitizePreview(preview, bodyBytes, contentType, sanitizeFixture)
336
+ : undefined;
337
+ const safePreview = sanitized?.preview ?? preview;
338
+ const previewChanged = !bytesEqual(safePreview, preview);
339
+ const previewSanitized = previewChanged || sanitized?.redactionReason !== undefined;
340
+ return {
341
+ __apifuse_stream__: true,
342
+ status: response.status,
343
+ ok: response.ok,
344
+ headers: evidenceHeaders(response.headers, sanitizeFixture !== undefined),
345
+ body_sha256: bodySha256,
346
+ body_bytes: bodyBytes,
347
+ body_preview_base64: Buffer.from(safePreview).toString("base64"),
348
+ ...(request ? { request } : {}),
349
+ ...(previewSanitized ? { preview_sanitized: true } : {}),
350
+ ...(sanitized?.redactionReason ? { preview_redaction_reason: sanitized.redactionReason } : {}),
351
+ };
352
+ }
353
+ function sanitizePreview(preview, bodyBytes, contentType, sanitizeFixture) {
354
+ let text;
355
+ try {
356
+ text = new TextDecoder("utf-8", { fatal: true }).decode(preview);
357
+ }
358
+ catch {
359
+ return sanitizePartiallyDecodablePreview(preview);
360
+ }
361
+ const declaredTextual = isTextualContentType(contentType);
362
+ const sniffedTextual = looksLikeJson(text) || looksLikeXml(text) || looksLikeText(text);
363
+ const sanitizedPrimitive = sanitizeFixtureString(text);
364
+ const containsTextualSecret = sanitizedPrimitive !== text;
365
+ if (hasKnownBinaryMagic(preview) && !containsTextualSecret)
366
+ return undefined;
367
+ if (!declaredTextual && !sniffedTextual && !containsTextualSecret)
368
+ return undefined;
369
+ const mediaType = contentType?.split(";", 1)[0]?.trim().toLowerCase() ?? "";
370
+ if (looksLikePemPrivateKey(text)) {
371
+ return redactedSanitizedPreview(preview.byteLength, "pem-private-key");
372
+ }
373
+ if (mediaType.includes("xml") || looksLikeXml(text)) {
374
+ return redactedSanitizedPreview(preview.byteLength, "textual-xml");
375
+ }
376
+ let sanitizedText;
377
+ const jsonPreview = isJsonContentType(mediaType) || looksLikeJson(text);
378
+ if (jsonPreview) {
379
+ if (bodyBytes > preview.byteLength) {
380
+ return redactedSanitizedPreview(preview.byteLength, "truncated-json");
381
+ }
382
+ try {
383
+ sanitizedText = JSON.stringify(sanitizeFixture(JSON.parse(text)));
384
+ }
385
+ catch (error) {
386
+ if (!(error instanceof SyntaxError)) {
387
+ throw new Error("Stream JSON preview sanitizer failed.", { cause: error });
388
+ }
389
+ return redactedSanitizedPreview(preview.byteLength, "malformed-json", true);
390
+ }
391
+ }
392
+ else if (mediaType === "application/x-www-form-urlencoded") {
393
+ if (bodyBytes > preview.byteLength) {
394
+ return redactedSanitizedPreview(preview.byteLength, "truncated-form");
395
+ }
396
+ const form = new URLSearchParams(text);
397
+ let sanitized;
398
+ try {
399
+ sanitized = sanitizeFixture(Object.fromEntries(form.entries()));
400
+ }
401
+ catch (error) {
402
+ throw new Error("Stream form preview sanitizer failed.", { cause: error });
403
+ }
404
+ if (!isStringRecord(sanitized)) {
405
+ return redactedSanitizedPreview(preview.byteLength, "sanitizer-output-invalid");
406
+ }
407
+ sanitizedText = new URLSearchParams(sanitized).toString();
408
+ }
409
+ else {
410
+ if (hasSensitiveDelimitedHeader(text)) {
411
+ return redactedSanitizedPreview(preview.byteLength, "sensitive-delimited-column");
412
+ }
413
+ const tokenRedaction = redactHighEntropyTokens(text);
414
+ sanitizedText = sanitizeFixtureString(tokenRedaction.text);
415
+ const sanitizedBytes = new TextEncoder().encode(sanitizedText);
416
+ if (sanitizedBytes.byteLength > preview.byteLength) {
417
+ return redactedSanitizedPreview(preview.byteLength, "sanitized-preview-too-large");
418
+ }
419
+ return {
420
+ preview: fitSanitizedPreview(sanitizedBytes, preview.byteLength),
421
+ ...(tokenRedaction.redacted ? { redactionReason: "high-entropy-token" } : {}),
422
+ };
423
+ }
424
+ const sanitizedBytes = new TextEncoder().encode(sanitizedText);
425
+ if (sanitizedBytes.byteLength > preview.byteLength) {
426
+ return redactedSanitizedPreview(preview.byteLength, "sanitized-preview-too-large", jsonPreview);
427
+ }
428
+ return { preview: fitSanitizedPreview(sanitizedBytes, preview.byteLength) };
429
+ }
430
+ function sanitizePartiallyDecodablePreview(preview) {
431
+ const text = new TextDecoder("utf-8").decode(preview);
432
+ const containsTextualSecret = sanitizeFixtureString(text) !== text;
433
+ if (!containsTextualSecret) {
434
+ if (hasKnownBinaryMagic(preview))
435
+ return undefined;
436
+ // Undecodable non-binary data cannot be proven safe. Declared text in particular must fail closed.
437
+ return redactedSanitizedPreview(preview.byteLength, "undecodable-text");
438
+ }
439
+ let sanitized = sanitizeDecodableWindows(preview);
440
+ const retainedText = new TextDecoder("utf-8").decode(sanitized);
441
+ if (sanitizeFixtureString(retainedText) !== retainedText) {
442
+ sanitized = redactedPreview(preview.byteLength);
443
+ }
444
+ return {
445
+ preview: sanitized,
446
+ ...(looksLikePemPrivateKey(text)
447
+ ? { redactionReason: "pem-private-key" }
448
+ : redactHighEntropyTokens(text).redacted
449
+ ? { redactionReason: "high-entropy-token" }
450
+ : {}),
451
+ };
452
+ }
453
+ function sanitizeDecodableWindows(preview) {
454
+ const sanitized = preview.slice();
455
+ let start = 0;
456
+ for (let index = 0; index <= preview.byteLength; index += 1) {
457
+ const byte = preview[index];
458
+ if (index < preview.byteLength && byte !== undefined && byte < 0x80)
459
+ continue;
460
+ if (index > start) {
461
+ const window = preview.subarray(start, index);
462
+ const text = new TextDecoder("utf-8", { fatal: true }).decode(window);
463
+ const safeText = sanitizeFixtureString(text);
464
+ if (safeText !== text) {
465
+ sanitized.set(fitSanitizedPreview(new TextEncoder().encode(safeText), window.byteLength), start);
466
+ }
467
+ }
468
+ start = index + 1;
469
+ }
470
+ return sanitized;
471
+ }
472
+ function looksLikePemPrivateKey(text) {
473
+ return /-----BEGIN (?:[A-Z0-9 ]+ )?PRIVATE KEY-----/.test(text);
474
+ }
475
+ function redactHighEntropyTokens(text) {
476
+ let redacted = false;
477
+ const sanitized = text.replace(/[A-Za-z0-9_+/=.:~-]{24,}/g, (candidate) => {
478
+ if (!isSensitiveFixtureValue(candidate))
479
+ return candidate;
480
+ redacted = true;
481
+ return REDACTED_FIXTURE_VALUE;
482
+ });
483
+ return { text: sanitized, redacted };
484
+ }
485
+ function hasSensitiveDelimitedHeader(text) {
486
+ const lines = text.split(/\r?\n/);
487
+ const firstLine = lines[0]?.trim();
488
+ if (!firstLine)
489
+ return false;
490
+ if (lines.length > 1 && isSensitiveFixtureKey(firstLine))
491
+ return true;
492
+ for (const delimiter of [",", "\t", ";", "|"]) {
493
+ const columns = splitDelimitedRow(firstLine, delimiter);
494
+ if (columns.length > 1 && columns.some((column) => isSensitiveFixtureKey(column.trim()))) {
495
+ return true;
496
+ }
497
+ }
498
+ return false;
499
+ }
500
+ function splitDelimitedRow(row, delimiter) {
501
+ const values = [];
502
+ let value = "";
503
+ let quote = "";
504
+ for (let index = 0; index < row.length; index += 1) {
505
+ const character = row.charAt(index);
506
+ if (quote) {
507
+ if (character === quote && row[index + 1] === quote) {
508
+ value += quote;
509
+ index += 1;
510
+ }
511
+ else if (character === quote) {
512
+ quote = "";
513
+ }
514
+ else {
515
+ value += character;
516
+ }
517
+ }
518
+ else if ((character === '"' || character === "'") && value.length === 0) {
519
+ quote = character;
520
+ }
521
+ else if (character === delimiter) {
522
+ values.push(value);
523
+ value = "";
524
+ }
525
+ else {
526
+ value += character;
527
+ }
528
+ }
529
+ values.push(value);
530
+ return values;
531
+ }
532
+ function fitSanitizedPreview(sanitized, targetBytes) {
533
+ if (sanitized.byteLength > targetBytes)
534
+ return validRedactedPreview(targetBytes);
535
+ if (sanitized.byteLength === targetBytes)
536
+ return sanitized;
537
+ const fitted = new Uint8Array(targetBytes);
538
+ fitted.fill(0x20);
539
+ fitted.set(sanitized);
540
+ return fitted;
541
+ }
542
+ function redactedPreview(byteLength) {
543
+ const redacted = new Uint8Array(byteLength);
544
+ redacted.fill(0x20);
545
+ redacted.set(new TextEncoder().encode(REDACTED_FIXTURE_VALUE).subarray(0, byteLength));
546
+ return redacted;
547
+ }
548
+ function validRedactedPreview(byteLength) {
549
+ if (byteLength >= 2) {
550
+ const redacted = new Uint8Array(byteLength);
551
+ redacted.fill(0x20);
552
+ redacted.set(new TextEncoder().encode("{}"));
553
+ return redacted;
554
+ }
555
+ return redactedPreview(byteLength);
556
+ }
557
+ function redactedSanitizedPreview(byteLength, redactionReason, validJson = false) {
558
+ return {
559
+ preview: validJson ? validRedactedPreview(byteLength) : redactedPreview(byteLength),
560
+ redactionReason,
561
+ };
562
+ }
563
+ function isTextualContentType(contentType) {
564
+ const mediaType = contentType?.split(";", 1)[0]?.trim().toLowerCase() ?? "";
565
+ return (mediaType.startsWith("text/") ||
566
+ isJsonContentType(mediaType) ||
567
+ mediaType === "application/xml" ||
568
+ mediaType.endsWith("+xml") ||
569
+ mediaType === "application/x-www-form-urlencoded");
570
+ }
571
+ function isJsonContentType(mediaType) {
572
+ return mediaType === "application/json" || mediaType.endsWith("+json");
573
+ }
574
+ function looksLikeJson(text) {
575
+ const trimmed = text.trimStart();
576
+ return trimmed.startsWith("{") || trimmed.startsWith("[");
577
+ }
578
+ function looksLikeXml(text) {
579
+ return /^\s*<\??[a-z_][\w:.-]*/i.test(text);
580
+ }
581
+ function looksLikeText(text) {
582
+ if (text.length === 0 || text.includes("\0"))
583
+ return false;
584
+ let readable = 0;
585
+ let suspicious = 0;
586
+ for (const character of text) {
587
+ const codePoint = character.codePointAt(0) ?? 0;
588
+ if (character === "\uFFFD" || (codePoint < 0x20 && !"\t\n\r".includes(character))) {
589
+ suspicious += 1;
590
+ }
591
+ else {
592
+ readable += 1;
593
+ }
594
+ }
595
+ return readable > 0 && suspicious / (readable + suspicious) < 0.02;
596
+ }
597
+ function hasKnownBinaryMagic(bytes) {
598
+ const signatures = [
599
+ [0x89, 0x50, 0x4e, 0x47], // PNG
600
+ [0xff, 0xd8, 0xff], // JPEG
601
+ [0x47, 0x49, 0x46, 0x38], // GIF
602
+ [0x25, 0x50, 0x44, 0x46], // PDF
603
+ [0x50, 0x4b, 0x03, 0x04], // ZIP and ZIP-based formats
604
+ [0x1f, 0x8b], // gzip
605
+ [0x7f, 0x45, 0x4c, 0x46], // ELF
606
+ [0x52, 0x61, 0x72, 0x21], // RAR
607
+ [0x37, 0x7a, 0xbc, 0xaf, 0x27, 0x1c], // 7z
608
+ ];
609
+ return signatures.some((signature) => bytes.byteLength >= signature.length &&
610
+ signature.every((byte, index) => bytes[index] === byte));
611
+ }
612
+ function findLatestStreamCaptureGroup(value) {
613
+ if (hasStreamEvidenceMarker(value)) {
614
+ parseStreamEvidenceRecord(value);
615
+ return [value];
616
+ }
617
+ if (!Array.isArray(value))
618
+ return undefined;
619
+ let rightmostDirectMarker = -1;
620
+ for (let index = value.length - 1; index >= 0; index -= 1) {
621
+ const candidate = value[index];
622
+ if (Array.isArray(candidate)) {
623
+ const nested = findLatestStreamCaptureGroup(candidate);
624
+ if (nested)
625
+ return nested;
626
+ }
627
+ if (hasStreamEvidenceMarker(candidate)) {
628
+ rightmostDirectMarker = index;
629
+ break;
630
+ }
631
+ }
632
+ if (rightmostDirectMarker < 0)
633
+ return undefined;
634
+ const directRecords = value
635
+ .map((candidate, index) => hasStreamEvidenceMarker(candidate)
636
+ ? { index, record: parseStreamEvidenceRecord(candidate) }
637
+ : undefined)
638
+ .filter((candidate) => candidate !== undefined);
639
+ if (directRecords.some(({ record }) => record.request === undefined)) {
640
+ return [value[rightmostDirectMarker]];
641
+ }
642
+ let startIndex = directRecords[0]?.index ?? 0;
643
+ let previous = directRecords[0];
644
+ if (!previous)
645
+ return undefined;
646
+ for (const current of directRecords.slice(1)) {
647
+ const previousRequest = previous.record.request;
648
+ const currentRequest = current.record.request;
649
+ if (!previousRequest || !currentRequest) {
650
+ throw new Error("Stream capture grouping invariant violated: request provenance is missing.");
651
+ }
652
+ if (currentRequest.ordinal <= previousRequest.ordinal) {
653
+ startIndex = current.index;
654
+ }
655
+ previous = current;
656
+ }
657
+ return value.slice(startIndex);
658
+ }
659
+ export function hasStreamEvidenceMarker(value) {
660
+ if (value === null || typeof value !== "object" || Array.isArray(value))
661
+ return false;
662
+ const record = value;
663
+ return (record.__apifuse_stream__ === true &&
664
+ ("body_sha256" in record ||
665
+ "body_bytes" in record ||
666
+ "body_preview_base64" in record ||
667
+ "headers" in record));
668
+ }
669
+ function isStringRecord(value) {
670
+ return (value !== null &&
671
+ typeof value === "object" &&
672
+ !Array.isArray(value) &&
673
+ Object.values(value).every((entry) => typeof entry === "string"));
674
+ }
675
+ function validateStreamEvidenceRequest(value) {
676
+ if (value === null || typeof value !== "object" || Array.isArray(value)) {
677
+ throw new Error('Stream evidence field "request" must be an object.');
678
+ }
679
+ const request = value;
680
+ if (!Number.isSafeInteger(request.ordinal) || request.ordinal < 1) {
681
+ throw new Error('Stream evidence request field "ordinal" must be a positive safe integer.');
682
+ }
683
+ if (typeof request.method !== "string" || !/^[A-Z]+$/.test(request.method)) {
684
+ throw new Error('Stream evidence request field "method" must be an uppercase HTTP method.');
685
+ }
686
+ if (typeof request.path !== "string" ||
687
+ !request.path.startsWith("/") ||
688
+ request.path.includes("?") ||
689
+ request.path.includes("#")) {
690
+ throw new Error('Stream evidence request field "path" must be a query-free absolute path.');
691
+ }
692
+ }
693
+ function streamEvidenceRequest(value) {
694
+ validateStreamEvidenceRequest(value);
695
+ const request = value;
696
+ return {
697
+ ordinal: request.ordinal,
698
+ method: request.method,
699
+ path: request.path,
700
+ };
701
+ }
702
+ function isStreamEvidenceRedactionReason(value) {
703
+ return typeof value === "string" && STREAM_EVIDENCE_REDACTION_REASON_SET.has(value);
704
+ }
705
+ function streamEvidenceHeaders(headers) {
706
+ return selectEvidenceHeaders(Object.entries(headers));
707
+ }
708
+ function evidenceHeaders(headers, sanitize) {
709
+ return selectEvidenceHeaders(Object.entries(headers).map(([name, value]) => {
710
+ const normalizedName = name.toLowerCase();
711
+ return [
712
+ normalizedName,
713
+ sanitize ? sanitizeEvidenceHeaderValue(normalizedName, value) : value,
714
+ ];
715
+ }));
716
+ }
717
+ function sanitizeEvidenceHeaderValue(name, value) {
718
+ if (name === "content-disposition")
719
+ return sanitizeFixtureString(value);
720
+ if (name !== "content-type")
721
+ return value;
722
+ const [mediaType = "", ...parameters] = value.split(";");
723
+ if (parameters.length === 0)
724
+ return mediaType;
725
+ return [mediaType, ...parameters.map((parameter) => sanitizeFixtureString(parameter))].join(";");
726
+ }
727
+ function selectEvidenceHeaders(entries) {
728
+ const source = Object.fromEntries(entries);
729
+ return Object.fromEntries(STREAM_EVIDENCE_HEADER_NAMES.flatMap((name) => {
730
+ const value = source[name];
731
+ return value === undefined ? [] : [[name, value]];
732
+ }));
733
+ }
734
+ function bytesEqual(left, right) {
735
+ if (left.byteLength !== right.byteLength)
736
+ return false;
737
+ return left.every((byte, index) => byte === right[index]);
738
+ }
739
+ function withFinalizationTimeout(promise, timeoutMs, onTimeout) {
740
+ if (!Number.isFinite(timeoutMs) || timeoutMs <= 0)
741
+ return promise;
742
+ return new Promise((resolve, reject) => {
743
+ const timeout = setTimeout(() => {
744
+ reject(onTimeout());
745
+ }, timeoutMs);
746
+ promise.then((value) => {
747
+ clearTimeout(timeout);
748
+ resolve(value);
749
+ }, (error) => {
750
+ clearTimeout(timeout);
751
+ reject(error);
752
+ });
753
+ });
754
+ }
755
+ function headerValue(headers, targetName) {
756
+ for (const [name, value] of Object.entries(headers)) {
757
+ if (name.toLowerCase() === targetName)
758
+ return value;
759
+ }
760
+ return undefined;
761
+ }
762
+ function streamCaptureError(phase, requestUrl, response, bodyBytes, cause) {
763
+ return new Error(`Stream capture ${phase}: url=${sanitizeUrlForLogs(requestUrl)} status=${response.status} bytes_read=${bodyBytes}`, cause === undefined ? undefined : { cause });
764
+ }
765
+ function streamResponseFromBytes(status, ok, headers, bytes) {
766
+ const body = new ReadableStream({
767
+ start(controller) {
768
+ if (bytes.byteLength > 0)
769
+ controller.enqueue(bytes.slice());
770
+ controller.close();
771
+ },
772
+ });
773
+ return streamResponseFromBody(status, ok, headers, body);
774
+ }
775
+ function streamResponseFromBody(status, ok, headers, body) {
776
+ return {
777
+ status,
778
+ ok,
779
+ headers: { ...headers },
780
+ body,
781
+ bytes: () => readableBytes(body),
782
+ textChunks: () => readableTextChunks(body),
783
+ lines: () => readableLines(body),
784
+ };
785
+ }