@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.
- package/AUTHORING.md +537 -33
- package/CHANGELOG.md +205 -1
- package/README.md +83 -22
- package/SUBMISSION.md +2 -2
- package/bin/apifuse-check.ts +165 -11
- package/bin/apifuse-dev.ts +57 -13
- package/bin/apifuse-migrate-operation-declaration.ts +55 -0
- package/bin/apifuse-migrate-shape.ts +202 -0
- package/bin/apifuse-pack-check.ts +22 -2
- package/bin/apifuse-pack-smoke.ts +57 -2
- package/bin/apifuse-pack-types.ts +357 -38
- package/bin/apifuse-perf.ts +14 -13
- package/bin/apifuse-record.ts +709 -72
- package/bin/apifuse-submit-check.ts +2344 -324
- package/bin/apifuse-sync-assets.ts +117 -0
- 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 +27 -0
- package/dist/cli/create.d.ts +4 -1
- package/dist/cli/create.js +42 -37
- package/dist/cli/migrate-operation-declaration.d.ts +59 -0
- package/dist/cli/migrate-operation-declaration.js +1249 -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 +52 -0
- package/dist/cli/migrate-provider-shape.js +578 -0
- package/dist/cli/prompt-assets.d.ts +80 -0
- package/dist/cli/prompt-assets.js +743 -0
- package/dist/cli/templates/provider/AGENTS.md.tpl +17 -8
- package/dist/cli/templates/provider/Dockerfile.tpl +1 -1
- package/dist/cli/templates/provider/README.md.tpl +5 -5
- 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 +179 -16
- package/dist/config/loader.js +435 -127
- 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 +33 -0
- package/dist/declaration-validation.js +266 -0
- package/dist/define.d.ts +66 -29
- package/dist/define.js +778 -69
- package/dist/dev.d.ts +3 -0
- package/dist/dev.js +1 -1
- package/dist/engine.d.ts +78 -0
- package/dist/engine.js +133 -0
- package/dist/error-observability.d.ts +7 -0
- package/dist/error-observability.js +61 -0
- package/dist/error-resolution.d.ts +4 -0
- package/dist/error-resolution.js +122 -0
- package/dist/errors.d.ts +33 -0
- package/dist/errors.js +40 -0
- package/dist/fixture-sanitization.d.ts +28 -0
- package/dist/fixture-sanitization.js +227 -0
- package/dist/health-scenario.d.ts +1842 -0
- package/dist/health-scenario.js +624 -0
- package/dist/index.d.ts +21 -10
- package/dist/index.js +12 -7
- package/dist/lint.d.ts +23 -11
- package/dist/lint.js +572 -69
- 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 +10 -2
- package/dist/provider.js +4 -1
- package/dist/runtime/auth-flow.d.ts +5 -1
- package/dist/runtime/auth-flow.js +6 -0
- package/dist/runtime/browser.d.ts +1 -0
- package/dist/runtime/browser.js +492 -49
- package/dist/runtime/cache.d.ts +1 -0
- package/dist/runtime/cache.js +169 -15
- package/dist/runtime/choice-wordlist.d.ts +9 -0
- package/dist/runtime/choice-wordlist.js +138 -0
- package/dist/runtime/choice.d.ts +13 -1
- package/dist/runtime/choice.js +490 -102
- package/dist/runtime/chrome149-header-order.d.ts +58 -0
- package/dist/runtime/chrome149-header-order.js +289 -0
- package/dist/runtime/env.js +12 -0
- package/dist/runtime/executor.d.ts +3 -2
- package/dist/runtime/executor.js +26 -25
- package/dist/runtime/http.d.ts +1 -0
- package/dist/runtime/http.js +515 -53
- package/dist/runtime/insights.js +2 -2
- package/dist/runtime/instrumentation.d.ts +2 -2
- package/dist/runtime/instrumentation.js +366 -8
- 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 +96 -0
- package/dist/runtime/native-network.js +1232 -0
- package/dist/runtime/ocr.d.ts +29 -0
- package/dist/runtime/ocr.js +440 -0
- package/dist/runtime/otlp.d.ts +71 -2
- package/dist/runtime/otlp.js +397 -16
- package/dist/runtime/proxy-errors.js +6 -2
- package/dist/runtime/proxy-nodemaven.d.ts +56 -0
- package/dist/runtime/proxy-nodemaven.js +146 -0
- package/dist/runtime/proxy-telemetry.d.ts +80 -1
- package/dist/runtime/proxy-telemetry.js +154 -47
- package/dist/runtime/redirects.d.ts +29 -0
- package/dist/runtime/redirects.js +36 -0
- package/dist/runtime/redis.d.ts +1 -1
- package/dist/runtime/redis.js +4 -2
- package/dist/runtime/request-options.d.ts +68 -1
- package/dist/runtime/request-options.js +548 -0
- package/dist/runtime/resolver-config.d.ts +6 -0
- package/dist/runtime/resolver-config.js +6 -0
- package/dist/runtime/resolver-public.d.ts +1 -0
- package/dist/runtime/resolver-public.js +1 -0
- package/dist/runtime/resolver-shared.d.ts +14 -0
- package/dist/runtime/resolver-shared.js +12 -0
- package/dist/runtime/resolver-vendors/bindings.d.ts +48 -0
- package/dist/runtime/resolver-vendors/bindings.js +40 -0
- package/dist/runtime/resolver-vendors/browser.d.ts +22 -0
- package/dist/runtime/resolver-vendors/browser.js +377 -0
- package/dist/runtime/resolver-vendors/capsolver.d.ts +22 -0
- package/dist/runtime/resolver-vendors/capsolver.js +531 -0
- package/dist/runtime/resolver-vendors/hosts.d.ts +2 -0
- package/dist/runtime/resolver-vendors/hosts.js +33 -0
- package/dist/runtime/resolver-vendors/twocaptcha.d.ts +24 -0
- package/dist/runtime/resolver-vendors/twocaptcha.js +408 -0
- package/dist/runtime/resolver-vendors/types.d.ts +94 -0
- package/dist/runtime/resolver-vendors/types.js +96 -0
- package/dist/runtime/resolver.d.ts +72 -0
- package/dist/runtime/resolver.js +771 -0
- package/dist/runtime/secrets.d.ts +27 -0
- package/dist/runtime/secrets.js +51 -0
- package/dist/runtime/state.d.ts +3 -0
- package/dist/runtime/state.js +277 -71
- package/dist/runtime/stealth-cookies.d.ts +20 -0
- package/dist/runtime/stealth-cookies.js +111 -0
- package/dist/runtime/stealth.d.ts +41 -5
- package/dist/runtime/stealth.js +997 -291
- package/dist/runtime/stt.js +1 -12
- 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 +5 -0
- package/dist/runtime/trace.js +43 -10
- 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/index.d.ts +5 -2
- package/dist/server/index.js +2 -2
- 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.d.ts +14 -3
- package/dist/server/self-test.js +129 -58
- package/dist/server/serve-implementation.d.ts +230 -0
- package/dist/server/serve-implementation.js +2265 -0
- package/dist/server/serve.d.ts +1 -70
- package/dist/server/serve.js +1 -1131
- package/dist/server/trace-output.d.ts +6 -0
- package/dist/server/trace-output.js +79 -0
- package/dist/server/types.d.ts +30 -5
- package/dist/server/types.js +13 -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 +215 -0
- package/dist/stateful/stateful-provider-runtime-context.d.ts +32 -0
- package/dist/stateful/stateful-provider-runtime-context.js +60 -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 +9 -4
- package/dist/stealth/profiles.js +160 -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 +489 -21
- package/dist/trace-sanitization.d.ts +5 -0
- package/dist/trace-sanitization.js +45 -0
- package/dist/types.d.ts +673 -131
- package/dist/types.js +1 -0
- package/package.json +45 -5
- package/src/auth-turn/index.ts +1 -1
- package/src/auth.ts +118 -0
- package/src/ceremonies/index.ts +189 -46
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/approval-override.ts.txt +6 -0
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/codemod-syntax.ts.txt +3 -0
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/connection-precedence.ts.txt +10 -0
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/docs-conflict.ts.txt +8 -0
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/examples-hoisted-array-spread.ts.txt +15 -0
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/examples-imported-array-spread.ts.txt +9 -0
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/examples-map.ts.txt +5 -0
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/examples-mixed-array-spread.ts.txt +24 -0
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/examples-operation.ts.txt +16 -0
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/factory-map.ts.txt +3 -0
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/hoist-all.ts.txt +31 -0
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/hoisted-const.ts.txt +11 -0
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/imported-spread.ts.txt +11 -0
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/inline-map.ts.txt +11 -0
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/inline-spread-cast-tail.ts.txt +21 -0
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/inline-spread-ekitan.ts.txt +11 -0
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/inline-spread-override.ts.txt +14 -0
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/missing-english-locale.ts.txt +7 -0
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/no-safety.ts.txt +6 -0
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/non-literal.ts.txt +7 -0
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/redundant-approval.ts.txt +6 -0
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/safety-conflict.ts.txt +7 -0
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/stream.ts.txt +7 -0
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/tool-router-spread.ts.txt +15 -0
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/unparseable.ts.txt +4 -0
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/verbatim-template.ts.txt +12 -0
- package/src/cli/commands.ts +33 -0
- package/src/cli/create.ts +50 -37
- package/src/cli/migrate-operation-declaration.ts +1751 -0
- package/src/cli/migrate-operation-shape.ts +184 -0
- package/src/cli/migrate-provider-shape.ts +772 -0
- package/src/cli/prompt-assets.ts +865 -0
- package/src/cli/templates/provider/AGENTS.md.tpl +17 -8
- package/src/cli/templates/provider/Dockerfile.tpl +1 -1
- package/src/cli/templates/provider/README.md.tpl +5 -5
- 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 +690 -163
- 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 +330 -0
- package/src/define.ts +1048 -131
- package/src/dev.ts +4 -1
- package/src/engine.ts +279 -0
- package/src/error-observability.ts +64 -0
- package/src/error-resolution.ts +127 -0
- package/src/errors.ts +68 -0
- package/src/fixture-sanitization.ts +264 -0
- package/src/health-scenario.ts +875 -0
- package/src/index.ts +220 -13
- package/src/lint.ts +698 -90
- 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 +161 -3
- package/src/runtime/auth-flow.ts +12 -0
- package/src/runtime/browser.ts +661 -63
- package/src/runtime/cache.ts +189 -14
- package/src/runtime/choice-wordlist.ts +145 -0
- package/src/runtime/choice.ts +631 -120
- package/src/runtime/chrome149-header-order.ts +330 -0
- package/src/runtime/env.ts +13 -0
- package/src/runtime/executor.ts +43 -31
- package/src/runtime/http.ts +641 -61
- package/src/runtime/insights.ts +2 -2
- package/src/runtime/instrumentation.ts +520 -15
- package/src/runtime/native-network-errors.ts +99 -0
- package/src/runtime/native-network.ts +1605 -0
- package/src/runtime/ocr.ts +523 -0
- package/src/runtime/otlp.ts +467 -21
- package/src/runtime/proxy-errors.ts +12 -4
- package/src/runtime/proxy-nodemaven.ts +221 -0
- package/src/runtime/proxy-telemetry.ts +244 -75
- package/src/runtime/redirects.ts +66 -0
- package/src/runtime/redis.ts +7 -2
- package/src/runtime/request-options.ts +680 -1
- package/src/runtime/resolver-config.ts +6 -0
- package/src/runtime/resolver-public.ts +23 -0
- package/src/runtime/resolver-shared.ts +33 -0
- package/src/runtime/resolver-vendors/bindings.ts +56 -0
- package/src/runtime/resolver-vendors/browser.ts +533 -0
- package/src/runtime/resolver-vendors/capsolver.ts +707 -0
- package/src/runtime/resolver-vendors/hosts.ts +38 -0
- package/src/runtime/resolver-vendors/twocaptcha.ts +540 -0
- package/src/runtime/resolver-vendors/types.ts +212 -0
- package/src/runtime/resolver.ts +1157 -0
- package/src/runtime/secrets.ts +64 -0
- package/src/runtime/state.ts +394 -77
- package/src/runtime/stealth-cookies.ts +132 -0
- package/src/runtime/stealth.ts +1280 -336
- package/src/runtime/stt.ts +1 -19
- package/src/runtime/timeout.ts +18 -0
- package/src/runtime/trace-config.ts +78 -0
- package/src/runtime/trace.ts +57 -17
- package/src/serve.ts +6 -1
- package/src/server/error-observability.ts +1 -0
- package/src/server/index.ts +34 -2
- package/src/server/self-test-input-tokens.ts +29 -14
- package/src/server/self-test.ts +191 -68
- package/src/server/serve-implementation.ts +3378 -0
- package/src/server/serve.ts +1 -1632
- package/src/server/trace-output.ts +129 -0
- package/src/server/types.ts +13 -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 +287 -0
- package/src/stateful/stateful-provider-runtime-context.ts +92 -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 +202 -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 +658 -15
- package/src/trace-sanitization.ts +63 -0
- package/src/types.ts +788 -174
- package/dist/cli/templates/provider/CLAUDE.md.tpl +0 -1
- package/src/cli/templates/provider/CLAUDE.md.tpl +0 -1
- /package/dist/cli/templates/provider/{skills → .agents/skills}/fixtures-and-recording/SKILL.md.tpl +0 -0
- /package/dist/cli/templates/provider/{skills → .agents/skills}/health-checks-and-fail-closed/SKILL.md.tpl +0 -0
- /package/dist/cli/templates/provider/{skills → .agents/skills}/normalization-standards/SKILL.md.tpl +0 -0
- /package/dist/cli/templates/provider/{skills → .agents/skills}/pagination-and-counts/SKILL.md.tpl +0 -0
- /package/dist/cli/templates/provider/{skills → .agents/skills}/upstream-contract-verification/SKILL.md.tpl +0 -0
- /package/dist/cli/templates/provider/{skills → .agents/skills}/upstream-notes/README.md.tpl +0 -0
- /package/src/cli/templates/provider/{skills → .agents/skills}/fixtures-and-recording/SKILL.md.tpl +0 -0
- /package/src/cli/templates/provider/{skills → .agents/skills}/health-checks-and-fail-closed/SKILL.md.tpl +0 -0
- /package/src/cli/templates/provider/{skills → .agents/skills}/normalization-standards/SKILL.md.tpl +0 -0
- /package/src/cli/templates/provider/{skills → .agents/skills}/pagination-and-counts/SKILL.md.tpl +0 -0
- /package/src/cli/templates/provider/{skills → .agents/skills}/upstream-contract-verification/SKILL.md.tpl +0 -0
- /package/src/cli/templates/provider/{skills → .agents/skills}/upstream-notes/README.md.tpl +0 -0
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { EnvContext, ProviderDefinition } from "../types.js";
|
|
2
|
+
/**
|
|
3
|
+
* Canonical error code for a declared-but-unprovisioned provider secret.
|
|
4
|
+
*
|
|
5
|
+
* The SDK is the single source of truth for env/secret presence validation:
|
|
6
|
+
* providers declare secrets in `defineProvider({ secrets: [...] })` and the
|
|
7
|
+
* runtime enforces presence before any handler or auth-flow code runs.
|
|
8
|
+
* Provider-local presence guards (requireServiceKey/requireApiKey style) are a
|
|
9
|
+
* deprecated antipattern — see the `sdk-owned-secret-presence` submit-check
|
|
10
|
+
* rule.
|
|
11
|
+
*/
|
|
12
|
+
export declare const MISSING_SECRET_CODE = "MISSING_SECRET";
|
|
13
|
+
/**
|
|
14
|
+
* Names of declared `required: true` secrets whose env values are unset or
|
|
15
|
+
* whitespace-only. Whitespace-only values count as missing for parity with the
|
|
16
|
+
* `.trim()` guards well-built providers used before the SDK owned this check —
|
|
17
|
+
* a blank value provisioned by a broken secret pipeline must not pass the gate.
|
|
18
|
+
*/
|
|
19
|
+
export declare function listMissingRequiredSecrets(provider: ProviderDefinition, env: EnvContext): string[];
|
|
20
|
+
/**
|
|
21
|
+
* Throws the canonical structured missing-secret error when any declared
|
|
22
|
+
* `required: true` secret is absent. All missing names are reported in a
|
|
23
|
+
* single error so operators can provision the full set in one pass instead of
|
|
24
|
+
* discovering them one deploy at a time (the 2026-07-22 unprovisioned-secret
|
|
25
|
+
* incident failure mode).
|
|
26
|
+
*/
|
|
27
|
+
export declare function assertRequiredSecretsPresent(provider: ProviderDefinition, env: EnvContext): void;
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { ProviderSecretError } from "../errors.js";
|
|
2
|
+
/**
|
|
3
|
+
* Canonical error code for a declared-but-unprovisioned provider secret.
|
|
4
|
+
*
|
|
5
|
+
* The SDK is the single source of truth for env/secret presence validation:
|
|
6
|
+
* providers declare secrets in `defineProvider({ secrets: [...] })` and the
|
|
7
|
+
* runtime enforces presence before any handler or auth-flow code runs.
|
|
8
|
+
* Provider-local presence guards (requireServiceKey/requireApiKey style) are a
|
|
9
|
+
* deprecated antipattern — see the `sdk-owned-secret-presence` submit-check
|
|
10
|
+
* rule.
|
|
11
|
+
*/
|
|
12
|
+
export const MISSING_SECRET_CODE = "MISSING_SECRET";
|
|
13
|
+
/**
|
|
14
|
+
* Names of declared `required: true` secrets whose env values are unset or
|
|
15
|
+
* whitespace-only. Whitespace-only values count as missing for parity with the
|
|
16
|
+
* `.trim()` guards well-built providers used before the SDK owned this check —
|
|
17
|
+
* a blank value provisioned by a broken secret pipeline must not pass the gate.
|
|
18
|
+
*/
|
|
19
|
+
export function listMissingRequiredSecrets(provider, env) {
|
|
20
|
+
const missing = [];
|
|
21
|
+
for (const secret of provider.secrets ?? []) {
|
|
22
|
+
if (secret.required !== true) {
|
|
23
|
+
continue;
|
|
24
|
+
}
|
|
25
|
+
const value = env.get(secret.name);
|
|
26
|
+
if (value === undefined || value.trim() === "") {
|
|
27
|
+
missing.push(secret.name);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
return missing;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Throws the canonical structured missing-secret error when any declared
|
|
34
|
+
* `required: true` secret is absent. All missing names are reported in a
|
|
35
|
+
* single error so operators can provision the full set in one pass instead of
|
|
36
|
+
* discovering them one deploy at a time (the 2026-07-22 unprovisioned-secret
|
|
37
|
+
* incident failure mode).
|
|
38
|
+
*/
|
|
39
|
+
export function assertRequiredSecretsPresent(provider, env) {
|
|
40
|
+
const missing = listMissingRequiredSecrets(provider, env);
|
|
41
|
+
if (missing.length === 0) {
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
const names = missing.join(", ");
|
|
45
|
+
throw new ProviderSecretError(`Missing required provider secret${missing.length > 1 ? "s" : ""}: ${names}`, {
|
|
46
|
+
code: MISSING_SECRET_CODE,
|
|
47
|
+
category: "credential_unavailable",
|
|
48
|
+
retryable: false,
|
|
49
|
+
fix: `Provision ${names} in the provider environment (e.g. Doppler). Declared in defineProvider({ secrets: [...] }).`,
|
|
50
|
+
});
|
|
51
|
+
}
|
package/dist/runtime/state.d.ts
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
import { ProviderError } from "../errors.js";
|
|
2
2
|
import type { ProviderRuntimeState } from "../types.js";
|
|
3
|
+
import { type ProviderRedisClient } from "./redis.js";
|
|
3
4
|
type RedisProviderRuntimeStateOptions = {
|
|
4
5
|
readonly redisUrl: string;
|
|
5
6
|
readonly providerId?: string;
|
|
7
|
+
/** Test seam; production callers use redisUrl-backed client sharing. */
|
|
8
|
+
readonly __redisClient?: ProviderRedisClient;
|
|
6
9
|
};
|
|
7
10
|
export declare class UnsupportedProviderStateError extends ProviderError {
|
|
8
11
|
constructor(message?: string);
|
package/dist/runtime/state.js
CHANGED
|
@@ -1,21 +1,93 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
1
2
|
import { providerStateRedisUrlFromEnv } from "../config/loader.js";
|
|
2
3
|
import { ProviderError } from "../errors.js";
|
|
3
4
|
import { createProviderRedisClient, ensureRedisReady, withRedisTimeout, } from "./redis.js";
|
|
4
5
|
const DEFAULT_REDIS_TIMEOUT_MS = 250;
|
|
5
|
-
const REDIS_STATE_PREFIX = "apifuse:provider-state:
|
|
6
|
+
const REDIS_STATE_PREFIX = "apifuse:provider-state:v2";
|
|
7
|
+
const PROVIDER_SCOPE_DISCRIMINATOR = "scope:provider";
|
|
8
|
+
const MISSING_CONNECTION_SCOPE_DISCRIMINATOR = "scope:connection:missing";
|
|
9
|
+
const SET_WITH_QUOTA_SCRIPT = `
|
|
10
|
+
local now = tonumber(ARGV[1])
|
|
11
|
+
local max_entries = tonumber(ARGV[2])
|
|
12
|
+
local expires_at = tonumber(ARGV[3])
|
|
13
|
+
local index_ttl = tonumber(ARGV[4])
|
|
14
|
+
local envelope = ARGV[5]
|
|
15
|
+
|
|
16
|
+
redis.call("ZREMRANGEBYSCORE", KEYS[2], "-inf", now)
|
|
17
|
+
local indexed = redis.call("ZSCORE", KEYS[2], KEYS[1])
|
|
18
|
+
if not indexed and redis.call("ZCARD", KEYS[2]) >= max_entries then
|
|
19
|
+
return {0, false}
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
redis.call("SET", KEYS[1], envelope, "PXAT", expires_at)
|
|
23
|
+
redis.call("ZADD", KEYS[2], expires_at, KEYS[1])
|
|
24
|
+
redis.call("PEXPIRE", KEYS[2], index_ttl)
|
|
25
|
+
return {1, envelope}
|
|
26
|
+
`;
|
|
27
|
+
const COMPARE_AND_SET_WITH_QUOTA_SCRIPT = `
|
|
28
|
+
local current = redis.call("GET", KEYS[1])
|
|
29
|
+
if current then
|
|
30
|
+
local ok, decoded = pcall(cjson.decode, current)
|
|
31
|
+
if ok and type(decoded) == "table" and decoded.deleted == true then
|
|
32
|
+
current = false
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
local now = tonumber(ARGV[2])
|
|
37
|
+
local max_entries = tonumber(ARGV[3])
|
|
38
|
+
local expires_at = tonumber(ARGV[4])
|
|
39
|
+
local index_ttl = tonumber(ARGV[5])
|
|
40
|
+
local envelope = ARGV[6]
|
|
41
|
+
|
|
42
|
+
local current_version = 0
|
|
43
|
+
local current_decoded = false
|
|
44
|
+
if current then
|
|
45
|
+
local ok, decoded = pcall(cjson.decode, current)
|
|
46
|
+
if not ok or type(decoded) ~= "table" or type(decoded.version) ~= "number" then
|
|
47
|
+
return {-2, current}
|
|
48
|
+
end
|
|
49
|
+
current_version = decoded.version
|
|
50
|
+
current_decoded = decoded
|
|
51
|
+
end
|
|
52
|
+
if current_version ~= tonumber(ARGV[1]) then
|
|
53
|
+
return {-1, current or false}
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
redis.call("ZREMRANGEBYSCORE", KEYS[2], "-inf", now)
|
|
57
|
+
local indexed = redis.call("ZSCORE", KEYS[2], KEYS[1])
|
|
58
|
+
if not indexed and redis.call("ZCARD", KEYS[2]) >= max_entries then
|
|
59
|
+
return {0, false}
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
if current_decoded and type(current_decoded.createdAt) == "string" then
|
|
63
|
+
local next_decoded = cjson.decode(envelope)
|
|
64
|
+
next_decoded.createdAt = current_decoded.createdAt
|
|
65
|
+
envelope = cjson.encode(next_decoded)
|
|
66
|
+
end
|
|
67
|
+
redis.call("SET", KEYS[1], envelope, "PXAT", expires_at)
|
|
68
|
+
redis.call("ZADD", KEYS[2], expires_at, KEYS[1])
|
|
69
|
+
redis.call("PEXPIRE", KEYS[2], index_ttl)
|
|
70
|
+
return {1, envelope}
|
|
71
|
+
`;
|
|
72
|
+
const DELETE_WITH_INDEX_SCRIPT = `
|
|
73
|
+
redis.call("SET", KEYS[1], ARGV[1], "PX", ARGV[2])
|
|
74
|
+
redis.call("ZREM", KEYS[2], KEYS[1])
|
|
75
|
+
return 1
|
|
76
|
+
`;
|
|
6
77
|
const redisBackends = new Map();
|
|
7
|
-
function getRedisBackend(redisUrl) {
|
|
78
|
+
function getRedisBackend(redisUrl, injectedRedis) {
|
|
8
79
|
const existing = redisBackends.get(redisUrl);
|
|
9
80
|
if (existing)
|
|
10
81
|
return existing;
|
|
11
|
-
const redis =
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
82
|
+
const redis = injectedRedis ??
|
|
83
|
+
createProviderRedisClient({
|
|
84
|
+
redisUrl,
|
|
85
|
+
timeoutMs: DEFAULT_REDIS_TIMEOUT_MS,
|
|
86
|
+
onError: () => {
|
|
87
|
+
// Runtime state operations fail closed at their call sites. Avoid noisy
|
|
88
|
+
// unhandled Redis errors from background reconnect attempts.
|
|
89
|
+
},
|
|
90
|
+
});
|
|
19
91
|
const backend = { redis };
|
|
20
92
|
redisBackends.set(redisUrl, backend);
|
|
21
93
|
return backend;
|
|
@@ -36,15 +108,26 @@ async function requireRedisReady(redis) {
|
|
|
36
108
|
return;
|
|
37
109
|
throw new UnsupportedProviderStateError("Provider runtime state Redis is unavailable");
|
|
38
110
|
}
|
|
39
|
-
function
|
|
40
|
-
|
|
111
|
+
function connectionScopeDiscriminator(connectionId) {
|
|
112
|
+
if (connectionId === undefined)
|
|
113
|
+
return MISSING_CONNECTION_SCOPE_DISCRIMINATOR;
|
|
114
|
+
const digest = createHash("sha256").update(connectionId, "utf8").digest("hex");
|
|
115
|
+
return `scope:connection:sha256:${digest}`;
|
|
116
|
+
}
|
|
117
|
+
function providerStatePrefix(providerId, namespace, scopeDiscriminator) {
|
|
118
|
+
return `${REDIS_STATE_PREFIX}:${providerId ?? "default"}:${namespace}:${scopeDiscriminator}`;
|
|
41
119
|
}
|
|
42
|
-
function providerStateKey(providerId, namespace, key) {
|
|
43
|
-
return `${providerStatePrefix(providerId, namespace)}:${key}`;
|
|
120
|
+
function providerStateKey(providerId, namespace, scopeDiscriminator, key) {
|
|
121
|
+
return `${providerStatePrefix(providerId, namespace, scopeDiscriminator)}:${key}`;
|
|
44
122
|
}
|
|
45
|
-
function publicStateKey(
|
|
46
|
-
const prefix
|
|
47
|
-
|
|
123
|
+
function publicStateKey(redisKey, prefixes) {
|
|
124
|
+
for (const prefix of prefixes) {
|
|
125
|
+
const prefixWithSeparator = `${prefix}:`;
|
|
126
|
+
if (redisKey.startsWith(prefixWithSeparator)) {
|
|
127
|
+
return redisKey.slice(prefixWithSeparator.length);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
return redisKey;
|
|
48
131
|
}
|
|
49
132
|
function parseStateDurationMs(ttl) {
|
|
50
133
|
const match = /^(\d+)(ms|s|m|h|d)$/.exec(ttl ?? "1h");
|
|
@@ -63,13 +146,21 @@ function parseStateDurationMs(ttl) {
|
|
|
63
146
|
: 86_400_000;
|
|
64
147
|
return Math.max(1, amount * multiplier);
|
|
65
148
|
}
|
|
66
|
-
function resolveExpiresAt(ttl) {
|
|
67
|
-
return new Date(Date.now() + parseStateDurationMs(ttl)).toISOString();
|
|
68
|
-
}
|
|
69
149
|
function envelopeFromJson(key, raw) {
|
|
70
150
|
if (!raw)
|
|
71
151
|
return null;
|
|
72
|
-
|
|
152
|
+
// A corrupt/undecodable persisted envelope must be treated as absent rather
|
|
153
|
+
// than throwing a raw JSON.parse SyntaxError: an uncaught SyntaxError escapes
|
|
154
|
+
// the provider error taxonomy, is masked as internal_error 500, and is then
|
|
155
|
+
// retried by the hub (2026-07-22 catchtable reserve RCA, candidate A). Returning
|
|
156
|
+
// null also keeps list() from aborting the whole scan on a single bad entry.
|
|
157
|
+
let parsed;
|
|
158
|
+
try {
|
|
159
|
+
parsed = JSON.parse(raw);
|
|
160
|
+
}
|
|
161
|
+
catch {
|
|
162
|
+
return null;
|
|
163
|
+
}
|
|
73
164
|
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
74
165
|
return null;
|
|
75
166
|
}
|
|
@@ -90,6 +181,20 @@ function envelopeFromJson(key, raw) {
|
|
|
90
181
|
updatedAt: record.updatedAt,
|
|
91
182
|
};
|
|
92
183
|
}
|
|
184
|
+
function isStateTombstone(raw) {
|
|
185
|
+
if (!raw)
|
|
186
|
+
return false;
|
|
187
|
+
try {
|
|
188
|
+
const parsed = JSON.parse(raw);
|
|
189
|
+
return (parsed !== null &&
|
|
190
|
+
typeof parsed === "object" &&
|
|
191
|
+
!Array.isArray(parsed) &&
|
|
192
|
+
parsed.deleted === true);
|
|
193
|
+
}
|
|
194
|
+
catch {
|
|
195
|
+
return false;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
93
198
|
function redisEnvelope(value, version, createdAt, expiresAt) {
|
|
94
199
|
const updatedAt = new Date().toISOString();
|
|
95
200
|
return { value, version, expiresAt, createdAt, updatedAt };
|
|
@@ -99,21 +204,38 @@ class RedisProviderStateNamespace {
|
|
|
99
204
|
providerId;
|
|
100
205
|
namespaceName;
|
|
101
206
|
options;
|
|
102
|
-
|
|
207
|
+
scopeDiscriminator;
|
|
208
|
+
constructor(backend, providerId, namespaceName, options, scopeDiscriminator) {
|
|
103
209
|
this.backend = backend;
|
|
104
210
|
this.providerId = providerId;
|
|
105
211
|
this.namespaceName = namespaceName;
|
|
106
212
|
this.options = options;
|
|
213
|
+
this.scopeDiscriminator = scopeDiscriminator;
|
|
107
214
|
}
|
|
108
215
|
redisKey(key) {
|
|
109
|
-
return providerStateKey(this.providerId, this.namespaceName, key);
|
|
216
|
+
return providerStateKey(this.providerId, this.namespaceName, this.scopeDiscriminator, key);
|
|
217
|
+
}
|
|
218
|
+
statePrefix() {
|
|
219
|
+
return providerStatePrefix(this.providerId, this.namespaceName, this.scopeDiscriminator);
|
|
110
220
|
}
|
|
111
|
-
|
|
112
|
-
|
|
221
|
+
indexKey() {
|
|
222
|
+
// Keep bookkeeping outside the caller-owned keyspace. A provider may use
|
|
223
|
+
// any state key (including "__index"), so a suffix inside the namespace
|
|
224
|
+
// could turn the ZSET into a string and break every subsequent write.
|
|
225
|
+
const namespaceIdentity = Buffer.from(providerStatePrefix(this.providerId, this.namespaceName, this.scopeDiscriminator), "utf8").toString("base64url");
|
|
226
|
+
return `${REDIS_STATE_PREFIX}:index:${namespaceIdentity}`;
|
|
113
227
|
}
|
|
114
|
-
async
|
|
228
|
+
async indexedKeys(limit) {
|
|
115
229
|
await requireRedisReady(this.backend.redis);
|
|
116
|
-
|
|
230
|
+
const now = Date.now();
|
|
231
|
+
return await withRequiredRedis(async () => {
|
|
232
|
+
await this.backend.redis.zremrangebyscore(this.indexKey(), "-inf", now);
|
|
233
|
+
return await this.backend.redis.zrangebyscore(this.indexKey(), now + 1, "+inf", "LIMIT", 0, limit);
|
|
234
|
+
});
|
|
235
|
+
}
|
|
236
|
+
async readRaw(key) {
|
|
237
|
+
await requireRedisReady(this.backend.redis);
|
|
238
|
+
return await withRequiredRedis(() => this.backend.redis.get(this.redisKey(key)));
|
|
117
239
|
}
|
|
118
240
|
enforceValueSize(value) {
|
|
119
241
|
const bytes = Buffer.byteLength(JSON.stringify(value), "utf8");
|
|
@@ -121,52 +243,68 @@ class RedisProviderStateNamespace {
|
|
|
121
243
|
throw new UnsupportedProviderStateError(`Provider runtime state value exceeds maxValueBytes (${bytes} > ${this.options.maxValueBytes})`);
|
|
122
244
|
}
|
|
123
245
|
}
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
246
|
+
quotaExceeded() {
|
|
247
|
+
return new UnsupportedProviderStateError(`Provider runtime state namespace quota exceeded (${this.options.maxEntries + 1} > ${this.options.maxEntries})`);
|
|
248
|
+
}
|
|
249
|
+
writeTiming(ttl) {
|
|
250
|
+
const ttlMs = parseStateDurationMs(ttl ?? this.options.defaultTtl);
|
|
251
|
+
const maxTtlMs = parseStateDurationMs(this.options.maxTtl);
|
|
252
|
+
if (ttlMs > maxTtlMs) {
|
|
253
|
+
throw new UnsupportedProviderStateError(`Provider runtime state ttl exceeds maxTtl (${ttlMs} > ${maxTtlMs})`);
|
|
130
254
|
}
|
|
255
|
+
const expiresAtMs = Date.now() + ttlMs;
|
|
256
|
+
return {
|
|
257
|
+
expiresAt: new Date(expiresAtMs).toISOString(),
|
|
258
|
+
expiresAtMs,
|
|
259
|
+
indexTtlMs: maxTtlMs,
|
|
260
|
+
};
|
|
131
261
|
}
|
|
132
262
|
async list(options) {
|
|
133
|
-
const
|
|
134
|
-
|
|
135
|
-
return options?.prefix ? publicKey.startsWith(options.prefix) : true;
|
|
136
|
-
});
|
|
137
|
-
const limited = keys.slice(0, Math.max(0, options?.limit ?? keys.length));
|
|
138
|
-
if (limited.length === 0)
|
|
263
|
+
const requestedLimit = Math.max(0, options?.limit ?? this.options.maxEntries);
|
|
264
|
+
if (requestedLimit === 0)
|
|
139
265
|
return [];
|
|
140
|
-
const
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
266
|
+
const keys = await this.indexedKeys(this.options.maxEntries);
|
|
267
|
+
if (keys.length === 0)
|
|
268
|
+
return [];
|
|
269
|
+
const values = await withRequiredRedis(() => this.backend.redis.mget(keys));
|
|
270
|
+
const rows = [];
|
|
271
|
+
for (const [index, raw] of values.entries()) {
|
|
272
|
+
const redisKey = keys[index];
|
|
273
|
+
if (!redisKey)
|
|
274
|
+
continue;
|
|
275
|
+
const publicKey = publicStateKey(redisKey, [this.statePrefix()]);
|
|
276
|
+
if (options?.prefix && !publicKey.startsWith(options.prefix))
|
|
277
|
+
continue;
|
|
278
|
+
const value = envelopeFromJson(publicKey, raw);
|
|
279
|
+
if (!value)
|
|
280
|
+
continue;
|
|
281
|
+
rows.push(value);
|
|
282
|
+
}
|
|
283
|
+
return rows.slice(0, requestedLimit);
|
|
148
284
|
}
|
|
149
285
|
async get(key) {
|
|
150
|
-
await
|
|
151
|
-
|
|
286
|
+
const raw = await this.readRaw(key);
|
|
287
|
+
if (isStateTombstone(raw))
|
|
288
|
+
return null;
|
|
152
289
|
return envelopeFromJson(key, raw);
|
|
153
290
|
}
|
|
154
291
|
async set(key, value, options) {
|
|
155
292
|
this.enforceValueSize(value);
|
|
156
|
-
await this.enforceMaxEntries(key);
|
|
157
293
|
const current = await this.get(key);
|
|
158
294
|
const createdAt = current?.createdAt ?? new Date().toISOString();
|
|
159
295
|
const version = (current?.version ?? 0) + 1;
|
|
160
|
-
const
|
|
161
|
-
const
|
|
162
|
-
|
|
163
|
-
const
|
|
164
|
-
|
|
296
|
+
const timing = this.writeTiming(options?.ttl);
|
|
297
|
+
const envelope = redisEnvelope(value, version, createdAt, timing.expiresAt);
|
|
298
|
+
await requireRedisReady(this.backend.redis);
|
|
299
|
+
const result = await withRequiredRedis(() => this.backend.redis.eval(SET_WITH_QUOTA_SCRIPT, 2, this.redisKey(key), this.indexKey(), String(Date.now()), String(this.options.maxEntries), String(timing.expiresAtMs), String(timing.indexTtlMs), JSON.stringify(envelope)));
|
|
300
|
+
if (!Array.isArray(result) || Number(result[0]) !== 1) {
|
|
301
|
+
throw this.quotaExceeded();
|
|
302
|
+
}
|
|
165
303
|
return {
|
|
166
304
|
key,
|
|
167
305
|
value,
|
|
168
306
|
version,
|
|
169
|
-
expiresAt,
|
|
307
|
+
expiresAt: timing.expiresAt,
|
|
170
308
|
createdAt,
|
|
171
309
|
updatedAt: envelope.updatedAt,
|
|
172
310
|
};
|
|
@@ -179,15 +317,29 @@ class RedisProviderStateNamespace {
|
|
|
179
317
|
}
|
|
180
318
|
async compareAndSet(key, expectedVersion, value, options) {
|
|
181
319
|
this.enforceValueSize(value);
|
|
182
|
-
const
|
|
183
|
-
|
|
184
|
-
|
|
320
|
+
const createdAt = new Date().toISOString();
|
|
321
|
+
const timing = this.writeTiming(options?.ttl);
|
|
322
|
+
const envelope = redisEnvelope(value, expectedVersion + 1, createdAt, timing.expiresAt);
|
|
323
|
+
await requireRedisReady(this.backend.redis);
|
|
324
|
+
const result = await withRequiredRedis(() => this.backend.redis.eval(COMPARE_AND_SET_WITH_QUOTA_SCRIPT, 2, this.redisKey(key), this.indexKey(), String(expectedVersion), String(Date.now()), String(this.options.maxEntries), String(timing.expiresAtMs), String(timing.indexTtlMs), JSON.stringify(envelope)));
|
|
325
|
+
if (Array.isArray(result) && Number(result[0]) === 0) {
|
|
326
|
+
throw this.quotaExceeded();
|
|
327
|
+
}
|
|
328
|
+
if (!Array.isArray(result) || Number(result[0]) !== 1) {
|
|
329
|
+
const rawCurrent = Array.isArray(result) && typeof result[1] === "string" ? result[1] : null;
|
|
330
|
+
return { ok: false, current: envelopeFromJson(key, rawCurrent) };
|
|
331
|
+
}
|
|
332
|
+
const rawWritten = typeof result[1] === "string" ? result[1] : null;
|
|
333
|
+
const written = envelopeFromJson(key, rawWritten);
|
|
334
|
+
if (!written) {
|
|
335
|
+
throw new UnsupportedProviderStateError("Provider runtime state CAS returned an invalid value");
|
|
185
336
|
}
|
|
186
|
-
return { ok: true, value:
|
|
337
|
+
return { ok: true, value: written };
|
|
187
338
|
}
|
|
188
339
|
async delete(key) {
|
|
189
340
|
await requireRedisReady(this.backend.redis);
|
|
190
|
-
|
|
341
|
+
const tombstone = JSON.stringify({ deleted: true });
|
|
342
|
+
await withRequiredRedis(() => this.backend.redis.eval(DELETE_WITH_INDEX_SCRIPT, 2, this.redisKey(key), this.indexKey(), tombstone, String(parseStateDurationMs(this.options.maxTtl))));
|
|
191
343
|
}
|
|
192
344
|
async increment(key, field, delta = 1, options) {
|
|
193
345
|
const current = (await this.get(key))?.value ?? {};
|
|
@@ -198,12 +350,19 @@ class RedisProviderStateNamespace {
|
|
|
198
350
|
class RedisProviderRuntimeState {
|
|
199
351
|
backend;
|
|
200
352
|
providerId;
|
|
201
|
-
|
|
202
|
-
|
|
353
|
+
redisUrl;
|
|
354
|
+
scopeDiscriminator;
|
|
355
|
+
constructor(options, scopeDiscriminator = MISSING_CONNECTION_SCOPE_DISCRIMINATOR) {
|
|
356
|
+
this.backend = getRedisBackend(options.redisUrl, options.__redisClient);
|
|
203
357
|
this.providerId = options.providerId;
|
|
358
|
+
this.redisUrl = options.redisUrl;
|
|
359
|
+
this.scopeDiscriminator = scopeDiscriminator;
|
|
360
|
+
}
|
|
361
|
+
forConnection(connectionId) {
|
|
362
|
+
return new RedisProviderRuntimeState({ redisUrl: this.redisUrl, providerId: this.providerId }, connectionScopeDiscriminator(connectionId));
|
|
204
363
|
}
|
|
205
364
|
namespace(name, options) {
|
|
206
|
-
return new RedisProviderStateNamespace(this.backend, this.providerId, name, options);
|
|
365
|
+
return new RedisProviderStateNamespace(this.backend, this.providerId, name, options, options.scope === "provider" ? PROVIDER_SCOPE_DISCRIMINATOR : this.scopeDiscriminator);
|
|
207
366
|
}
|
|
208
367
|
}
|
|
209
368
|
export class UnsupportedProviderStateError extends ProviderError {
|
|
@@ -236,6 +395,9 @@ class UnsupportedProviderStateNamespace {
|
|
|
236
395
|
}
|
|
237
396
|
}
|
|
238
397
|
class UnsupportedProviderRuntimeState {
|
|
398
|
+
forConnection(_connectionId) {
|
|
399
|
+
return new UnsupportedProviderRuntimeState();
|
|
400
|
+
}
|
|
239
401
|
namespace(_name, _options) {
|
|
240
402
|
return new UnsupportedProviderStateNamespace();
|
|
241
403
|
}
|
|
@@ -247,6 +409,23 @@ class MemoryProviderStateNamespace {
|
|
|
247
409
|
constructor(options) {
|
|
248
410
|
this.options = options;
|
|
249
411
|
}
|
|
412
|
+
enforceValueSize(value) {
|
|
413
|
+
const bytes = Buffer.byteLength(JSON.stringify(value), "utf8");
|
|
414
|
+
if (bytes > this.options.maxValueBytes) {
|
|
415
|
+
throw new UnsupportedProviderStateError(`Provider runtime state value exceeds maxValueBytes (${bytes} > ${this.options.maxValueBytes})`);
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
enforceWritePolicy(key, value, ttl) {
|
|
419
|
+
this.enforceValueSize(value);
|
|
420
|
+
const ttlMs = parseStateDurationMs(ttl ?? this.options.defaultTtl);
|
|
421
|
+
const maxTtlMs = parseStateDurationMs(this.options.maxTtl);
|
|
422
|
+
if (ttlMs > maxTtlMs) {
|
|
423
|
+
throw new UnsupportedProviderStateError(`Provider runtime state ttl exceeds maxTtl (${ttlMs} > ${maxTtlMs})`);
|
|
424
|
+
}
|
|
425
|
+
if (!this.values.has(key) && this.values.size >= this.options.maxEntries) {
|
|
426
|
+
throw new UnsupportedProviderStateError(`Provider runtime state namespace quota exceeded (${this.options.maxEntries + 1} > ${this.options.maxEntries})`);
|
|
427
|
+
}
|
|
428
|
+
}
|
|
250
429
|
pruneExpired(nowMs = Date.now()) {
|
|
251
430
|
for (const [key, row] of this.values.entries()) {
|
|
252
431
|
if (row.expiresAt && Date.parse(row.expiresAt) <= nowMs) {
|
|
@@ -265,6 +444,7 @@ class MemoryProviderStateNamespace {
|
|
|
265
444
|
}
|
|
266
445
|
async set(key, value, options) {
|
|
267
446
|
this.pruneExpired();
|
|
447
|
+
this.enforceWritePolicy(key, value, options?.ttl);
|
|
268
448
|
const now = new Date().toISOString();
|
|
269
449
|
const current = this.values.get(key);
|
|
270
450
|
const expiresAt = resolveMemoryStateExpiresAt(options?.ttl ?? this.options.defaultTtl);
|
|
@@ -282,8 +462,24 @@ class MemoryProviderStateNamespace {
|
|
|
282
462
|
async patch(_key, _partial, _options) {
|
|
283
463
|
throw new UnsupportedProviderStateError("In-memory provider runtime state does not support patch");
|
|
284
464
|
}
|
|
285
|
-
async compareAndSet(
|
|
286
|
-
|
|
465
|
+
async compareAndSet(key, expectedVersion, value, options) {
|
|
466
|
+
this.pruneExpired();
|
|
467
|
+
const current = this.values.get(key);
|
|
468
|
+
if ((current?.version ?? 0) !== expectedVersion) {
|
|
469
|
+
return { ok: false, current: current ?? null };
|
|
470
|
+
}
|
|
471
|
+
this.enforceWritePolicy(key, value, options?.ttl);
|
|
472
|
+
const now = new Date().toISOString();
|
|
473
|
+
const stored = {
|
|
474
|
+
key,
|
|
475
|
+
value,
|
|
476
|
+
version: expectedVersion + 1,
|
|
477
|
+
expiresAt: resolveMemoryStateExpiresAt(options?.ttl ?? this.options.defaultTtl),
|
|
478
|
+
createdAt: current?.createdAt ?? now,
|
|
479
|
+
updatedAt: now,
|
|
480
|
+
};
|
|
481
|
+
this.values.set(key, stored);
|
|
482
|
+
return { ok: true, value: stored };
|
|
287
483
|
}
|
|
288
484
|
async delete(key) {
|
|
289
485
|
this.values.delete(key);
|
|
@@ -293,13 +489,23 @@ class MemoryProviderStateNamespace {
|
|
|
293
489
|
}
|
|
294
490
|
}
|
|
295
491
|
class MemoryProviderRuntimeState {
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
492
|
+
backend;
|
|
493
|
+
scopeDiscriminator;
|
|
494
|
+
constructor(backend = { namespaces: new Map() }, scopeDiscriminator = MISSING_CONNECTION_SCOPE_DISCRIMINATOR) {
|
|
495
|
+
this.backend = backend;
|
|
496
|
+
this.scopeDiscriminator = scopeDiscriminator;
|
|
497
|
+
}
|
|
498
|
+
forConnection(connectionId) {
|
|
499
|
+
return new MemoryProviderRuntimeState(this.backend, connectionScopeDiscriminator(connectionId));
|
|
500
|
+
}
|
|
501
|
+
namespace(name, options) {
|
|
502
|
+
const scopeDiscriminator = options.scope === "provider" ? PROVIDER_SCOPE_DISCRIMINATOR : this.scopeDiscriminator;
|
|
503
|
+
const namespaceIdentity = `${scopeDiscriminator}\0${name}`;
|
|
504
|
+
const existing = this.backend.namespaces.get(namespaceIdentity);
|
|
299
505
|
if (existing)
|
|
300
506
|
return existing;
|
|
301
|
-
const created = new MemoryProviderStateNamespace(
|
|
302
|
-
this.namespaces.set(
|
|
507
|
+
const created = new MemoryProviderStateNamespace(options);
|
|
508
|
+
this.backend.namespaces.set(namespaceIdentity, created);
|
|
303
509
|
return created;
|
|
304
510
|
}
|
|
305
511
|
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { CookieJar, StealthCookieStore, StealthCookieStoreV1, StealthSessionCookies } from "../types.js";
|
|
2
|
+
export declare class StealthCookieJar implements CookieJar, StealthSessionCookies {
|
|
3
|
+
private cookies;
|
|
4
|
+
private readonly defaultUrl;
|
|
5
|
+
constructor(cookieStrings: readonly string[], defaultUrl?: string);
|
|
6
|
+
setFromCookieStrings(cookieStrings: readonly string[], url?: string): void;
|
|
7
|
+
get(name: string, url?: string): string | undefined;
|
|
8
|
+
getAll(url?: string): Record<string, string>;
|
|
9
|
+
has(name: string, url?: string): boolean;
|
|
10
|
+
toString(url?: string): string;
|
|
11
|
+
toHeader(url?: string): string;
|
|
12
|
+
snapshot(): Record<string, string>;
|
|
13
|
+
restore(cookies: Record<string, string>): void;
|
|
14
|
+
serialize(): StealthCookieStoreV1;
|
|
15
|
+
deserialize(state: StealthCookieStore): void;
|
|
16
|
+
clear(): void;
|
|
17
|
+
find(predicate: (cookie: string) => boolean, url?: string): string | undefined;
|
|
18
|
+
private normalizeUrl;
|
|
19
|
+
private getUniqueCookies;
|
|
20
|
+
}
|