@apifuse/provider-sdk 2.2.0-beta.7 → 2.2.0-beta.70

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 (478) hide show
  1. package/AUTHORING.md +943 -51
  2. package/CHANGELOG.md +273 -1
  3. package/LICENSE +21 -0
  4. package/README.md +117 -29
  5. package/SUBMISSION.md +3 -2
  6. package/bin/apifuse-check.ts +293 -13
  7. package/bin/apifuse-dev.ts +42 -19
  8. package/bin/apifuse-migrate-deployment.ts +200 -0
  9. package/bin/apifuse-migrate-operation-declaration.ts +55 -0
  10. package/bin/apifuse-migrate-shape.ts +202 -0
  11. package/bin/apifuse-pack-check.ts +19 -6
  12. package/bin/apifuse-pack-smoke.ts +57 -2
  13. package/bin/apifuse-pack-types.ts +736 -38
  14. package/bin/apifuse-perf.ts +59 -23
  15. package/bin/apifuse-record.ts +923 -94
  16. package/bin/apifuse-submit-check.ts +2313 -281
  17. package/bin/apifuse-test.ts +3 -1
  18. package/bin/submit-check-delimited-text.ts +50 -0
  19. package/dist/auth-turn/index.d.ts +3 -3
  20. package/dist/auth-turn/index.js +1 -1
  21. package/dist/auth.d.ts +14 -0
  22. package/dist/auth.js +67 -0
  23. package/dist/ceremonies/index.d.ts +16 -0
  24. package/dist/ceremonies/index.js +141 -36
  25. package/dist/cli/commands.d.ts +1 -1
  26. package/dist/cli/commands.js +30 -0
  27. package/dist/cli/create.d.ts +1 -1
  28. package/dist/cli/create.js +8 -2
  29. package/dist/cli/deployment-intent.d.ts +178 -0
  30. package/dist/cli/deployment-intent.js +277 -0
  31. package/dist/cli/local-connection.d.ts +111 -0
  32. package/dist/cli/local-connection.js +151 -0
  33. package/dist/cli/migrate-deployment-intent.d.ts +132 -0
  34. package/dist/cli/migrate-deployment-intent.js +1552 -0
  35. package/dist/cli/migrate-operation-declaration.d.ts +70 -0
  36. package/dist/cli/migrate-operation-declaration.js +2206 -0
  37. package/dist/cli/migrate-operation-shape.d.ts +44 -0
  38. package/dist/cli/migrate-operation-shape.js +113 -0
  39. package/dist/cli/migrate-provider-shape.d.ts +56 -0
  40. package/dist/cli/migrate-provider-shape.js +580 -0
  41. package/dist/cli/resolver-runtime.d.ts +21 -0
  42. package/dist/cli/resolver-runtime.js +31 -0
  43. package/dist/cli/templates/provider/.agents/skills/health-checks-and-fail-closed/SKILL.md.tpl +141 -23
  44. package/dist/cli/templates/provider/Dockerfile.tpl +1 -1
  45. package/dist/cli/templates/provider/README.md.tpl +41 -19
  46. package/dist/cli/templates/provider/index.ts.tpl +6 -3
  47. package/dist/cli/templates/provider/operations/ping.ts.tpl +4 -1
  48. package/dist/cli/templates/provider/provider.json.tpl +6 -0
  49. package/dist/config/loader.d.ts +202 -16
  50. package/dist/config/loader.js +491 -141
  51. package/dist/contract-serialization.js +4 -8
  52. package/dist/contract-types.d.ts +12 -5
  53. package/dist/contract.js +24 -10
  54. package/dist/declaration-validation.d.ts +34 -0
  55. package/dist/declaration-validation.js +305 -0
  56. package/dist/define.d.ts +71 -29
  57. package/dist/define.js +807 -69
  58. package/dist/dev.d.ts +5 -0
  59. package/dist/dev.js +8 -1
  60. package/dist/engine.d.ts +129 -0
  61. package/dist/engine.js +209 -0
  62. package/dist/error-observability.d.ts +7 -0
  63. package/dist/error-observability.js +61 -0
  64. package/dist/error-resolution.d.ts +18 -0
  65. package/dist/error-resolution.js +243 -0
  66. package/dist/errors.d.ts +74 -0
  67. package/dist/errors.js +99 -0
  68. package/dist/fixture-sanitization.d.ts +28 -0
  69. package/dist/fixture-sanitization.js +371 -0
  70. package/dist/handle-meta.d.ts +90 -0
  71. package/dist/handle-meta.js +73 -0
  72. package/dist/handle.d.ts +226 -0
  73. package/dist/handle.js +345 -0
  74. package/dist/health-scenario.d.ts +1901 -0
  75. package/dist/health-scenario.js +653 -0
  76. package/dist/i18n/catalog.js +21 -1
  77. package/dist/i18n/error-messages.d.ts +86 -0
  78. package/dist/i18n/error-messages.js +192 -0
  79. package/dist/i18n/index.d.ts +1 -0
  80. package/dist/i18n/index.js +1 -0
  81. package/dist/i18n/keys.d.ts +12 -0
  82. package/dist/i18n/keys.js +76 -5
  83. package/dist/i18n/operation-locale-namespace.d.ts +2 -0
  84. package/dist/i18n/operation-locale-namespace.js +7 -0
  85. package/dist/index.d.ts +40 -15
  86. package/dist/index.js +29 -11
  87. package/dist/lint.d.ts +43 -11
  88. package/dist/lint.js +2512 -177
  89. package/dist/native-address.d.ts +43 -0
  90. package/dist/native-address.js +281 -0
  91. package/dist/native-egress-policy.d.ts +31 -0
  92. package/dist/native-egress-policy.js +288 -0
  93. package/dist/observability.d.ts +5 -2
  94. package/dist/observability.js +48 -1
  95. package/dist/provider.d.ts +18 -8
  96. package/dist/provider.js +11 -6
  97. package/dist/runtime/auth-flow.d.ts +6 -1
  98. package/dist/runtime/auth-flow.js +8 -0
  99. package/dist/runtime/bounded-expiring-map.d.ts +21 -0
  100. package/dist/runtime/bounded-expiring-map.js +64 -0
  101. package/dist/runtime/browser-telemetry-binding.d.ts +5 -0
  102. package/dist/runtime/browser-telemetry-binding.js +83 -0
  103. package/dist/runtime/browser-telemetry.d.ts +76 -0
  104. package/dist/runtime/browser-telemetry.js +165 -0
  105. package/dist/runtime/browser.d.ts +4 -0
  106. package/dist/runtime/browser.js +688 -83
  107. package/dist/runtime/cache-telemetry.d.ts +71 -0
  108. package/dist/runtime/cache-telemetry.js +177 -0
  109. package/dist/runtime/cache.d.ts +3 -0
  110. package/dist/runtime/cache.js +245 -23
  111. package/dist/runtime/capability-response.d.ts +3 -0
  112. package/dist/runtime/capability-response.js +56 -0
  113. package/dist/runtime/capability-telemetry.d.ts +46 -0
  114. package/dist/runtime/capability-telemetry.js +68 -0
  115. package/dist/runtime/chrome149-header-order.d.ts +58 -0
  116. package/dist/runtime/chrome149-header-order.js +289 -0
  117. package/dist/runtime/diagnostic-env-values.d.ts +5 -0
  118. package/dist/runtime/diagnostic-env-values.js +57 -0
  119. package/dist/runtime/diagnostic-env.d.ts +14 -0
  120. package/dist/runtime/diagnostic-env.js +44 -0
  121. package/dist/runtime/diagnostic-redactor.d.ts +54 -0
  122. package/dist/runtime/diagnostic-redactor.js +571 -0
  123. package/dist/runtime/egress-lease.d.ts +65 -0
  124. package/dist/runtime/egress-lease.js +236 -0
  125. package/dist/runtime/engine-mode.d.ts +74 -0
  126. package/dist/runtime/engine-mode.js +152 -0
  127. package/dist/runtime/env.js +14 -1
  128. package/dist/runtime/executor.d.ts +3 -2
  129. package/dist/runtime/executor.js +23 -26
  130. package/dist/runtime/handle-wordlist.d.ts +43 -0
  131. package/dist/runtime/handle-wordlist.js +256 -0
  132. package/dist/runtime/handle.d.ts +71 -0
  133. package/dist/runtime/handle.js +908 -0
  134. package/dist/runtime/http-telemetry-binding.d.ts +10 -0
  135. package/dist/runtime/http-telemetry-binding.js +152 -0
  136. package/dist/runtime/http-telemetry-guard.d.ts +16 -0
  137. package/dist/runtime/http-telemetry-guard.js +202 -0
  138. package/dist/runtime/http-telemetry.d.ts +104 -0
  139. package/dist/runtime/http-telemetry.js +190 -0
  140. package/dist/runtime/http.d.ts +34 -0
  141. package/dist/runtime/http.js +680 -121
  142. package/dist/runtime/insights.js +2 -2
  143. package/dist/runtime/instrumentation.d.ts +2 -2
  144. package/dist/runtime/instrumentation.js +462 -19
  145. package/dist/runtime/native-network-errors.d.ts +33 -0
  146. package/dist/runtime/native-network-errors.js +69 -0
  147. package/dist/runtime/native-network.d.ts +105 -0
  148. package/dist/runtime/native-network.js +1433 -0
  149. package/dist/runtime/native-telemetry.d.ts +142 -0
  150. package/dist/runtime/native-telemetry.js +231 -0
  151. package/dist/runtime/ocr-telemetry.d.ts +82 -0
  152. package/dist/runtime/ocr-telemetry.js +192 -0
  153. package/dist/runtime/ocr.d.ts +29 -0
  154. package/dist/runtime/ocr.js +468 -0
  155. package/dist/runtime/otlp.d.ts +71 -2
  156. package/dist/runtime/otlp.js +398 -16
  157. package/dist/runtime/provider.d.ts +7 -0
  158. package/dist/runtime/provider.js +14 -0
  159. package/dist/runtime/proxy-errors.js +6 -2
  160. package/dist/runtime/proxy-nodemaven.d.ts +56 -0
  161. package/dist/runtime/proxy-nodemaven.js +147 -0
  162. package/dist/runtime/proxy-retry-policy.d.ts +4 -1
  163. package/dist/runtime/proxy-retry-policy.js +11 -2
  164. package/dist/runtime/proxy-telemetry.d.ts +133 -3
  165. package/dist/runtime/proxy-telemetry.js +218 -70
  166. package/dist/runtime/redirect-hop-headers.d.ts +19 -0
  167. package/dist/runtime/redirect-hop-headers.js +58 -0
  168. package/dist/runtime/redirects.d.ts +61 -0
  169. package/dist/runtime/redirects.js +149 -0
  170. package/dist/runtime/redis.d.ts +1 -1
  171. package/dist/runtime/redis.js +4 -2
  172. package/dist/runtime/request-options.d.ts +75 -1
  173. package/dist/runtime/request-options.js +588 -0
  174. package/dist/runtime/request-telemetry.d.ts +70 -0
  175. package/dist/runtime/request-telemetry.js +229 -0
  176. package/dist/runtime/resolver-config.d.ts +13 -0
  177. package/dist/runtime/resolver-config.js +14 -0
  178. package/dist/runtime/resolver-public.d.ts +3 -0
  179. package/dist/runtime/resolver-public.js +1 -0
  180. package/dist/runtime/resolver-runtime-options.d.ts +14 -0
  181. package/dist/runtime/resolver-runtime-options.js +33 -0
  182. package/dist/runtime/resolver-shared.d.ts +24 -0
  183. package/dist/runtime/resolver-shared.js +69 -0
  184. package/dist/runtime/resolver-span-attributes.d.ts +34 -0
  185. package/dist/runtime/resolver-span-attributes.js +93 -0
  186. package/dist/runtime/resolver-telemetry.d.ts +135 -0
  187. package/dist/runtime/resolver-telemetry.js +204 -0
  188. package/dist/runtime/resolver-usage.d.ts +32 -0
  189. package/dist/runtime/resolver-usage.js +54 -0
  190. package/dist/runtime/resolver-vendors/bindings.d.ts +53 -0
  191. package/dist/runtime/resolver-vendors/bindings.js +45 -0
  192. package/dist/runtime/resolver-vendors/browser.d.ts +24 -0
  193. package/dist/runtime/resolver-vendors/browser.js +423 -0
  194. package/dist/runtime/resolver-vendors/capsolver.d.ts +22 -0
  195. package/dist/runtime/resolver-vendors/capsolver.js +544 -0
  196. package/dist/runtime/resolver-vendors/hosts.d.ts +23 -0
  197. package/dist/runtime/resolver-vendors/hosts.js +68 -0
  198. package/dist/runtime/resolver-vendors/hypersolutions.d.ts +27 -0
  199. package/dist/runtime/resolver-vendors/hypersolutions.js +369 -0
  200. package/dist/runtime/resolver-vendors/twocaptcha.d.ts +24 -0
  201. package/dist/runtime/resolver-vendors/twocaptcha.js +425 -0
  202. package/dist/runtime/resolver-vendors/types.d.ts +125 -0
  203. package/dist/runtime/resolver-vendors/types.js +106 -0
  204. package/dist/runtime/resolver.d.ts +79 -0
  205. package/dist/runtime/resolver.js +1094 -0
  206. package/dist/runtime/state-telemetry.d.ts +79 -0
  207. package/dist/runtime/state-telemetry.js +194 -0
  208. package/dist/runtime/state.d.ts +10 -1
  209. package/dist/runtime/state.js +322 -82
  210. package/dist/runtime/stealth-akamai-sbsd.d.ts +54 -0
  211. package/dist/runtime/stealth-akamai-sbsd.js +142 -0
  212. package/dist/runtime/stealth-cookies.d.ts +24 -0
  213. package/dist/runtime/stealth-cookies.js +131 -0
  214. package/dist/runtime/stealth-owned-headers.d.ts +39 -0
  215. package/dist/runtime/stealth-owned-headers.js +57 -0
  216. package/dist/runtime/stealth-telemetry.d.ts +92 -0
  217. package/dist/runtime/stealth-telemetry.js +179 -0
  218. package/dist/runtime/stealth.d.ts +45 -6
  219. package/dist/runtime/stealth.js +1775 -320
  220. package/dist/runtime/stt-telemetry.d.ts +77 -0
  221. package/dist/runtime/stt-telemetry.js +176 -0
  222. package/dist/runtime/stt.js +18 -19
  223. package/dist/runtime/timeout.d.ts +5 -0
  224. package/dist/runtime/timeout.js +12 -0
  225. package/dist/runtime/trace-config.d.ts +12 -0
  226. package/dist/runtime/trace-config.js +62 -0
  227. package/dist/runtime/trace.d.ts +17 -0
  228. package/dist/runtime/trace.js +176 -25
  229. package/dist/runtime-boundary-lint.d.ts +137 -0
  230. package/dist/runtime-boundary-lint.js +590 -0
  231. package/dist/schema.d.ts +20 -0
  232. package/dist/schema.js +72 -2
  233. package/dist/serve.d.ts +1 -1
  234. package/dist/serve.js +1 -1
  235. package/dist/server/error-observability.d.ts +1 -0
  236. package/dist/server/error-observability.js +1 -0
  237. package/dist/server/error-stack-frames.d.ts +35 -0
  238. package/dist/server/error-stack-frames.js +219 -0
  239. package/dist/server/index.d.ts +21 -2
  240. package/dist/server/index.js +11 -2
  241. package/dist/server/runtime-diagnostics.d.ts +165 -0
  242. package/dist/server/runtime-diagnostics.js +360 -0
  243. package/dist/server/sdk-version.d.ts +3 -0
  244. package/dist/server/sdk-version.js +19 -0
  245. package/dist/server/self-test-input-tokens.d.ts +2 -1
  246. package/dist/server/self-test-input-tokens.js +18 -14
  247. package/dist/server/self-test-redaction.js +8 -33
  248. package/dist/server/self-test.d.ts +18 -3
  249. package/dist/server/self-test.js +139 -76
  250. package/dist/server/sensitive-values.d.ts +26 -0
  251. package/dist/server/sensitive-values.js +137 -0
  252. package/dist/server/serve-implementation.d.ts +285 -0
  253. package/dist/server/serve-implementation.js +3172 -0
  254. package/dist/server/serve.d.ts +1 -75
  255. package/dist/server/serve.js +1 -1170
  256. package/dist/server/state-backend-report.d.ts +70 -0
  257. package/dist/server/state-backend-report.js +113 -0
  258. package/dist/server/trace-output.d.ts +7 -0
  259. package/dist/server/trace-output.js +111 -0
  260. package/dist/server/types.d.ts +37 -5
  261. package/dist/server/types.js +34 -1
  262. package/dist/stateful/errors.d.ts +19 -0
  263. package/dist/stateful/errors.js +24 -0
  264. package/dist/stateful/http-provider-event-emitter.d.ts +40 -0
  265. package/dist/stateful/http-provider-event-emitter.js +237 -0
  266. package/dist/stateful/http-session-owner-registry.d.ts +44 -0
  267. package/dist/stateful/http-session-owner-registry.js +210 -0
  268. package/dist/stateful/index.d.ts +18 -0
  269. package/dist/stateful/index.js +18 -0
  270. package/dist/stateful/provider-event-delivery-failures.d.ts +32 -0
  271. package/dist/stateful/provider-event-delivery-failures.js +43 -0
  272. package/dist/stateful/provider-event-pipeline-metrics.d.ts +46 -0
  273. package/dist/stateful/provider-event-pipeline-metrics.js +48 -0
  274. package/dist/stateful/provider-event-pipeline.d.ts +50 -0
  275. package/dist/stateful/provider-event-pipeline.js +1 -0
  276. package/dist/stateful/provider-events.d.ts +101 -0
  277. package/dist/stateful/provider-events.js +289 -0
  278. package/dist/stateful/session-key.d.ts +15 -0
  279. package/dist/stateful/session-key.js +86 -0
  280. package/dist/stateful/stateful-provider-adapter-context.d.ts +5 -0
  281. package/dist/stateful/stateful-provider-adapter-context.js +42 -0
  282. package/dist/stateful/stateful-provider-adapter-metrics.d.ts +15 -0
  283. package/dist/stateful/stateful-provider-adapter-metrics.js +21 -0
  284. package/dist/stateful/stateful-provider-adapter.d.ts +98 -0
  285. package/dist/stateful/stateful-provider-adapter.js +287 -0
  286. package/dist/stateful/stateful-provider-observability.d.ts +62 -0
  287. package/dist/stateful/stateful-provider-observability.js +161 -0
  288. package/dist/stateful/stateful-provider-owner-forwarder.d.ts +41 -0
  289. package/dist/stateful/stateful-provider-owner-forwarder.js +237 -0
  290. package/dist/stateful/stateful-provider-runtime-context.d.ts +35 -0
  291. package/dist/stateful/stateful-provider-runtime-context.js +61 -0
  292. package/dist/stateful/stateful-provider-runtime-executor.d.ts +34 -0
  293. package/dist/stateful/stateful-provider-runtime-executor.js +52 -0
  294. package/dist/stateful/stateful-provider-session-routing.d.ts +67 -0
  295. package/dist/stateful/stateful-provider-session-routing.js +345 -0
  296. package/dist/stateful/stateful-provider-session-runtime.d.ts +98 -0
  297. package/dist/stateful/stateful-provider-session-runtime.js +245 -0
  298. package/dist/stateful-signing.d.ts +18 -0
  299. package/dist/stateful-signing.js +27 -0
  300. package/dist/stealth/profiles.d.ts +15 -4
  301. package/dist/stealth/profiles.js +169 -211
  302. package/dist/stream-evidence.d.ts +74 -0
  303. package/dist/stream-evidence.js +785 -0
  304. package/dist/stream.js +7 -1
  305. package/dist/testing/index.d.ts +2 -1
  306. package/dist/testing/index.js +2 -1
  307. package/dist/testing/run.d.ts +32 -2
  308. package/dist/testing/run.js +509 -25
  309. package/dist/trace-sanitization.d.ts +6 -0
  310. package/dist/trace-sanitization.js +68 -0
  311. package/dist/types.d.ts +899 -210
  312. package/dist/types.js +1 -0
  313. package/dist/typescript-module.d.ts +39 -0
  314. package/dist/typescript-module.js +72 -0
  315. package/package.json +52 -6
  316. package/src/auth-turn/index.ts +1 -1
  317. package/src/auth.ts +118 -0
  318. package/src/ceremonies/index.ts +189 -46
  319. package/src/cli/commands.ts +37 -0
  320. package/src/cli/create.ts +8 -2
  321. package/src/cli/deployment-intent.ts +364 -0
  322. package/src/cli/local-connection.ts +221 -0
  323. package/src/cli/migrate-deployment-intent.ts +1908 -0
  324. package/src/cli/migrate-operation-declaration.ts +3026 -0
  325. package/src/cli/migrate-operation-shape.ts +184 -0
  326. package/src/cli/migrate-provider-shape.ts +778 -0
  327. package/src/cli/resolver-runtime.ts +72 -0
  328. package/src/cli/templates/provider/.agents/skills/health-checks-and-fail-closed/SKILL.md.tpl +141 -23
  329. package/src/cli/templates/provider/Dockerfile.tpl +1 -1
  330. package/src/cli/templates/provider/README.md.tpl +41 -19
  331. package/src/cli/templates/provider/index.ts.tpl +6 -3
  332. package/src/cli/templates/provider/operations/ping.ts.tpl +4 -1
  333. package/src/cli/templates/provider/provider.json.tpl +6 -0
  334. package/src/config/loader.ts +774 -181
  335. package/src/contract-serialization.ts +5 -7
  336. package/src/contract-types.ts +12 -5
  337. package/src/contract.ts +24 -10
  338. package/src/declaration-validation.ts +376 -0
  339. package/src/define.ts +1105 -131
  340. package/src/dev.ts +13 -1
  341. package/src/engine.ts +390 -0
  342. package/src/error-observability.ts +64 -0
  343. package/src/error-resolution.ts +269 -0
  344. package/src/errors.ts +157 -0
  345. package/src/fixture-sanitization.ts +422 -0
  346. package/src/handle-meta.ts +143 -0
  347. package/src/handle.ts +694 -0
  348. package/src/health-scenario.ts +960 -0
  349. package/src/i18n/catalog.ts +23 -1
  350. package/src/i18n/error-messages.ts +255 -0
  351. package/src/i18n/index.ts +1 -0
  352. package/src/i18n/keys.ts +80 -6
  353. package/src/i18n/operation-locale-namespace.ts +10 -0
  354. package/src/index.ts +390 -23
  355. package/src/lint.ts +2975 -234
  356. package/src/native-address.ts +340 -0
  357. package/src/native-egress-policy.ts +358 -0
  358. package/src/observability.ts +51 -1
  359. package/src/provider.ts +207 -27
  360. package/src/runtime/auth-flow.ts +15 -0
  361. package/src/runtime/bounded-expiring-map.ts +67 -0
  362. package/src/runtime/browser-telemetry-binding.ts +92 -0
  363. package/src/runtime/browser-telemetry.ts +289 -0
  364. package/src/runtime/browser.ts +882 -99
  365. package/src/runtime/cache-telemetry.ts +247 -0
  366. package/src/runtime/cache.ts +258 -22
  367. package/src/runtime/capability-response.ts +53 -0
  368. package/src/runtime/capability-telemetry.ts +101 -0
  369. package/src/runtime/chrome149-header-order.ts +330 -0
  370. package/src/runtime/diagnostic-env-values.ts +56 -0
  371. package/src/runtime/diagnostic-env.ts +65 -0
  372. package/src/runtime/diagnostic-redactor.ts +668 -0
  373. package/src/runtime/egress-lease.ts +341 -0
  374. package/src/runtime/engine-mode.ts +233 -0
  375. package/src/runtime/env.ts +15 -1
  376. package/src/runtime/executor.ts +39 -32
  377. package/src/runtime/handle-wordlist.ts +266 -0
  378. package/src/runtime/handle.ts +1180 -0
  379. package/src/runtime/http-telemetry-binding.ts +162 -0
  380. package/src/runtime/http-telemetry-guard.ts +208 -0
  381. package/src/runtime/http-telemetry.ts +292 -0
  382. package/src/runtime/http.ts +896 -150
  383. package/src/runtime/insights.ts +2 -2
  384. package/src/runtime/instrumentation.ts +628 -28
  385. package/src/runtime/native-network-errors.ts +99 -0
  386. package/src/runtime/native-network.ts +1853 -0
  387. package/src/runtime/native-telemetry.ts +366 -0
  388. package/src/runtime/ocr-telemetry.ts +308 -0
  389. package/src/runtime/ocr.ts +564 -0
  390. package/src/runtime/otlp.ts +468 -21
  391. package/src/runtime/provider.ts +15 -0
  392. package/src/runtime/proxy-errors.ts +12 -4
  393. package/src/runtime/proxy-nodemaven.ts +222 -0
  394. package/src/runtime/proxy-retry-policy.ts +12 -1
  395. package/src/runtime/proxy-telemetry.ts +358 -103
  396. package/src/runtime/redirect-hop-headers.ts +71 -0
  397. package/src/runtime/redirects.ts +215 -0
  398. package/src/runtime/redis.ts +7 -2
  399. package/src/runtime/request-options.ts +728 -1
  400. package/src/runtime/request-telemetry.ts +375 -0
  401. package/src/runtime/resolver-config.ts +18 -0
  402. package/src/runtime/resolver-public.ts +44 -0
  403. package/src/runtime/resolver-runtime-options.ts +50 -0
  404. package/src/runtime/resolver-shared.ts +113 -0
  405. package/src/runtime/resolver-span-attributes.ts +139 -0
  406. package/src/runtime/resolver-telemetry.ts +362 -0
  407. package/src/runtime/resolver-usage.ts +99 -0
  408. package/src/runtime/resolver-vendors/bindings.ts +61 -0
  409. package/src/runtime/resolver-vendors/browser.ts +591 -0
  410. package/src/runtime/resolver-vendors/capsolver.ts +727 -0
  411. package/src/runtime/resolver-vendors/hosts.ts +81 -0
  412. package/src/runtime/resolver-vendors/hypersolutions.ts +480 -0
  413. package/src/runtime/resolver-vendors/twocaptcha.ts +555 -0
  414. package/src/runtime/resolver-vendors/types.ts +261 -0
  415. package/src/runtime/resolver.ts +1543 -0
  416. package/src/runtime/state-telemetry.ts +298 -0
  417. package/src/runtime/state.ts +435 -89
  418. package/src/runtime/stealth-akamai-sbsd.ts +207 -0
  419. package/src/runtime/stealth-cookies.ts +155 -0
  420. package/src/runtime/stealth-owned-headers.ts +67 -0
  421. package/src/runtime/stealth-telemetry.ts +309 -0
  422. package/src/runtime/stealth.ts +2275 -372
  423. package/src/runtime/stt-telemetry.ts +282 -0
  424. package/src/runtime/stt.ts +96 -91
  425. package/src/runtime/timeout.ts +18 -0
  426. package/src/runtime/trace-config.ts +78 -0
  427. package/src/runtime/trace.ts +257 -35
  428. package/src/runtime-boundary-lint.ts +707 -0
  429. package/src/schema.ts +80 -2
  430. package/src/serve.ts +6 -1
  431. package/src/server/error-observability.ts +1 -0
  432. package/src/server/error-stack-frames.ts +214 -0
  433. package/src/server/index.ts +184 -2
  434. package/src/server/runtime-diagnostics.ts +522 -0
  435. package/src/server/sdk-version.ts +22 -0
  436. package/src/server/self-test-input-tokens.ts +29 -14
  437. package/src/server/self-test-redaction.ts +10 -40
  438. package/src/server/self-test.ts +205 -89
  439. package/src/server/sensitive-values.ts +211 -0
  440. package/src/server/serve-implementation.ts +4596 -0
  441. package/src/server/serve.ts +1 -1685
  442. package/src/server/state-backend-report.ts +185 -0
  443. package/src/server/trace-output.ts +189 -0
  444. package/src/server/types.ts +34 -1
  445. package/src/stateful/README.md +146 -0
  446. package/src/stateful/errors.ts +35 -0
  447. package/src/stateful/http-provider-event-emitter.ts +314 -0
  448. package/src/stateful/http-session-owner-registry.ts +306 -0
  449. package/src/stateful/index.ts +18 -0
  450. package/src/stateful/provider-event-delivery-failures.ts +80 -0
  451. package/src/stateful/provider-event-pipeline-metrics.ts +95 -0
  452. package/src/stateful/provider-event-pipeline.ts +61 -0
  453. package/src/stateful/provider-events.ts +462 -0
  454. package/src/stateful/session-key.ts +111 -0
  455. package/src/stateful/stateful-provider-adapter-context.ts +59 -0
  456. package/src/stateful/stateful-provider-adapter-metrics.ts +48 -0
  457. package/src/stateful/stateful-provider-adapter.ts +562 -0
  458. package/src/stateful/stateful-provider-observability.ts +261 -0
  459. package/src/stateful/stateful-provider-owner-forwarder.ts +310 -0
  460. package/src/stateful/stateful-provider-runtime-context.ts +96 -0
  461. package/src/stateful/stateful-provider-runtime-executor.ts +96 -0
  462. package/src/stateful/stateful-provider-session-routing.ts +546 -0
  463. package/src/stateful/stateful-provider-session-runtime.ts +403 -0
  464. package/src/stateful-signing.ts +46 -0
  465. package/src/stealth/profiles.ts +212 -231
  466. package/src/stream-evidence.ts +988 -0
  467. package/src/stream.ts +8 -1
  468. package/src/testing/index.ts +10 -1
  469. package/src/testing/run.ts +685 -19
  470. package/src/trace-sanitization.ts +110 -0
  471. package/src/types.ts +1014 -273
  472. package/src/typescript-module.ts +100 -0
  473. package/dist/choice-token.d.ts +0 -24
  474. package/dist/choice-token.js +0 -74
  475. package/dist/runtime/choice.d.ts +0 -15
  476. package/dist/runtime/choice.js +0 -413
  477. package/src/choice-token.ts +0 -165
  478. package/src/runtime/choice.ts +0 -686
