@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.
- package/AUTHORING.md +943 -51
- package/CHANGELOG.md +273 -1
- package/LICENSE +21 -0
- package/README.md +117 -29
- package/SUBMISSION.md +3 -2
- package/bin/apifuse-check.ts +293 -13
- package/bin/apifuse-dev.ts +42 -19
- package/bin/apifuse-migrate-deployment.ts +200 -0
- package/bin/apifuse-migrate-operation-declaration.ts +55 -0
- package/bin/apifuse-migrate-shape.ts +202 -0
- package/bin/apifuse-pack-check.ts +19 -6
- package/bin/apifuse-pack-smoke.ts +57 -2
- package/bin/apifuse-pack-types.ts +736 -38
- package/bin/apifuse-perf.ts +59 -23
- package/bin/apifuse-record.ts +923 -94
- package/bin/apifuse-submit-check.ts +2313 -281
- package/bin/apifuse-test.ts +3 -1
- package/bin/submit-check-delimited-text.ts +50 -0
- package/dist/auth-turn/index.d.ts +3 -3
- package/dist/auth-turn/index.js +1 -1
- package/dist/auth.d.ts +14 -0
- package/dist/auth.js +67 -0
- package/dist/ceremonies/index.d.ts +16 -0
- package/dist/ceremonies/index.js +141 -36
- package/dist/cli/commands.d.ts +1 -1
- package/dist/cli/commands.js +30 -0
- package/dist/cli/create.d.ts +1 -1
- package/dist/cli/create.js +8 -2
- package/dist/cli/deployment-intent.d.ts +178 -0
- package/dist/cli/deployment-intent.js +277 -0
- package/dist/cli/local-connection.d.ts +111 -0
- package/dist/cli/local-connection.js +151 -0
- package/dist/cli/migrate-deployment-intent.d.ts +132 -0
- package/dist/cli/migrate-deployment-intent.js +1552 -0
- package/dist/cli/migrate-operation-declaration.d.ts +70 -0
- package/dist/cli/migrate-operation-declaration.js +2206 -0
- package/dist/cli/migrate-operation-shape.d.ts +44 -0
- package/dist/cli/migrate-operation-shape.js +113 -0
- package/dist/cli/migrate-provider-shape.d.ts +56 -0
- package/dist/cli/migrate-provider-shape.js +580 -0
- package/dist/cli/resolver-runtime.d.ts +21 -0
- package/dist/cli/resolver-runtime.js +31 -0
- package/dist/cli/templates/provider/.agents/skills/health-checks-and-fail-closed/SKILL.md.tpl +141 -23
- package/dist/cli/templates/provider/Dockerfile.tpl +1 -1
- package/dist/cli/templates/provider/README.md.tpl +41 -19
- package/dist/cli/templates/provider/index.ts.tpl +6 -3
- package/dist/cli/templates/provider/operations/ping.ts.tpl +4 -1
- package/dist/cli/templates/provider/provider.json.tpl +6 -0
- package/dist/config/loader.d.ts +202 -16
- package/dist/config/loader.js +491 -141
- package/dist/contract-serialization.js +4 -8
- package/dist/contract-types.d.ts +12 -5
- package/dist/contract.js +24 -10
- package/dist/declaration-validation.d.ts +34 -0
- package/dist/declaration-validation.js +305 -0
- package/dist/define.d.ts +71 -29
- package/dist/define.js +807 -69
- package/dist/dev.d.ts +5 -0
- package/dist/dev.js +8 -1
- package/dist/engine.d.ts +129 -0
- package/dist/engine.js +209 -0
- package/dist/error-observability.d.ts +7 -0
- package/dist/error-observability.js +61 -0
- package/dist/error-resolution.d.ts +18 -0
- package/dist/error-resolution.js +243 -0
- package/dist/errors.d.ts +74 -0
- package/dist/errors.js +99 -0
- package/dist/fixture-sanitization.d.ts +28 -0
- package/dist/fixture-sanitization.js +371 -0
- package/dist/handle-meta.d.ts +90 -0
- package/dist/handle-meta.js +73 -0
- package/dist/handle.d.ts +226 -0
- package/dist/handle.js +345 -0
- package/dist/health-scenario.d.ts +1901 -0
- package/dist/health-scenario.js +653 -0
- package/dist/i18n/catalog.js +21 -1
- package/dist/i18n/error-messages.d.ts +86 -0
- package/dist/i18n/error-messages.js +192 -0
- package/dist/i18n/index.d.ts +1 -0
- package/dist/i18n/index.js +1 -0
- package/dist/i18n/keys.d.ts +12 -0
- package/dist/i18n/keys.js +76 -5
- package/dist/i18n/operation-locale-namespace.d.ts +2 -0
- package/dist/i18n/operation-locale-namespace.js +7 -0
- package/dist/index.d.ts +40 -15
- package/dist/index.js +29 -11
- package/dist/lint.d.ts +43 -11
- package/dist/lint.js +2512 -177
- package/dist/native-address.d.ts +43 -0
- package/dist/native-address.js +281 -0
- package/dist/native-egress-policy.d.ts +31 -0
- package/dist/native-egress-policy.js +288 -0
- package/dist/observability.d.ts +5 -2
- package/dist/observability.js +48 -1
- package/dist/provider.d.ts +18 -8
- package/dist/provider.js +11 -6
- package/dist/runtime/auth-flow.d.ts +6 -1
- package/dist/runtime/auth-flow.js +8 -0
- package/dist/runtime/bounded-expiring-map.d.ts +21 -0
- package/dist/runtime/bounded-expiring-map.js +64 -0
- package/dist/runtime/browser-telemetry-binding.d.ts +5 -0
- package/dist/runtime/browser-telemetry-binding.js +83 -0
- package/dist/runtime/browser-telemetry.d.ts +76 -0
- package/dist/runtime/browser-telemetry.js +165 -0
- package/dist/runtime/browser.d.ts +4 -0
- package/dist/runtime/browser.js +688 -83
- package/dist/runtime/cache-telemetry.d.ts +71 -0
- package/dist/runtime/cache-telemetry.js +177 -0
- package/dist/runtime/cache.d.ts +3 -0
- package/dist/runtime/cache.js +245 -23
- package/dist/runtime/capability-response.d.ts +3 -0
- package/dist/runtime/capability-response.js +56 -0
- package/dist/runtime/capability-telemetry.d.ts +46 -0
- package/dist/runtime/capability-telemetry.js +68 -0
- package/dist/runtime/chrome149-header-order.d.ts +58 -0
- package/dist/runtime/chrome149-header-order.js +289 -0
- package/dist/runtime/diagnostic-env-values.d.ts +5 -0
- package/dist/runtime/diagnostic-env-values.js +57 -0
- package/dist/runtime/diagnostic-env.d.ts +14 -0
- package/dist/runtime/diagnostic-env.js +44 -0
- package/dist/runtime/diagnostic-redactor.d.ts +54 -0
- package/dist/runtime/diagnostic-redactor.js +571 -0
- package/dist/runtime/egress-lease.d.ts +65 -0
- package/dist/runtime/egress-lease.js +236 -0
- package/dist/runtime/engine-mode.d.ts +74 -0
- package/dist/runtime/engine-mode.js +152 -0
- package/dist/runtime/env.js +14 -1
- package/dist/runtime/executor.d.ts +3 -2
- package/dist/runtime/executor.js +23 -26
- package/dist/runtime/handle-wordlist.d.ts +43 -0
- package/dist/runtime/handle-wordlist.js +256 -0
- package/dist/runtime/handle.d.ts +71 -0
- package/dist/runtime/handle.js +908 -0
- package/dist/runtime/http-telemetry-binding.d.ts +10 -0
- package/dist/runtime/http-telemetry-binding.js +152 -0
- package/dist/runtime/http-telemetry-guard.d.ts +16 -0
- package/dist/runtime/http-telemetry-guard.js +202 -0
- package/dist/runtime/http-telemetry.d.ts +104 -0
- package/dist/runtime/http-telemetry.js +190 -0
- package/dist/runtime/http.d.ts +34 -0
- package/dist/runtime/http.js +680 -121
- package/dist/runtime/insights.js +2 -2
- package/dist/runtime/instrumentation.d.ts +2 -2
- package/dist/runtime/instrumentation.js +462 -19
- package/dist/runtime/native-network-errors.d.ts +33 -0
- package/dist/runtime/native-network-errors.js +69 -0
- package/dist/runtime/native-network.d.ts +105 -0
- package/dist/runtime/native-network.js +1433 -0
- package/dist/runtime/native-telemetry.d.ts +142 -0
- package/dist/runtime/native-telemetry.js +231 -0
- package/dist/runtime/ocr-telemetry.d.ts +82 -0
- package/dist/runtime/ocr-telemetry.js +192 -0
- package/dist/runtime/ocr.d.ts +29 -0
- package/dist/runtime/ocr.js +468 -0
- package/dist/runtime/otlp.d.ts +71 -2
- package/dist/runtime/otlp.js +398 -16
- package/dist/runtime/provider.d.ts +7 -0
- package/dist/runtime/provider.js +14 -0
- package/dist/runtime/proxy-errors.js +6 -2
- package/dist/runtime/proxy-nodemaven.d.ts +56 -0
- package/dist/runtime/proxy-nodemaven.js +147 -0
- package/dist/runtime/proxy-retry-policy.d.ts +4 -1
- package/dist/runtime/proxy-retry-policy.js +11 -2
- package/dist/runtime/proxy-telemetry.d.ts +133 -3
- package/dist/runtime/proxy-telemetry.js +218 -70
- package/dist/runtime/redirect-hop-headers.d.ts +19 -0
- package/dist/runtime/redirect-hop-headers.js +58 -0
- package/dist/runtime/redirects.d.ts +61 -0
- package/dist/runtime/redirects.js +149 -0
- package/dist/runtime/redis.d.ts +1 -1
- package/dist/runtime/redis.js +4 -2
- package/dist/runtime/request-options.d.ts +75 -1
- package/dist/runtime/request-options.js +588 -0
- package/dist/runtime/request-telemetry.d.ts +70 -0
- package/dist/runtime/request-telemetry.js +229 -0
- package/dist/runtime/resolver-config.d.ts +13 -0
- package/dist/runtime/resolver-config.js +14 -0
- package/dist/runtime/resolver-public.d.ts +3 -0
- package/dist/runtime/resolver-public.js +1 -0
- package/dist/runtime/resolver-runtime-options.d.ts +14 -0
- package/dist/runtime/resolver-runtime-options.js +33 -0
- package/dist/runtime/resolver-shared.d.ts +24 -0
- package/dist/runtime/resolver-shared.js +69 -0
- package/dist/runtime/resolver-span-attributes.d.ts +34 -0
- package/dist/runtime/resolver-span-attributes.js +93 -0
- package/dist/runtime/resolver-telemetry.d.ts +135 -0
- package/dist/runtime/resolver-telemetry.js +204 -0
- package/dist/runtime/resolver-usage.d.ts +32 -0
- package/dist/runtime/resolver-usage.js +54 -0
- package/dist/runtime/resolver-vendors/bindings.d.ts +53 -0
- package/dist/runtime/resolver-vendors/bindings.js +45 -0
- package/dist/runtime/resolver-vendors/browser.d.ts +24 -0
- package/dist/runtime/resolver-vendors/browser.js +423 -0
- package/dist/runtime/resolver-vendors/capsolver.d.ts +22 -0
- package/dist/runtime/resolver-vendors/capsolver.js +544 -0
- package/dist/runtime/resolver-vendors/hosts.d.ts +23 -0
- package/dist/runtime/resolver-vendors/hosts.js +68 -0
- package/dist/runtime/resolver-vendors/hypersolutions.d.ts +27 -0
- package/dist/runtime/resolver-vendors/hypersolutions.js +369 -0
- package/dist/runtime/resolver-vendors/twocaptcha.d.ts +24 -0
- package/dist/runtime/resolver-vendors/twocaptcha.js +425 -0
- package/dist/runtime/resolver-vendors/types.d.ts +125 -0
- package/dist/runtime/resolver-vendors/types.js +106 -0
- package/dist/runtime/resolver.d.ts +79 -0
- package/dist/runtime/resolver.js +1094 -0
- package/dist/runtime/state-telemetry.d.ts +79 -0
- package/dist/runtime/state-telemetry.js +194 -0
- package/dist/runtime/state.d.ts +10 -1
- package/dist/runtime/state.js +322 -82
- package/dist/runtime/stealth-akamai-sbsd.d.ts +54 -0
- package/dist/runtime/stealth-akamai-sbsd.js +142 -0
- package/dist/runtime/stealth-cookies.d.ts +24 -0
- package/dist/runtime/stealth-cookies.js +131 -0
- package/dist/runtime/stealth-owned-headers.d.ts +39 -0
- package/dist/runtime/stealth-owned-headers.js +57 -0
- package/dist/runtime/stealth-telemetry.d.ts +92 -0
- package/dist/runtime/stealth-telemetry.js +179 -0
- package/dist/runtime/stealth.d.ts +45 -6
- package/dist/runtime/stealth.js +1775 -320
- package/dist/runtime/stt-telemetry.d.ts +77 -0
- package/dist/runtime/stt-telemetry.js +176 -0
- package/dist/runtime/stt.js +18 -19
- package/dist/runtime/timeout.d.ts +5 -0
- package/dist/runtime/timeout.js +12 -0
- package/dist/runtime/trace-config.d.ts +12 -0
- package/dist/runtime/trace-config.js +62 -0
- package/dist/runtime/trace.d.ts +17 -0
- package/dist/runtime/trace.js +176 -25
- package/dist/runtime-boundary-lint.d.ts +137 -0
- package/dist/runtime-boundary-lint.js +590 -0
- package/dist/schema.d.ts +20 -0
- package/dist/schema.js +72 -2
- package/dist/serve.d.ts +1 -1
- package/dist/serve.js +1 -1
- package/dist/server/error-observability.d.ts +1 -0
- package/dist/server/error-observability.js +1 -0
- package/dist/server/error-stack-frames.d.ts +35 -0
- package/dist/server/error-stack-frames.js +219 -0
- package/dist/server/index.d.ts +21 -2
- package/dist/server/index.js +11 -2
- package/dist/server/runtime-diagnostics.d.ts +165 -0
- package/dist/server/runtime-diagnostics.js +360 -0
- package/dist/server/sdk-version.d.ts +3 -0
- package/dist/server/sdk-version.js +19 -0
- package/dist/server/self-test-input-tokens.d.ts +2 -1
- package/dist/server/self-test-input-tokens.js +18 -14
- package/dist/server/self-test-redaction.js +8 -33
- package/dist/server/self-test.d.ts +18 -3
- package/dist/server/self-test.js +139 -76
- package/dist/server/sensitive-values.d.ts +26 -0
- package/dist/server/sensitive-values.js +137 -0
- package/dist/server/serve-implementation.d.ts +285 -0
- package/dist/server/serve-implementation.js +3172 -0
- package/dist/server/serve.d.ts +1 -75
- package/dist/server/serve.js +1 -1170
- package/dist/server/state-backend-report.d.ts +70 -0
- package/dist/server/state-backend-report.js +113 -0
- package/dist/server/trace-output.d.ts +7 -0
- package/dist/server/trace-output.js +111 -0
- package/dist/server/types.d.ts +37 -5
- package/dist/server/types.js +34 -1
- package/dist/stateful/errors.d.ts +19 -0
- package/dist/stateful/errors.js +24 -0
- package/dist/stateful/http-provider-event-emitter.d.ts +40 -0
- package/dist/stateful/http-provider-event-emitter.js +237 -0
- package/dist/stateful/http-session-owner-registry.d.ts +44 -0
- package/dist/stateful/http-session-owner-registry.js +210 -0
- package/dist/stateful/index.d.ts +18 -0
- package/dist/stateful/index.js +18 -0
- package/dist/stateful/provider-event-delivery-failures.d.ts +32 -0
- package/dist/stateful/provider-event-delivery-failures.js +43 -0
- package/dist/stateful/provider-event-pipeline-metrics.d.ts +46 -0
- package/dist/stateful/provider-event-pipeline-metrics.js +48 -0
- package/dist/stateful/provider-event-pipeline.d.ts +50 -0
- package/dist/stateful/provider-event-pipeline.js +1 -0
- package/dist/stateful/provider-events.d.ts +101 -0
- package/dist/stateful/provider-events.js +289 -0
- package/dist/stateful/session-key.d.ts +15 -0
- package/dist/stateful/session-key.js +86 -0
- package/dist/stateful/stateful-provider-adapter-context.d.ts +5 -0
- package/dist/stateful/stateful-provider-adapter-context.js +42 -0
- package/dist/stateful/stateful-provider-adapter-metrics.d.ts +15 -0
- package/dist/stateful/stateful-provider-adapter-metrics.js +21 -0
- package/dist/stateful/stateful-provider-adapter.d.ts +98 -0
- package/dist/stateful/stateful-provider-adapter.js +287 -0
- package/dist/stateful/stateful-provider-observability.d.ts +62 -0
- package/dist/stateful/stateful-provider-observability.js +161 -0
- package/dist/stateful/stateful-provider-owner-forwarder.d.ts +41 -0
- package/dist/stateful/stateful-provider-owner-forwarder.js +237 -0
- package/dist/stateful/stateful-provider-runtime-context.d.ts +35 -0
- package/dist/stateful/stateful-provider-runtime-context.js +61 -0
- package/dist/stateful/stateful-provider-runtime-executor.d.ts +34 -0
- package/dist/stateful/stateful-provider-runtime-executor.js +52 -0
- package/dist/stateful/stateful-provider-session-routing.d.ts +67 -0
- package/dist/stateful/stateful-provider-session-routing.js +345 -0
- package/dist/stateful/stateful-provider-session-runtime.d.ts +98 -0
- package/dist/stateful/stateful-provider-session-runtime.js +245 -0
- package/dist/stateful-signing.d.ts +18 -0
- package/dist/stateful-signing.js +27 -0
- package/dist/stealth/profiles.d.ts +15 -4
- package/dist/stealth/profiles.js +169 -211
- package/dist/stream-evidence.d.ts +74 -0
- package/dist/stream-evidence.js +785 -0
- package/dist/stream.js +7 -1
- package/dist/testing/index.d.ts +2 -1
- package/dist/testing/index.js +2 -1
- package/dist/testing/run.d.ts +32 -2
- package/dist/testing/run.js +509 -25
- package/dist/trace-sanitization.d.ts +6 -0
- package/dist/trace-sanitization.js +68 -0
- package/dist/types.d.ts +899 -210
- package/dist/types.js +1 -0
- package/dist/typescript-module.d.ts +39 -0
- package/dist/typescript-module.js +72 -0
- package/package.json +52 -6
- package/src/auth-turn/index.ts +1 -1
- package/src/auth.ts +118 -0
- package/src/ceremonies/index.ts +189 -46
- package/src/cli/commands.ts +37 -0
- package/src/cli/create.ts +8 -2
- package/src/cli/deployment-intent.ts +364 -0
- package/src/cli/local-connection.ts +221 -0
- package/src/cli/migrate-deployment-intent.ts +1908 -0
- package/src/cli/migrate-operation-declaration.ts +3026 -0
- package/src/cli/migrate-operation-shape.ts +184 -0
- package/src/cli/migrate-provider-shape.ts +778 -0
- package/src/cli/resolver-runtime.ts +72 -0
- package/src/cli/templates/provider/.agents/skills/health-checks-and-fail-closed/SKILL.md.tpl +141 -23
- package/src/cli/templates/provider/Dockerfile.tpl +1 -1
- package/src/cli/templates/provider/README.md.tpl +41 -19
- package/src/cli/templates/provider/index.ts.tpl +6 -3
- package/src/cli/templates/provider/operations/ping.ts.tpl +4 -1
- package/src/cli/templates/provider/provider.json.tpl +6 -0
- package/src/config/loader.ts +774 -181
- package/src/contract-serialization.ts +5 -7
- package/src/contract-types.ts +12 -5
- package/src/contract.ts +24 -10
- package/src/declaration-validation.ts +376 -0
- package/src/define.ts +1105 -131
- package/src/dev.ts +13 -1
- package/src/engine.ts +390 -0
- package/src/error-observability.ts +64 -0
- package/src/error-resolution.ts +269 -0
- package/src/errors.ts +157 -0
- package/src/fixture-sanitization.ts +422 -0
- package/src/handle-meta.ts +143 -0
- package/src/handle.ts +694 -0
- package/src/health-scenario.ts +960 -0
- package/src/i18n/catalog.ts +23 -1
- package/src/i18n/error-messages.ts +255 -0
- package/src/i18n/index.ts +1 -0
- package/src/i18n/keys.ts +80 -6
- package/src/i18n/operation-locale-namespace.ts +10 -0
- package/src/index.ts +390 -23
- package/src/lint.ts +2975 -234
- package/src/native-address.ts +340 -0
- package/src/native-egress-policy.ts +358 -0
- package/src/observability.ts +51 -1
- package/src/provider.ts +207 -27
- package/src/runtime/auth-flow.ts +15 -0
- package/src/runtime/bounded-expiring-map.ts +67 -0
- package/src/runtime/browser-telemetry-binding.ts +92 -0
- package/src/runtime/browser-telemetry.ts +289 -0
- package/src/runtime/browser.ts +882 -99
- package/src/runtime/cache-telemetry.ts +247 -0
- package/src/runtime/cache.ts +258 -22
- package/src/runtime/capability-response.ts +53 -0
- package/src/runtime/capability-telemetry.ts +101 -0
- package/src/runtime/chrome149-header-order.ts +330 -0
- package/src/runtime/diagnostic-env-values.ts +56 -0
- package/src/runtime/diagnostic-env.ts +65 -0
- package/src/runtime/diagnostic-redactor.ts +668 -0
- package/src/runtime/egress-lease.ts +341 -0
- package/src/runtime/engine-mode.ts +233 -0
- package/src/runtime/env.ts +15 -1
- package/src/runtime/executor.ts +39 -32
- package/src/runtime/handle-wordlist.ts +266 -0
- package/src/runtime/handle.ts +1180 -0
- package/src/runtime/http-telemetry-binding.ts +162 -0
- package/src/runtime/http-telemetry-guard.ts +208 -0
- package/src/runtime/http-telemetry.ts +292 -0
- package/src/runtime/http.ts +896 -150
- package/src/runtime/insights.ts +2 -2
- package/src/runtime/instrumentation.ts +628 -28
- package/src/runtime/native-network-errors.ts +99 -0
- package/src/runtime/native-network.ts +1853 -0
- package/src/runtime/native-telemetry.ts +366 -0
- package/src/runtime/ocr-telemetry.ts +308 -0
- package/src/runtime/ocr.ts +564 -0
- package/src/runtime/otlp.ts +468 -21
- package/src/runtime/provider.ts +15 -0
- package/src/runtime/proxy-errors.ts +12 -4
- package/src/runtime/proxy-nodemaven.ts +222 -0
- package/src/runtime/proxy-retry-policy.ts +12 -1
- package/src/runtime/proxy-telemetry.ts +358 -103
- package/src/runtime/redirect-hop-headers.ts +71 -0
- package/src/runtime/redirects.ts +215 -0
- package/src/runtime/redis.ts +7 -2
- package/src/runtime/request-options.ts +728 -1
- package/src/runtime/request-telemetry.ts +375 -0
- package/src/runtime/resolver-config.ts +18 -0
- package/src/runtime/resolver-public.ts +44 -0
- package/src/runtime/resolver-runtime-options.ts +50 -0
- package/src/runtime/resolver-shared.ts +113 -0
- package/src/runtime/resolver-span-attributes.ts +139 -0
- package/src/runtime/resolver-telemetry.ts +362 -0
- package/src/runtime/resolver-usage.ts +99 -0
- package/src/runtime/resolver-vendors/bindings.ts +61 -0
- package/src/runtime/resolver-vendors/browser.ts +591 -0
- package/src/runtime/resolver-vendors/capsolver.ts +727 -0
- package/src/runtime/resolver-vendors/hosts.ts +81 -0
- package/src/runtime/resolver-vendors/hypersolutions.ts +480 -0
- package/src/runtime/resolver-vendors/twocaptcha.ts +555 -0
- package/src/runtime/resolver-vendors/types.ts +261 -0
- package/src/runtime/resolver.ts +1543 -0
- package/src/runtime/state-telemetry.ts +298 -0
- package/src/runtime/state.ts +435 -89
- package/src/runtime/stealth-akamai-sbsd.ts +207 -0
- package/src/runtime/stealth-cookies.ts +155 -0
- package/src/runtime/stealth-owned-headers.ts +67 -0
- package/src/runtime/stealth-telemetry.ts +309 -0
- package/src/runtime/stealth.ts +2275 -372
- package/src/runtime/stt-telemetry.ts +282 -0
- package/src/runtime/stt.ts +96 -91
- package/src/runtime/timeout.ts +18 -0
- package/src/runtime/trace-config.ts +78 -0
- package/src/runtime/trace.ts +257 -35
- package/src/runtime-boundary-lint.ts +707 -0
- package/src/schema.ts +80 -2
- package/src/serve.ts +6 -1
- package/src/server/error-observability.ts +1 -0
- package/src/server/error-stack-frames.ts +214 -0
- package/src/server/index.ts +184 -2
- package/src/server/runtime-diagnostics.ts +522 -0
- package/src/server/sdk-version.ts +22 -0
- package/src/server/self-test-input-tokens.ts +29 -14
- package/src/server/self-test-redaction.ts +10 -40
- package/src/server/self-test.ts +205 -89
- package/src/server/sensitive-values.ts +211 -0
- package/src/server/serve-implementation.ts +4596 -0
- package/src/server/serve.ts +1 -1685
- package/src/server/state-backend-report.ts +185 -0
- package/src/server/trace-output.ts +189 -0
- package/src/server/types.ts +34 -1
- package/src/stateful/README.md +146 -0
- package/src/stateful/errors.ts +35 -0
- package/src/stateful/http-provider-event-emitter.ts +314 -0
- package/src/stateful/http-session-owner-registry.ts +306 -0
- package/src/stateful/index.ts +18 -0
- package/src/stateful/provider-event-delivery-failures.ts +80 -0
- package/src/stateful/provider-event-pipeline-metrics.ts +95 -0
- package/src/stateful/provider-event-pipeline.ts +61 -0
- package/src/stateful/provider-events.ts +462 -0
- package/src/stateful/session-key.ts +111 -0
- package/src/stateful/stateful-provider-adapter-context.ts +59 -0
- package/src/stateful/stateful-provider-adapter-metrics.ts +48 -0
- package/src/stateful/stateful-provider-adapter.ts +562 -0
- package/src/stateful/stateful-provider-observability.ts +261 -0
- package/src/stateful/stateful-provider-owner-forwarder.ts +310 -0
- package/src/stateful/stateful-provider-runtime-context.ts +96 -0
- package/src/stateful/stateful-provider-runtime-executor.ts +96 -0
- package/src/stateful/stateful-provider-session-routing.ts +546 -0
- package/src/stateful/stateful-provider-session-runtime.ts +403 -0
- package/src/stateful-signing.ts +46 -0
- package/src/stealth/profiles.ts +212 -231
- package/src/stream-evidence.ts +988 -0
- package/src/stream.ts +8 -1
- package/src/testing/index.ts +10 -1
- package/src/testing/run.ts +685 -19
- package/src/trace-sanitization.ts +110 -0
- package/src/types.ts +1014 -273
- package/src/typescript-module.ts +100 -0
- package/dist/choice-token.d.ts +0 -24
- package/dist/choice-token.js +0 -74
- package/dist/runtime/choice.d.ts +0 -15
- package/dist/runtime/choice.js +0 -413
- package/src/choice-token.ts +0 -165
- package/src/runtime/choice.ts +0 -686
package/src/runtime/state.ts
CHANGED
|
@@ -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:
|
|
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 =
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
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
|
|
74
|
-
|
|
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
|
|
78
|
-
|
|
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
|
|
171
|
+
function providerStateKey(
|
|
82
172
|
providerId: string | undefined,
|
|
83
173
|
namespace: string,
|
|
84
|
-
|
|
174
|
+
scopeDiscriminator: string,
|
|
175
|
+
key: string,
|
|
85
176
|
): string {
|
|
86
|
-
|
|
87
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
165
|
-
|
|
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
|
|
305
|
+
private async indexedKeys(limit: number): Promise<string[]> {
|
|
169
306
|
await requireRedisReady(this.backend.redis);
|
|
170
|
-
|
|
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
|
|
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
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
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
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
const
|
|
199
|
-
|
|
200
|
-
const
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
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
|
|
211
|
-
|
|
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
|
|
222
|
-
const
|
|
223
|
-
|
|
224
|
-
const
|
|
225
|
-
|
|
226
|
-
|
|
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
|
|
257
|
-
|
|
258
|
-
|
|
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:
|
|
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
|
-
|
|
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(
|
|
285
|
-
|
|
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(
|
|
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(
|
|
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 =
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
707
|
+
key: string,
|
|
708
|
+
expectedVersion: number,
|
|
709
|
+
value: T,
|
|
710
|
+
options?: StateWriteOptions,
|
|
404
711
|
): Promise<StateCasResult<T>> {
|
|
405
|
-
|
|
406
|
-
|
|
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
|
-
|
|
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
|
-
|
|
430
|
-
|
|
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(
|
|
433
|
-
this.namespaces.set(
|
|
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(
|
|
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(
|
|
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(
|
|
479
|
-
|
|
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 {
|