@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,96 @@
1
+ import type { SessionKey } from "./session-key.js";
2
+ import type { StatefulProviderMetricEmitter } from "./stateful-provider-observability.js";
3
+ import {
4
+ type StatefulOperationExecutor,
5
+ type StatefulOperationRequest,
6
+ type StatefulOperationResult,
7
+ type StatefulOwnerForwarder,
8
+ type StatefulOwnerPod,
9
+ StatefulSessionRouter,
10
+ } from "./stateful-provider-session-routing.js";
11
+ import type { SessionOwnerRegistry } from "./stateful-provider-session-runtime.js";
12
+
13
+ export interface StatefulProviderInvocationMetadata<TInput = unknown> {
14
+ readonly requestId: string;
15
+ readonly sessionKey: SessionKey;
16
+ readonly providerId: string;
17
+ readonly operationId: string;
18
+ readonly connectionId: string;
19
+ readonly serviceAccountId: string;
20
+ readonly input: TInput;
21
+ readonly idempotencyKey?: string;
22
+ readonly deadlineAt?: string;
23
+ readonly runtimeContext?: unknown;
24
+ }
25
+
26
+ export interface StatefulProviderRuntimeExecutorOptions {
27
+ readonly currentPod: StatefulOwnerPod;
28
+ readonly registry: SessionOwnerRegistry;
29
+ readonly forwarder: StatefulOwnerForwarder;
30
+ readonly executor: StatefulOperationExecutor;
31
+ readonly leaseDurationMs: number;
32
+ readonly leaseRenewalFraction?: number;
33
+ readonly clock?: () => Date;
34
+ readonly metricEmitter?: StatefulProviderMetricEmitter;
35
+ }
36
+
37
+ export class StatefulProviderRuntimeExecutor {
38
+ readonly #router: StatefulSessionRouter;
39
+
40
+ constructor(options: StatefulProviderRuntimeExecutorOptions) {
41
+ this.#router = new StatefulSessionRouter({
42
+ currentPod: options.currentPod,
43
+ registry: options.registry,
44
+ forwarder: options.forwarder,
45
+ executor: options.executor,
46
+ leaseDurationMs: options.leaseDurationMs,
47
+ ...(options.leaseRenewalFraction !== undefined
48
+ ? { leaseRenewalFraction: options.leaseRenewalFraction }
49
+ : {}),
50
+ ...(options.clock ? { clock: options.clock } : {}),
51
+ ...(options.metricEmitter ? { metricEmitter: options.metricEmitter } : {}),
52
+ });
53
+ }
54
+
55
+ invoke(request: StatefulOperationRequest): Promise<StatefulOperationResult> {
56
+ return this.#router.route(request);
57
+ }
58
+
59
+ release(): Promise<void> {
60
+ return this.#router.release();
61
+ }
62
+
63
+ releaseSession(sessionKey: SessionKey): Promise<boolean> {
64
+ return this.#router.releaseSession(sessionKey);
65
+ }
66
+ }
67
+
68
+ export function buildStatefulOperationRequest<TInput>(
69
+ metadata: StatefulProviderInvocationMetadata<TInput>,
70
+ ): StatefulOperationRequest<TInput> {
71
+ requireNonEmpty(metadata.requestId, "requestId");
72
+ requireNonEmpty(metadata.sessionKey, "sessionKey");
73
+ requireNonEmpty(metadata.providerId, "providerId");
74
+ requireNonEmpty(metadata.operationId, "operationId");
75
+ requireNonEmpty(metadata.connectionId, "connectionId");
76
+ requireNonEmpty(metadata.serviceAccountId, "serviceAccountId");
77
+
78
+ return {
79
+ requestId: metadata.requestId,
80
+ sessionKey: metadata.sessionKey,
81
+ providerId: metadata.providerId,
82
+ operationId: metadata.operationId,
83
+ connectionId: metadata.connectionId,
84
+ serviceAccountId: metadata.serviceAccountId,
85
+ input: metadata.input,
86
+ ...(metadata.idempotencyKey ? { idempotencyKey: metadata.idempotencyKey } : {}),
87
+ ...(metadata.deadlineAt ? { deadlineAt: metadata.deadlineAt } : {}),
88
+ ...(metadata.runtimeContext !== undefined ? { runtimeContext: metadata.runtimeContext } : {}),
89
+ };
90
+ }
91
+
92
+ function requireNonEmpty(value: string, field: string): void {
93
+ if (value.trim().length === 0) {
94
+ throw new Error(`Stateful provider invocation ${field} is required.`);
95
+ }
96
+ }
@@ -0,0 +1,546 @@
1
+ import type { SessionKey } from "./session-key.js";
2
+ import { StatefulRoutingDeadlineError } from "./errors.js";
3
+ import {
4
+ NOOP_STATEFUL_PROVIDER_METRIC_EMITTER,
5
+ type StatefulProviderMetricEmitter,
6
+ } from "./stateful-provider-observability.js";
7
+ import type {
8
+ SessionOwnerRecord,
9
+ SessionOwnerRegistry,
10
+ SessionOwnerStatus,
11
+ } from "./stateful-provider-session-runtime.js";
12
+
13
+ export {
14
+ forwardingContextFromStatefulRuntimeContext,
15
+ isStatefulOperationRuntimeContext,
16
+ providerContextFromStatefulRuntimeContext,
17
+ type StatefulForwardingRuntimeContext,
18
+ type StatefulLocalRuntimeContext,
19
+ type StatefulOperationRuntimeContext,
20
+ statefulForwardingContextFromProviderRequest,
21
+ withStatefulLocalProviderContext,
22
+ } from "./stateful-provider-runtime-context.js";
23
+
24
+ export interface StatefulOperationRequest<TInput = unknown> {
25
+ readonly requestId: string;
26
+ readonly sessionKey: SessionKey;
27
+ readonly providerId: string;
28
+ readonly operationId: string;
29
+ readonly connectionId: string;
30
+ readonly serviceAccountId: string;
31
+ readonly input: TInput;
32
+ readonly idempotencyKey?: string;
33
+ readonly deadlineAt?: string;
34
+ readonly runtimeContext?: unknown;
35
+ }
36
+
37
+ export interface StatefulOperationResult<TOutput = unknown> {
38
+ readonly output: TOutput;
39
+ }
40
+
41
+ export interface StatefulOwnerPod {
42
+ readonly podId: string;
43
+ readonly endpoint: string;
44
+ }
45
+
46
+ export interface StatefulOperationExecutor<_TSession = unknown> {
47
+ executeLocal(
48
+ request: StatefulOperationRequest,
49
+ owner: SessionOwnerRecord,
50
+ signal: AbortSignal,
51
+ validateOwnership?: StatefulOwnershipValidator,
52
+ ): Promise<StatefulOperationResult>;
53
+ }
54
+
55
+ export type StatefulOwnershipValidator = (
56
+ owner: SessionOwnerRecord,
57
+ signal: AbortSignal,
58
+ ) => Promise<SessionOwnerRecord>;
59
+
60
+ export interface StatefulOwnerForwarder {
61
+ forward(
62
+ owner: SessionOwnerRecord,
63
+ request: StatefulOperationRequest,
64
+ signal: AbortSignal,
65
+ ): Promise<StatefulOperationResult>;
66
+ }
67
+
68
+ export interface StatefulSessionRouterOptions {
69
+ readonly currentPod: StatefulOwnerPod;
70
+ readonly registry: SessionOwnerRegistry;
71
+ readonly forwarder: StatefulOwnerForwarder;
72
+ readonly executor: StatefulOperationExecutor;
73
+ readonly leaseDurationMs: number;
74
+ readonly leaseRenewalFraction?: number;
75
+ /** @deprecated Acquired sessions always begin in the acquiring state. */
76
+ readonly ownerStatus?: Exclude<SessionOwnerStatus, "expired">;
77
+ readonly clock?: () => Date;
78
+ readonly metricEmitter?: StatefulProviderMetricEmitter;
79
+ }
80
+
81
+ type DeadlineSignal = {
82
+ readonly signal: AbortSignal;
83
+ readonly cleanup: () => void;
84
+ };
85
+
86
+ export { StatefulRoutingDeadlineError } from "./errors.js";
87
+
88
+ export class StatefulRoutingOwnershipError extends Error {
89
+ readonly requestId: string;
90
+ readonly sessionKey: SessionKey;
91
+
92
+ constructor(requestId: string, sessionKey: SessionKey, message: string) {
93
+ super(message);
94
+ this.name = "StatefulRoutingOwnershipError";
95
+ this.requestId = requestId;
96
+ this.sessionKey = sessionKey;
97
+ }
98
+ }
99
+
100
+ type ManagedLease = {
101
+ readonly sessionKey: SessionKey;
102
+ record: SessionOwnerRecord;
103
+ timer?: ReturnType<typeof setTimeout>;
104
+ };
105
+
106
+ const STATEFUL_SESSION_ESTABLISHMENT_FAILURE = Symbol.for(
107
+ "@apifuse/provider-sdk/stateful/session-establishment-failure@1",
108
+ );
109
+
110
+ function isStatefulSessionEstablishmentFailure(error: unknown): boolean {
111
+ if ((typeof error !== "object" || error === null) && typeof error !== "function") return false;
112
+ return (
113
+ Object.getOwnPropertyDescriptor(error, STATEFUL_SESSION_ESTABLISHMENT_FAILURE)?.value === true
114
+ );
115
+ }
116
+
117
+ export class StatefulSessionRouter {
118
+ readonly #currentPod: StatefulOwnerPod;
119
+ readonly #registry: SessionOwnerRegistry;
120
+ readonly #forwarder: StatefulOwnerForwarder;
121
+ readonly #executor: StatefulOperationExecutor;
122
+ readonly #leaseDurationMs: number;
123
+ readonly #leaseRenewIntervalMs: number;
124
+ readonly #clock: () => Date;
125
+ readonly #metricEmitter: StatefulProviderMetricEmitter;
126
+ readonly #managedLeases = new Map<SessionKey, ManagedLease>();
127
+ readonly #latestLocalAttempts = new Map<SessionKey, number>();
128
+ readonly #establishmentQueues = new Map<SessionKey, Promise<void>>();
129
+ #nextLocalAttempt = 0;
130
+
131
+ constructor(options: StatefulSessionRouterOptions) {
132
+ validateRouterLeaseDuration(options.leaseDurationMs);
133
+ const renewalFraction = options.leaseRenewalFraction ?? 1 / 3;
134
+ validateLeaseRenewalFraction(renewalFraction);
135
+ this.#currentPod = options.currentPod;
136
+ this.#registry = options.registry;
137
+ this.#forwarder = options.forwarder;
138
+ this.#executor = options.executor;
139
+ this.#leaseDurationMs = options.leaseDurationMs;
140
+ this.#leaseRenewIntervalMs = Math.max(1, Math.floor(options.leaseDurationMs * renewalFraction));
141
+ this.#clock = options.clock ?? (() => new Date());
142
+ this.#metricEmitter = options.metricEmitter ?? NOOP_STATEFUL_PROVIDER_METRIC_EMITTER;
143
+ }
144
+
145
+ async route<TInput = unknown>(
146
+ request: StatefulOperationRequest<TInput>,
147
+ ): Promise<StatefulOperationResult> {
148
+ const now = this.#clock();
149
+ if (isExpiredDeadline(request, now)) {
150
+ this.#metricEmitter.increment("apifuse_stateful_provider_routing_deadline_expired_total", {
151
+ requestId: request.requestId,
152
+ providerId: request.providerId,
153
+ connectionId: request.connectionId,
154
+ serviceAccountId: request.serviceAccountId,
155
+ operationId: request.operationId,
156
+ sessionKey: request.sessionKey,
157
+ redactedErrorCode: "deadline_expired",
158
+ });
159
+ throw new StatefulRoutingDeadlineError(request.requestId, request.deadlineAt);
160
+ }
161
+ const deadlineSignal = makeDeadlineSignal(request, now);
162
+
163
+ try {
164
+ const route = await this.runEstablishmentTask(request.sessionKey, async () => {
165
+ const resolved = await this.#registry.resolve(
166
+ request.sessionKey,
167
+ now,
168
+ deadlineSignal.signal,
169
+ );
170
+ if (resolved) {
171
+ validateOwnerGeneration(resolved);
172
+ return this.prepareRouteToOwner(request, resolved, deadlineSignal.signal);
173
+ }
174
+
175
+ const acquired = await this.#registry.acquire(
176
+ {
177
+ sessionKey: request.sessionKey,
178
+ ownerPodId: this.#currentPod.podId,
179
+ ownerEndpoint: this.#currentPod.endpoint,
180
+ leaseDurationMs: this.#leaseDurationMs,
181
+ status: "acquiring",
182
+ now,
183
+ },
184
+ deadlineSignal.signal,
185
+ );
186
+ validateOwnerGeneration(acquired.record);
187
+ this.#metricEmitter.increment(
188
+ "apifuse_stateful_provider_routing_reacquire_total",
189
+ metricLabels(request, acquired.record),
190
+ );
191
+ return this.prepareRouteToOwner(request, acquired.record, deadlineSignal.signal);
192
+ });
193
+ return await this.routeToOwner(request, route, deadlineSignal);
194
+ } finally {
195
+ deadlineSignal.cleanup();
196
+ }
197
+ }
198
+
199
+ async release(): Promise<void> {
200
+ const sessionKeys = new Set([
201
+ ...this.#managedLeases.keys(),
202
+ ...this.#latestLocalAttempts.keys(),
203
+ ]);
204
+ await Promise.all([...sessionKeys].map((key) => this.releaseSession(key)));
205
+ }
206
+
207
+ async releaseSession(sessionKey: SessionKey): Promise<boolean> {
208
+ return this.runEstablishmentTask(sessionKey, async () => {
209
+ try {
210
+ const managed = this.#managedLeases.get(sessionKey);
211
+ if (!managed) return false;
212
+ this.stopManagedLease(managed);
213
+ this.#managedLeases.delete(sessionKey);
214
+ return await this.#registry.release({
215
+ sessionKey,
216
+ ownerPodId: managed.record.ownerPodId,
217
+ generation: managed.record.generation,
218
+ });
219
+ } finally {
220
+ this.#latestLocalAttempts.delete(sessionKey);
221
+ }
222
+ });
223
+ }
224
+
225
+ /** @internal Test-only visibility into local-attempt lifecycle cleanup. */
226
+ get latestLocalAttemptCountForTesting(): number {
227
+ return this.#latestLocalAttempts.size;
228
+ }
229
+
230
+ private async prepareRouteToOwner<TInput = unknown>(
231
+ request: StatefulOperationRequest<TInput>,
232
+ owner: SessionOwnerRecord,
233
+ signal: AbortSignal,
234
+ ): Promise<{ readonly owner: SessionOwnerRecord; readonly localAttempt?: number }> {
235
+ if (owner.sessionKey !== request.sessionKey) {
236
+ throw new StatefulRoutingOwnershipError(
237
+ request.requestId,
238
+ request.sessionKey,
239
+ "Resolved stateful session owner does not match request session key.",
240
+ );
241
+ }
242
+ if (owner.ownerPodId !== this.#currentPod.podId) {
243
+ this.forgetManagedLease(request.sessionKey);
244
+ this.#metricEmitter.increment(
245
+ "apifuse_stateful_provider_routing_forward_total",
246
+ metricLabels(request, owner),
247
+ );
248
+ return { owner };
249
+ }
250
+
251
+ const localAttempt = ++this.#nextLocalAttempt;
252
+ this.#latestLocalAttempts.set(request.sessionKey, localAttempt);
253
+ try {
254
+ const validatedOwner = await this.validateLocalOwnership(request, owner, signal, false);
255
+ this.ensureManagedLease(request.sessionKey, validatedOwner);
256
+ this.#metricEmitter.increment(
257
+ "apifuse_stateful_provider_routing_local_total",
258
+ metricLabels(request, validatedOwner),
259
+ );
260
+ return { owner: validatedOwner, localAttempt };
261
+ } catch (error) {
262
+ if (this.#latestLocalAttempts.get(request.sessionKey) === localAttempt) {
263
+ this.#latestLocalAttempts.delete(request.sessionKey);
264
+ }
265
+ throw error;
266
+ }
267
+ }
268
+
269
+ private async routeToOwner<TInput = unknown>(
270
+ request: StatefulOperationRequest<TInput>,
271
+ route: { readonly owner: SessionOwnerRecord; readonly localAttempt?: number },
272
+ deadlineSignal: DeadlineSignal,
273
+ ): Promise<StatefulOperationResult> {
274
+ const { owner, localAttempt } = route;
275
+ if (localAttempt !== undefined) {
276
+ try {
277
+ return await this.#executor.executeLocal(
278
+ request,
279
+ owner,
280
+ deadlineSignal.signal,
281
+ (expected, signal) =>
282
+ this.runEstablishmentTask(request.sessionKey, () =>
283
+ this.validateLocalOwnership(request, expected, signal, true),
284
+ ),
285
+ );
286
+ } catch (error) {
287
+ if (isStatefulSessionEstablishmentFailure(error)) {
288
+ await this.releaseFailedEstablishment(request.sessionKey, owner, localAttempt);
289
+ }
290
+ throw error;
291
+ }
292
+ }
293
+ return this.#forwarder.forward(owner, request, deadlineSignal.signal);
294
+ }
295
+
296
+ private ensureManagedLease(sessionKey: SessionKey, owner: SessionOwnerRecord): void {
297
+ const current = this.#managedLeases.get(sessionKey);
298
+ if (
299
+ current &&
300
+ current.record.ownerPodId === owner.ownerPodId &&
301
+ current.record.generation === owner.generation
302
+ ) {
303
+ current.record = owner;
304
+ return;
305
+ }
306
+ if (current) this.stopManagedLease(current);
307
+ const managed: ManagedLease = { sessionKey, record: owner };
308
+ this.#managedLeases.set(sessionKey, managed);
309
+ this.scheduleRenewal(managed);
310
+ }
311
+
312
+ private scheduleRenewal(managed: ManagedLease): void {
313
+ managed.timer = setTimeout(
314
+ () => void this.renewManagedLease(managed),
315
+ this.#leaseRenewIntervalMs,
316
+ );
317
+ managed.timer.unref?.();
318
+ }
319
+
320
+ private async renewManagedLease(managed: ManagedLease): Promise<void> {
321
+ if (this.#managedLeases.get(managed.sessionKey) !== managed) return;
322
+ try {
323
+ const renewed = await this.#registry.renew({
324
+ sessionKey: managed.sessionKey,
325
+ ownerPodId: managed.record.ownerPodId,
326
+ generation: managed.record.generation,
327
+ leaseDurationMs: this.#leaseDurationMs,
328
+ now: this.#clock(),
329
+ });
330
+ if (!renewed) {
331
+ this.forgetManagedLease(managed.sessionKey);
332
+ return;
333
+ }
334
+ validateOwnerGeneration(renewed);
335
+ managed.record = renewed;
336
+ } catch {
337
+ // A transient control-plane failure is retried on the next renewal interval.
338
+ } finally {
339
+ if (this.#managedLeases.get(managed.sessionKey) === managed) {
340
+ this.scheduleRenewal(managed);
341
+ }
342
+ }
343
+ }
344
+
345
+ private async validateLocalOwnership(
346
+ request: StatefulOperationRequest,
347
+ expected: SessionOwnerRecord,
348
+ signal: AbortSignal,
349
+ markConnected: boolean,
350
+ ): Promise<SessionOwnerRecord> {
351
+ const resolved = await this.#registry.resolve(request.sessionKey, this.#clock(), signal);
352
+ if (!sameOwner(resolved, expected)) {
353
+ this.forgetManagedLease(request.sessionKey);
354
+ throw ownershipChanged(request);
355
+ }
356
+ if (!markConnected) return resolved;
357
+ const connected = await this.#registry.renew(
358
+ {
359
+ sessionKey: request.sessionKey,
360
+ ownerPodId: resolved.ownerPodId,
361
+ generation: resolved.generation,
362
+ leaseDurationMs: this.#leaseDurationMs,
363
+ status: "connected",
364
+ now: this.#clock(),
365
+ },
366
+ signal,
367
+ );
368
+ if (!sameOwner(connected, expected)) {
369
+ this.forgetManagedLease(request.sessionKey);
370
+ throw ownershipChanged(request);
371
+ }
372
+ validateOwnerGeneration(connected);
373
+ this.ensureManagedLease(request.sessionKey, connected);
374
+ return connected;
375
+ }
376
+
377
+ private forgetManagedLease(sessionKey: SessionKey): void {
378
+ this.#latestLocalAttempts.delete(sessionKey);
379
+ const managed = this.#managedLeases.get(sessionKey);
380
+ if (!managed) return;
381
+ this.stopManagedLease(managed);
382
+ this.#managedLeases.delete(sessionKey);
383
+ }
384
+
385
+ private async releaseFailedEstablishment(
386
+ sessionKey: SessionKey,
387
+ owner: SessionOwnerRecord,
388
+ localAttempt: number,
389
+ ): Promise<void> {
390
+ await this.runEstablishmentTask(sessionKey, async () => {
391
+ if (this.#latestLocalAttempts.get(sessionKey) !== localAttempt) return;
392
+ try {
393
+ const managed = this.#managedLeases.get(sessionKey);
394
+ if (
395
+ managed &&
396
+ managed.record.ownerPodId === owner.ownerPodId &&
397
+ managed.record.generation === owner.generation
398
+ ) {
399
+ this.stopManagedLease(managed);
400
+ this.#managedLeases.delete(sessionKey);
401
+ }
402
+ try {
403
+ await this.#registry.release({
404
+ sessionKey,
405
+ ownerPodId: owner.ownerPodId,
406
+ generation: owner.generation,
407
+ });
408
+ } catch (error) {
409
+ try {
410
+ console.error(
411
+ JSON.stringify({
412
+ event: "stateful_session_establishment_lease_release_failed",
413
+ sessionKey: owner.sessionKey,
414
+ ownerPodId: owner.ownerPodId,
415
+ generation: owner.generation,
416
+ errorClass: error instanceof Error ? error.name : "UnknownError",
417
+ }),
418
+ );
419
+ } catch {}
420
+ }
421
+ } finally {
422
+ if (this.#latestLocalAttempts.get(sessionKey) === localAttempt) {
423
+ this.#latestLocalAttempts.delete(sessionKey);
424
+ }
425
+ }
426
+ });
427
+ }
428
+
429
+ private async runEstablishmentTask<T>(
430
+ sessionKey: SessionKey,
431
+ task: () => T | Promise<T>,
432
+ ): Promise<T> {
433
+ const previous = this.#establishmentQueues.get(sessionKey) ?? Promise.resolve();
434
+ const next = previous.then(task, task);
435
+ const settled = next.then(
436
+ () => undefined,
437
+ () => undefined,
438
+ );
439
+ this.#establishmentQueues.set(sessionKey, settled);
440
+ try {
441
+ return await next;
442
+ } finally {
443
+ if (this.#establishmentQueues.get(sessionKey) === settled) {
444
+ this.#establishmentQueues.delete(sessionKey);
445
+ }
446
+ }
447
+ }
448
+
449
+ private stopManagedLease(managed: ManagedLease): void {
450
+ if (managed.timer) clearTimeout(managed.timer);
451
+ managed.timer = undefined;
452
+ }
453
+ }
454
+
455
+ function metricLabels(
456
+ request: StatefulOperationRequest,
457
+ owner: SessionOwnerRecord,
458
+ ): {
459
+ readonly requestId: string;
460
+ readonly providerId: string;
461
+ readonly connectionId: string;
462
+ readonly serviceAccountId: string;
463
+ readonly operationId: string;
464
+ readonly sessionKey: string;
465
+ readonly ownerPodId: string;
466
+ readonly generation: number;
467
+ } {
468
+ return {
469
+ requestId: request.requestId,
470
+ providerId: request.providerId,
471
+ connectionId: request.connectionId,
472
+ serviceAccountId: request.serviceAccountId,
473
+ operationId: request.operationId,
474
+ sessionKey: request.sessionKey,
475
+ ownerPodId: owner.ownerPodId,
476
+ generation: owner.generation,
477
+ };
478
+ }
479
+
480
+ function validateRouterLeaseDuration(leaseDurationMs: number): void {
481
+ if (!Number.isFinite(leaseDurationMs) || leaseDurationMs <= 0) {
482
+ throw new Error("Stateful session router leaseDurationMs must be a positive finite number.");
483
+ }
484
+ }
485
+
486
+ function validateLeaseRenewalFraction(fraction: number): void {
487
+ if (!Number.isFinite(fraction) || fraction <= 0 || fraction >= 1) {
488
+ throw new Error(
489
+ "Stateful session router leaseRenewalFraction must be greater than 0 and less than 1.",
490
+ );
491
+ }
492
+ }
493
+
494
+ function validateOwnerGeneration(owner: SessionOwnerRecord): void {
495
+ if (!Number.isInteger(owner.generation) || owner.generation <= 0) {
496
+ throw new Error("Stateful session owner generation must be a positive integer.");
497
+ }
498
+ }
499
+
500
+ function sameOwner(
501
+ actual: SessionOwnerRecord | null,
502
+ expected: SessionOwnerRecord,
503
+ ): actual is SessionOwnerRecord {
504
+ return (
505
+ actual !== null &&
506
+ actual.sessionKey === expected.sessionKey &&
507
+ actual.ownerPodId === expected.ownerPodId &&
508
+ actual.generation === expected.generation
509
+ );
510
+ }
511
+
512
+ function ownershipChanged(request: StatefulOperationRequest): StatefulRoutingOwnershipError {
513
+ return new StatefulRoutingOwnershipError(
514
+ request.requestId,
515
+ request.sessionKey,
516
+ "Stateful session ownership changed before the local provider invocation.",
517
+ );
518
+ }
519
+
520
+ function isExpiredDeadline<TInput>(
521
+ request: StatefulOperationRequest<TInput>,
522
+ now: Date,
523
+ ): request is StatefulOperationRequest<TInput> & {
524
+ readonly deadlineAt: string;
525
+ } {
526
+ if (!request.deadlineAt) return false;
527
+ const deadlineMs = Date.parse(request.deadlineAt);
528
+ return !Number.isFinite(deadlineMs) || deadlineMs <= now.getTime();
529
+ }
530
+
531
+ function makeDeadlineSignal<TInput>(
532
+ request: StatefulOperationRequest<TInput>,
533
+ now: Date,
534
+ ): DeadlineSignal {
535
+ const controller = new AbortController();
536
+ if (!request.deadlineAt) {
537
+ return { signal: controller.signal, cleanup: () => {} };
538
+ }
539
+
540
+ const delayMs = Date.parse(request.deadlineAt) - now.getTime();
541
+ const timer = setTimeout(() => controller.abort(), delayMs);
542
+ return {
543
+ signal: controller.signal,
544
+ cleanup: () => clearTimeout(timer),
545
+ };
546
+ }