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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (368) hide show
  1. package/AUTHORING.md +537 -33
  2. package/CHANGELOG.md +205 -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 +1249 -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-hoisted-array-spread.ts.txt +15 -0
  228. package/src/cli/__tests__/fixtures/migrate-operation-declaration/examples-imported-array-spread.ts.txt +9 -0
  229. package/src/cli/__tests__/fixtures/migrate-operation-declaration/examples-map.ts.txt +5 -0
  230. package/src/cli/__tests__/fixtures/migrate-operation-declaration/examples-mixed-array-spread.ts.txt +24 -0
  231. package/src/cli/__tests__/fixtures/migrate-operation-declaration/examples-operation.ts.txt +16 -0
  232. package/src/cli/__tests__/fixtures/migrate-operation-declaration/factory-map.ts.txt +3 -0
  233. package/src/cli/__tests__/fixtures/migrate-operation-declaration/hoist-all.ts.txt +31 -0
  234. package/src/cli/__tests__/fixtures/migrate-operation-declaration/hoisted-const.ts.txt +11 -0
  235. package/src/cli/__tests__/fixtures/migrate-operation-declaration/imported-spread.ts.txt +11 -0
  236. package/src/cli/__tests__/fixtures/migrate-operation-declaration/inline-map.ts.txt +11 -0
  237. package/src/cli/__tests__/fixtures/migrate-operation-declaration/inline-spread-cast-tail.ts.txt +21 -0
  238. package/src/cli/__tests__/fixtures/migrate-operation-declaration/inline-spread-ekitan.ts.txt +11 -0
  239. package/src/cli/__tests__/fixtures/migrate-operation-declaration/inline-spread-override.ts.txt +14 -0
  240. package/src/cli/__tests__/fixtures/migrate-operation-declaration/missing-english-locale.ts.txt +7 -0
  241. package/src/cli/__tests__/fixtures/migrate-operation-declaration/no-safety.ts.txt +6 -0
  242. package/src/cli/__tests__/fixtures/migrate-operation-declaration/non-literal.ts.txt +7 -0
  243. package/src/cli/__tests__/fixtures/migrate-operation-declaration/redundant-approval.ts.txt +6 -0
  244. package/src/cli/__tests__/fixtures/migrate-operation-declaration/safety-conflict.ts.txt +7 -0
  245. package/src/cli/__tests__/fixtures/migrate-operation-declaration/stream.ts.txt +7 -0
  246. package/src/cli/__tests__/fixtures/migrate-operation-declaration/tool-router-spread.ts.txt +15 -0
  247. package/src/cli/__tests__/fixtures/migrate-operation-declaration/unparseable.ts.txt +4 -0
  248. package/src/cli/__tests__/fixtures/migrate-operation-declaration/verbatim-template.ts.txt +12 -0
  249. package/src/cli/commands.ts +33 -0
  250. package/src/cli/create.ts +50 -37
  251. package/src/cli/migrate-operation-declaration.ts +1751 -0
  252. package/src/cli/migrate-operation-shape.ts +184 -0
  253. package/src/cli/migrate-provider-shape.ts +772 -0
  254. package/src/cli/prompt-assets.ts +865 -0
  255. package/src/cli/templates/provider/AGENTS.md.tpl +17 -8
  256. package/src/cli/templates/provider/Dockerfile.tpl +1 -1
  257. package/src/cli/templates/provider/README.md.tpl +5 -5
  258. package/src/cli/templates/provider/index.ts.tpl +6 -3
  259. package/src/cli/templates/provider/operations/ping.ts.tpl +4 -1
  260. package/src/cli/templates/provider/provider.json.tpl +6 -0
  261. package/src/config/loader.ts +690 -163
  262. package/src/contract-serialization.ts +5 -7
  263. package/src/contract-types.ts +12 -5
  264. package/src/contract.ts +24 -10
  265. package/src/declaration-validation.ts +330 -0
  266. package/src/define.ts +1048 -131
  267. package/src/dev.ts +4 -1
  268. package/src/engine.ts +279 -0
  269. package/src/error-observability.ts +64 -0
  270. package/src/error-resolution.ts +127 -0
  271. package/src/errors.ts +68 -0
  272. package/src/fixture-sanitization.ts +264 -0
  273. package/src/health-scenario.ts +875 -0
  274. package/src/index.ts +220 -13
  275. package/src/lint.ts +698 -90
  276. package/src/native-address.ts +340 -0
  277. package/src/native-egress-policy.ts +358 -0
  278. package/src/observability.ts +51 -1
  279. package/src/provider.ts +161 -3
  280. package/src/runtime/auth-flow.ts +12 -0
  281. package/src/runtime/browser.ts +661 -63
  282. package/src/runtime/cache.ts +189 -14
  283. package/src/runtime/choice-wordlist.ts +145 -0
  284. package/src/runtime/choice.ts +631 -120
  285. package/src/runtime/chrome149-header-order.ts +330 -0
  286. package/src/runtime/env.ts +13 -0
  287. package/src/runtime/executor.ts +43 -31
  288. package/src/runtime/http.ts +641 -61
  289. package/src/runtime/insights.ts +2 -2
  290. package/src/runtime/instrumentation.ts +520 -15
  291. package/src/runtime/native-network-errors.ts +99 -0
  292. package/src/runtime/native-network.ts +1605 -0
  293. package/src/runtime/ocr.ts +523 -0
  294. package/src/runtime/otlp.ts +467 -21
  295. package/src/runtime/proxy-errors.ts +12 -4
  296. package/src/runtime/proxy-nodemaven.ts +221 -0
  297. package/src/runtime/proxy-telemetry.ts +244 -75
  298. package/src/runtime/redirects.ts +66 -0
  299. package/src/runtime/redis.ts +7 -2
  300. package/src/runtime/request-options.ts +680 -1
  301. package/src/runtime/resolver-config.ts +6 -0
  302. package/src/runtime/resolver-public.ts +23 -0
  303. package/src/runtime/resolver-shared.ts +33 -0
  304. package/src/runtime/resolver-vendors/bindings.ts +56 -0
  305. package/src/runtime/resolver-vendors/browser.ts +533 -0
  306. package/src/runtime/resolver-vendors/capsolver.ts +707 -0
  307. package/src/runtime/resolver-vendors/hosts.ts +38 -0
  308. package/src/runtime/resolver-vendors/twocaptcha.ts +540 -0
  309. package/src/runtime/resolver-vendors/types.ts +212 -0
  310. package/src/runtime/resolver.ts +1157 -0
  311. package/src/runtime/secrets.ts +64 -0
  312. package/src/runtime/state.ts +394 -77
  313. package/src/runtime/stealth-cookies.ts +132 -0
  314. package/src/runtime/stealth.ts +1280 -336
  315. package/src/runtime/stt.ts +1 -19
  316. package/src/runtime/timeout.ts +18 -0
  317. package/src/runtime/trace-config.ts +78 -0
  318. package/src/runtime/trace.ts +57 -17
  319. package/src/serve.ts +6 -1
  320. package/src/server/error-observability.ts +1 -0
  321. package/src/server/index.ts +34 -2
  322. package/src/server/self-test-input-tokens.ts +29 -14
  323. package/src/server/self-test.ts +191 -68
  324. package/src/server/serve-implementation.ts +3378 -0
  325. package/src/server/serve.ts +1 -1632
  326. package/src/server/trace-output.ts +129 -0
  327. package/src/server/types.ts +13 -1
  328. package/src/stateful/README.md +146 -0
  329. package/src/stateful/errors.ts +35 -0
  330. package/src/stateful/http-provider-event-emitter.ts +314 -0
  331. package/src/stateful/http-session-owner-registry.ts +306 -0
  332. package/src/stateful/index.ts +18 -0
  333. package/src/stateful/provider-event-delivery-failures.ts +80 -0
  334. package/src/stateful/provider-event-pipeline-metrics.ts +95 -0
  335. package/src/stateful/provider-event-pipeline.ts +61 -0
  336. package/src/stateful/provider-events.ts +462 -0
  337. package/src/stateful/session-key.ts +111 -0
  338. package/src/stateful/stateful-provider-adapter-context.ts +59 -0
  339. package/src/stateful/stateful-provider-adapter-metrics.ts +48 -0
  340. package/src/stateful/stateful-provider-adapter.ts +562 -0
  341. package/src/stateful/stateful-provider-observability.ts +261 -0
  342. package/src/stateful/stateful-provider-owner-forwarder.ts +287 -0
  343. package/src/stateful/stateful-provider-runtime-context.ts +92 -0
  344. package/src/stateful/stateful-provider-runtime-executor.ts +96 -0
  345. package/src/stateful/stateful-provider-session-routing.ts +546 -0
  346. package/src/stateful/stateful-provider-session-runtime.ts +403 -0
  347. package/src/stateful-signing.ts +46 -0
  348. package/src/stealth/profiles.ts +202 -231
  349. package/src/stream-evidence.ts +988 -0
  350. package/src/stream.ts +8 -1
  351. package/src/testing/index.ts +10 -1
  352. package/src/testing/run.ts +658 -15
  353. package/src/trace-sanitization.ts +63 -0
  354. package/src/types.ts +788 -174
  355. package/dist/cli/templates/provider/CLAUDE.md.tpl +0 -1
  356. package/src/cli/templates/provider/CLAUDE.md.tpl +0 -1
  357. /package/dist/cli/templates/provider/{skills → .agents/skills}/fixtures-and-recording/SKILL.md.tpl +0 -0
  358. /package/dist/cli/templates/provider/{skills → .agents/skills}/health-checks-and-fail-closed/SKILL.md.tpl +0 -0
  359. /package/dist/cli/templates/provider/{skills → .agents/skills}/normalization-standards/SKILL.md.tpl +0 -0
  360. /package/dist/cli/templates/provider/{skills → .agents/skills}/pagination-and-counts/SKILL.md.tpl +0 -0
  361. /package/dist/cli/templates/provider/{skills → .agents/skills}/upstream-contract-verification/SKILL.md.tpl +0 -0
  362. /package/dist/cli/templates/provider/{skills → .agents/skills}/upstream-notes/README.md.tpl +0 -0
  363. /package/src/cli/templates/provider/{skills → .agents/skills}/fixtures-and-recording/SKILL.md.tpl +0 -0
  364. /package/src/cli/templates/provider/{skills → .agents/skills}/health-checks-and-fail-closed/SKILL.md.tpl +0 -0
  365. /package/src/cli/templates/provider/{skills → .agents/skills}/normalization-standards/SKILL.md.tpl +0 -0
  366. /package/src/cli/templates/provider/{skills → .agents/skills}/pagination-and-counts/SKILL.md.tpl +0 -0
  367. /package/src/cli/templates/provider/{skills → .agents/skills}/upstream-contract-verification/SKILL.md.tpl +0 -0
  368. /package/src/cli/templates/provider/{skills → .agents/skills}/upstream-notes/README.md.tpl +0 -0
