@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
@@ -0,0 +1,129 @@
1
+ import { isSensitiveFixtureKey, REDACTED_FIXTURE_VALUE } from "../fixture-sanitization.js";
2
+ import {
3
+ OTEL_EXPORTER_OTLP_ENDPOINT,
4
+ OTEL_EXPORTER_OTLP_TRACES_ENDPOINT,
5
+ type OTLPExportOptions,
6
+ type OTLPExportResolution,
7
+ type OTLPResourceResolution,
8
+ resolveOTLPExportOptions,
9
+ resolveOTLPResourceAttributes,
10
+ } from "../runtime/otlp.js";
11
+ import {
12
+ type CreateTraceContextOptions,
13
+ resolveTraceContextOptions,
14
+ type Span,
15
+ } from "../runtime/trace.js";
16
+ import {
17
+ sanitizeSpanForOutput,
18
+ sanitizeSpanNameForOutput,
19
+ sanitizeTraceAttributes,
20
+ } from "../trace-sanitization.js";
21
+ import type { TraceConfig } from "../types.js";
22
+
23
+ type EnvLike = Record<string, string | undefined>;
24
+
25
+ // Warn once per environment object: process.env in production, each injected env in tests.
26
+ const warnedEnvironments = new WeakSet<EnvLike>();
27
+ const warnedResourceEnvironments = new WeakSet<EnvLike>();
28
+
29
+ function warnDiscardedResourceAttributes(env: EnvLike, resolution: OTLPResourceResolution): void {
30
+ if (resolution.discarded.length === 0 || warnedResourceEnvironments.has(env)) return;
31
+ warnedResourceEnvironments.add(env);
32
+ console.warn(
33
+ `[apifuse] ${resolution.discarded.join(", ")} could not be parsed as an OTel key=value list and was ignored.`,
34
+ );
35
+ }
36
+
37
+ function warnExportDisabled(
38
+ env: EnvLike,
39
+ resolution: Exclude<OTLPExportResolution, { status: "resolved" }>,
40
+ ): void {
41
+ if (warnedEnvironments.has(env)) return;
42
+ warnedEnvironments.add(env);
43
+ console.warn(
44
+ resolution.status === "unconfigured"
45
+ ? `[apifuse] OTLP trace export is enabled but no endpoint is configured; set ${OTEL_EXPORTER_OTLP_TRACES_ENDPOINT} or ${OTEL_EXPORTER_OTLP_ENDPOINT}. Trace export is disabled.`
46
+ : `[apifuse] OTLP trace export is enabled but ${resolution.source} ${resolution.reason}. Trace export is disabled.`,
47
+ );
48
+ }
49
+
50
+ function resolveServerOTLPExportOptions(
51
+ config: TraceConfig,
52
+ env: EnvLike,
53
+ ): OTLPExportOptions | undefined {
54
+ const resolution = resolveOTLPExportOptions(
55
+ {
56
+ endpoint: config.otlp?.endpoint ?? config.endpoint,
57
+ headers: config.otlp?.headers,
58
+ timeout: config.otlp?.timeout,
59
+ },
60
+ env,
61
+ );
62
+ if (resolution.status === "resolved") return resolution.options;
63
+ warnExportDisabled(env, resolution);
64
+ return undefined;
65
+ }
66
+
67
+ /**
68
+ * Per-request attributes carry client-supplied values (request_id) and get the full attribute
69
+ * sanitizer, exactly as on the console path. Operator-configured attributes from the process
70
+ * environment are identifiers rather than request data: their values are kept, with control
71
+ * characters neutralized, length bounded, and secret-named keys redacted.
72
+ */
73
+ function resolveExportResourceAttributes(
74
+ requestAttributes: Record<string, string>,
75
+ env: EnvLike,
76
+ ): Record<string, string> {
77
+ const resolution = resolveOTLPResourceAttributes({}, env);
78
+ warnDiscardedResourceAttributes(env, resolution);
79
+ const operatorAttributes = Object.fromEntries(
80
+ Object.entries(resolution.attributes).map(([key, value]) => [
81
+ sanitizeSpanNameForOutput(key),
82
+ isSensitiveFixtureKey(key) ? REDACTED_FIXTURE_VALUE : sanitizeSpanNameForOutput(value),
83
+ ]),
84
+ );
85
+ const sanitizedRequestAttributes = Object.fromEntries(
86
+ Object.entries(sanitizeTraceAttributes(requestAttributes)).map(([key, value]) => [
87
+ key,
88
+ String(value),
89
+ ]),
90
+ );
91
+ return { ...operatorAttributes, ...sanitizedRequestAttributes };
92
+ }
93
+
94
+ /** Server-only trace output policy. Shared programmatic trace callers stay in-memory. */
95
+ export function resolveServerTraceContextOptions(
96
+ config: TraceConfig,
97
+ resourceAttributes: Record<string, string>,
98
+ env: EnvLike = process.env,
99
+ ): CreateTraceContextOptions {
100
+ const resolved = resolveTraceContextOptions(config);
101
+ const outputEnabled = config.enabled !== false && config.exporter !== "none";
102
+ const consoleHook =
103
+ outputEnabled && (config.exporter === "console" || config.exporter === "json")
104
+ ? (span: Span) => console.log(JSON.stringify(sanitizeSpanForOutput(span, resourceAttributes)))
105
+ : undefined;
106
+ const onSpan =
107
+ consoleHook && resolved.onSpan
108
+ ? (span: Span) => {
109
+ consoleHook(span);
110
+ resolved.onSpan?.(span);
111
+ }
112
+ : (consoleHook ?? resolved.onSpan);
113
+ const exportOptions =
114
+ outputEnabled && config.exporter === "otlp"
115
+ ? resolveServerOTLPExportOptions(config, env)
116
+ : undefined;
117
+
118
+ return {
119
+ maxSpans: resolved.maxSpans,
120
+ onSpan,
121
+ ...(exportOptions
122
+ ? {
123
+ exportOptions,
124
+ resourceAttributes: resolveExportResourceAttributes(resourceAttributes, env),
125
+ sanitizeSpanForExport: (span: Span) => sanitizeSpanForOutput(span),
126
+ }
127
+ : {}),
128
+ };
129
+ }
@@ -1,8 +1,15 @@
1
1
  import { z } from "zod";