@@ -1,3 +1,4 @@
1
+ import { createHash } from "node:crypto";
1
2
  import { providerStateRedisUrlFromEnv } from "../config/loader.js";
2
3
  import { ProviderError } from "../errors.js";
3
4
  import type {
@@ -16,11 +17,88 @@ import {
16
17
  } from "./redis.js";
17
18
 
18
19
  const DEFAULT_REDIS_TIMEOUT_MS = 250;
19
- const REDIS_STATE_PREFIX = "apifuse:provider-state:v1";
20
+ const REDIS_STATE_PREFIX = "apifuse:provider-state:v2";
21
+ const PROVIDER_SCOPE_DISCRIMINATOR = "scope:provider";
22
+ const MISSING_CONNECTION_SCOPE_DISCRIMINATOR = "scope:connection:missing";
23
+ const SET_WITH_QUOTA_SCRIPT = `
24
+ local now = tonumber(ARGV[1])
25
+ local max_entries = tonumber(ARGV[2])
26
+ local expires_at = tonumber(ARGV[3])
27
+ local index_ttl = tonumber(ARGV[4])
28
+ local envelope = ARGV[5]
29
+
30
+ redis.call("ZREMRANGEBYSCORE", KEYS[2], "-inf", now)
31
+ local indexed = redis.call("ZSCORE", KEYS[2], KEYS[1])
32
+ if not indexed and redis.call("ZCARD", KEYS[2]) >= max_entries then
33
+ return {0, false}
34
+ end
35
+
36
+ redis.call("SET", KEYS[1], envelope, "PXAT", expires_at)
37
+ redis.call("ZADD", KEYS[2], expires_at, KEYS[1])
38
+ redis.call("PEXPIRE", KEYS[2], index_ttl)
39
+ return {1, envelope}
40
+ `;
41
+
42
+ const COMPARE_AND_SET_WITH_QUOTA_SCRIPT = `
43
+ local current = redis.call("GET", KEYS[1])
44
+ if current then
45
+ local ok, decoded = pcall(cjson.decode, current)
46
+ if ok and type(decoded) == "table" and decoded.deleted == true then
47
+ current = false
48
+ end
49
+ end
50
+
51
+ local now = tonumber(ARGV[2])
52
+ local max_entries = tonumber(ARGV[3])
53
+ local expires_at = tonumber(ARGV[4])
54
+ local index_ttl = tonumber(ARGV[5])
55
+ local envelope = ARGV[6]
56
+
57
+ local current_version = 0
58
+ local current_decoded = false
59
+ if current then
60
+ local ok, decoded = pcall(cjson.decode, current)
61
+ if not ok or type(decoded) ~= "table" or type(decoded.version) ~= "number" then
62
+ return {-2, current}
63
+ end
64
+ current_version = decoded.version
65
+ current_decoded = decoded
66
+ end
67
+ if current_version ~= tonumber(ARGV[1]) then
68
+ return {-1, current or false}
69
+ end
70
+
71
+ redis.call("ZREMRANGEBYSCORE", KEYS[2], "-inf", now)
72
+ local indexed = redis.call("ZSCORE", KEYS[2], KEYS[1])
73
+ if not indexed and redis.call("ZCARD", KEYS[2]) >= max_entries then
74
+ return {0, false}
75
+ end
76
+
77
+ if current_decoded and type(current_decoded.createdAt) == "string" then
78
+ -- Preserve the original createdAt by patching the encoded envelope in place.
79
+ -- The envelope puts its metadata before "value", so the first match is the
80
+ -- envelope's own key, never a provider value; re-encoding with cjson would
81
+ -- silently turn [] inside the value into {}.
82
+ local preserved = string.gsub(current_decoded.createdAt, "%%", "%%%%")
83
+ envelope = (string.gsub(envelope, '"createdAt":"[^"]*"', '"createdAt":"' .. preserved .. '"', 1))
84
+ end
85
+ redis.call("SET", KEYS[1], envelope, "PXAT", expires_at)
86
+ redis.call("ZADD", KEYS[2], expires_at, KEYS[1])
87
+ redis.call("PEXPIRE", KEYS[2], index_ttl)
88
+ return {1, envelope}
89
+ `;
90
+
91
+ const DELETE_WITH_INDEX_SCRIPT = `
92
+ redis.call("SET", KEYS[1], ARGV[1], "PX", ARGV[2])
93
+ redis.call("ZREM", KEYS[2], KEYS[1])
94
+ return 1
95
+ `;
20
96
 
21
97
  type RedisProviderRuntimeStateOptions = {
22
98
  readonly redisUrl: string;
23
99
  readonly providerId?: string;
100
+ /** Test seam; production callers use redisUrl-backed client sharing. */
101
+ readonly __redisClient?: ProviderRedisClient;
24
102
  };
25
103
 
26
104
  type RedisStateEnvelope = {
@@ -31,23 +109,29 @@ type RedisStateEnvelope = {
31
109
  readonly updatedAt: string;
32
110
  };
33
111
 
112
+ type RedisStateTombstone = {
113
+ readonly deleted: true;
114
+ };
115
+
34
116
  type RedisBackend = {
35
117
  readonly redis: ProviderRedisClient;
36
118
  };
37
119
 
38
120
  const redisBackends = new Map<string, RedisBackend>();
39
121
 
40
- function getRedisBackend(redisUrl: string): RedisBackend {
122
+ function getRedisBackend(redisUrl: string, injectedRedis?: ProviderRedisClient): RedisBackend {
41
123
  const existing = redisBackends.get(redisUrl);
42
124
  if (existing) return existing;
43
- const redis = createProviderRedisClient({
44
- redisUrl,
45
- timeoutMs: DEFAULT_REDIS_TIMEOUT_MS,
46
- onError: () => {
47
- // Runtime state operations fail closed at their call sites. Avoid noisy
48
- // unhandled Redis errors from background reconnect attempts.
49
- },
50
- });
125
+ const redis =
126
+ injectedRedis ??
127
+ createProviderRedisClient({
128
+ redisUrl,
129
+ timeoutMs: DEFAULT_REDIS_TIMEOUT_MS,
130
+ onError: () => {
131
+ // Runtime state operations fail closed at their call sites. Avoid noisy
132
+ // unhandled Redis errors from background reconnect attempts.
133
+ },
134
+ });
51
135
  const backend = { redis };
52
136
  redisBackends.set(redisUrl, backend);
53
137
  return backend;
@@ -70,21 +154,37 @@ async function requireRedisReady(redis: ProviderRedisClient): Promise<void> {
70
154
  throw new UnsupportedProviderStateError("Provider runtime state Redis is unavailable");
71
155
  }
72
156
 
73
- function providerStatePrefix(providerId: string | undefined, namespace: string): string {
74
- return `${REDIS_STATE_PREFIX}:${providerId ?? "default"}:${namespace}`;
157
+ function connectionScopeDiscriminator(connectionId: string | undefined): string {
158
+ if (connectionId === undefined) return MISSING_CONNECTION_SCOPE_DISCRIMINATOR;
159
+ const digest = createHash("sha256").update(connectionId, "utf8").digest("hex");
160
+ return `scope:connection:sha256:${digest}`;
75
161
  }
76
162
 
77
- function providerStateKey(providerId: string | undefined, namespace: string, key: string): string {
78
- return `${providerStatePrefix(providerId, namespace)}:${key}`;
163
+ function providerStatePrefix(
164
+ providerId: string | undefined,
165
+ namespace: string,
166
+ scopeDiscriminator: string,
167
+ ): string {
168
+ return `${REDIS_STATE_PREFIX}:${providerId ?? "default"}:${namespace}:${scopeDiscriminator}`;
79
169
  }
80
170
 
81
- function publicStateKey(
171
+ function providerStateKey(
82
172
  providerId: string | undefined,
83
173
  namespace: string,
84
- redisKey: string,
174
+ scopeDiscriminator: string,
175
+ key: string,
85
176
  ): string {
86
- const prefix = `${providerStatePrefix(providerId, namespace)}:`;
87
- return redisKey.startsWith(prefix) ? redisKey.slice(prefix.length) : redisKey;
177
+ return `${providerStatePrefix(providerId, namespace, scopeDiscriminator)}:${key}`;
178
+ }
179
+
180
+ function publicStateKey(redisKey: string, prefixes: readonly string[]): string {
181
+ for (const prefix of prefixes) {
182
+ const prefixWithSeparator = `${prefix}:`;
183
+ if (redisKey.startsWith(prefixWithSeparator)) {
184
+ return redisKey.slice(prefixWithSeparator.length);
185
+ }
186
+ }
187
+ return redisKey;
88
188
  }
89
189
 
90
190
  function parseStateDurationMs(ttl: StateWriteOptions["ttl"]): number {
@@ -105,17 +205,23 @@ function parseStateDurationMs(ttl: StateWriteOptions["ttl"]): number {
105
205
  return Math.max(1, amount * multiplier);
106
206
  }
107
207
 
108
- function resolveExpiresAt(ttl: StateWriteOptions["ttl"]): string {
109
- return new Date(Date.now() + parseStateDurationMs(ttl)).toISOString();
110
- }
111
-
112
208
  function envelopeFromJson(
113
209
  key: string,
114
210
  raw: string | null,
115
211
  // biome-ignore lint/suspicious/noExplicitAny: state envelopes deserialize caller-owned generic values.
116
212
  ): StateValue<any> | null {
117
213
  if (!raw) return null;
118
- const parsed: unknown = JSON.parse(raw);
214
+ // A corrupt/undecodable persisted envelope must be treated as absent rather
215
+ // than throwing a raw JSON.parse SyntaxError: an uncaught SyntaxError escapes
216
+ // the provider error taxonomy, is masked as internal_error 500, and is then
217
+ // retried by the hub (2026-07-22 catchtable reserve RCA, candidate A). Returning
218
+ // null also keeps list() from aborting the whole scan on a single bad entry.
219
+ let parsed: unknown;
220
+ try {
221
+ parsed = JSON.parse(raw);
222
+ } catch {
223
+ return null;
224
+ }
119
225
  if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
120
226
  return null;
121
227
  }
@@ -139,6 +245,21 @@ function envelopeFromJson(
139
245
  };
140
246
  }
141
247
 
248
+ function isStateTombstone(raw: string | null): boolean {
249
+ if (!raw) return false;
250
+ try {
251
+ const parsed: unknown = JSON.parse(raw);
252
+ return (
253
+ parsed !== null &&
254
+ typeof parsed === "object" &&
255
+ !Array.isArray(parsed) &&
256
+ (parsed as { deleted?: unknown }).deleted === true
257
+ );
258
+ } catch {
259
+ return false;
260
+ }
261
+ }
262
+
142
263
  function redisEnvelope(
143
264
  value: unknown,
144
265
  version: number,
@@ -146,7 +267,11 @@ function redisEnvelope(
146
267
  expiresAt: string,
147
268
  ): RedisStateEnvelope {
148
269
  const updatedAt = new Date().toISOString();
149
- return { value, version, expiresAt, createdAt, updatedAt };
270
+ // Metadata precedes `value` on purpose: the compare-and-set Lua script patches
271
+ // `createdAt` in the encoded string (first occurrence) instead of decoding and
272
+ // re-encoding with cjson, which would turn empty arrays inside `value` into
273
+ // objects. Keep the key order stable.
274
+ return { version, expiresAt, createdAt, updatedAt, value };
150
275
  }
151
276
 
152
277
  class RedisProviderStateNamespace implements ProviderStateNamespace {
@@ -155,81 +280,141 @@ class RedisProviderStateNamespace implements ProviderStateNamespace {
155
280
  private readonly providerId: string | undefined,
156
281
  private readonly namespaceName: string,
157
282
  private readonly options: StateNamespaceOptions,
283
+ private readonly scopeDiscriminator: string,
158
284
  ) {}
159
285
 
160
286
  private redisKey(key: string): string {
161
- return providerStateKey(this.providerId, this.namespaceName, key);
287
+ return providerStateKey(this.providerId, this.namespaceName, this.scopeDiscriminator, key);
288
+ }
289
+
290
+ private statePrefix(): string {
291
+ return providerStatePrefix(this.providerId, this.namespaceName, this.scopeDiscriminator);
162
292
  }
163
293
 
164
- private prefix(): string {
165
- return `${providerStatePrefix(this.providerId, this.namespaceName)}:`;
294
+ private indexKey(): string {
295
+ // Keep bookkeeping outside the caller-owned keyspace. A provider may use
296
+ // any state key (including "__index"), so a suffix inside the namespace
297
+ // could turn the ZSET into a string and break every subsequent write.
298
+ const namespaceIdentity = Buffer.from(
299
+ providerStatePrefix(this.providerId, this.namespaceName, this.scopeDiscriminator),
300
+ "utf8",
301
+ ).toString("base64url");
302
+ return `${REDIS_STATE_PREFIX}:index:${namespaceIdentity}`;
166
303
  }
167
304
 
168
- private async activeKeys(): Promise<string[]> {
305
+ private async indexedKeys(limit: number): Promise<string[]> {
169
306
  await requireRedisReady(this.backend.redis);
170
- return await withRequiredRedis(() => this.backend.redis.keys(`${this.prefix()}*`));
307
+ const now = Date.now();
308
+ return await withRequiredRedis(async () => {
309
+ await this.backend.redis.zremrangebyscore(this.indexKey(), "-inf", now);
310
+ return await this.backend.redis.zrangebyscore(
311
+ this.indexKey(),
312
+ now + 1,
313
+ "+inf",
314
+ "LIMIT",
315
+ 0,
316
+ limit,
317
+ );
318
+ });
319
+ }
320
+
321
+ private async readRaw(key: string): Promise<string | null> {
322
+ await requireRedisReady(this.backend.redis);
323
+ return await withRequiredRedis(() => this.backend.redis.get(this.redisKey(key)));
171
324
  }
172
325
 
173
326
  private enforceValueSize(value: unknown): void {
174
327
  const bytes = Buffer.byteLength(JSON.stringify(value), "utf8");
175
328
  if (bytes > this.options.maxValueBytes) {
176
- throw new UnsupportedProviderStateError(
329
+ throw stateViolationError(
177
330
  `Provider runtime state value exceeds maxValueBytes (${bytes} > ${this.options.maxValueBytes})`,
331
+ "size",
178
332
  );
179
333
  }
180
334
  }
181
335
 
182
- private async enforceMaxEntries(key: string): Promise<void> {
183
- const keys = await this.activeKeys();
184
- const redisKey = this.redisKey(key);
185
- const otherKeys = keys.filter((candidate) => candidate !== redisKey);
186
- if (otherKeys.length >= this.options.maxEntries) {
187
- throw new UnsupportedProviderStateError(
188
- `Provider runtime state namespace quota exceeded (${otherKeys.length + 1} > ${this.options.maxEntries})`,
336
+ private quotaExceeded(): UnsupportedProviderStateError {
337
+ return stateViolationError(
338
+ `Provider runtime state namespace quota exceeded (${this.options.maxEntries + 1} > ${this.options.maxEntries})`,
339
+ "quota",
340
+ );
341
+ }
342
+
343
+ private writeTiming(ttl: StateWriteOptions["ttl"]): {
344
+ expiresAt: string;
345
+ expiresAtMs: number;
346
+ indexTtlMs: number;
347
+ } {
348
+ const ttlMs = parseStateDurationMs(ttl ?? this.options.defaultTtl);
349
+ const maxTtlMs = parseStateDurationMs(this.options.maxTtl);
350
+ if (ttlMs > maxTtlMs) {
351
+ throw stateViolationError(
352
+ `Provider runtime state ttl exceeds maxTtl (${ttlMs} > ${maxTtlMs})`,
353
+ "ttl",
189
354
  );
190
355
  }
356
+ const expiresAtMs = Date.now() + ttlMs;
357
+ return {
358
+ expiresAt: new Date(expiresAtMs).toISOString(),
359
+ expiresAtMs,
360
+ indexTtlMs: maxTtlMs,
361
+ };
191
362
  }
192
363
 
193
364
  async list<T>(options?: { limit?: number; prefix?: string }): Promise<StateValue<T>[]> {
194
- const keys = (await this.activeKeys()).filter((key) => {
195
- const publicKey = publicStateKey(this.providerId, this.namespaceName, key);
196
- return options?.prefix ? publicKey.startsWith(options.prefix) : true;
197
- });
198
- const limited = keys.slice(0, Math.max(0, options?.limit ?? keys.length));
199
- if (limited.length === 0) return [];
200
- const values = await withRequiredRedis(() => this.backend.redis.mget(limited));
201
- return values.flatMap((raw, index) => {
202
- const key = limited[index];
203
- if (!key) return [];
204
- const value = envelopeFromJson(publicStateKey(this.providerId, this.namespaceName, key), raw);
205
- return value ? [value] : [];
206
- });
365
+ const requestedLimit = Math.max(0, options?.limit ?? this.options.maxEntries);
366
+ if (requestedLimit === 0) return recordStateRedisNoop([]);
367
+ const keys = await this.indexedKeys(this.options.maxEntries);
368
+ if (keys.length === 0) return [];
369
+ const values = await withRequiredRedis(() => this.backend.redis.mget(keys));
370
+ const rows: StateValue<T>[] = [];
371
+ for (const [index, raw] of values.entries()) {
372
+ const redisKey = keys[index];
373
+ if (!redisKey) continue;
374
+ const publicKey = publicStateKey(redisKey, [this.statePrefix()]);
375
+ if (options?.prefix && !publicKey.startsWith(options.prefix)) continue;
376
+ const value = envelopeFromJson(publicKey, raw);
377
+ if (!value) continue;
378
+ rows.push(value);
379
+ }
380
+ return rows.slice(0, requestedLimit);
207
381
  }
208
382
 
209
383
  async get<T>(key: string): Promise<StateValue<T> | null> {
210
- await requireRedisReady(this.backend.redis);
211
- const raw = await withRequiredRedis(() => this.backend.redis.get(this.redisKey(key)));
384
+ const raw = await this.readRaw(key);
385
+ if (isStateTombstone(raw)) return null;
212
386
  return envelopeFromJson(key, raw);
213
387
  }
214
388
 
215
389
  async set<T>(key: string, value: T, options?: StateWriteOptions): Promise<StateValue<T>> {
216
390
  this.enforceValueSize(value);
217
- await this.enforceMaxEntries(key);
218
391
  const current = await this.get<T>(key);
219
392
  const createdAt = current?.createdAt ?? new Date().toISOString();
220
393
  const version = (current?.version ?? 0) + 1;
221
- const ttl = options?.ttl ?? this.options.defaultTtl;
222
- const ttlMs = parseStateDurationMs(ttl);
223
- const expiresAt = resolveExpiresAt(ttl);
224
- const envelope = redisEnvelope(value, version, createdAt, expiresAt);
225
- await withRequiredRedis(() =>
226
- this.backend.redis.set(this.redisKey(key), JSON.stringify(envelope), "PX", ttlMs),
394
+ const timing = this.writeTiming(options?.ttl);
395
+ const envelope = redisEnvelope(value, version, createdAt, timing.expiresAt);
396
+ await requireRedisReady(this.backend.redis);
397
+ const result = await withRequiredRedis(() =>
398
+ this.backend.redis.eval(
399
+ SET_WITH_QUOTA_SCRIPT,
400
+ 2,
401
+ this.redisKey(key),
402
+ this.indexKey(),
403
+ String(Date.now()),
404
+ String(this.options.maxEntries),
405
+ String(timing.expiresAtMs),
406
+ String(timing.indexTtlMs),
407
+ JSON.stringify(envelope),
408
+ ),
227
409
  );
410
+ if (!Array.isArray(result) || Number(result[0]) !== 1) {
411
+ throw this.quotaExceeded();
412
+ }
228
413
  return {
229
414
  key,
230
415
  value,
231
416
  version,
232
- expiresAt,
417
+ expiresAt: timing.expiresAt,
233
418
  createdAt,
234
419
  updatedAt: envelope.updatedAt,
235
420
  };
@@ -253,16 +438,54 @@ class RedisProviderStateNamespace implements ProviderStateNamespace {
253
438
  options?: StateWriteOptions,
254
439
  ): Promise<StateCasResult<T>> {
255
440
  this.enforceValueSize(value);
256
- const current = await this.get<T>(key);
257
- if ((current?.version ?? 0) !== expectedVersion) {
258
- return { ok: false, current };
441
+ const createdAt = new Date().toISOString();
442
+ const timing = this.writeTiming(options?.ttl);
443
+ const envelope = redisEnvelope(value, expectedVersion + 1, createdAt, timing.expiresAt);
444
+ await requireRedisReady(this.backend.redis);
445
+ const result = await withRequiredRedis(() =>
446
+ this.backend.redis.eval(
447
+ COMPARE_AND_SET_WITH_QUOTA_SCRIPT,
448
+ 2,
449
+ this.redisKey(key),
450
+ this.indexKey(),
451
+ String(expectedVersion),
452
+ String(Date.now()),
453
+ String(this.options.maxEntries),
454
+ String(timing.expiresAtMs),
455
+ String(timing.indexTtlMs),
456
+ JSON.stringify(envelope),
457
+ ),
458
+ );
459
+ if (Array.isArray(result) && Number(result[0]) === 0) {
460
+ throw this.quotaExceeded();
461
+ }
462
+ if (!Array.isArray(result) || Number(result[0]) !== 1) {
463
+ const rawCurrent = Array.isArray(result) && typeof result[1] === "string" ? result[1] : null;
464
+ return { ok: false, current: envelopeFromJson(key, rawCurrent) };
465
+ }
466
+ const rawWritten = typeof result[1] === "string" ? result[1] : null;
467
+ const written = envelopeFromJson(key, rawWritten) as StateValue<T> | null;
468
+ if (!written) {
469
+ throw new UnsupportedProviderStateError(
470
+ "Provider runtime state CAS returned an invalid value",
471
+ );
259
472
  }
260
- return { ok: true, value: await this.set(key, value, options) };
473
+ return { ok: true, value: written };
261
474
  }
262
475
 
263
476
  async delete(key: string): Promise<void> {
264
477
  await requireRedisReady(this.backend.redis);
265
- await withRequiredRedis(() => this.backend.redis.del(this.redisKey(key)));
478
+ const tombstone = JSON.stringify({ deleted: true } satisfies RedisStateTombstone);
479
+ await withRequiredRedis(() =>
480
+ this.backend.redis.eval(
481
+ DELETE_WITH_INDEX_SCRIPT,
482
+ 2,
483
+ this.redisKey(key),
484
+ this.indexKey(),
485
+ tombstone,
486
+ String(parseStateDurationMs(this.options.maxTtl)),
487
+ ),
488
+ );
266
489
  }
267
490
 
268
491
  async increment(
@@ -280,14 +503,34 @@ class RedisProviderStateNamespace implements ProviderStateNamespace {
280
503
  class RedisProviderRuntimeState implements ProviderRuntimeState {
281
504
  readonly backend: RedisBackend;
282
505
  readonly providerId?: string;
506
+ readonly redisUrl: string;
507
+ readonly scopeDiscriminator: string;
283
508
 
284
- constructor(options: RedisProviderRuntimeStateOptions) {
285
- this.backend = getRedisBackend(options.redisUrl);
509
+ constructor(
510
+ options: RedisProviderRuntimeStateOptions,
511
+ scopeDiscriminator = MISSING_CONNECTION_SCOPE_DISCRIMINATOR,
512
+ ) {
513
+ this.backend = getRedisBackend(options.redisUrl, options.__redisClient);
286
514
  this.providerId = options.providerId;
515
+ this.redisUrl = options.redisUrl;
516
+ this.scopeDiscriminator = scopeDiscriminator;
517
+ }
518
+
519
+ forConnection(connectionId: string | undefined): ProviderRuntimeState {
520
+ return new RedisProviderRuntimeState(
521
+ { redisUrl: this.redisUrl, providerId: this.providerId },
522
+ connectionScopeDiscriminator(connectionId),
523
+ );
287
524
  }
288
525
 
289
526
  namespace(name: string, options: StateNamespaceOptions): ProviderStateNamespace {
290
- return new RedisProviderStateNamespace(this.backend, this.providerId, name, options);
527
+ return new RedisProviderStateNamespace(
528
+ this.backend,
529
+ this.providerId,
530
+ name,
531
+ options,
532
+ options.scope === "provider" ? PROVIDER_SCOPE_DISCRIMINATOR : this.scopeDiscriminator,
533
+ );
291
534
  }
292
535
  }
293
536
 
@@ -295,9 +538,34 @@ export class UnsupportedProviderStateError extends ProviderError {
295
538
  constructor(message = "Provider runtime state is not available in this runtime") {
296
539
  super(message, { code: "PROVIDER_STATE_UNSUPPORTED" });
297
540
  this.name = "UnsupportedProviderStateError";
541
+ stateViolationKinds.set(this, "unsupported");
298
542
  }
299
543
  }
300
544
 
545
+ // Keep the exact no-command result out of band, without inspecting caller options twice.
546
+ const stateRedisNoopResults = new WeakSet<object>();
547
+ function recordStateRedisNoop<T extends object>(result: T): T {
548
+ stateRedisNoopResults.add(result);
549
+ return result;
550
+ }
551
+ export function isStateRedisNoop(result: unknown): boolean {
552
+ return result !== null && typeof result === "object" && stateRedisNoopResults.has(result);
553
+ }
554
+
555
+ type StateViolationKind = "quota" | "ttl" | "size" | "unsupported";
556
+ const stateViolationKinds = new WeakMap<object, StateViolationKind>();
557
+ function stateViolationError(
558
+ message: string,
559
+ kind: Exclude<StateViolationKind, "unsupported">,
560
+ ): UnsupportedProviderStateError {
561
+ const error = new UnsupportedProviderStateError(message);
562
+ stateViolationKinds.set(error, kind);
563
+ return error;
564
+ }
565
+ export function getStateViolation(error: unknown): StateViolationKind | undefined {
566
+ return error !== null && typeof error === "object" ? stateViolationKinds.get(error) : undefined;
567
+ }
568
+
301
569
  class UnsupportedProviderStateNamespace implements ProviderStateNamespace {
302
570
  async list<T>(_options?: { limit?: number; prefix?: string }): Promise<StateValue<T>[]> {
303
571
  throw new UnsupportedProviderStateError();
@@ -337,6 +605,10 @@ class UnsupportedProviderStateNamespace implements ProviderStateNamespace {
337
605
  }
338
606
 
339
607
  class UnsupportedProviderRuntimeState implements ProviderRuntimeState {
608
+ forConnection(_connectionId: string | undefined): ProviderRuntimeState {
609
+ return new UnsupportedProviderRuntimeState();
610
+ }
611
+
340
612
  namespace(_name: string, _options: StateNamespaceOptions): ProviderStateNamespace {
341
613
  return new UnsupportedProviderStateNamespace();
342
614
  }
@@ -346,9 +618,40 @@ class MemoryProviderStateNamespace implements ProviderStateNamespace {
346
618
  // biome-ignore lint/suspicious/noExplicitAny: in-memory state stores heterogeneous generic values by key.
347
619
  readonly values = new Map<string, StateValue<any>>();
348
620
 
349
- constructor(private readonly options: StateNamespaceOptions) {}
621
+ constructor(
622
+ private readonly options: StateNamespaceOptions,
623
+ private readonly now: () => number = Date.now,
624
+ ) {}
625
+
626
+ private enforceValueSize(value: unknown): void {
627
+ const bytes = Buffer.byteLength(JSON.stringify(value), "utf8");
628
+ if (bytes > this.options.maxValueBytes) {
629
+ throw stateViolationError(
630
+ `Provider runtime state value exceeds maxValueBytes (${bytes} > ${this.options.maxValueBytes})`,
631
+ "size",
632
+ );
633
+ }
634
+ }
635
+
636
+ private enforceWritePolicy(key: string, value: unknown, ttl: StateWriteOptions["ttl"]): void {
637
+ this.enforceValueSize(value);
638
+ const ttlMs = parseStateDurationMs(ttl ?? this.options.defaultTtl);
639
+ const maxTtlMs = parseStateDurationMs(this.options.maxTtl);
640
+ if (ttlMs > maxTtlMs) {
641
+ throw stateViolationError(
642
+ `Provider runtime state ttl exceeds maxTtl (${ttlMs} > ${maxTtlMs})`,
643
+ "ttl",
644
+ );
645
+ }
646
+ if (!this.values.has(key) && this.values.size >= this.options.maxEntries) {
647
+ throw stateViolationError(
648
+ `Provider runtime state namespace quota exceeded (${this.options.maxEntries + 1} > ${this.options.maxEntries})`,
649
+ "quota",
650
+ );
651
+ }
652
+ }
350
653
 
351
- private pruneExpired(nowMs = Date.now()): void {
654
+ private pruneExpired(nowMs = this.now()): void {
352
655
  for (const [key, row] of this.values.entries()) {
353
656
  if (row.expiresAt && Date.parse(row.expiresAt) <= nowMs) {
354
657
  this.values.delete(key);
@@ -371,9 +674,13 @@ class MemoryProviderStateNamespace implements ProviderStateNamespace {
371
674
 
372
675
  async set<T>(key: string, value: T, options?: StateWriteOptions): Promise<StateValue<T>> {
373
676
  this.pruneExpired();
374
- const now = new Date().toISOString();
677
+ this.enforceWritePolicy(key, value, options?.ttl);
678
+ const now = new Date(this.now()).toISOString();
375
679
  const current = this.values.get(key);
376
- const expiresAt = resolveMemoryStateExpiresAt(options?.ttl ?? this.options.defaultTtl);
680
+ const expiresAt = resolveMemoryStateExpiresAt(
681
+ options?.ttl ?? this.options.defaultTtl,
682
+ this.now(),
683
+ );
377
684
  const row = {
378
685
  key,
379
686
  value,
@@ -397,14 +704,28 @@ class MemoryProviderStateNamespace implements ProviderStateNamespace {
397
704
  }
398
705
 
399
706
  async compareAndSet<T>(
400
- _key: string,
401
- _expectedVersion: number,
402
- _value: T,
403
- _options?: StateWriteOptions,
707
+ key: string,
708
+ expectedVersion: number,
709
+ value: T,
710
+ options?: StateWriteOptions,
404
711
  ): Promise<StateCasResult<T>> {
405
- throw new UnsupportedProviderStateError(
406
- "In-memory provider runtime state does not support compareAndSet",
407
- );
712
+ this.pruneExpired();
713
+ const current = this.values.get(key) as StateValue<T> | undefined;
714
+ if ((current?.version ?? 0) !== expectedVersion) {
715
+ return { ok: false, current: current ?? null };
716
+ }
717
+ this.enforceWritePolicy(key, value, options?.ttl);
718
+ const now = new Date(this.now()).toISOString();
719
+ const stored = {
720
+ key,
721
+ value,
722
+ version: expectedVersion + 1,
723
+ expiresAt: resolveMemoryStateExpiresAt(options?.ttl ?? this.options.defaultTtl, this.now()),
724
+ createdAt: current?.createdAt ?? now,
725
+ updatedAt: now,
726
+ } satisfies StateValue<T>;
727
+ this.values.set(key, stored);
728
+ return { ok: true, value: stored };
408
729
  }
409
730
 
410
731
  async delete(key: string): Promise<void> {
@@ -423,21 +744,40 @@ class MemoryProviderStateNamespace implements ProviderStateNamespace {
423
744
  }
424
745
  }
425
746
 
747
+ type MemoryProviderStateBackend = {
748
+ readonly namespaces: Map<string, MemoryProviderStateNamespace>;
749
+ /** Clock used for expiry; tests inject a fake clock so ttl behaviour matches production. */
750
+ readonly now: () => number;
751
+ };
752
+
426
753
  class MemoryProviderRuntimeState implements ProviderRuntimeState {
427
- readonly namespaces = new Map<string, MemoryProviderStateNamespace>();
754
+ constructor(
755
+ private readonly backend: MemoryProviderStateBackend = {
756
+ namespaces: new Map(),
757
+ now: Date.now,
758
+ },
759
+ private readonly scopeDiscriminator = MISSING_CONNECTION_SCOPE_DISCRIMINATOR,
760
+ ) {}
428
761
 
429
- namespace(name: string, _options: StateNamespaceOptions): ProviderStateNamespace {
430
- const existing = this.namespaces.get(name);
762
+ forConnection(connectionId: string | undefined): ProviderRuntimeState {
763
+ return new MemoryProviderRuntimeState(this.backend, connectionScopeDiscriminator(connectionId));
764
+ }
765
+
766
+ namespace(name: string, options: StateNamespaceOptions): ProviderStateNamespace {
767
+ const scopeDiscriminator =
768
+ options.scope === "provider" ? PROVIDER_SCOPE_DISCRIMINATOR : this.scopeDiscriminator;
769
+ const namespaceIdentity = `${scopeDiscriminator}\0${name}`;
770
+ const existing = this.backend.namespaces.get(namespaceIdentity);
431
771
  if (existing) return existing;
432
- const created = new MemoryProviderStateNamespace(_options);
433
- this.namespaces.set(name, created);
772
+ const created = new MemoryProviderStateNamespace(options, this.backend.now);
773
+ this.backend.namespaces.set(namespaceIdentity, created);
434
774
  return created;
435
775
  }
436
776
  }
437
777
 
438
- function resolveMemoryStateExpiresAt(ttl: StateWriteOptions["ttl"]): string {
778
+ function resolveMemoryStateExpiresAt(ttl: StateWriteOptions["ttl"], nowMs = Date.now()): string {
439
779
  const match = /^(\d+)(ms|s|m|h|d)$/.exec(ttl ?? "1h");
440
- if (!match) return new Date(Date.now() + 3_600_000).toISOString();
780
+ if (!match) return new Date(nowMs + 3_600_000).toISOString();
441
781
  const amount = Number(match[1]);
442
782
  const unit = match[2];
443
783
  const multiplier =
@@ -450,7 +790,7 @@ function resolveMemoryStateExpiresAt(ttl: StateWriteOptions["ttl"]): string {
450
790
  : unit === "h"
451
791
  ? 3_600_000
452
792
  : 86_400_000;
453
- return new Date(Date.now() + amount * multiplier).toISOString();
793
+ return new Date(nowMs + amount * multiplier).toISOString();
454
794
  }
455
795
 
456
796
  export function createRedisProviderRuntimeState(
@@ -475,8 +815,14 @@ export function createProviderRuntimeStateFromEnv(
475
815
  return createUnsupportedProviderRuntimeState();
476
816
  }
477
817
 
478
- export function createMemoryProviderRuntimeState(): ProviderRuntimeState {
479
- return new MemoryProviderRuntimeState();
818
+ export function createMemoryProviderRuntimeState(options?: {
819
+ /** Clock for expiry and timestamps; defaults to `Date.now`. */
820
+ readonly now?: () => number;
821
+ }): ProviderRuntimeState {
822
+ return new MemoryProviderRuntimeState({
823
+ namespaces: new Map(),
824
+ now: options?.now ?? Date.now,
825
+ });
480
826
  }
481
827
 
482
828
  export function createUnsupportedProviderRuntimeState(): ProviderRuntimeState {