package/dist/stream.js CHANGED
@@ -41,16 +41,22 @@ function sseFieldValue(value, field) {
41
41
  }
42
42
  export async function* readableBytes(body) {
43
43
  const reader = body.getReader();
44
+ let completed = false;
44
45
  try {
45
46
  for (;;) {
46
47
  const { value, done } = await reader.read();
47
- if (done)
48
+ if (done) {
49
+ completed = true;
48
50
  return;
51
+ }
49
52
  if (value)
50
53
  yield value;
51
54
  }
52
55
  }
53
56
  finally {
57
+ if (!completed) {
58
+ await reader.cancel().catch(() => undefined);
59
+ }
54
60
  reader.releaseLock();
55
61
  }
56
62
  }
@@ -1,2 +1,3 @@
1
1
  export { describeTransform, snapshotTransform, toMatchShape } from "./helpers.js";
2
- export { runStandardTests } from "./run.js";
2
+ export { resetProviderCacheForTests } from "../runtime/cache.js";
3
+ export { runStandardTests, type StandardTestsManifest, type StandardTestsOptions, type StandardTestsResult, type StandardTestsUpstreamCall, type StandardTestsUpstreamResponse, type StandardTestsUpstreamStub, } from "./run.js";
@@ -1,2 +1,3 @@
1
1
  export { describeTransform, snapshotTransform, toMatchShape } from "./helpers.js";
