@apifuse/provider-sdk 2.2.0-beta.5 → 2.2.0-beta.50
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 +201 -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 +1178 -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-map.ts.txt +5 -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 +1654 -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,562 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
ProviderEventOwnerFence,
|
|
3
|
+
ProviderEventPublishOptions,
|
|
4
|
+
} from "./provider-event-pipeline.js";
|
|
5
|
+
import type { SessionKey } from "./session-key.js";
|
|
6
|
+
import {
|
|
7
|
+
makeStatefulProviderCloseContext,
|
|
8
|
+
makeStatefulProviderSessionContext,
|
|
9
|
+
} from "./stateful-provider-adapter-context.js";
|
|
10
|
+
import {
|
|
11
|
+
emitStatefulSessionInvalidatedMetric,
|
|
12
|
+
observeStatefulSessionOperationDuration,
|
|
13
|
+
} from "./stateful-provider-adapter-metrics.js";
|
|
14
|
+
import {
|
|
15
|
+
NOOP_STATEFUL_PROVIDER_METRIC_EMITTER,
|
|
16
|
+
type StatefulProviderMetricEmitter,
|
|
17
|
+
} from "./stateful-provider-observability.js";
|
|
18
|
+
import type {
|
|
19
|
+
StatefulOperationExecutor,
|
|
20
|
+
StatefulOperationRequest,
|
|
21
|
+
StatefulOperationResult,
|
|
22
|
+
StatefulOwnershipValidator,
|
|
23
|
+
} from "./stateful-provider-session-routing.js";
|
|
24
|
+
import type {
|
|
25
|
+
ManagedSession,
|
|
26
|
+
ManagedSessionIdentity,
|
|
27
|
+
SessionOwnerRecord,
|
|
28
|
+
SessionPoolPolicy,
|
|
29
|
+
} from "./stateful-provider-session-runtime.js";
|
|
30
|
+
import { PodLocalSessionPool } from "./stateful-provider-session-runtime.js";
|
|
31
|
+
|
|
32
|
+
export type StatefulProviderConcurrencyPolicy =
|
|
33
|
+
| { readonly mode: "serialize" }
|
|
34
|
+
| { readonly mode: "parallel"; readonly maxInFlight?: number };
|
|
35
|
+
|
|
36
|
+
export type StatefulProviderReconnectPolicy = "resume" | "recreate" | "unsupported";
|
|
37
|
+
|
|
38
|
+
export type StatefulProviderAdapterPolicy = {
|
|
39
|
+
readonly concurrency: StatefulProviderConcurrencyPolicy;
|
|
40
|
+
readonly reconnect: StatefulProviderReconnectPolicy;
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
export type StatefulSessionHealth = {
|
|
44
|
+
readonly status: "ready" | "connecting" | "reconnecting" | "degraded" | "closed";
|
|
45
|
+
readonly reason?: string;
|
|
46
|
+
readonly lastError?: string;
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
export class StatefulSessionInvalidatedError extends Error {
|
|
50
|
+
readonly name = "StatefulSessionInvalidatedError";
|
|
51
|
+
readonly reason: string;
|
|
52
|
+
readonly retryable: boolean;
|
|
53
|
+
|
|
54
|
+
constructor(message: string, options: { readonly reason: string; readonly retryable?: boolean }) {
|
|
55
|
+
super(message);
|
|
56
|
+
this.reason = options.reason;
|
|
57
|
+
this.retryable = options.retryable ?? false;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export interface StatefulProviderSessionContext {
|
|
62
|
+
readonly sessionKey: string;
|
|
63
|
+
readonly providerId: string;
|
|
64
|
+
/** Present for operation-created sessions; optional for direct pool users. */
|
|
65
|
+
readonly connectionId?: string;
|
|
66
|
+
/** Present for operation-created sessions; optional for direct pool users. */
|
|
67
|
+
readonly serviceAccountId?: string;
|
|
68
|
+
readonly ownerPodId?: string;
|
|
69
|
+
readonly ownerEndpoint?: string;
|
|
70
|
+
readonly ownerStatus?: string;
|
|
71
|
+
readonly generation: number;
|
|
72
|
+
readonly signal: AbortSignal;
|
|
73
|
+
readonly requestId?: string;
|
|
74
|
+
readonly operationId?: string;
|
|
75
|
+
readonly idempotencyKey?: string;
|
|
76
|
+
readonly deadlineAt?: string;
|
|
77
|
+
readonly runtimeContext?: unknown;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export type StatefulProviderEventPublish<TEvent = unknown> = (
|
|
81
|
+
event: TEvent,
|
|
82
|
+
) => void | Promise<void>;
|
|
83
|
+
export type StatefulProviderEventDisposer = () => void | Promise<void>;
|
|
84
|
+
|
|
85
|
+
export interface StatefulProviderEventPublisher<TEvent = unknown> {
|
|
86
|
+
publish(event: TEvent, options: ProviderEventPublishOptions): unknown;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export type StatefulWriteReconciliationResult<TSharedState = unknown> = {
|
|
90
|
+
readonly result: StatefulOperationResult;
|
|
91
|
+
readonly sharedState?: TSharedState;
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
export interface StatefulProviderAdapter<TSession, TSharedState = unknown, TEvent = unknown> {
|
|
95
|
+
readonly providerId: string;
|
|
96
|
+
readonly policy: StatefulProviderAdapterPolicy;
|
|
97
|
+
/** Optional until durable cursor/checkpoint state exists for a provider. */
|
|
98
|
+
restore?(ctx: StatefulProviderSessionContext): Promise<TSharedState | undefined>;
|
|
99
|
+
connect(
|
|
100
|
+
ctx: StatefulProviderSessionContext,
|
|
101
|
+
sharedState: TSharedState | undefined,
|
|
102
|
+
): Promise<TSession>;
|
|
103
|
+
invoke(
|
|
104
|
+
ctx: StatefulProviderSessionContext,
|
|
105
|
+
session: TSession,
|
|
106
|
+
request: StatefulOperationRequest,
|
|
107
|
+
): Promise<StatefulOperationResult>;
|
|
108
|
+
/** Requires a matching session-manager checkpointStore. */
|
|
109
|
+
snapshot?(
|
|
110
|
+
ctx: StatefulProviderSessionContext,
|
|
111
|
+
session: TSession,
|
|
112
|
+
): Promise<TSharedState | undefined>;
|
|
113
|
+
/** Caller-driven through StatefulProviderSessionManager.health(). */
|
|
114
|
+
health?(ctx: StatefulProviderSessionContext, session: TSession): Promise<StatefulSessionHealth>;
|
|
115
|
+
close(ctx: StatefulProviderSessionContext, session: TSession, reason: string): Promise<void>;
|
|
116
|
+
/** Caller-driven through StatefulProviderSessionManager.reconcileWrite(). */
|
|
117
|
+
reconcileWrite?(
|
|
118
|
+
ctx: StatefulProviderSessionContext,
|
|
119
|
+
ledgerEntry: unknown,
|
|
120
|
+
sharedState: TSharedState | undefined,
|
|
121
|
+
): Promise<StatefulWriteReconciliationResult<TSharedState>>;
|
|
122
|
+
/** Registers a connection-owned event source. Its disposer runs before checkpoint and close. */
|
|
123
|
+
subscribe?(
|
|
124
|
+
ctx: StatefulProviderSessionContext,
|
|
125
|
+
session: TSession,
|
|
126
|
+
publish: StatefulProviderEventPublish<TEvent>,
|
|
127
|
+
): StatefulProviderEventDisposer | Promise<StatefulProviderEventDisposer>;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
export type StatefulProviderSessionManagerOptions<TSession, TSharedState, TEvent = unknown> = {
|
|
131
|
+
readonly adapter: StatefulProviderAdapter<TSession, TSharedState, TEvent>;
|
|
132
|
+
readonly poolPolicy: SessionPoolPolicy;
|
|
133
|
+
readonly checkpointStore?: (
|
|
134
|
+
ctx: StatefulProviderSessionContext,
|
|
135
|
+
state: TSharedState,
|
|
136
|
+
) => Promise<void>;
|
|
137
|
+
readonly eventPublisher?: StatefulProviderEventPublisher<TEvent>;
|
|
138
|
+
readonly clock?: () => Date;
|
|
139
|
+
readonly metricEmitter?: StatefulProviderMetricEmitter;
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
type ParallelLimiter = {
|
|
143
|
+
active: number;
|
|
144
|
+
readonly waiting: Array<() => void>;
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
const STATEFUL_SESSION_ESTABLISHMENT_FAILURE = Symbol.for(
|
|
148
|
+
"@apifuse/provider-sdk/stateful/session-establishment-failure@1",
|
|
149
|
+
);
|
|
150
|
+
|
|
151
|
+
function markStatefulSessionEstablishmentFailure(error: unknown): unknown {
|
|
152
|
+
if ((typeof error === "object" && error !== null) || typeof error === "function") {
|
|
153
|
+
try {
|
|
154
|
+
Object.defineProperty(error, STATEFUL_SESSION_ESTABLISHMENT_FAILURE, {
|
|
155
|
+
value: true,
|
|
156
|
+
enumerable: false,
|
|
157
|
+
});
|
|
158
|
+
return error;
|
|
159
|
+
} catch {}
|
|
160
|
+
}
|
|
161
|
+
const wrapped = new Error("Stateful session establishment failed.", { cause: error });
|
|
162
|
+
Object.defineProperty(wrapped, STATEFUL_SESSION_ESTABLISHMENT_FAILURE, { value: true });
|
|
163
|
+
return wrapped;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
export class StatefulProviderSessionManager<TSession, TSharedState = unknown, TEvent = unknown>
|
|
167
|
+
implements StatefulOperationExecutor<TSession>
|
|
168
|
+
{
|
|
169
|
+
readonly #adapter: StatefulProviderAdapter<TSession, TSharedState, TEvent>;
|
|
170
|
+
readonly #pool: PodLocalSessionPool<TSession>;
|
|
171
|
+
readonly #checkpointStore?: StatefulProviderSessionManagerOptions<
|
|
172
|
+
TSession,
|
|
173
|
+
TSharedState,
|
|
174
|
+
TEvent
|
|
175
|
+
>["checkpointStore"];
|
|
176
|
+
readonly #eventPublisher?: StatefulProviderEventPublisher<TEvent>;
|
|
177
|
+
readonly #clock: () => Date;
|
|
178
|
+
readonly #metricEmitter: StatefulProviderMetricEmitter;
|
|
179
|
+
readonly #eventDisposers = new Map<string, StatefulProviderEventDisposer>();
|
|
180
|
+
readonly #eventOwnerFences = new Map<string, ProviderEventOwnerFence>();
|
|
181
|
+
readonly #parallelLimiters = new Map<string, ParallelLimiter>();
|
|
182
|
+
|
|
183
|
+
constructor(options: StatefulProviderSessionManagerOptions<TSession, TSharedState, TEvent>) {
|
|
184
|
+
validateAdapterConfiguration(options);
|
|
185
|
+
this.#adapter = options.adapter;
|
|
186
|
+
this.#checkpointStore = options.checkpointStore;
|
|
187
|
+
this.#eventPublisher = options.eventPublisher;
|
|
188
|
+
this.#clock = options.clock ?? (() => new Date());
|
|
189
|
+
this.#metricEmitter = options.metricEmitter ?? NOOP_STATEFUL_PROVIDER_METRIC_EMITTER;
|
|
190
|
+
this.#pool = new PodLocalSessionPool<TSession>(options.poolPolicy, (session, reason) =>
|
|
191
|
+
this.closeManagedSession(session, reason),
|
|
192
|
+
);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
async invoke(
|
|
196
|
+
owner: SessionOwnerRecord,
|
|
197
|
+
request: StatefulOperationRequest,
|
|
198
|
+
signal: AbortSignal,
|
|
199
|
+
validateOwnership?: StatefulOwnershipValidator,
|
|
200
|
+
): Promise<StatefulOperationResult> {
|
|
201
|
+
const startedAt = this.#clock().getTime();
|
|
202
|
+
const run = async () => {
|
|
203
|
+
const managedSession = await this.getOrCreate(owner, request, signal, validateOwnership);
|
|
204
|
+
const validatedOwner = await this.validatePooledOwnership(
|
|
205
|
+
owner,
|
|
206
|
+
request.sessionKey,
|
|
207
|
+
signal,
|
|
208
|
+
validateOwnership,
|
|
209
|
+
);
|
|
210
|
+
const ctx = makeStatefulProviderSessionContext(
|
|
211
|
+
this.#adapter.providerId,
|
|
212
|
+
validatedOwner,
|
|
213
|
+
request,
|
|
214
|
+
signal,
|
|
215
|
+
);
|
|
216
|
+
try {
|
|
217
|
+
const result = await this.#adapter.invoke(ctx, managedSession.value, request);
|
|
218
|
+
this.observeDuration(request, owner, startedAt);
|
|
219
|
+
return result;
|
|
220
|
+
} catch (error) {
|
|
221
|
+
if (!(error instanceof StatefulSessionInvalidatedError)) throw error;
|
|
222
|
+
emitStatefulSessionInvalidatedMetric({
|
|
223
|
+
metricEmitter: this.#metricEmitter,
|
|
224
|
+
request,
|
|
225
|
+
owner,
|
|
226
|
+
reason: error.reason,
|
|
227
|
+
});
|
|
228
|
+
await this.#pool.invalidate(owner.sessionKey, error.reason);
|
|
229
|
+
if (!error.retryable || this.#adapter.policy.reconnect === "unsupported") throw error;
|
|
230
|
+
const reconnected = await this.getOrCreate(owner, request, signal, validateOwnership);
|
|
231
|
+
const revalidatedOwner = await this.validatePooledOwnership(
|
|
232
|
+
owner,
|
|
233
|
+
request.sessionKey,
|
|
234
|
+
signal,
|
|
235
|
+
validateOwnership,
|
|
236
|
+
);
|
|
237
|
+
const reconnectedCtx = makeStatefulProviderSessionContext(
|
|
238
|
+
this.#adapter.providerId,
|
|
239
|
+
revalidatedOwner,
|
|
240
|
+
request,
|
|
241
|
+
signal,
|
|
242
|
+
);
|
|
243
|
+
const result = await this.#adapter.invoke(reconnectedCtx, reconnected.value, request);
|
|
244
|
+
this.observeDuration(request, owner, startedAt);
|
|
245
|
+
return result;
|
|
246
|
+
}
|
|
247
|
+
};
|
|
248
|
+
|
|
249
|
+
return this.schedule(owner.sessionKey, run);
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
executeLocal(
|
|
253
|
+
request: StatefulOperationRequest,
|
|
254
|
+
owner: SessionOwnerRecord,
|
|
255
|
+
signal: AbortSignal,
|
|
256
|
+
validateOwnership?: StatefulOwnershipValidator,
|
|
257
|
+
): Promise<StatefulOperationResult> {
|
|
258
|
+
return this.invoke(owner, request, signal, validateOwnership);
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
async health(
|
|
262
|
+
owner: SessionOwnerRecord,
|
|
263
|
+
request: StatefulOperationRequest,
|
|
264
|
+
signal: AbortSignal,
|
|
265
|
+
validateOwnership?: StatefulOwnershipValidator,
|
|
266
|
+
): Promise<StatefulSessionHealth> {
|
|
267
|
+
if (!this.#adapter.health) {
|
|
268
|
+
throw new Error(
|
|
269
|
+
`Stateful provider adapter "${this.#adapter.providerId}" has no health hook.`,
|
|
270
|
+
);
|
|
271
|
+
}
|
|
272
|
+
return this.schedule(owner.sessionKey, async () => {
|
|
273
|
+
const session = await this.getOrCreate(owner, request, signal, validateOwnership);
|
|
274
|
+
const validatedOwner = await this.validatePooledOwnership(
|
|
275
|
+
owner,
|
|
276
|
+
request.sessionKey,
|
|
277
|
+
signal,
|
|
278
|
+
validateOwnership,
|
|
279
|
+
);
|
|
280
|
+
return this.#adapter.health?.(
|
|
281
|
+
makeStatefulProviderSessionContext(
|
|
282
|
+
this.#adapter.providerId,
|
|
283
|
+
validatedOwner,
|
|
284
|
+
request,
|
|
285
|
+
signal,
|
|
286
|
+
),
|
|
287
|
+
session.value,
|
|
288
|
+
) as Promise<StatefulSessionHealth>;
|
|
289
|
+
});
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
async reconcileWrite(
|
|
293
|
+
owner: SessionOwnerRecord,
|
|
294
|
+
request: StatefulOperationRequest,
|
|
295
|
+
ledgerEntry: unknown,
|
|
296
|
+
signal: AbortSignal,
|
|
297
|
+
validateOwnership?: StatefulOwnershipValidator,
|
|
298
|
+
): Promise<StatefulWriteReconciliationResult<TSharedState>> {
|
|
299
|
+
if (!this.#adapter.reconcileWrite) {
|
|
300
|
+
throw new Error(
|
|
301
|
+
`Stateful provider adapter "${this.#adapter.providerId}" does not support write reconciliation.`,
|
|
302
|
+
);
|
|
303
|
+
}
|
|
304
|
+
return this.schedule(owner.sessionKey, async () => {
|
|
305
|
+
const restoreCtx = makeStatefulProviderSessionContext(
|
|
306
|
+
this.#adapter.providerId,
|
|
307
|
+
owner,
|
|
308
|
+
request,
|
|
309
|
+
signal,
|
|
310
|
+
);
|
|
311
|
+
const sharedState = this.#adapter.restore
|
|
312
|
+
? await this.#adapter.restore(restoreCtx)
|
|
313
|
+
: undefined;
|
|
314
|
+
const validatedOwner = validateOwnership ? await validateOwnership(owner, signal) : owner;
|
|
315
|
+
const ctx = makeStatefulProviderSessionContext(
|
|
316
|
+
this.#adapter.providerId,
|
|
317
|
+
validatedOwner,
|
|
318
|
+
request,
|
|
319
|
+
signal,
|
|
320
|
+
);
|
|
321
|
+
return this.#adapter.reconcileWrite?.(ctx, ledgerEntry, sharedState) as Promise<
|
|
322
|
+
StatefulWriteReconciliationResult<TSharedState>
|
|
323
|
+
>;
|
|
324
|
+
});
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
closeAll(reason: string): Promise<void> {
|
|
328
|
+
return this.#pool.closeAll(reason);
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
private async getOrCreate(
|
|
332
|
+
owner: SessionOwnerRecord,
|
|
333
|
+
request: StatefulOperationRequest,
|
|
334
|
+
signal: AbortSignal,
|
|
335
|
+
validateOwnership?: StatefulOwnershipValidator,
|
|
336
|
+
): Promise<ManagedSession<TSession>> {
|
|
337
|
+
return this.#pool.getOrCreate(
|
|
338
|
+
owner.sessionKey,
|
|
339
|
+
owner.generation,
|
|
340
|
+
() => this.connect(owner, request, signal, validateOwnership),
|
|
341
|
+
this.#clock(),
|
|
342
|
+
managedSessionIdentity(owner, request),
|
|
343
|
+
);
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
private async connect(
|
|
347
|
+
owner: SessionOwnerRecord,
|
|
348
|
+
request: StatefulOperationRequest,
|
|
349
|
+
signal: AbortSignal,
|
|
350
|
+
validateOwnership?: StatefulOwnershipValidator,
|
|
351
|
+
): Promise<TSession> {
|
|
352
|
+
const ctx = makeStatefulProviderSessionContext(
|
|
353
|
+
this.#adapter.providerId,
|
|
354
|
+
owner,
|
|
355
|
+
request,
|
|
356
|
+
signal,
|
|
357
|
+
);
|
|
358
|
+
let session: TSession | undefined;
|
|
359
|
+
let connected = false;
|
|
360
|
+
try {
|
|
361
|
+
const sharedState = this.#adapter.restore ? await this.#adapter.restore(ctx) : undefined;
|
|
362
|
+
session = await this.#adapter.connect(ctx, sharedState);
|
|
363
|
+
connected = true;
|
|
364
|
+
const validatedOwner = validateOwnership ? await validateOwnership(owner, signal) : owner;
|
|
365
|
+
const validatedCtx = makeStatefulProviderSessionContext(
|
|
366
|
+
this.#adapter.providerId,
|
|
367
|
+
validatedOwner,
|
|
368
|
+
request,
|
|
369
|
+
signal,
|
|
370
|
+
);
|
|
371
|
+
this.refreshEventOwnerFence(validatedOwner, request.sessionKey);
|
|
372
|
+
if (!this.#adapter.subscribe || !this.#eventPublisher) return session;
|
|
373
|
+
const identityKey = sessionIdentityKey(owner.sessionKey, owner.generation);
|
|
374
|
+
const disposer = await this.#adapter.subscribe(validatedCtx, session, (event) => {
|
|
375
|
+
const ownerFence = this.#eventOwnerFences.get(identityKey);
|
|
376
|
+
if (!ownerFence) return;
|
|
377
|
+
this.#eventPublisher?.publish(event, { ownerFence });
|
|
378
|
+
});
|
|
379
|
+
if (typeof disposer !== "function") {
|
|
380
|
+
throw new Error(
|
|
381
|
+
`Stateful provider adapter "${this.#adapter.providerId}" subscribe hook must return a disposer.`,
|
|
382
|
+
);
|
|
383
|
+
}
|
|
384
|
+
this.#eventDisposers.set(identityKey, disposer);
|
|
385
|
+
return session;
|
|
386
|
+
} catch (error) {
|
|
387
|
+
this.#eventOwnerFences.delete(sessionIdentityKey(owner.sessionKey, owner.generation));
|
|
388
|
+
let establishmentError = error;
|
|
389
|
+
if (connected) {
|
|
390
|
+
try {
|
|
391
|
+
await this.#adapter.close(ctx, session as TSession, "session-establishment-failed");
|
|
392
|
+
} catch (closeError) {
|
|
393
|
+
establishmentError = new AggregateError(
|
|
394
|
+
[error, closeError],
|
|
395
|
+
"Stateful session establishment and cleanup both failed.",
|
|
396
|
+
);
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
throw markStatefulSessionEstablishmentFailure(establishmentError);
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
private async validatePooledOwnership(
|
|
404
|
+
owner: SessionOwnerRecord,
|
|
405
|
+
sessionKey: SessionKey,
|
|
406
|
+
signal: AbortSignal,
|
|
407
|
+
validateOwnership?: StatefulOwnershipValidator,
|
|
408
|
+
): Promise<SessionOwnerRecord> {
|
|
409
|
+
try {
|
|
410
|
+
const validatedOwner = validateOwnership ? await validateOwnership(owner, signal) : owner;
|
|
411
|
+
this.refreshEventOwnerFence(validatedOwner, sessionKey);
|
|
412
|
+
return validatedOwner;
|
|
413
|
+
} catch (error) {
|
|
414
|
+
await this.#pool
|
|
415
|
+
.invalidate(owner.sessionKey, "ownership-validation-failed")
|
|
416
|
+
.catch(() => undefined);
|
|
417
|
+
throw markStatefulSessionEstablishmentFailure(error);
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
private refreshEventOwnerFence(owner: SessionOwnerRecord, sessionKey: SessionKey): void {
|
|
422
|
+
this.#eventOwnerFences.set(sessionIdentityKey(owner.sessionKey, owner.generation), {
|
|
423
|
+
sessionKey,
|
|
424
|
+
generation: owner.generation,
|
|
425
|
+
ownerPodId: owner.ownerPodId,
|
|
426
|
+
ownerEndpoint: owner.ownerEndpoint,
|
|
427
|
+
});
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
private async closeManagedSession(
|
|
431
|
+
session: ManagedSession<TSession>,
|
|
432
|
+
reason: string,
|
|
433
|
+
): Promise<void> {
|
|
434
|
+
const ctx = makeStatefulProviderCloseContext(this.#adapter.providerId, session);
|
|
435
|
+
const errors: unknown[] = [];
|
|
436
|
+
const disposerKey = sessionIdentityKey(session.sessionKey, session.generation);
|
|
437
|
+
const disposer = this.#eventDisposers.get(disposerKey);
|
|
438
|
+
this.#eventDisposers.delete(disposerKey);
|
|
439
|
+
this.#eventOwnerFences.delete(disposerKey);
|
|
440
|
+
if (disposer) {
|
|
441
|
+
try {
|
|
442
|
+
await disposer();
|
|
443
|
+
} catch (error) {
|
|
444
|
+
errors.push(error);
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
if (this.#adapter.snapshot && this.#checkpointStore) {
|
|
448
|
+
try {
|
|
449
|
+
const state = await this.#adapter.snapshot(ctx, session.value);
|
|
450
|
+
if (state !== undefined) await this.#checkpointStore(ctx, state);
|
|
451
|
+
} catch (error) {
|
|
452
|
+
errors.push(error);
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
try {
|
|
456
|
+
await this.#adapter.close(ctx, session.value, reason);
|
|
457
|
+
} catch (error) {
|
|
458
|
+
errors.push(error);
|
|
459
|
+
}
|
|
460
|
+
if (errors.length === 1) throw errors[0];
|
|
461
|
+
if (errors.length > 1) {
|
|
462
|
+
throw new AggregateError(errors, "Stateful session cleanup failed in multiple hooks.");
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
private observeDuration(
|
|
467
|
+
request: StatefulOperationRequest,
|
|
468
|
+
owner: SessionOwnerRecord,
|
|
469
|
+
startedAt: number,
|
|
470
|
+
): void {
|
|
471
|
+
observeStatefulSessionOperationDuration({
|
|
472
|
+
metricEmitter: this.#metricEmitter,
|
|
473
|
+
request,
|
|
474
|
+
owner,
|
|
475
|
+
durationMs: this.#clock().getTime() - startedAt,
|
|
476
|
+
});
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
private async runLimited<R>(
|
|
480
|
+
sessionKey: string,
|
|
481
|
+
maxInFlight: number,
|
|
482
|
+
run: () => Promise<R>,
|
|
483
|
+
): Promise<R> {
|
|
484
|
+
const limiter = this.#parallelLimiters.get(sessionKey) ?? { active: 0, waiting: [] };
|
|
485
|
+
this.#parallelLimiters.set(sessionKey, limiter);
|
|
486
|
+
if (limiter.active >= maxInFlight) {
|
|
487
|
+
await new Promise<void>((resolve) => limiter.waiting.push(resolve));
|
|
488
|
+
}
|
|
489
|
+
limiter.active += 1;
|
|
490
|
+
try {
|
|
491
|
+
return await run();
|
|
492
|
+
} finally {
|
|
493
|
+
limiter.active -= 1;
|
|
494
|
+
limiter.waiting.shift()?.();
|
|
495
|
+
if (limiter.active === 0 && limiter.waiting.length === 0) {
|
|
496
|
+
this.#parallelLimiters.delete(sessionKey);
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
private schedule<R>(sessionKey: string, run: () => Promise<R>): Promise<R> {
|
|
502
|
+
const concurrency = this.#adapter.policy.concurrency;
|
|
503
|
+
if (concurrency.mode === "serialize") return this.#pool.runExclusive(sessionKey, run);
|
|
504
|
+
if (concurrency.maxInFlight === undefined) return run();
|
|
505
|
+
return this.runLimited(sessionKey, concurrency.maxInFlight, run);
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
function validateAdapterConfiguration<TSession, TSharedState, TEvent>(
|
|
510
|
+
options: StatefulProviderSessionManagerOptions<TSession, TSharedState, TEvent>,
|
|
511
|
+
): void {
|
|
512
|
+
const adapter = options.adapter;
|
|
513
|
+
if (!adapter.policy?.concurrency || !adapter.policy.reconnect) {
|
|
514
|
+
throw new Error(
|
|
515
|
+
`Stateful provider adapter "${adapter.providerId}" requires concurrency and reconnect policies.`,
|
|
516
|
+
);
|
|
517
|
+
}
|
|
518
|
+
const concurrency = adapter.policy.concurrency;
|
|
519
|
+
if (concurrency.mode !== "serialize" && concurrency.mode !== "parallel") {
|
|
520
|
+
throw new Error('Stateful concurrency policy mode must be "serialize" or "parallel".');
|
|
521
|
+
}
|
|
522
|
+
if (concurrency.mode === "serialize" && "maxInFlight" in concurrency) {
|
|
523
|
+
throw new Error("Stateful serialize concurrency must not declare maxInFlight.");
|
|
524
|
+
}
|
|
525
|
+
if (
|
|
526
|
+
concurrency.mode === "parallel" &&
|
|
527
|
+
concurrency.maxInFlight !== undefined &&
|
|
528
|
+
(!Number.isInteger(concurrency.maxInFlight) || concurrency.maxInFlight <= 0)
|
|
529
|
+
) {
|
|
530
|
+
throw new Error("Stateful parallel concurrency maxInFlight must be a positive integer.");
|
|
531
|
+
}
|
|
532
|
+
if (!(["resume", "recreate", "unsupported"] as const).includes(adapter.policy.reconnect)) {
|
|
533
|
+
throw new Error('Stateful reconnect policy must be "resume", "recreate", or "unsupported".');
|
|
534
|
+
}
|
|
535
|
+
if (Boolean(adapter.snapshot) !== Boolean(options.checkpointStore)) {
|
|
536
|
+
throw new Error(
|
|
537
|
+
`Stateful provider adapter "${adapter.providerId}" snapshot and session-manager checkpointStore must be configured together.`,
|
|
538
|
+
);
|
|
539
|
+
}
|
|
540
|
+
if (Boolean(adapter.subscribe) !== Boolean(options.eventPublisher)) {
|
|
541
|
+
throw new Error(
|
|
542
|
+
`Stateful provider adapter "${adapter.providerId}" subscribe and session-manager eventPublisher must be configured together.`,
|
|
543
|
+
);
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
function managedSessionIdentity(
|
|
548
|
+
owner: SessionOwnerRecord,
|
|
549
|
+
request: StatefulOperationRequest,
|
|
550
|
+
): ManagedSessionIdentity {
|
|
551
|
+
return {
|
|
552
|
+
connectionId: request.connectionId,
|
|
553
|
+
serviceAccountId: request.serviceAccountId,
|
|
554
|
+
ownerPodId: owner.ownerPodId,
|
|
555
|
+
ownerEndpoint: owner.ownerEndpoint,
|
|
556
|
+
ownerStatus: owner.status,
|
|
557
|
+
};
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
function sessionIdentityKey(sessionKey: string, generation: number): string {
|
|
561
|
+
return `${sessionKey}\u001f${generation}`;
|
|
562
|
+
}
|