2
+ import { PROVIDER_ERROR_SOURCES } from "../observability.js";
2
3
 
3
4
  import { HttpRetryPreset } from "../types.js";
4
5
 
5
- export const ConnectionModeSchema = z.enum(["oauth2", "credentials", "platform-managed", "none"]);
6
+ export const ConnectionModeSchema = z.enum([
7
+ "oauth2",
8
+ "oauth2_proxied",
9
+ "credentials",
10
+ "platform-managed",
11
+ "none",
12
+ ]);
6
13
 
7
14
  export const OperationConnectionSchema = z.object({
8
15
  id: z.string(),
@@ -26,6 +33,11 @@ export const ErrorEnvelopeSchema = z.object({
26
33
  code: z.string(),
27
34
  message: z.string(),
28
35
  requestId: z.string().optional(),
36
+ retryable: z.boolean(),
37
+ // Who stopped the request (honest-provider-error-contract); optional so
38
+ // older peers (stateful forwarding owners) remain parseable. Derived from
39
+ // the PROVIDER_ERROR_SOURCES registry so the enum cannot drift.
40
+ source: z.enum(PROVIDER_ERROR_SOURCES).optional(),
29
41
  fix: z.string().optional(),
30
42
  details: z.unknown().optional(),
31
43
  });
@@ -0,0 +1,146 @@
1
+ # Stateful module compatibility note
2
+
3
+ The `@apifuse/provider-sdk/stateful` entry point is new and unreleased. Its initial contract includes
4
+ the following compatibility-sensitive behavior:
5
+
6
+ - Session identifiers use the branded `SessionKey` returned by `buildSessionKey`.
7
+ `parseSessionKey` returns `SessionKeyParts`; validate and brand an external string with
8
+ `buildSessionKey(parseSessionKey(value))`. Registry inputs, routing requests, event owner fences,
9
+ and release APIs no longer accept arbitrary strings. A decoded `SessionOwnerRecord.sessionKey`
10
+ remains a string and must be checked where external data enters trusted SDK code (a trust
11
+ boundary).
12
+ - Adapters declare `policy.concurrency` (`serialize` or bounded/unbounded `parallel`) and
13
+ `policy.reconnect` (`resume`, `recreate`, or `unsupported`). `restore`, `snapshot`, and `health`
14
+ are optional; a declared `snapshot` must be paired with the manager's `checkpointStore`.
15
+ - Code in the process hosting the adapter (the provider pod) publishes through
16
+ `ProviderEventPublisher`; persistent services that survive provider-pod restarts (durable
17
+ platform code) implement `ProviderEventPipeline`. An adapter's
18
+ `subscribe(ctx, session, publish)` callback accepts only an event. The session manager, not the
19
+ adapter, binds the registry's current write authorization (the authoritative
20
+ `{ sessionKey, generation, ownerPodId, ownerEndpoint }` fence) before calling the publisher.
21
+ Adapters must not construct or cache owner fences themselves.
22
+ - `subscribe` must return a disposer. The manager runs it before checkpointing and closing a
23
+ session. `health()` and `reconcileWrite()` use the same per-session concurrency scheduler and
24
+ optional ownership validation semantics as invocation.
25
+ - `HttpProviderEventEmitter.flush(timeoutMs)` resolves to
26
+ `{ delivered: number, failed: number, pending: number }`. Counts are cumulative for the emitter's
27
+ lifetime; `pending` is the buffer remaining when the flush returns.
28
+ - `serve()` resolves to a `ProviderServerHandle` with `port` and idempotent `close({ timeoutMs? })`.
29
+ Close stops listeners, runs shutdown hooks in declaration order, and enforces the configured
30
+ timeout. Signal-enabled handles share one process handler per signal; a signal closes every
31
+ registered server before it is re-raised. Closing the last handle removes the shared handlers.
32
+ - `ProviderServerStatefulForwardEnvelope` is strict. Its owner fence is flattened into
33
+ `sessionKey`, `ownerPodId`, and `generation`; a nested `owner` object is not accepted. The
34
+ envelope also carries operation/session identity, `sourcePodId`, `forwardedAt`, optional
35
+ `idempotencyKey`, optional ISO `deadlineAt`, and the nested `operationRequest`. Unknown or
36
+ malformed fields fail closed, and the owner-side executor receives `deadlineAt` on its request.
37
+ - Stateful forwarding is configured as a pair: `internalOperationExecutor` requires
38
+ `statefulForwarding`, and `statefulForwarding` requires an `internalOperationExecutor`.
39
+ `statefulForwarding` supplies the signing secret and `validateOwnerFence`; it may also set
40
+ `maxSkewMs` and `replayCacheMaxEntries`. Replay-cache capacity returns HTTP 503 with
41
+ `Retry-After`; fail-closed nonce handling means invalid, replayed, or untrackable nonces are
42
+ rejected instead of allowing an unverifiable request.
43
+ - Stateful signatures bind the method, route, timestamp, nonce, and exact body. Forwarding also
44
+ rejects provider/source mismatches, stale owner fences, nonce replay, and request-scoped files.
45
+
46
+ ## Minimal composition
47
+
48
+ Inside the module that defines your `provider`, the following wires an adapter, session manager,
49
+ router, signed owner forwarding, and graceful shutdown. Replace the environment variable names and
50
+ the service-account mapping with your deployment's values.
51
+
52
+ ```ts
53
+ import {
54
+ buildSessionKey,
55
+ createStatefulOwnerFenceValidator,
56
+ HttpSessionOwnerRegistry,
57
+ HttpStatefulOwnerForwarder,
58
+ parseSessionKey,
59
+ statefulForwardingContextFromProviderRequest,
60
+ StatefulProviderSessionManager,
61
+ StatefulSessionRouter,
62
+ type StatefulProviderAdapter,
63
+ withStatefulLocalProviderContext,
64
+ } from "@apifuse/provider-sdk/stateful";
65
+ import { serve, type ProviderServerOperationExecutor } from "@apifuse/provider-sdk/server";
66
+
67
+ type Session = { closed: boolean };
68
+
69
+ const adapter = {
70
+ providerId: provider.id,
71
+ policy: { concurrency: { mode: "serialize" }, reconnect: "recreate" },
72
+ connect: async () => ({ closed: false }),
73
+ invoke: async (_ctx, _session, request) => ({ output: request.input }),
74
+ close: async (_ctx, session) => {
75
+ session.closed = true;
76
+ },
77
+ } satisfies StatefulProviderAdapter<Session>;
78
+
79
+ const registry = new HttpSessionOwnerRegistry({
80
+ baseUrl: process.env.STATEFUL_CONTROL_PLANE_URL as string,
81
+ secret: process.env.STATEFUL_CONTROL_PLANE_SECRET as string,
82
+ });
83
+ const manager = new StatefulProviderSessionManager({
84
+ adapter,
85
+ poolPolicy: { maxSessions: 100, idleTimeoutMs: "unlimited", maxLifetimeMs: "unlimited" },
86
+ });
87
+ const currentPod = {
88
+ podId: process.env.POD_ID as string,
89
+ endpoint: process.env.POD_ENDPOINT as string,
90
+ };
91
+ const router = new StatefulSessionRouter({
92
+ currentPod,
93
+ registry,
94
+ forwarder: new HttpStatefulOwnerForwarder({
95
+ currentPodId: currentPod.podId,
96
+ secret: process.env.STATEFUL_FORWARDING_SECRET as string,
97
+ }),
98
+ executor: manager,
99
+ leaseDurationMs: 30_000,
100
+ });
101
+
102
+ const executeStateful: ProviderServerOperationExecutor = async ({
103
+ ctx,
104
+ request,
105
+ operationId,
106
+ internalStatefulForward,
107
+ }) => {
108
+ const keyParts = internalStatefulForward
109
+ ? parseSessionKey(internalStatefulForward.sessionKey)
110
+ : {
111
+ providerId: provider.id,
112
+ serviceAccountId: request.connection?.externalRef as string,
113
+ connectionId: (request.connection?.id ?? request.connectionId) as string,
114
+ };
115
+ const result = await router.route({
116
+ requestId: request.requestId,
117
+ sessionKey: buildSessionKey(keyParts),
118
+ providerId: provider.id,
119
+ operationId,
120
+ connectionId: keyParts.connectionId,
121
+ serviceAccountId: keyParts.serviceAccountId,
122
+ input: request.input,
123
+ ...(request.deadlineAt ? { deadlineAt: request.deadlineAt } : {}),
124
+ runtimeContext: withStatefulLocalProviderContext(
125
+ ctx,
126
+ statefulForwardingContextFromProviderRequest(request),
127
+ ),
128
+ });
129
+ return result.output;
130
+ };
131
+
132
+ await serve(provider, {
133
+ operationExecutor: executeStateful,
134
+ internalOperationExecutor: executeStateful,
135
+ statefulForwarding: {
136
+ secret: process.env.STATEFUL_FORWARDING_SECRET as string,
137
+ validateOwnerFence: createStatefulOwnerFenceValidator(registry),
138
+ },
139
+ shutdown: {
140
+ hooks: [
141
+ () => manager.closeAll("server-shutdown"),
142
+ () => router.release(),
143
+ ],
144
+ },
145
+ });
146
+ ```
@@ -0,0 +1,35 @@
1
+ export type StatefulControlPlaneOperation = "resolve" | "acquire" | "renew" | "release";
2
+
3
+ export class StatefulRoutingDeadlineError extends Error {
4
+ readonly requestId: string;
5
+ readonly deadlineAt: string;
6
+
7
+ constructor(requestId: string, deadlineAt: string) {
8
+ super(`Stateful operation request ${requestId} deadline has expired.`);
9
+ this.name = "StatefulRoutingDeadlineError";
10
+ this.requestId = requestId;
11
+ this.deadlineAt = deadlineAt;
12
+ }
13
+ }
14
+
15
+ export class StatefulControlPlaneError extends Error {
16
+ readonly code: string;
17
+ readonly operation: StatefulControlPlaneOperation;
18
+ readonly status?: number;
19
+ readonly cause?: unknown;
20
+
21
+ constructor(input: {
22
+ readonly code: string;
23
+ readonly message: string;
24
+ readonly operation: StatefulControlPlaneOperation;
25
+ readonly status?: number;
26
+ readonly cause?: unknown;
27
+ }) {
28
+ super(input.message);
29
+ this.name = "StatefulControlPlaneError";
30
+ this.code = input.code;
31
+ this.operation = input.operation;
32
+ this.status = input.status;
33
+ this.cause = input.cause;
34
+ }
35
+ }
@@ -0,0 +1,314 @@
1
+ import { statefulSignedHeaders } from "../stateful-signing.js";
2
+ import type {
3
+ ProviderEventDeliveryFailureReason,
4
+ ProviderEventDeliveryFailureRecorder,
5
+ } from "./provider-event-delivery-failures.js";
6
+ import type {
7
+ ProviderEventPublisher,
8
+ ProviderEventPublishOptions,
9
+ PublishAck,
10
+ } from "./provider-event-pipeline.js";
11
+ import {
12
+ NOOP_PROVIDER_EVENT_METRIC_EMITTER,
13
+ providerEventMetricLabels,
14
+ type ProviderEventMetricEmitter,
15
+ } from "./provider-event-pipeline-metrics.js";
16
+ import type { ProviderEvent } from "./provider-events.js";
17
+
18
+ type FetchTransport = (url: string | URL | Request, init?: RequestInit) => Promise<Response>;
19
+
20
+ export type HttpProviderEventEmitterOptions = {
21
+ readonly baseUrl: string;
22
+ readonly secret: string;
23
+ readonly fetch?: FetchTransport;
24
+ readonly clock?: () => Date;
25
+ readonly random?: () => number;
26
+ readonly metricEmitter?: ProviderEventMetricEmitter;
27
+ readonly failureRecorder?: ProviderEventDeliveryFailureRecorder;
28
+ readonly maxBufferedEvents?: number;
29
+ readonly retryBaseMs?: number;
30
+ readonly retryMaxMs?: number;
31
+ readonly maxAttempts?: number;
32
+ readonly jitterRatio?: number;
33
+ };
34
+
35
+ type BufferedEvent = {
36
+ readonly event: ProviderEvent;
37
+ readonly rawBody: string;
38
+ readonly idempotencyKey: string;
39
+ attempts: number;
40
+ dropped: boolean;
41
+ abortController?: AbortController;
42
+ };
43
+
44
+ const DEFAULT_MAX_BUFFERED_EVENTS = 1_000;
45
+ const DEFAULT_RETRY_BASE_MS = 500;
46
+ const DEFAULT_RETRY_MAX_MS = 30_000;
47
+ const DEFAULT_MAX_ATTEMPTS = 10;
48
+ const DEFAULT_JITTER_RATIO = 0.2;
49
+
50
+ export type ProviderEventFlushReport = {
51
+ /** Events delivered during this emitter's lifetime. */
52
+ readonly delivered: number;
53
+ /** Events permanently dropped during this emitter's lifetime. */
54
+ readonly failed: number;
55
+ /** Events still buffered when flush returned. */
56
+ readonly pending: number;
57
+ };
58
+
59
+ const CONSOLE_PROVIDER_EVENT_DELIVERY_FAILURE_RECORDER = {
60
+ record(failure) {
61
+ console.error(
62
+ JSON.stringify({
63
+ event: "provider_event_delivery_failed",
64
+ eventId: failure.eventId,
65
+ reason: failure.reason,
66
+ attempts: failure.attempts,
67
+ }),
68
+ );
69
+ },
70
+ } satisfies ProviderEventDeliveryFailureRecorder;
71
+
72
+ /**
73
+ * Provider-pod HTTP publication with a bounded best-effort delivery guarantee and at-least-once
74
+ * transport retries. This is not durable at-least-once delivery: buffered events can be lost if
75
+ * the process is killed. Graceful shutdown should drain the in-memory buffer with flush().
76
+ */
77
+ export class HttpProviderEventEmitter implements ProviderEventPublisher {
78
+ readonly #baseUrl: string;
79
+ readonly #secret: string;
80
+ readonly #fetch: FetchTransport;
81
+ readonly #clock: () => Date;
82
+ readonly #random: () => number;
83
+ readonly #metricEmitter: ProviderEventMetricEmitter;
84
+ readonly #failureRecorder: ProviderEventDeliveryFailureRecorder;
85
+ readonly #maxBufferedEvents: number;
86
+ readonly #retryBaseMs: number;
87
+ readonly #retryMaxMs: number;
88
+ readonly #maxAttempts: number;
89
+ readonly #jitterRatio: number;
90
+ readonly #queue: BufferedEvent[] = [];
91
+ #drainPromise?: Promise<void>;
92
+ #deliveredCount = 0;
93
+ #failedCount = 0;
94
+
95
+ constructor(options: HttpProviderEventEmitterOptions) {
96
+ if (options.baseUrl.trim().length === 0) {
97
+ throw new Error("Provider event ingest baseUrl is required.");
98
+ }
99
+ if (options.secret.trim().length === 0) {
100
+ throw new Error("Provider event ingest secret is required.");
101
+ }
102
+ this.#baseUrl = options.baseUrl.replace(/\/+$/, "");
103
+ this.#secret = options.secret;
104
+ this.#fetch = options.fetch ?? globalThis.fetch;
105
+ this.#clock = options.clock ?? (() => new Date());
106
+ this.#random = options.random ?? Math.random;
107
+ this.#metricEmitter = options.metricEmitter ?? NOOP_PROVIDER_EVENT_METRIC_EMITTER;
108
+ this.#failureRecorder =
109
+ options.failureRecorder ?? CONSOLE_PROVIDER_EVENT_DELIVERY_FAILURE_RECORDER;
110
+ this.#maxBufferedEvents = positiveInteger(
111
+ options.maxBufferedEvents ?? DEFAULT_MAX_BUFFERED_EVENTS,
112
+ "maxBufferedEvents",
113
+ );
114
+ this.#retryBaseMs = nonnegativeFinite(
115
+ options.retryBaseMs ?? DEFAULT_RETRY_BASE_MS,
116
+ "retryBaseMs",
117
+ );
118
+ this.#retryMaxMs = nonnegativeFinite(options.retryMaxMs ?? DEFAULT_RETRY_MAX_MS, "retryMaxMs");
119
+ this.#maxAttempts = positiveInteger(options.maxAttempts ?? DEFAULT_MAX_ATTEMPTS, "maxAttempts");
120
+ this.#jitterRatio = boundedRatio(options.jitterRatio ?? DEFAULT_JITTER_RATIO, "jitterRatio");
121
+ if (this.#retryMaxMs < this.#retryBaseMs) {
122
+ throw new Error("Provider event retryMaxMs must be greater than or equal to retryBaseMs.");
123
+ }
124
+ }
125
+
126
+ publish(event: ProviderEvent, options: ProviderEventPublishOptions): PublishAck {
127
+ const accepted = this.#enqueue(event, options);
128
+ return { accepted, queued: this.#queue.length };
129
+ }
130
+
131
+ pendingCount(): number {
132
+ return this.#queue.length;
133
+ }
134
+
135
+ async flush(timeoutMs = 30_000): Promise<ProviderEventFlushReport> {
136
+ nonnegativeFinite(timeoutMs, "flush timeout");
137
+ this.#ensureDrain();
138
+ if (this.#queue.length === 0 || timeoutMs === 0) return this.#flushReport();
139
+ let timeout: ReturnType<typeof setTimeout> | undefined;
140
+ const timedOut = new Promise<void>((resolve) => {
141
+ timeout = setTimeout(resolve, timeoutMs);
142
+ });
143
+ await Promise.race([this.#drainPromise ?? Promise.resolve(), timedOut]);
144
+ if (timeout) clearTimeout(timeout);
145
+ return this.#flushReport();
146
+ }
147
+
148
+ #flushReport(): ProviderEventFlushReport {
149
+ return {
150
+ delivered: this.#deliveredCount,
151
+ failed: this.#failedCount,
152
+ pending: this.#queue.length,
153
+ };
154
+ }
155
+
156
+ #enqueue(event: ProviderEvent, options: ProviderEventPublishOptions): boolean {
157
+ let rawBody: string;
158
+ try {
159
+ rawBody = JSON.stringify({
160
+ ...event,
161
+ ownerFence: options.ownerFence,
162
+ });
163
+ } catch (error) {
164
+ this.#recordFailure(event, "attempts_exhausted", 0, error);
165
+ return false;
166
+ }
167
+ if (this.#queue.length >= this.#maxBufferedEvents) {
168
+ const oldest = this.#queue.shift();
169
+ if (oldest) {
170
+ oldest.dropped = true;
171
+ oldest.abortController?.abort();
172
+ this.#recordFailure(oldest.event, "buffer_overflow", oldest.attempts);
173
+ }
174
+ }
175
+ this.#queue.push({
176
+ event,
177
+ rawBody,
178
+ idempotencyKey: options.idempotencyKey ?? event.eventId,
179
+ attempts: 0,
180
+ dropped: false,
181
+ });
182
+ this.#ensureDrain();
183
+ return true;
184
+ }
185
+
186
+ #ensureDrain(): void {
187
+ if (this.#drainPromise || this.#queue.length === 0) return;
188
+ this.#drainPromise = Promise.resolve()
189
+ .then(() => this.#drain())
190
+ .finally(() => {
191
+ this.#drainPromise = undefined;
192
+ if (this.#queue.length > 0) this.#ensureDrain();
193
+ });
194
+ }
195
+
196
+ async #drain(): Promise<void> {
197
+ while (this.#queue.length > 0) {
198
+ const current = this.#queue[0];
199
+ if (!current) return;
200
+ current.attempts += 1;
201
+ const delivered = await this.#send(current);
202
+ if (current.dropped) continue;
203
+ if (delivered) {
204
+ if (this.#queue[0] === current) this.#queue.shift();
205
+ this.#deliveredCount += 1;
206
+ continue;
207
+ }
208
+ if (current.attempts >= this.#maxAttempts) {
209
+ if (this.#queue[0] === current) this.#queue.shift();
210
+ this.#recordFailure(current.event, "attempts_exhausted", current.attempts);
211
+ continue;
212
+ }
213
+ await delay(this.#retryDelay(current.attempts));
214
+ }
215
+ }
216
+
217
+ async #send(buffered: BufferedEvent): Promise<boolean> {
218
+ const timestamp = this.#clock().toISOString();
219
+ const path = "/v1/stateful/events";
220
+ const abortController = new AbortController();
221
+ buffered.abortController = abortController;
222
+ try {
223
+ const response = await this.#fetch(`${this.#baseUrl}${path}`, {
224
+ method: "POST",
225
+ headers: {
226
+ "content-type": "application/json",
227
+ "x-apifuse-event-id": buffered.idempotencyKey,
228
+ ...statefulSignedHeaders({
229
+ secret: this.#secret,
230
+ timestamp,
231
+ rawBody: buffered.rawBody,
232
+ method: "POST",
233
+ path,
234
+ }),
235
+ },
236
+ body: buffered.rawBody,
237
+ signal: abortController.signal,
238
+ });
239
+ return response.ok;
240
+ } catch {
241
+ return false;
242
+ } finally {
243
+ if (buffered.abortController === abortController) {
244
+ buffered.abortController = undefined;
245
+ }
246
+ }
247
+ }
248
+
249
+ #retryDelay(attempts: number): number {
250
+ const exponential = Math.min(
251
+ this.#retryMaxMs,
252
+ this.#retryBaseMs * 2 ** Math.max(0, attempts - 1),
253
+ );
254
+ const jitter = 1 + (this.#random() * 2 - 1) * this.#jitterRatio;
255
+ return Math.max(0, Math.round(exponential * jitter));
256
+ }
257
+
258
+ #recordFailure(
259
+ event: ProviderEvent,
260
+ reason: ProviderEventDeliveryFailureReason,
261
+ attempts: number,
262
+ error?: unknown,
263
+ ): void {
264
+ this.#failedCount += 1;
265
+ this.#incrementDropMetric(event);
266
+ try {
267
+ const recorded = this.#failureRecorder.record({
268
+ eventId: event.eventId,
269
+ reason,
270
+ attempts,
271
+ failedAt: this.#clock().toISOString(),
272
+ ...(error ? { error: "Provider event could not be serialized." } : {}),
273
+ });
274
+ void Promise.resolve(recorded).catch(() => {});
275
+ } catch {}
276
+ }
277
+
278
+ #incrementDropMetric(
279
+ event: ProviderEvent,
280
+ emitter: ProviderEventMetricEmitter = this.#metricEmitter,
281
+ ): void {
282
+ try {
283
+ emitter.increment(
284
+ "apifuse_stateful_provider_event_drop_total",
285
+ providerEventMetricLabels(event),
286
+ );
287
+ } catch {}
288
+ }
289
+ }
290
+
291
+ function delay(ms: number): Promise<void> {
292
+ return new Promise((resolve) => setTimeout(resolve, ms));
293
+ }
294
+
295
+ function positiveInteger(value: number, name: string): number {
296
+ if (!Number.isInteger(value) || value <= 0) {
297
+ throw new Error(`Provider event ${name} must be a positive integer.`);
298
+ }
299
+ return value;
300
+ }
301
+
302
+ function nonnegativeFinite(value: number, name: string): number {
303
+ if (!Number.isFinite(value) || value < 0) {
304
+ throw new Error(`Provider event ${name} must be a non-negative finite number.`);
305
+ }
306
+ return value;
307
+ }
308
+
309
+ function boundedRatio(value: number, name: string): number {
310
+ if (!Number.isFinite(value) || value < 0 || value > 1) {
311
+ throw new Error(`Provider event ${name} must be between 0 and 1.`);
312
+ }
313
+ return value;
314
+ }