2
- export { runStandardTests } from "./run.js";
2
+ export { resetProviderCacheForTests } from "../runtime/cache.js";
3
+ export { runStandardTests, } from "./run.js";
@@ -1,4 +1,4 @@
1
- import type { AuthMode, ProviderDefinition } from "../types.js";
1
+ import type { AuthMode, ProviderContext, ProviderDefinition } from "../types.js";
2
2
  export interface StandardTestsManifest {
3
3
  id?: string;
4
4
  displayName?: string;
@@ -22,7 +22,34 @@ export interface StandardTestsOptions {
22
22
  validateAuthMode?: boolean;
23
23
  /** Override inferred __fixtures__ directory for tests generated outside providers/<id>. */
24
24
  fixtureDir?: string;
25
+ /** Opt in to real-handler E2E with strict, offline canned upstream responses. */
26
+ upstreamStub?: StandardTestsUpstreamStub;
27
+ /** Require a committed snapshot; missing files fail unless --update-snapshots is passed. */
28
+ requireSnapshot?: boolean;
25
29
  }
30
+ export interface StandardTestsUpstreamCall {
31
+ /** Operation whose real handler initiated the call. */
32
+ operationName: string;
33
+ /** ProviderContext transport surface used by the handler. */
34
+ transport: "http" | "stealth" | "browser" | "native";
35
+ method: string;
36
+ url?: string;
37
+ body?: unknown;
38
+ options?: unknown;
39
+ }
40
+ export interface StandardTestsUpstreamResponse {
41
+ status?: number;
42
+ headers?: Readonly<Record<string, string>>;
43
+ /** JSON-compatible values are encoded as JSON; strings and bytes are preserved. */
44
+ body?: unknown;
45
+ }
46
+ export type StandardTestsUpstreamStub = (call: StandardTestsUpstreamCall) => Response | StandardTestsUpstreamResponse | undefined | Promise<Response | StandardTestsUpstreamResponse | undefined>;
47
+ export interface StandardTestsResult {
48
+ warnings: readonly string[];
49
+ }
50
+ export declare function createSnapshotContext(rawFixture: unknown): ProviderContext;
51
+ /** Internal execution seam exported for focused SDK tests; use runStandardTests as public API. */
52
+ export declare function executeStandardTestHandler(provider: ProviderDefinition, operationName: string, upstreamStub: StandardTestsUpstreamStub): Promise<unknown>;
26
53
  /**
27
54
  * Run standard SDK tests for a provider in one line.
28
55
  *
@@ -31,4 +58,7 @@ export interface StandardTestsOptions {
31
58
  * import { runStandardTests } from "@apifuse/provider-sdk/testing";
32
59
  * runStandardTests(myProvider, rawFixture, manifest, { snapshot: true });
33
60
  */
34
- export declare function runStandardTests(provider: ProviderDefinition, rawFixture?: unknown, manifest?: StandardTestsManifest, options?: StandardTestsOptions): void;
61
+ export declare function runStandardTests(provider: ProviderDefinition, options: StandardTestsOptions & {
62
+ upstreamStub: StandardTestsUpstreamStub;
63
+ }): StandardTestsResult;
64
+ export declare function runStandardTests(provider: ProviderDefinition, rawFixture?: unknown, manifest?: StandardTestsManifest, options?: StandardTestsOptions): StandardTestsResult;
@@ -2,14 +2,25 @@ import { describe, expect, it } from "bun:test";
2
2
  import { createProviderCache } from "../runtime/cache.js";
3
3
  import { createTestProviderChoiceContext } from "../runtime/choice.js";
4
4
  import { createMemoryProviderRuntimeState } from "../runtime/state.js";
5
+ import { createUnsupportedOcrClient } from "../runtime/ocr.js";
5
6
  import { createUnsupportedSttClient } from "../runtime/stt.js";
7
+ import { createNativeEgressAuthorization, NativeNetworkError, snapshotNativeConnectInput, snapshotNativeGrantInput, } from "../runtime/native-network.js";
6
8
  import { safeParseSchemaSync } from "../schema.js";
9
+ import { requestPathForFixture } from "../fixture-sanitization.js";
10
+ import { findStreamCaptureGroup, replayStreamEvidence } from "../stream-evidence.js";
7
11
  // Mirrors CONNECTOR_ID_REGEX in ../define.ts, which defineProvider() enforces.
8
12
  // A single lowercase segment (no hyphen) is a valid id, so the trailing group
9
13
  // is optional (`*`), matching providers like `kakaomap`, `kstartup`, `triple`.
10
14
  const CONNECTOR_ID_REGEX = /^[a-z][a-z0-9]*(-[a-z][a-z0-9]*)*$/;
11
- const VALID_AUTH_MODES = ["none", "platform-managed", "credentials", "oauth2"];
15
+ const VALID_AUTH_MODES = [
16
+ "none",
17
+ "platform-managed",
18
+ "credentials",
19
+ "oauth2",
20
+ "oauth2_proxied",
21
+ ];
12
22
  const UPDATE_SNAPSHOT_ARGS = new Set(["-u", "--update-snapshots"]);
23
+ const snapshotCaptureStates = new WeakMap();
13
24
  function isFixtureEnvelope(value) {
14
25
  return (value !== null &&
15
26
  typeof value === "object" &&
@@ -74,10 +85,355 @@ function jsonResponse(data) {
74
85
  bytes: async () => bodyBytes.slice(0),
75
86
  };
76
87
  }
88
+ function headersToRecord(headers) {
89
+ return Object.fromEntries(headers.entries());
90
+ }
91
+ async function normalizeUpstreamResponse(response) {
92
+ if (response instanceof Response) {
93
+ const bytes = new Uint8Array(await response.arrayBuffer());
94
+ const text = new TextDecoder().decode(bytes);
95
+ const headers = headersToRecord(response.headers);
96
+ let data = text;
97
+ if (response.headers.get("content-type")?.includes("application/json")) {
98
+ data = text.length > 0 ? JSON.parse(text) : null;
99
+ }
100
+ return { status: response.status, headers, data, text, bytes };
101
+ }
102
+ const headers = { ...(response.headers ?? {}) };
103
+ const body = response.body ?? null;
104
+ let bytes;
105
+ let text;
106
+ let data;
107
+ if (body instanceof Uint8Array) {
108
+ bytes = body.slice(0);
109
+ text = new TextDecoder().decode(bytes);
110
+ data = text;
111
+ }
112
+ else if (body instanceof ArrayBuffer) {
113
+ bytes = new Uint8Array(body.slice(0));
114
+ text = new TextDecoder().decode(bytes);
115
+ data = text;
116
+ }
117
+ else if (typeof body === "string") {
118
+ text = body;
119
+ bytes = new TextEncoder().encode(text);
120
+ data = body;
121
+ }
122
+ else {
123
+ text = JSON.stringify(body);
124
+ bytes = new TextEncoder().encode(text);
125
+ data = body;
126
+ if (!Object.keys(headers).some((name) => name.toLowerCase() === "content-type")) {
127
+ headers["content-type"] = "application/json";
128
+ }
129
+ }
130
+ return { status: response.status ?? 200, headers, data, text, bytes };
131
+ }
132
+ function toHttpResponse(response) {
133
+ return {
134
+ status: response.status,
135
+ ok: response.status >= 200 && response.status < 300,
136
+ headers: response.headers,
137
+ data: response.data,
138
+ json: async () => JSON.parse(response.text),
139
+ text: async () => response.text,
140
+ arrayBuffer: async () => response.bytes.slice(0).buffer,
141
+ bytes: async () => response.bytes.slice(0),
142
+ };
143
+ }
144
+ const emptyCookieJar = {
145
+ get: () => undefined,
146
+ getAll: () => ({}),
147
+ toString: () => "",
148
+ };
149
+ function emptyCookieStore() {
150
+ return {
151
+ version: 1,
152
+ jar: { cookies: [] },
153
+ };
154
+ }
155
+ function toStealthResponse(response, url) {
156
+ return {
157
+ status: response.status,
158
+ ok: response.status >= 200 && response.status < 300,
159
+ url,
160
+ redirected: false,
161
+ headers: response.headers,
162
+ rawHeaders: Object.entries(response.headers),
163
+ body: response.text,
164
+ cookies: emptyCookieJar,
165
+ json: async () => JSON.parse(response.text),
166
+ arrayBuffer: async () => response.bytes.slice(0).buffer,
167
+ bytes: async () => response.bytes.slice(0),
168
+ };
169
+ }
170
+ function streamFromBytes(bytes) {
171
+ return new ReadableStream({
172
+ start(controller) {
173
+ controller.enqueue(bytes.slice(0));
174
+ controller.close();
175
+ },
176
+ });
177
+ }
178
+ async function* singleBytes(bytes) {
179
+ yield bytes.slice(0);
180
+ }
181
+ async function* singleText(textValue) {
182
+ yield textValue;
183
+ }
184
+ function createUpstreamContext(provider, operationName, upstreamStub) {
185
+ const credential = {
186
+ mode: "none",
187
+ get: () => undefined,
188
+ getAll: () => ({}),
189
+ getAccessToken: () => undefined,
190
+ getScopes: () => [],
191
+ };
192
+ const request = { headers: {} };
193
+ const state = createMemoryProviderRuntimeState();
194
+ const nativeEgress = provider.native
195
+ ? createNativeEgressAuthorization({ egress: provider.native.network })
196
+ : undefined;
197
+ const requireNativeEgress = () => {
198
+ if (!nativeEgress)
199
+ throw new NativeNetworkError("Native egress authorization is unavailable", "native_egress_policy_invalid");
200
+ return nativeEgress;
201
+ };
202
+ const dispatch = async (call) => {
203
+ const canned = await upstreamStub({ operationName, ...call });
204
+ if (canned === undefined) {
205
+ throw new Error(`Unmatched upstream call for operation "${operationName}": ${call.transport}.${call.method}${call.url ? ` ${call.url}` : ""}. Add a canned response to upstreamStub; live network passthrough is disabled.`);
206
+ }
207
+ return normalizeUpstreamResponse(canned);
208
+ };
209
+ const httpCall = async (method, url, body, options) => toHttpResponse(await dispatch({ transport: "http", method, url, body, options }));
210
+ const stealthCall = async (url, options) => toStealthResponse(await dispatch({
211
+ transport: "stealth",
212
+ method: options?.method?.toUpperCase() ?? "GET",
213
+ url,
214
+ body: options?.body,
215
+ options,
216
+ }), url);
217
+ const createBrowserPage = () => {
218
+ let currentUrl = "about:blank";
219
+ let currentResponse;
220
+ const browserAction = async (method, body) => {
221
+ currentResponse = await dispatch({
222
+ transport: "browser",
223
+ method,
224
+ url: currentUrl,
225
+ body,
226
+ });
227
+ return currentResponse;
228
+ };
229
+ const page = {
230
+ id: `standard-test-${operationName}`,
231
+ url: async () => currentUrl,
232
+ title: async () => currentResponse?.text.match(/<title[^>]*>([^<]*)<\/title>/i)?.[1] ?? "",
233
+ userAgent: async () => String((await browserAction("userAgent")).data),
234
+ content: async () => currentResponse?.text ?? "",
235
+ evaluate: async (fn) => (await browserAction("evaluate", typeof fn === "string" ? fn : String(fn))).data,
236
+ locator: (selector) => ({
237
+ click: async () => {
238
+ await browserAction("locator.click", { selector });
239
+ },
240
+ fill: async (textValue) => {
241
+ await browserAction("locator.fill", { selector, text: textValue });
242
+ },
243
+ textContent: async () => {
244
+ const value = (await browserAction("locator.textContent", { selector })).data;
245
+ return value === null || value === undefined ? null : String(value);
246
+ },
247
+ waitFor: async (options) => {
248
+ await browserAction("locator.waitFor", { selector, options });
249
+ },
250
+ }),
251
+ close: async () => { },
252
+ cookies: async () => (await browserAction("cookies")).data,
253
+ fill: async (selector, textValue) => {
254
+ await browserAction("fill", { selector, text: textValue });
255
+ },
256
+ goto: async (url) => {
257
+ currentUrl = url;
258
+ await browserAction("goto");
259
+ },
260
+ screenshot: async (options) => Buffer.from((await browserAction("screenshot", options)).bytes),
261
+ click: async (selector) => {
262
+ await browserAction("click", { selector });
263
+ },
264
+ type: async (selector, textValue) => {
265
+ await browserAction("type", { selector, text: textValue });
266
+ },
267
+ waitForSelector: async (selector, options) => {
268
+ await browserAction("waitForSelector", { selector, options });
269
+ },
270
+ frames: async () => [page],
271
+ withResourcePolicy: async (_policy, run) => run(),
272
+ };
273
+ return page;
274
+ };
275
+ const createStealthSession = () => ({
276
+ fetch: stealthCall,
277
+ cookies: {
278
+ ...emptyCookieJar,
279
+ has: () => false,
280
+ setFromCookieStrings: () => { },
281
+ toHeader: () => "",
282
+ snapshot: () => ({}),
283
+ restore: () => { },
284
+ serialize: emptyCookieStore,
285
+ deserialize: () => { },
286
+ clear: () => { },
287
+ },
288
+ redirects: {
289
+ run: async (options) => {
290
+ const final = await stealthCall(options.url, options);
291
+ return {
292
+ final,
293
+ hops: [],
294
+ reason: "completed",
295
+ cookies: {},
296
+ cookieStore: emptyCookieStore(),
297
+ };
298
+ },
299
+ },
300
+ close: () => { },
301
+ });
302
+ return {
303
+ env: { get: () => undefined },
304
+ credential,
305
+ request,
306
+ http: {
307
+ request: (url, options) => httpCall(options?.method?.toUpperCase() ?? "GET", url, options?.body, options),
308
+ get: (url, options) => httpCall("GET", url, undefined, options),
309
+ post: (url, body, options) => httpCall("POST", url, body, options),
310
+ put: (url, body, options) => httpCall("PUT", url, body, options),
311
+ delete: (url, options) => httpCall("DELETE", url, undefined, options),
312
+ stream: async (url, options) => {
313
+ const response = await dispatch({
314
+ transport: "http",
315
+ method: options?.method?.toUpperCase() ?? "GET",
316
+ url,
317
+ body: options?.body,
318
+ options,
319
+ });
320
+ return {
321
+ status: response.status,
322
+ ok: response.status >= 200 && response.status < 300,
323
+ headers: response.headers,
324
+ body: streamFromBytes(response.bytes),
325
+ bytes: () => singleBytes(response.bytes),
326
+ textChunks: () => singleText(response.text),
327
+ lines: () => singleText(response.text),
328
+ };
329
+ },
330
+ sse: async (url, options) => {
331
+ const response = await dispatch({
332
+ transport: "http",
333
+ method: options?.method?.toUpperCase() ?? "GET",
334
+ url,
335
+ body: options?.body,
336
+ options,
337
+ });
338
+ async function* messages() {
339
+ for (const block of response.text.split(/\r?\n\r?\n/)) {
340
+ const data = block
341
+ .split(/\r?\n/)
342
+ .filter((line) => line.startsWith("data:"))
343
+ .map((line) => line.slice(5).trimStart())
344
+ .join("\n");
345
+ if (!data)
346
+ continue;
347
+ yield { event: "message", data, json: () => JSON.parse(data) };
348
+ }
349
+ }
350
+ return messages();
351
+ },
352
+ },
353
+ cache: createProviderCache({ providerId: `standard-test-${operationName}` }),
354
+ state,
355
+ stealth: {
356
+ fetch: stealthCall,
357
+ createSession: createStealthSession,
358
+ },
359
+ browser: {
360
+ engine: "playwright-stealth",
361
+ newPage: async () => createBrowserPage(),
362
+ rawPage: async () => createBrowserPage(),
363
+ withIsolatedContext: async (handler) => handler(createBrowserPage()),
364
+ solveChallenge: async (challenge) => (await dispatch({
365
+ transport: "browser",
366
+ method: "solveChallenge",
367
+ body: challenge,
368
+ })).data,
369
+ },
370
+ ...(provider.native
371
+ ? {
372
+ native: {
373
+ network: {
374
+ connectTcp: async (options) => {
375
+ const request = snapshotNativeConnectInput(options);
376
+ requireNativeEgress().assertConnect(request, "disabled");
377
+ return createNativeConnection(await dispatch({
378
+ transport: "native",
379
+ method: "connectTcp",
380
+ url: `tcp://${request.host}:${request.port}`,
381
+ options: request,
382
+ }), dispatch, `tcp://${request.host}:${request.port}`);
383
+ },
384
+ connectTls: async (options) => {
385
+ const request = snapshotNativeConnectInput(options);
386
+ requireNativeEgress().assertConnect(request, "required");
387
+ return createNativeConnection(await dispatch({
388
+ transport: "native",
389
+ method: "connectTls",
390
+ url: `tls://${request.host}:${request.port}`,
391
+ options: request,
392
+ }), dispatch, `tls://${request.host}:${request.port}`);
393
+ },
394
+ grantTcpEgress: (input) => {
395
+ return requireNativeEgress().grant(snapshotNativeGrantInput(input));
396
+ },
397
+ },
398
+ },
399
+ }
400
+ : {}),
401
+ trace: { span: async (_name, fn) => fn() },
402
+ auth: { requestField: async (name) => unsupported(`ctx.auth.requestField(${name})`) },
403
+ ocr: createUnsupportedOcrClient("Standard test upstream context does not support ctx.ocr.recognize"),
404
+ stt: createUnsupportedSttClient("Standard test upstream context does not support ctx.stt.transcribe"),
405
+ resolver: {
406
+ solve: async () => unsupported("ctx.resolver.solve"),
407
+ },
408
+ choice: createTestProviderChoiceContext({
409
+ providerId: `standard-test-${operationName}`,
410
+ request,
411
+ credential,
412
+ state,
413
+ }),
414
+ };
415
+ }
416
+ function createNativeConnection(initialResponse, dispatch, url) {
417
+ let unread = initialResponse.bytes.slice(0);
418
+ return {
419
+ read: async () => {
420
+ if (unread.byteLength === 0)
421
+ return null;
422
+ const bytes = unread;
423
+ unread = new Uint8Array();
424
+ return bytes;
425
+ },
426
+ write: async (body) => {
427
+ const response = await dispatch({ transport: "native", method: "write", url, body });
428
+ unread = response.bytes.slice(0);
429
+ },
430
+ close: async () => { },
431
+ };
432
+ }
77
433
  function unsupported(name) {
78
434
  throw new Error(`Standard test snapshot context does not support ${name}`);
79
435
  }
80
- function createSnapshotContext(rawFixture) {
436
+ export function createSnapshotContext(rawFixture) {
81
437
  const credential = {
82
438
  mode: "none",
83
439
  get: () => undefined,
@@ -87,17 +443,52 @@ function createSnapshotContext(rawFixture) {
87
443
  };
88
444
  const request = { headers: {} };
89
445
  const state = createMemoryProviderRuntimeState();
90
- return {
446
+ const streamCaptureGroup = findStreamCaptureGroup(rawFixture);
447
+ let nextCaptureItem = 0;
448
+ const replayJsonResponse = () => {
449
+ if (!streamCaptureGroup)
450
+ return jsonResponse(rawFixture);
451
+ const item = streamCaptureGroup.items[nextCaptureItem];
452
+ if (!item) {
453
+ throw new Error(`Stream fixture exhausted: no recorded response exists for ordinary HTTP call ${nextCaptureItem + 1}.`);
454
+ }
455
+ if (item.kind !== "response") {
456
+ throw new Error(`Stream fixture call-order mismatch: expected a stream call at position ${nextCaptureItem + 1}, received an ordinary HTTP call.`);
457
+ }
458
+ nextCaptureItem += 1;
459
+ return jsonResponse(item.value);
460
+ };
461
+ const context = {
91
462
  env: { get: () => undefined },
92
463
  credential,
93
464
  request,
94
465
  http: {
95
- request: async () => jsonResponse(rawFixture),
96
- get: async () => jsonResponse(rawFixture),
97
- post: async () => jsonResponse(rawFixture),
98
- put: async () => jsonResponse(rawFixture),
99
- delete: async () => jsonResponse(rawFixture),
100
- stream: async () => unsupported("ctx.http.stream"),
466
+ request: async () => replayJsonResponse(),
467
+ get: async () => replayJsonResponse(),
468
+ post: async () => replayJsonResponse(),
469
+ put: async () => replayJsonResponse(),
470
+ delete: async () => replayJsonResponse(),
471
+ stream: async (...args) => {
472
+ if (!streamCaptureGroup)
473
+ return unsupported("ctx.http.stream");
474
+ const item = streamCaptureGroup.items[nextCaptureItem];
475
+ if (!item) {
476
+ throw new Error(`Stream fixture exhausted: no recorded evidence exists for stream call ${nextCaptureItem + 1}.`);
477
+ }
478
+ if (item.kind !== "stream") {
479
+ throw new Error(`Stream fixture call-order mismatch: expected an ordinary HTTP call at position ${nextCaptureItem + 1}, received a stream call.`);
480
+ }
481
+ if (item.evidence.request) {
482
+ const expected = item.evidence.request;
483
+ const actualMethod = (args[1]?.method ?? "GET").toUpperCase();
484
+ const actualPath = replayRequestPath(args[0], expected.path);
485
+ if (actualMethod !== expected.method || actualPath !== expected.path) {
486
+ throw new Error(`Stream fixture request mismatch: expected ${expected.method} ${expected.path}, received ${actualMethod} ${actualPath}.`);
487
+ }
488
+ }
489
+ nextCaptureItem += 1;
490
+ return replayStreamEvidence(item.evidence);
491
+ },
101
492
  sse: async () => unsupported("ctx.http.sse"),
102
493
  },
103
494
  cache: createProviderCache({ providerId: "standard-test" }),
@@ -119,7 +510,11 @@ function createSnapshotContext(rawFixture) {
119
510
  auth: {
120
511
  requestField: async (name) => unsupported(`ctx.auth.requestField(${name})`),
121
512
  },
513
+ ocr: createUnsupportedOcrClient("Standard test snapshot context does not support ctx.ocr.recognize"),
122
514
  stt: createUnsupportedSttClient("Standard test snapshot context does not support ctx.stt.transcribe"),
515
+ resolver: {
516
+ solve: async () => unsupported("ctx.resolver.solve"),
517
+ },
123
518
  choice: createTestProviderChoiceContext({
124
519
  providerId: "standard-test",
125
520
  request,
@@ -127,15 +522,39 @@ function createSnapshotContext(rawFixture) {
127
522
  state,
128
523
  }),
129
524
  };
525
+ snapshotCaptureStates.set(context, {
526
+ assertConsumed() {
527
+ if (streamCaptureGroup && nextCaptureItem !== streamCaptureGroup.items.length) {
528
+ throw new Error(`Stream fixture has ${streamCaptureGroup.items.length - nextCaptureItem} unconsumed capture item${streamCaptureGroup.items.length - nextCaptureItem === 1 ? "" : "s"} after handler completion.`);
529
+ }
530
+ },
531
+ consumed: () => nextCaptureItem,
532
+ });
533
+ return context;
534
+ }
535
+ function replayRequestPath(requestUrl, expectedPath) {
536
+ if (/^[a-z][a-z\d+.-]*:\/\//i.test(requestUrl) || requestUrl.startsWith("/")) {
537
+ return requestPathForFixture(requestUrl);
538
+ }
539
+ return requestPathForFixture(new URL(requestUrl, `https://fixture.invalid${expectedPath}`).toString());
130
540
  }
131
541
  async function transformSnapshotOutput(provider, rawFixture) {
132
542
  const entries = Object.entries(provider.operations);
133
- const context = createSnapshotContext(rawFixture);
543
+ const captureStates = [];
134
544
  const outputs = await Promise.all(entries.map(async ([operationName, operation]) => {
545
+ const context = createSnapshotContext(rawFixture);
135
546
  const request = operation.fixtures?.request ?? {};
136
547
  const output = await operation.handler(context, request);
548
+ const captureState = snapshotCaptureStates.get(context);
549
+ if (captureState)
550
+ captureStates.push(captureState);
551
+ if (captureState?.consumed())
552
+ captureState.assertConsumed();
137
553
  return [operationName, output];
138
554
  }));
555
+ if (findStreamCaptureGroup(rawFixture) && !captureStates.some((state) => state.consumed() > 0)) {
556
+ captureStates[0]?.assertConsumed();
557
+ }
139
558
  if (outputs.length === 1) {
140
559
  return outputs[0]?.[1];
141
560
  }
@@ -154,7 +573,7 @@ function formatJsonDiff(current, expected) {
154
573
  const expectedLine = expectedLines[index];
155
574
  if (currentLine === expectedLine) {
156
575
  if (currentLine !== undefined) {
157
- lines.push(` ${currentLine}`);
576
+ lines.push(` ${currentLine}`);
158
577
  }
159
578
  continue;
160
579
  }
@@ -180,16 +599,51 @@ function expectSchemaFixture(operationName, fieldName, fixture, result) {
180
599
  function parseSchemaFixture(operationName, fieldName, schema, fixture) {
181
600
  expectSchemaFixture(operationName, fieldName, fixture, safeParseSchemaSync(schema, fixture, `operations.${operationName}.fixtures.${fieldName}`));
182
601
  }
183
- /**
184
- * Run standard SDK tests for a provider in one line.
185
- *
186
- * Usage:
187
- * import { myProvider } from "../index";
188
- * import { runStandardTests } from "@apifuse/provider-sdk/testing";
189
- * runStandardTests(myProvider, rawFixture, manifest, { snapshot: true });
190
- */
191
- export function runStandardTests(provider, rawFixture, manifest, options = {}) {
602
+ async function materializeHandlerOutput(output) {
603
+ if (!(output instanceof Response))
604
+ return output;
605
+ const contentType = output.headers.get("content-type") ?? "";
606
+ if (contentType.includes("application/json"))
607
+ return output.json();
608
+ return output.text();
609
+ }
610
+ /** Internal execution seam exported for focused SDK tests; use runStandardTests as public API. */
611
+ export async function executeStandardTestHandler(provider, operationName, upstreamStub) {
612
+ const operation = provider.operations[operationName];
613
+ if (!operation)
614
+ throw new Error(`Unknown operation "${operationName}".`);
615
+ if (operation.fixtures?.request === undefined) {
616
+ throw new Error(`Operation "${operationName}" has no fixtures.request for handler E2E execution.`);
617
+ }
618
+ const context = createUpstreamContext(provider, operationName, upstreamStub);
619
+ const output = await materializeHandlerOutput(await operation.handler(context, operation.fixtures.request));
620
+ const result = safeParseSchemaSync(operation.output, output, `operations.${operationName}.handler.output`);
621
+ if (!result.success) {
622
+ throw new Error([
623
+ `Handler output for operation "${operationName}" failed schema validation.`,
624
+ formatJsonDiff({ valid: false, value: output, error: result.error }, { valid: true, value: output }),
625
+ ].join("\n"));
626
+ }
627
+ return output;
628
+ }
629
+ function isOptionsShortcut(value) {
630
+ return (value !== null &&
631
+ typeof value === "object" &&
632
+ !Array.isArray(value) &&
633
+ Object.hasOwn(value, "upstreamStub"));
634
+ }
635
+ export function runStandardTests(provider, rawFixtureOrOptions, manifest, legacyOptions) {
636
+ const shortcut = manifest === undefined && legacyOptions === undefined && isOptionsShortcut(rawFixtureOrOptions);
637
+ const rawFixture = shortcut ? undefined : rawFixtureOrOptions;
638
+ const options = shortcut ? rawFixtureOrOptions : (legacyOptions ?? {});
192
639
  const operations = Object.entries(provider.operations);
640
+ const warnings = options.upstreamStub
641
+ ? operations
642
+ .filter(([, operation]) => operation.fixtures?.request === undefined)
643
+ .map(([operationName]) => `[provider-sdk] Operation "${provider.id}.${operationName}" has no fixtures.request, so runStandardTests cannot invoke its handler E2E.`)
644
+ : operations.map(([operationName]) => `[provider-sdk] Operation "${provider.id}.${operationName}" has no handler E2E coverage in runStandardTests; configure upstreamStub to invoke the real handler.`);
645
+ for (const warning of warnings)
646
+ console.warn(warning);
193
647
  const assertFixtureValidation = () => {
194
648
  expect(rawFixture).toBeDefined();
195
649
  expect(isJsonCompatible(rawFixture)).toBe(true);
@@ -289,12 +743,26 @@ export function runStandardTests(provider, rawFixture, manifest, options = {}) {
289
743
  const actual = await transformSnapshotOutput(provider, rawFixture);
290
744
  const serialized = stableStringify(actual);
291
745
  const snapshotFile = Bun.file(snapshotPath);
292
- if (shouldUpdateSnapshots() || !(await snapshotFile.exists())) {
746
+ const snapshotExists = await snapshotFile.exists();
747
+ if (!snapshotExists && options.requireSnapshot && !shouldUpdateSnapshots()) {
748
+ throw new Error(`Required golden snapshot is missing: ${snapshotPath}. Regenerate it with bun test --update-snapshots.`);
749
+ }
750
+ if (shouldUpdateSnapshots() || !snapshotExists) {
293
751
  await Bun.write(snapshotPath, serialized);
294
752
  }
295
753
  const expected = JSON.parse(await Bun.file(snapshotPath).text());
296
754
  expect(actual).toEqual(expected);
297
755
  });
298
756
  }
757
+ if (options.upstreamStub) {
758
+ for (const [operationName, operation] of operations) {
759
+ if (operation.fixtures?.request === undefined)
760
+ continue;
761
+ it(`invokes the real ${operationName} handler with canned upstream responses`, async () => {
762
+ await executeStandardTestHandler(provider, operationName, options.upstreamStub);
763
+ });
764
+ }
765
+ }
299
766
  });
767
+ return { warnings };
300
768
  }