@apifuse/provider-sdk 2.2.0-beta.62 → 2.2.0-beta.64
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 +367 -9
- package/CHANGELOG.md +8 -0
- package/LICENSE +21 -0
- package/README.md +1 -0
- package/bin/apifuse-check.ts +84 -2
- package/bin/apifuse-dev.ts +11 -31
- package/bin/apifuse-migrate-deployment.ts +200 -0
- package/bin/apifuse-pack-check.ts +11 -4
- package/bin/apifuse-pack-types.ts +187 -4
- package/bin/apifuse-record.ts +44 -32
- package/bin/apifuse-submit-check.ts +9 -5
- package/bin/apifuse-test.ts +3 -1
- package/dist/cli/commands.d.ts +1 -1
- package/dist/cli/commands.js +11 -0
- package/dist/cli/deployment-intent.d.ts +178 -0
- package/dist/cli/deployment-intent.js +277 -0
- package/dist/cli/migrate-deployment-intent.d.ts +132 -0
- package/dist/cli/migrate-deployment-intent.js +1552 -0
- package/dist/cli/migrate-provider-shape.d.ts +5 -1
- package/dist/cli/migrate-provider-shape.js +4 -2
- package/dist/cli/resolver-runtime.d.ts +21 -0
- package/dist/cli/resolver-runtime.js +31 -0
- package/dist/define.d.ts +3 -0
- package/dist/define.js +5 -0
- package/dist/dev.d.ts +3 -1
- package/dist/dev.js +6 -1
- package/dist/engine.d.ts +35 -1
- package/dist/engine.js +21 -2
- package/dist/error-resolution.d.ts +1 -0
- package/dist/error-resolution.js +54 -0
- package/dist/errors.d.ts +39 -1
- package/dist/errors.js +50 -0
- package/dist/handle-meta.d.ts +36 -1
- package/dist/handle-meta.js +16 -0
- package/dist/handle.d.ts +23 -5
- package/dist/handle.js +54 -8
- package/dist/health-scenario.d.ts +38 -0
- package/dist/i18n/error-messages.d.ts +86 -0
- package/dist/i18n/error-messages.js +192 -0
- package/dist/i18n/index.d.ts +1 -0
- package/dist/i18n/index.js +1 -0
- package/dist/i18n/keys.d.ts +12 -0
- package/dist/i18n/keys.js +76 -5
- package/dist/index.d.ts +11 -3
- package/dist/index.js +9 -1
- package/dist/lint.d.ts +15 -0
- package/dist/lint.js +469 -82
- package/dist/provider.d.ts +6 -6
- package/dist/provider.js +3 -3
- package/dist/runtime/browser-telemetry-binding.d.ts +5 -0
- package/dist/runtime/browser-telemetry-binding.js +83 -0
- package/dist/runtime/browser-telemetry.d.ts +76 -0
- package/dist/runtime/browser-telemetry.js +165 -0
- package/dist/runtime/browser.d.ts +3 -0
- package/dist/runtime/browser.js +208 -47
- package/dist/runtime/cache-telemetry.d.ts +71 -0
- package/dist/runtime/cache-telemetry.js +177 -0
- package/dist/runtime/cache.js +67 -7
- package/dist/runtime/capability-response.d.ts +3 -0
- package/dist/runtime/capability-response.js +56 -0
- package/dist/runtime/capability-telemetry.d.ts +46 -0
- package/dist/runtime/capability-telemetry.js +68 -0
- package/dist/runtime/diagnostic-env.d.ts +3 -0
- package/dist/runtime/diagnostic-env.js +10 -1
- package/dist/runtime/engine-mode.d.ts +74 -0
- package/dist/runtime/engine-mode.js +152 -0
- package/dist/runtime/handle.js +9 -1
- package/dist/runtime/http-telemetry-guard.d.ts +10 -0
- package/dist/runtime/http-telemetry-guard.js +62 -10
- package/dist/runtime/http.js +141 -35
- package/dist/runtime/instrumentation.js +92 -13
- package/dist/runtime/ocr-telemetry.d.ts +82 -0
- package/dist/runtime/ocr-telemetry.js +192 -0
- package/dist/runtime/ocr.js +35 -8
- package/dist/runtime/request-telemetry.d.ts +2 -0
- package/dist/runtime/request-telemetry.js +2 -1
- package/dist/runtime/resolver-runtime-options.d.ts +14 -0
- package/dist/runtime/resolver-runtime-options.js +33 -0
- package/dist/runtime/resolver-shared.d.ts +3 -2
- package/dist/runtime/resolver-shared.js +4 -4
- package/dist/runtime/resolver-telemetry.d.ts +1 -0
- package/dist/runtime/resolver-telemetry.js +1 -1
- package/dist/runtime/resolver-vendors/browser.d.ts +2 -0
- package/dist/runtime/resolver-vendors/browser.js +46 -0
- package/dist/runtime/resolver-vendors/hosts.d.ts +21 -0
- package/dist/runtime/resolver-vendors/hosts.js +35 -0
- package/dist/runtime/resolver-vendors/hypersolutions.js +2 -2
- package/dist/runtime/resolver-vendors/types.d.ts +5 -4
- package/dist/runtime/resolver.d.ts +4 -1
- package/dist/runtime/resolver.js +46 -13
- package/dist/runtime/state-telemetry.d.ts +79 -0
- package/dist/runtime/state-telemetry.js +194 -0
- package/dist/runtime/state.d.ts +3 -0
- package/dist/runtime/state.js +26 -7
- package/dist/runtime/stealth-cookies.d.ts +5 -1
- package/dist/runtime/stealth-cookies.js +21 -1
- package/dist/runtime/stealth-owned-headers.d.ts +26 -0
- package/dist/runtime/stealth-owned-headers.js +37 -0
- package/dist/runtime/stealth-telemetry.d.ts +92 -0
- package/dist/runtime/stealth-telemetry.js +179 -0
- package/dist/runtime/stealth.d.ts +4 -1
- package/dist/runtime/stealth.js +228 -85
- package/dist/runtime/stt-telemetry.d.ts +77 -0
- package/dist/runtime/stt-telemetry.js +176 -0
- package/dist/runtime/stt.js +15 -6
- package/dist/runtime-boundary-lint.d.ts +132 -0
- package/dist/runtime-boundary-lint.js +586 -0
- package/dist/server/error-stack-frames.d.ts +35 -0
- package/dist/server/error-stack-frames.js +219 -0
- package/dist/server/index.d.ts +7 -0
- package/dist/server/index.js +4 -0
- package/dist/server/sdk-version.d.ts +3 -0
- package/dist/server/sdk-version.js +19 -0
- package/dist/server/self-test.js +1 -18
- package/dist/server/sensitive-values.d.ts +1 -1
- package/dist/server/sensitive-values.js +9 -0
- package/dist/server/serve-implementation.d.ts +51 -2
- package/dist/server/serve-implementation.js +337 -90
- package/dist/server/types.d.ts +1 -0
- package/dist/server/types.js +7 -0
- package/dist/stateful/stateful-provider-owner-forwarder.js +22 -0
- package/dist/stateful/stateful-provider-runtime-context.d.ts +3 -0
- package/dist/stateful/stateful-provider-runtime-context.js +1 -0
- package/dist/types.d.ts +123 -1
- package/dist/typescript-module.d.ts +39 -0
- package/dist/typescript-module.js +72 -0
- package/package.json +6 -1
- package/src/cli/commands.ts +14 -0
- package/src/cli/deployment-intent.ts +364 -0
- package/src/cli/migrate-deployment-intent.ts +1908 -0
- package/src/cli/migrate-provider-shape.ts +10 -4
- package/src/cli/resolver-runtime.ts +72 -0
- package/src/define.ts +13 -0
- package/src/dev.ts +9 -2
- package/src/engine.ts +38 -2
- package/src/error-resolution.ts +58 -0
- package/src/errors.ts +77 -1
- package/src/handle-meta.ts +51 -1
- package/src/handle.ts +92 -10
- package/src/health-scenario.ts +38 -0
- package/src/i18n/error-messages.ts +255 -0
- package/src/i18n/index.ts +1 -0
- package/src/i18n/keys.ts +80 -6
- package/src/index.ts +86 -0
- package/src/lint.ts +604 -91
- package/src/provider.ts +12 -0
- package/src/runtime/browser-telemetry-binding.ts +92 -0
- package/src/runtime/browser-telemetry.ts +289 -0
- package/src/runtime/browser.ts +249 -65
- package/src/runtime/cache-telemetry.ts +247 -0
- package/src/runtime/cache.ts +68 -17
- package/src/runtime/capability-response.ts +53 -0
- package/src/runtime/capability-telemetry.ts +101 -0
- package/src/runtime/diagnostic-env.ts +11 -1
- package/src/runtime/engine-mode.ts +233 -0
- package/src/runtime/handle.ts +9 -1
- package/src/runtime/http-telemetry-guard.ts +66 -13
- package/src/runtime/http.ts +168 -37
- package/src/runtime/instrumentation.ts +105 -16
- package/src/runtime/ocr-telemetry.ts +308 -0
- package/src/runtime/ocr.ts +114 -74
- package/src/runtime/request-telemetry.ts +2 -1
- package/src/runtime/resolver-runtime-options.ts +50 -0
- package/src/runtime/resolver-shared.ts +9 -4
- package/src/runtime/resolver-telemetry.ts +1 -1
- package/src/runtime/resolver-vendors/browser.ts +65 -7
- package/src/runtime/resolver-vendors/hosts.ts +43 -0
- package/src/runtime/resolver-vendors/hypersolutions.ts +2 -2
- package/src/runtime/resolver-vendors/types.ts +5 -4
- package/src/runtime/resolver.ts +64 -21
- package/src/runtime/state-telemetry.ts +298 -0
- package/src/runtime/state.ts +46 -45
- package/src/runtime/stealth-cookies.ts +24 -1
- package/src/runtime/stealth-owned-headers.ts +41 -0
- package/src/runtime/stealth-telemetry.ts +304 -0
- package/src/runtime/stealth.ts +355 -150
- package/src/runtime/stt-telemetry.ts +282 -0
- package/src/runtime/stt.ts +93 -71
- package/src/runtime-boundary-lint.ts +703 -0
- package/src/server/error-stack-frames.ts +214 -0
- package/src/server/index.ts +53 -0
- package/src/server/sdk-version.ts +22 -0
- package/src/server/self-test.ts +1 -21
- package/src/server/sensitive-values.ts +14 -0
- package/src/server/serve-implementation.ts +569 -108
- package/src/server/types.ts +7 -0
- package/src/stateful/stateful-provider-owner-forwarder.ts +23 -0
- package/src/stateful/stateful-provider-runtime-context.ts +4 -0
- package/src/types.ts +128 -1
- package/src/typescript-module.ts +100 -0
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/approval-override.ts.txt +0 -6
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/codemod-syntax.ts.txt +0 -3
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/connection-precedence.ts.txt +0 -10
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-ambiguous-a.ts.txt +0 -3
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-ambiguous-b.ts.txt +0 -3
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-ambiguous-barrel.ts.txt +0 -2
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-ambiguous-index.ts.txt +0 -3
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-computed.ts.txt +0 -6
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-cycle-a.ts.txt +0 -1
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-cycle-b.ts.txt +0 -1
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-cycle-index.ts.txt +0 -3
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-daiso-barrel.ts.txt +0 -7
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-daiso-catalog.ts.txt +0 -19
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-daiso-docs.ts.txt +0 -6
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-daiso-index.ts.txt +0 -3
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-daiso-stores.ts.txt +0 -8
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-indirect-config.ts.txt +0 -5
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-nonstatic.ts.txt +0 -4
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-package-index.ts.txt +0 -3
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-reexport-index.ts.txt +0 -3
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-reexport-leaf.ts.txt +0 -10
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-reexport-one.ts.txt +0 -1
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-reexport-two.ts.txt +0 -1
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-runtime-composed-index.ts.txt +0 -3
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-runtime-composed-one.ts.txt +0 -6
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-runtime-composed-two.ts.txt +0 -6
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-runtime-mixed-index.ts.txt +0 -14
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-runtime-registry.ts.txt +0 -3
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-star-package-barrel.ts.txt +0 -2
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-star-package-index.ts.txt +0 -3
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-star-package-leaf.ts.txt +0 -3
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/docs-conflict.ts.txt +0 -8
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/examples-hoisted-array-spread.ts.txt +0 -15
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/examples-imported-array-spread.ts.txt +0 -9
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/examples-kebab-operation-id.ts.txt +0 -17
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/examples-map.ts.txt +0 -5
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/examples-mixed-array-spread.ts.txt +0 -24
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/examples-operation.ts.txt +0 -16
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/examples-snake-operation-id.ts.txt +0 -21
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/factory-duplicate-provider.ts.txt +0 -11
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/factory-id-unresolved.ts.txt +0 -7
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/factory-map.ts.txt +0 -3
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/factory-one-to-many.ts.txt +0 -18
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/factory-one-to-one.ts.txt +0 -13
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/hoist-all.ts.txt +0 -31
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/hoisted-const.ts.txt +0 -11
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/imported-spread.ts.txt +0 -11
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/inline-map.ts.txt +0 -11
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/inline-spread-cast-tail.ts.txt +0 -21
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/inline-spread-ekitan.ts.txt +0 -11
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/inline-spread-override.ts.txt +0 -14
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/locale-canonical-en.json +0 -1
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/locale-canonical-ja.json +0 -1
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/locale-canonical-ko.json +0 -1
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/locale-existing-operation-namespace-en.json +0 -8
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/missing-english-locale.ts.txt +0 -7
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/no-safety.ts.txt +0 -6
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/non-literal.ts.txt +0 -7
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/redundant-approval.ts.txt +0 -6
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/registry-binding-ambiguous.ts.txt +0 -12
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/registry-imported-other.ts.txt +0 -7
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/registry-imported.ts.txt +0 -5
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/registry-key-ambiguous.ts.txt +0 -19
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/registry-shorthand.ts.txt +0 -9
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/registry-typed.ts.txt +0 -11
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/registry-unrelated.ts.txt +0 -13
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/safety-conflict.ts.txt +0 -7
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/stream.ts.txt +0 -7
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/tool-router-spread.ts.txt +0 -15
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/unparseable.ts.txt +0 -4
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/verbatim-template.ts.txt +0 -12
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import { createInProcessProviderEngine, PROVIDER_ENGINE_MODE_ENV, } from "../engine.js";
|
|
2
|
+
import { ProviderError, ValidationError } from "../errors.js";
|
|
3
|
+
/**
|
|
4
|
+
* Engine attachment selection. Internal on purpose: ADR-0011 removes the
|
|
5
|
+
* in-process lane, so every symbol exported here would become a breaking removal
|
|
6
|
+
* later. The server surfaces the outcome on the `provider_engine_mode` boot
|
|
7
|
+
* event instead.
|
|
8
|
+
*/
|
|
9
|
+
const KNOWN_PROVIDER_ENGINE_MODES = ["in-process", "remote"];
|
|
10
|
+
/** Mode used when neither `engineMode` nor `APIFUSE__ENGINE__MODE` names one. */
|
|
11
|
+
export const DEFAULT_PROVIDER_ENGINE_MODE = "in-process";
|
|
12
|
+
/** Remote engine client endpoint; engine-owned, so never declarable as a provider secret. */
|
|
13
|
+
export const REMOTE_ENGINE_CLIENT_URL_ENV = "APIFUSE__ENGINE__URL";
|
|
14
|
+
/** Remote engine client credential; registered with the diagnostic redactor by sensitive-values. */
|
|
15
|
+
export const REMOTE_ENGINE_CLIENT_API_KEY_ENV = "APIFUSE__ENGINE__API_KEY";
|
|
16
|
+
/** Engine env names that only mean something in remote mode. */
|
|
17
|
+
const REMOTE_ONLY_ENGINE_ENV_NAMES = [
|
|
18
|
+
REMOTE_ENGINE_CLIENT_URL_ENV,
|
|
19
|
+
REMOTE_ENGINE_CLIENT_API_KEY_ENV,
|
|
20
|
+
];
|
|
21
|
+
function isKnownMode(value) {
|
|
22
|
+
return KNOWN_PROVIDER_ENGINE_MODES.includes(value);
|
|
23
|
+
}
|
|
24
|
+
function normalize(value) {
|
|
25
|
+
if (typeof value !== "string")
|
|
26
|
+
return undefined;
|
|
27
|
+
const trimmed = value.trim().toLowerCase();
|
|
28
|
+
return trimmed === "" ? undefined : trimmed;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Resolve the engine attachment mode once at boot.
|
|
32
|
+
*
|
|
33
|
+
* Precedence: an explicit `engine` object, then `APIFUSE__ENGINE__MODE`, then the
|
|
34
|
+
* `engineMode` option, then the default. The manifest outranks the option so
|
|
35
|
+
* provider code cannot silently downgrade what the deployment asked for; every
|
|
36
|
+
* disagreement is reported in `warnings` with a truthful `source` rather than
|
|
37
|
+
* being dropped.
|
|
38
|
+
*
|
|
39
|
+
* Deployment-authored input is never fatal (an unrecognized env value is warned
|
|
40
|
+
* about and ignored). A malformed `engineMode` option is a fault in the calling
|
|
41
|
+
* code, which no manifest can reach, so it throws.
|
|
42
|
+
*/
|
|
43
|
+
export function resolveProviderEngineMode(input = {}) {
|
|
44
|
+
const environment = input.environment ?? process.env;
|
|
45
|
+
const warnings = [];
|
|
46
|
+
// Read directly: this is a mode name, not a credential, so it must not be
|
|
47
|
+
// registered with the diagnostic redactor the way readDiagnosticEnv is.
|
|
48
|
+
const rawEnv = normalize(environment[PROVIDER_ENGINE_MODE_ENV]);
|
|
49
|
+
let envMode;
|
|
50
|
+
if (rawEnv !== undefined) {
|
|
51
|
+
if (isKnownMode(rawEnv))
|
|
52
|
+
envMode = rawEnv;
|
|
53
|
+
else
|
|
54
|
+
warnings.push("invalid_env_value");
|
|
55
|
+
}
|
|
56
|
+
const optionMode = normalize(input.option);
|
|
57
|
+
if (input.option !== undefined && (optionMode === undefined || !isKnownMode(optionMode))) {
|
|
58
|
+
throw new ValidationError(`Invalid engineMode option "${String(input.option)}"; expected ${KNOWN_PROVIDER_ENGINE_MODES.join(" or ")}`, { fix: 'Pass engineMode: "in-process" | "remote", or omit it to use the default.' });
|
|
59
|
+
}
|
|
60
|
+
if (optionMode !== undefined && envMode !== undefined && optionMode !== envMode) {
|
|
61
|
+
warnings.push("env_overrode_option");
|
|
62
|
+
}
|
|
63
|
+
const requested = envMode ?? optionMode;
|
|
64
|
+
const requestedSource = envMode !== undefined ? "env" : optionMode !== undefined ? "option" : undefined;
|
|
65
|
+
// Normalized like every other mode input: an un-normalized host `kind` would
|
|
66
|
+
// reach the boot event and /readyz verbatim and mis-score the fleet audit.
|
|
67
|
+
const engineKind = input.engine === undefined ? undefined : (normalize(input.engine.kind) ?? "custom");
|
|
68
|
+
if (engineKind !== undefined) {
|
|
69
|
+
if (requested !== undefined && engineKind !== requested) {
|
|
70
|
+
warnings.push("engine_object_overrode_requested_mode");
|
|
71
|
+
}
|
|
72
|
+
return finalize(engineKind, "engine", environment, warnings);
|
|
73
|
+
}
|
|
74
|
+
return finalize(requested ?? DEFAULT_PROVIDER_ENGINE_MODE, requestedSource ?? "default", environment, warnings);
|
|
75
|
+
}
|
|
76
|
+
function finalize(mode, source, environment, warnings) {
|
|
77
|
+
// Presence of the remote engine env never selects a mode (the manifest
|
|
78
|
+
// generator projects it fleet-wide before the SDK can use it), but staying
|
|
79
|
+
// silent about it would hide a half-applied cutover. Skipped for a host-supplied
|
|
80
|
+
// engine: that object may be a remote client that consumes the env itself, so
|
|
81
|
+
// claiming the env is unused would be a false report.
|
|
82
|
+
if (mode !== "remote" &&
|
|
83
|
+
source !== "engine" &&
|
|
84
|
+
REMOTE_ONLY_ENGINE_ENV_NAMES.some((name) => normalize(environment[name]) !== undefined)) {
|
|
85
|
+
warnings.push("engine_env_ignored");
|
|
86
|
+
}
|
|
87
|
+
// Fail-closed for the ADR-0011 audit: everything that is not the remote lane
|
|
88
|
+
// still attaches capabilities inside the pod process and is scheduled for
|
|
89
|
+
// removal, including an opaque host engine that declares no kind.
|
|
90
|
+
return { mode, source, deprecated: mode !== "remote", warnings };
|
|
91
|
+
}
|
|
92
|
+
const UNAVAILABLE_ENGINES = new WeakSet();
|
|
93
|
+
/** True when the engine cannot attach, so readiness must report the process as unready. */
|
|
94
|
+
export function isUnavailableProviderEngine(engine) {
|
|
95
|
+
return UNAVAILABLE_ENGINES.has(engine);
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Engine that refuses every attachment with `reason`.
|
|
99
|
+
*
|
|
100
|
+
* Requesting a mode this release cannot serve must not crash the process: the SDK
|
|
101
|
+
* answers a structured error per request and reports the process unready, the same
|
|
102
|
+
* trade-off `provider_secrets_missing` already makes at boot. Non-retryable, so a
|
|
103
|
+
* caller-side retry loop cannot turn a configuration fault into load.
|
|
104
|
+
*/
|
|
105
|
+
export function createUnavailableProviderEngine(kind, reason) {
|
|
106
|
+
const engine = {
|
|
107
|
+
kind,
|
|
108
|
+
attach: () => {
|
|
109
|
+
throw new ProviderError(reason.message, {
|
|
110
|
+
code: reason.code,
|
|
111
|
+
retryable: false,
|
|
112
|
+
fix: reason.fix,
|
|
113
|
+
});
|
|
114
|
+
},
|
|
115
|
+
};
|
|
116
|
+
UNAVAILABLE_ENGINES.add(engine);
|
|
117
|
+
return engine;
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Engine for a resolved mode. `remote` has no client in this release, so it
|
|
121
|
+
* resolves to an engine that fails every request instead of falling back to
|
|
122
|
+
* in-process (ADR-0011 Pitfall 2: in-process is never a fallback).
|
|
123
|
+
*/
|
|
124
|
+
export function createEngineForMode(mode) {
|
|
125
|
+
// Default-deny on the mode name, not just on "remote": the mode union is open
|
|
126
|
+
// so a later lane can be named, and falling through to in-process for an
|
|
127
|
+
// unserved lane is the silent downgrade ADR-0011 Pitfall 2 forbids.
|
|
128
|
+
if (mode === DEFAULT_PROVIDER_ENGINE_MODE)
|
|
129
|
+
return createInProcessProviderEngine();
|
|
130
|
+
return createUnavailableProviderEngine(mode, {
|
|
131
|
+
message: `Provider engine mode "${mode}" was requested but this @apifuse/provider-sdk release only serves the ${DEFAULT_PROVIDER_ENGINE_MODE} engine`,
|
|
132
|
+
code: "PROVIDER_ENGINE_MODE_UNSUPPORTED",
|
|
133
|
+
fix: `Unset ${PROVIDER_ENGINE_MODE_ENV} (or the engineMode option), or upgrade to a release that serves this mode.`,
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Fail fast for the CLIs (`apifuse dev`, `apifuse record`). They are developer
|
|
138
|
+
* tools, not pods: an unusable mode should stop the command rather than start a
|
|
139
|
+
* server that refuses every request.
|
|
140
|
+
*/
|
|
141
|
+
export function assertProcessEngineModeSupported(environment = process.env) {
|
|
142
|
+
const resolution = resolveProviderEngineMode({ environment });
|
|
143
|
+
if (resolution.mode !== DEFAULT_PROVIDER_ENGINE_MODE) {
|
|
144
|
+
throw new ProviderError(`Provider engine mode "${resolution.mode}" was requested but this @apifuse/provider-sdk release only serves the ${DEFAULT_PROVIDER_ENGINE_MODE} engine`, {
|
|
145
|
+
code: "PROVIDER_ENGINE_MODE_UNSUPPORTED",
|
|
146
|
+
retryable: false,
|
|
147
|
+
details: { mode: resolution.mode, source: resolution.source },
|
|
148
|
+
fix: `Unset ${PROVIDER_ENGINE_MODE_ENV}, or upgrade to a release that serves this mode.`,
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
return resolution;
|
|
152
|
+
}
|
package/dist/runtime/handle.js
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
import { randomInt, randomUUID } from "node:crypto";
|
|
12
12
|
import { isProviderError } from "../errors.js";
|
|
13
13
|
import { HandleError, handleRecoverySentence, isDraftKind, } from "../handle.js";
|
|
14
|
-
import { formatHandleIssuers } from "../handle-meta.js";
|
|
14
|
+
import { formatHandleIssuers, handleHasDirectionalFieldNames } from "../handle-meta.js";
|
|
15
15
|
import { BOUND_HANDLE_WORD_COUNT, findHandleWordWithinDistance, HIGH_PUBLIC_HANDLE_WORD_COUNT, ISSUABLE_HANDLE_WORDS, PUBLIC_HANDLE_WORD_COUNT, } from "./handle-wordlist.js";
|
|
16
16
|
import { createMemoryProviderRuntimeState } from "./state.js";
|
|
17
17
|
const RECORD_VERSION = 1;
|
|
@@ -153,6 +153,14 @@ export function formatNormalizationClasses(classes) {
|
|
|
153
153
|
// Errors (spec §5)
|
|
154
154
|
// ---------------------------------------------------------------------------
|
|
155
155
|
function publicCollapsedMessage(kind) {
|
|
156
|
+
// "a new one" is unambiguous only while the kind has one name. When it is
|
|
157
|
+
// issued under `next_cursor` and accepted under `cursor`, the caller has to
|
|
158
|
+
// be told which field to read, so use the full recovery sentence — it names
|
|
159
|
+
// both keys and is identical for invalid / expired / not-found, which is
|
|
160
|
+
// what keeps the public collapse indistinguishable.
|
|
161
|
+
if (handleHasDirectionalFieldNames(kind)) {
|
|
162
|
+
return `\`${kind.fieldName}\` is not valid or has expired. ${handleRecoverySentence(kind)}`;
|
|
163
|
+
}
|
|
156
164
|
const issuers = formatHandleIssuers(kind.issuedBy);
|
|
157
165
|
const again = issuers ? `Call ${issuers} again to get a new one.` : "Request a new one.";
|
|
158
166
|
return `\`${kind.fieldName}\` is not valid or has expired. ${again}`;
|
|
@@ -1,6 +1,16 @@
|
|
|
1
1
|
import type { HttpRetrySummary } from "../types.js";
|
|
2
2
|
import { type HttpTelemetryRequestSink, type HttpTelemetrySink } from "./http-telemetry.js";
|
|
3
|
+
/** Shared synchronous observer boundary for transport and capability contributors. */
|
|
4
|
+
export declare function observeTelemetry<T>(callback: () => T, fail: () => void): T | undefined;
|
|
5
|
+
/** Isolate synchronous void capability hooks using the same Promise defence. */
|
|
6
|
+
export declare function createTelemetryObserver(sink: {
|
|
7
|
+
markTelemetryFailed?(): void;
|
|
8
|
+
} | undefined): (callback: () => unknown) => void;
|
|
3
9
|
/** Keep all public observers, including property access, outside transport failures. */
|
|
4
10
|
export declare function startHttpTelemetry(sink: HttpTelemetrySink | undefined, retryPreset: HttpRetrySummary["preset"]): HttpTelemetryRequestSink & {
|
|
5
11
|
observe(callback: () => void): void;
|
|
6
12
|
};
|
|
13
|
+
/** Generic observer guard shared by capability hooks. Returned thenables are absorbed. */
|
|
14
|
+
export declare function observeTelemetryCallback(sink: {
|
|
15
|
+
markTelemetryFailed?: () => unknown;
|
|
16
|
+
} | undefined, callback: () => unknown): void;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { isPromise } from "node:util/types";
|
|
1
2
|
import { HttpTelemetryCollector, } from "./http-telemetry.js";
|
|
2
3
|
const intrinsicPromiseThen = Promise.prototype.then;
|
|
3
4
|
const ignoreObserverResult = () => { };
|
|
@@ -44,7 +45,7 @@ function isAsyncObserverReturn(value) {
|
|
|
44
45
|
if (!value || (typeof value !== "object" && typeof value !== "function"))
|
|
45
46
|
return false;
|
|
46
47
|
try {
|
|
47
|
-
if (!("then" in value) || typeof value.then !== "function")
|
|
48
|
+
if (!isPromise(value) && (!("then" in value) || typeof value.then !== "function"))
|
|
48
49
|
return false;
|
|
49
50
|
}
|
|
50
51
|
catch {
|
|
@@ -53,8 +54,23 @@ function isAsyncObserverReturn(value) {
|
|
|
53
54
|
absorbObserverPromise(value);
|
|
54
55
|
return true;
|
|
55
56
|
}
|
|
56
|
-
/**
|
|
57
|
-
export function
|
|
57
|
+
/** Shared synchronous observer boundary for transport and capability contributors. */
|
|
58
|
+
export function observeTelemetry(callback, fail) {
|
|
59
|
+
try {
|
|
60
|
+
const result = callback();
|
|
61
|
+
if (isAsyncObserverReturn(result)) {
|
|
62
|
+
fail();
|
|
63
|
+
return undefined;
|
|
64
|
+
}
|
|
65
|
+
return result;
|
|
66
|
+
}
|
|
67
|
+
catch {
|
|
68
|
+
fail();
|
|
69
|
+
return undefined;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
/** Isolate synchronous void capability hooks using the same Promise defence. */
|
|
73
|
+
export function createTelemetryObserver(sink) {
|
|
58
74
|
let failed = false;
|
|
59
75
|
const fail = () => {
|
|
60
76
|
if (failed)
|
|
@@ -64,24 +80,37 @@ export function startHttpTelemetry(sink, retryPreset) {
|
|
|
64
80
|
isAsyncObserverReturn(sink?.markTelemetryFailed?.());
|
|
65
81
|
}
|
|
66
82
|
catch {
|
|
67
|
-
// The failure
|
|
83
|
+
// The failure marker is also an observer.
|
|
68
84
|
}
|
|
69
85
|
};
|
|
70
|
-
|
|
86
|
+
return (callback) => {
|
|
71
87
|
try {
|
|
72
88
|
const result = callback();
|
|
73
|
-
if (
|
|
74
|
-
|
|
89
|
+
if (result !== undefined) {
|
|
90
|
+
isAsyncObserverReturn(result);
|
|
75
91
|
fail();
|
|
76
|
-
return undefined;
|
|
77
92
|
}
|
|
78
|
-
return result;
|
|
79
93
|
}
|
|
80
94
|
catch {
|
|
81
95
|
fail();
|
|
82
|
-
return undefined;
|
|
83
96
|
}
|
|
84
97
|
};
|
|
98
|
+
}
|
|
99
|
+
/** Keep all public observers, including property access, outside transport failures. */
|
|
100
|
+
export function startHttpTelemetry(sink, retryPreset) {
|
|
101
|
+
let failed = false;
|
|
102
|
+
const fail = () => {
|
|
103
|
+
if (failed)
|
|
104
|
+
return;
|
|
105
|
+
failed = true;
|
|
106
|
+
try {
|
|
107
|
+
isAsyncObserverReturn(sink?.markTelemetryFailed?.());
|
|
108
|
+
}
|
|
109
|
+
catch {
|
|
110
|
+
// The failure reporter is itself an observer. Never recurse.
|
|
111
|
+
}
|
|
112
|
+
};
|
|
113
|
+
const observe = (callback) => observeTelemetry(callback, fail);
|
|
85
114
|
const result = observe(() => (sink ?? new HttpTelemetryCollector()).startRequest({ retryPreset }));
|
|
86
115
|
const request = observe(() => {
|
|
87
116
|
if (!result ||
|
|
@@ -148,3 +177,26 @@ export function startHttpTelemetry(sink, retryPreset) {
|
|
|
148
177
|
},
|
|
149
178
|
};
|
|
150
179
|
}
|
|
180
|
+
/** Generic observer guard shared by capability hooks. Returned thenables are absorbed. */
|
|
181
|
+
export function observeTelemetryCallback(sink, callback) {
|
|
182
|
+
let failed = false;
|
|
183
|
+
const fail = () => {
|
|
184
|
+
if (failed)
|
|
185
|
+
return;
|
|
186
|
+
failed = true;
|
|
187
|
+
try {
|
|
188
|
+
isAsyncObserverReturn(sink?.markTelemetryFailed?.());
|
|
189
|
+
}
|
|
190
|
+
catch {
|
|
191
|
+
// Observer failure is never transport failure.
|
|
192
|
+
}
|
|
193
|
+
};
|
|
194
|
+
try {
|
|
195
|
+
const result = callback();
|
|
196
|
+
if (isAsyncObserverReturn(result) || result !== undefined)
|
|
197
|
+
fail();
|
|
198
|
+
}
|
|
199
|
+
catch {
|
|
200
|
+
fail();
|
|
201
|
+
}
|
|
202
|
+
}
|
package/dist/runtime/http.js
CHANGED
|
@@ -84,19 +84,92 @@ function hasHeader(headers, name) {
|
|
|
84
84
|
const needle = name.toLowerCase();
|
|
85
85
|
return Object.keys(headers).some((key) => key.toLowerCase() === needle);
|
|
86
86
|
}
|
|
87
|
-
function
|
|
88
|
-
const
|
|
87
|
+
function withClientHeaderDefaults(headers, clientOptions, body) {
|
|
88
|
+
const merged = {
|
|
89
89
|
...(clientOptions.userAgent ? { "User-Agent": clientOptions.userAgent } : {}),
|
|
90
|
-
...
|
|
90
|
+
...headers,
|
|
91
91
|
};
|
|
92
|
-
if (body !== undefined && !hasHeader(
|
|
93
|
-
|
|
92
|
+
if (body !== undefined && !hasHeader(merged, "Content-Type")) {
|
|
93
|
+
merged["Content-Type"] = "application/json";
|
|
94
94
|
}
|
|
95
|
+
return merged;
|
|
96
|
+
}
|
|
97
|
+
function withClientHeaders(options, clientOptions, body) {
|
|
98
|
+
// The ambient client signal is merged into the per-request one here so every
|
|
99
|
+
// downstream read (retry loop, sleep, body consumption) sees a single signal.
|
|
100
|
+
// The header-factory branch needs it too, otherwise factory-header requests
|
|
101
|
+
// would lose ambient cancellation entirely.
|
|
102
|
+
const signal = mergeAbortSignals(clientOptions.signal, options?.signal);
|
|
103
|
+
// A header factory is resolved per issued attempt (resolveAttemptHeaders);
|
|
104
|
+
// the client defaults are applied to its output at that point.
|
|
105
|
+
if (typeof options?.headers === "function")
|
|
106
|
+
return { ...options, signal };
|
|
95
107
|
return {
|
|
96
108
|
...options,
|
|
97
|
-
headers,
|
|
109
|
+
headers: withClientHeaderDefaults(options?.headers, clientOptions, body),
|
|
110
|
+
signal,
|
|
98
111
|
};
|
|
99
112
|
}
|
|
113
|
+
function headerFactoryFailure(message, cause) {
|
|
114
|
+
// A dedicated code keeps a broken proof minter out of the retry predicate:
|
|
115
|
+
// toHttpTransportError would otherwise brand a plain throw as
|
|
116
|
+
// transport_network_error, which the default retry policy re-issues.
|
|
117
|
+
// retryable:false is explicit so the served error envelope does not
|
|
118
|
+
// advertise a deterministic signing failure as retryable, and
|
|
119
|
+
// category:provider_error keeps a provider-local minting bug out of the
|
|
120
|
+
// upstream-failure signal (status 0 would otherwise fall through the
|
|
121
|
+
// observability ladder to upstream_http / source upstream_failure).
|
|
122
|
+
return new TransportError(message, {
|
|
123
|
+
code: "http_header_factory_failed",
|
|
124
|
+
status: 0,
|
|
125
|
+
retryable: false,
|
|
126
|
+
category: "provider_error",
|
|
127
|
+
...(cause instanceof Error ? { cause } : {}),
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
function isHeaderRecord(value) {
|
|
131
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
132
|
+
}
|
|
133
|
+
// Settle `promise`, or reject with the signal's reason as soon as it aborts, so
|
|
134
|
+
// a pending header factory cannot outlive the request deadline or an ambient
|
|
135
|
+
// cancellation (native fetch has not started yet, so nothing else observes them).
|
|
136
|
+
function settleBeforeAbort(promise, signal) {
|
|
137
|
+
if (!signal)
|
|
138
|
+
return promise;
|
|
139
|
+
return new Promise((resolve, reject) => {
|
|
140
|
+
const onAbort = () => reject(signal.reason);
|
|
141
|
+
if (signal.aborted)
|
|
142
|
+
onAbort();
|
|
143
|
+
else
|
|
144
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
145
|
+
// Always attach handlers: a late rejection after the abort must settle
|
|
146
|
+
// here (a no-op on the already-rejected promise), never go unhandled.
|
|
147
|
+
promise.then(resolve, reject).finally(() => signal.removeEventListener("abort", onAbort));
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
async function resolveAttemptHeaders(options, clientOptions, attempt, signal) {
|
|
151
|
+
const { headers } = options;
|
|
152
|
+
if (typeof headers !== "function")
|
|
153
|
+
return headers;
|
|
154
|
+
// The per-call timeout may have fired during proxy resolution: do not start
|
|
155
|
+
// the factory at all once the request is already aborted.
|
|
156
|
+
if (signal?.aborted)
|
|
157
|
+
throw signal.reason;
|
|
158
|
+
let minted;
|
|
159
|
+
try {
|
|
160
|
+
minted = await settleBeforeAbort(Promise.resolve().then(() => headers(attempt())), signal);
|
|
161
|
+
}
|
|
162
|
+
catch (error) {
|
|
163
|
+
// An abort is classified by toHttpTransportError (cancelled / timed out).
|
|
164
|
+
if (signal?.aborted)
|
|
165
|
+
throw error;
|
|
166
|
+
throw headerFactoryFailure("Request header factory failed", error);
|
|
167
|
+
}
|
|
168
|
+
if (!isHeaderRecord(minted)) {
|
|
169
|
+
throw headerFactoryFailure("Request header factory must return a header record");
|
|
170
|
+
}
|
|
171
|
+
return withClientHeaderDefaults(minted, clientOptions, options.body);
|
|
172
|
+
}
|
|
100
173
|
function parseHttpData(body, headers) {
|
|
101
174
|
const contentType = headers["content-type"] ?? headers["Content-Type"] ?? headers["CONTENT-TYPE"];
|
|
102
175
|
if (contentType?.includes("application/json")) {
|
|
@@ -552,23 +625,29 @@ function serializeHttpRequestUrl(baseUrl, url, options) {
|
|
|
552
625
|
throw redactSensitiveRequestError(error, url, options.sensitiveParams);
|
|
553
626
|
}
|
|
554
627
|
}
|
|
555
|
-
async function fetchNativeHttp(baseUrl, url, method, options, clientOptions, warn, statusRetryCodes, proxyAttemptOffset = 0, dedupe, observeProxy) {
|
|
628
|
+
async function fetchNativeHttp(baseUrl, url, method, options, clientOptions, warn, statusRetryCodes, proxyAttemptOffset = 0, dedupe, observeProxy, nextHeaderAttempt = () => 1) {
|
|
556
629
|
const serializedUrl = serializeHttpRequestUrl(baseUrl, url, options);
|
|
557
630
|
const { requestUrl } = serializedUrl;
|
|
558
631
|
const controller = options.timeout ? new AbortController() : undefined;
|
|
559
|
-
const signal = mergeAbortSignals(
|
|
560
|
-
|
|
632
|
+
const signal = mergeAbortSignals(options.signal, controller?.signal);
|
|
633
|
+
let timeoutHandle = options.timeout
|
|
561
634
|
? setTimeout(() => controller?.abort(), options.timeout)
|
|
562
635
|
: undefined;
|
|
636
|
+
const disarmTimeout = () => {
|
|
637
|
+
if (!timeoutHandle)
|
|
638
|
+
return;
|
|
639
|
+
clearTimeout(timeoutHandle);
|
|
640
|
+
timeoutHandle = undefined;
|
|
641
|
+
};
|
|
563
642
|
let proxy;
|
|
564
643
|
try {
|
|
565
|
-
throwIfAmbientAborted(
|
|
644
|
+
throwIfAmbientAborted(options.signal);
|
|
566
645
|
// Resolve inside the try (and after the timeout is armed) so allocator
|
|
567
646
|
// failures are branded as TransportErrors and count against the request
|
|
568
647
|
// deadline, exactly as an inline resolve would.
|
|
569
648
|
proxy = await resolveNativeProxy(options, clientOptions, warn, proxyAttemptOffset);
|
|
570
649
|
observeProxy?.(Boolean(proxy));
|
|
571
|
-
throwIfAmbientAborted(
|
|
650
|
+
throwIfAmbientAborted(options.signal);
|
|
572
651
|
// For a registry allocator chain, skip an endpoint a prior attempt already
|
|
573
652
|
// tried rather than re-issuing the same request. Returning the sentinel
|
|
574
653
|
// (instead of breaking) lets the loop keep advancing the flat offset until
|
|
@@ -579,8 +658,12 @@ async function fetchNativeHttp(baseUrl, url, method, options, clientOptions, war
|
|
|
579
658
|
}
|
|
580
659
|
dedupe.attempted.add(proxy);
|
|
581
660
|
}
|
|
661
|
+
// Resolve after the dedupe check so a skipped duplicate offset never burns
|
|
662
|
+
// a single-use proof, and with the serialized URL so URL-bound proofs
|
|
663
|
+
// match what is actually issued.
|
|
664
|
+
const requestHeaders = await resolveAttemptHeaders(options, clientOptions, () => ({ attempt: nextHeaderAttempt(), url: requestUrl, method }), signal);
|
|
582
665
|
const requestInit = {
|
|
583
|
-
headers:
|
|
666
|
+
headers: requestHeaders,
|
|
584
667
|
method,
|
|
585
668
|
...(proxy ? { proxy } : {}),
|
|
586
669
|
...(signal ? { signal } : {}),
|
|
@@ -608,36 +691,45 @@ async function fetchNativeHttp(baseUrl, url, method, options, clientOptions, war
|
|
|
608
691
|
status: response.status,
|
|
609
692
|
});
|
|
610
693
|
}
|
|
611
|
-
|
|
694
|
+
// `timeout` bounds the header phase (types.ts RequestOptions.timeout), so it
|
|
695
|
+
// is disarmed before the body is read: awaiting the buffered body inside
|
|
696
|
+
// this try brands a mid-body failure as a transport error, but must not
|
|
697
|
+
// extend the deadline over the download. Cancellation still applies —
|
|
698
|
+
// `signal` stays attached to the native response. The discarded non-2xx
|
|
699
|
+
// bodies drained above stay under the deadline on purpose: nothing else
|
|
700
|
+
// bounds them, and their content is never returned to the caller.
|
|
701
|
+
disarmTimeout();
|
|
702
|
+
return await toNativeHttpResponse(response);
|
|
612
703
|
}
|
|
613
704
|
catch (error) {
|
|
614
705
|
if (error instanceof SyntaxError) {
|
|
615
706
|
throw redactSensitiveError(error, serializedUrl.sensitiveValues, serializedUrl.requestUrl, serializedUrl.redactedUrl);
|
|
616
707
|
}
|
|
617
|
-
const transportError = redactSensitiveError(toHttpTransportError(error,
|
|
708
|
+
const transportError = redactSensitiveError(toHttpTransportError(error, options.signal, controller?.signal), serializedUrl.sensitiveValues, serializedUrl.requestUrl, serializedUrl.redactedUrl);
|
|
618
709
|
transportError.proxyUsed = Boolean(proxy);
|
|
619
710
|
throw transportError;
|
|
620
711
|
}
|
|
621
712
|
finally {
|
|
622
|
-
|
|
623
|
-
clearTimeout(timeoutHandle);
|
|
713
|
+
disarmTimeout();
|
|
624
714
|
}
|
|
625
715
|
}
|
|
626
716
|
async function fetchNativeHttpStream(baseUrl, url, method, options, clientOptions, warn, observeProxy) {
|
|
627
717
|
const serializedUrl = serializeHttpRequestUrl(baseUrl, url, options);
|
|
628
718
|
const { requestUrl } = serializedUrl;
|
|
629
719
|
const controller = options.timeout ? new AbortController() : undefined;
|
|
630
|
-
const signal = mergeAbortSignals(
|
|
720
|
+
const signal = mergeAbortSignals(options.signal, controller?.signal);
|
|
631
721
|
const timeoutHandle = options.timeout
|
|
632
722
|
? setTimeout(() => controller?.abort(), options.timeout)
|
|
633
723
|
: undefined;
|
|
634
724
|
try {
|
|
635
|
-
throwIfAmbientAborted(
|
|
725
|
+
throwIfAmbientAborted(options.signal);
|
|
636
726
|
const proxy = await resolveNativeProxy(options, clientOptions, warn);
|
|
637
727
|
observeProxy?.(Boolean(proxy));
|
|
638
|
-
throwIfAmbientAborted(
|
|
728
|
+
throwIfAmbientAborted(options.signal);
|
|
729
|
+
// Streams never retry: a header factory is resolved once, as attempt 1.
|
|
730
|
+
const headers = await resolveAttemptHeaders(options, clientOptions, () => ({ attempt: 1, url: requestUrl, method }), signal);
|
|
639
731
|
const requestInit = {
|
|
640
|
-
headers
|
|
732
|
+
headers,
|
|
641
733
|
method,
|
|
642
734
|
...(proxy ? { proxy } : {}),
|
|
643
735
|
...(signal ? { signal } : {}),
|
|
@@ -653,15 +745,15 @@ async function fetchNativeHttpStream(baseUrl, url, method, options, clientOption
|
|
|
653
745
|
status: response.status,
|
|
654
746
|
});
|
|
655
747
|
}
|
|
656
|
-
//
|
|
657
|
-
// stays attached to
|
|
658
|
-
return toNativeHttpStreamResponse(response, serializedUrl,
|
|
748
|
+
// The stream timeout remains header-scoped; composed ambient and local
|
|
749
|
+
// cancellation stays attached to native fetch for the body's full lifetime.
|
|
750
|
+
return toNativeHttpStreamResponse(response, serializedUrl, options.signal);
|
|
659
751
|
}
|
|
660
752
|
catch (error) {
|
|
661
753
|
if (error instanceof SyntaxError) {
|
|
662
754
|
throw redactSensitiveError(error, serializedUrl.sensitiveValues, serializedUrl.requestUrl, serializedUrl.redactedUrl);
|
|
663
755
|
}
|
|
664
|
-
throw redactSensitiveError(toHttpTransportError(error,
|
|
756
|
+
throw redactSensitiveError(toHttpTransportError(error, options.signal, controller?.signal), serializedUrl.sensitiveValues, serializedUrl.requestUrl, serializedUrl.redactedUrl);
|
|
665
757
|
}
|
|
666
758
|
finally {
|
|
667
759
|
if (timeoutHandle)
|
|
@@ -772,6 +864,15 @@ export function createHttpClient(baseUrl, clientOptions = {}) {
|
|
|
772
864
|
method: methodName,
|
|
773
865
|
});
|
|
774
866
|
const dedupeContext = dedupeAllocatorEndpoints ? { attempted: new Set() } : undefined;
|
|
867
|
+
// Numbers the attempts a header factory sees: only requests that reached
|
|
868
|
+
// header resolution count, so a failed proxy allocation (which increments
|
|
869
|
+
// the telemetry `issued` count below) or a dedupe-skipped offset does not
|
|
870
|
+
// make the first request that is actually built report `attempt: 2`.
|
|
871
|
+
let headerAttempts = 0;
|
|
872
|
+
const nextHeaderAttempt = () => {
|
|
873
|
+
headerAttempts += 1;
|
|
874
|
+
return headerAttempts;
|
|
875
|
+
};
|
|
775
876
|
const executeOnce = async (proxyAttemptOffset = 0) => {
|
|
776
877
|
const started = performance.now();
|
|
777
878
|
let proxyUsed = false;
|
|
@@ -783,7 +884,7 @@ export function createHttpClient(baseUrl, clientOptions = {}) {
|
|
|
783
884
|
const outcome = await fetchNativeHttp(baseUrl, url, methodName, attemptOptions, clientOptions, warnOnce, statusRetryEnabled ? retryOptions?.statusCodes : undefined, proxyAttemptOffset, dedupeContext, (used, status) => {
|
|
784
885
|
proxyUsed = used;
|
|
785
886
|
observedStatus = status;
|
|
786
|
-
});
|
|
887
|
+
}, nextHeaderAttempt);
|
|
787
888
|
if (!isDedupeSkipOutcome(outcome))
|
|
788
889
|
record({
|
|
789
890
|
status: outcome.status,
|
|
@@ -800,7 +901,7 @@ export function createHttpClient(baseUrl, clientOptions = {}) {
|
|
|
800
901
|
}
|
|
801
902
|
};
|
|
802
903
|
if (!retryEnabled || !retryOptions) {
|
|
803
|
-
throwIfAmbientAborted(
|
|
904
|
+
throwIfAmbientAborted(headersOptions.signal);
|
|
804
905
|
const outcome = await executeOnce();
|
|
805
906
|
if (isDedupeSkipOutcome(outcome)) {
|
|
806
907
|
// Single-shot path never de-duplicates (dedupeContext is undefined),
|
|
@@ -822,7 +923,7 @@ export function createHttpClient(baseUrl, clientOptions = {}) {
|
|
|
822
923
|
// allocations skip offsets.
|
|
823
924
|
let issued = 0;
|
|
824
925
|
for (let attempt = 1; attempt <= transportAttemptCap; attempt += 1) {
|
|
825
|
-
throwIfAmbientAborted(
|
|
926
|
+
throwIfAmbientAborted(headersOptions.signal);
|
|
826
927
|
// Whether this offset actually issued a request (vs. a skipped duplicate),
|
|
827
928
|
// so the catch counts a thrown *transport* failure once without
|
|
828
929
|
// double-counting a status outcome that already incremented before it
|
|
@@ -840,7 +941,7 @@ export function createHttpClient(baseUrl, clientOptions = {}) {
|
|
|
840
941
|
issuedThisAttempt = true;
|
|
841
942
|
if (isHttpStatusOutcome(outcome)) {
|
|
842
943
|
if (outcome.retryable && issued < retryOptions.attempts) {
|
|
843
|
-
await sleep(computeProxyTransportRetryDelayMs(retryOptions, attempt, outcome.headers),
|
|
944
|
+
await sleep(computeProxyTransportRetryDelayMs(retryOptions, attempt, outcome.headers), headersOptions.signal);
|
|
844
945
|
continue;
|
|
845
946
|
}
|
|
846
947
|
throw toUpstreamHttpError(outcome.status);
|
|
@@ -857,7 +958,7 @@ export function createHttpClient(baseUrl, clientOptions = {}) {
|
|
|
857
958
|
return response;
|
|
858
959
|
}
|
|
859
960
|
catch (error) {
|
|
860
|
-
throwIfAmbientAborted(
|
|
961
|
+
throwIfAmbientAborted(headersOptions.signal);
|
|
861
962
|
if (!issuedThisAttempt)
|
|
862
963
|
issued += 1;
|
|
863
964
|
lastError = error;
|
|
@@ -870,7 +971,7 @@ export function createHttpClient(baseUrl, clientOptions = {}) {
|
|
|
870
971
|
options: retryOptions,
|
|
871
972
|
proxyUsed,
|
|
872
973
|
})) {
|
|
873
|
-
await sleep(computeProxyTransportRetryDelayMs(retryOptions, attempt),
|
|
974
|
+
await sleep(computeProxyTransportRetryDelayMs(retryOptions, attempt), headersOptions.signal);
|
|
874
975
|
continue;
|
|
875
976
|
}
|
|
876
977
|
throw error;
|
|
@@ -911,7 +1012,7 @@ export function createHttpClient(baseUrl, clientOptions = {}) {
|
|
|
911
1012
|
// Streams never retry. Observe the header fetch without consuming its body.
|
|
912
1013
|
const telemetry = startHttpTelemetry(scopedHttpTelemetry(clientOptions.httpTelemetry), "off");
|
|
913
1014
|
const started = performance.now();
|
|
914
|
-
const alreadyAborted =
|
|
1015
|
+
const alreadyAborted = headersOptions.signal?.aborted === true;
|
|
915
1016
|
let proxyUsed = false;
|
|
916
1017
|
const record = (event) => {
|
|
917
1018
|
if (alreadyAborted)
|
|
@@ -941,10 +1042,15 @@ export function createHttpClient(baseUrl, clientOptions = {}) {
|
|
|
941
1042
|
delete: async (url, options) => request(url, "DELETE", options),
|
|
942
1043
|
stream: async (url, options = {}) => streamRequest(url, options.method ?? "GET", options),
|
|
943
1044
|
sse: async (url, options = {}) => {
|
|
944
|
-
const
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
1045
|
+
const callerHeaders = options.headers;
|
|
1046
|
+
const headers = typeof callerHeaders === "function"
|
|
1047
|
+
? async (attempt) => {
|
|
1048
|
+
// Pass a malformed result through unchanged so the factory
|
|
1049
|
+
// validation rejects it instead of spreading it into `{}`.
|
|
1050
|
+
const minted = await callerHeaders(attempt);
|
|
1051
|
+
return isHeaderRecord(minted) ? { Accept: "text/event-stream", ...minted } : minted;
|
|
1052
|
+
}
|
|
1053
|
+
: { Accept: "text/event-stream", ...callerHeaders };
|
|
948
1054
|
const response = await streamRequest(url, options.method ?? "GET", {
|
|
949
1055
|
...options,
|
|
950
1056
|
headers,
|