@apifuse/provider-sdk 2.2.0-beta.1 → 2.2.0-beta.10
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 +106 -0
- package/CHANGELOG.md +37 -0
- package/README.md +5 -1
- package/SUBMISSION.md +1 -1
- package/bin/apifuse-check.ts +44 -59
- package/bin/apifuse-create.ts +1 -1
- package/bin/apifuse-dev.ts +27 -52
- package/bin/apifuse-pack-check.ts +14 -0
- package/bin/apifuse-pack-smoke.ts +22 -81
- package/bin/apifuse-pack-types.ts +266 -0
- package/bin/apifuse-perf.ts +45 -127
- package/bin/apifuse-record.ts +53 -70
- package/bin/apifuse-submit-check.ts +510 -45
- package/bin/apifuse-sync-assets.ts +117 -0
- package/bin/apifuse.ts +1 -1
- package/bin/submit-check-delimited-text.ts +50 -0
- package/bin/submit-check-xml-semantics.ts +204 -0
- package/bin/submit-check-xml.ts +134 -0
- package/dist/auth-turn/index.d.ts +3 -3
- package/dist/auth.d.ts +2 -2
- package/dist/auth.js +9 -18
- package/dist/ceremonies/index.d.ts +1 -1
- package/dist/ceremonies/index.js +8 -18
- package/dist/cli/commands.d.ts +1 -1
- package/dist/cli/commands.js +8 -0
- package/dist/cli/create.d.ts +3 -0
- package/dist/cli/create.js +34 -35
- 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/config/loader.d.ts +149 -8
- package/dist/config/loader.js +378 -89
- package/dist/contract-serialization.d.ts +2 -2
- package/dist/contract-serialization.js +3 -6
- package/dist/contract-types.d.ts +2 -2
- package/dist/contract.d.ts +3 -3
- package/dist/contract.js +4 -6
- package/dist/define.d.ts +8 -1
- package/dist/define.js +119 -122
- package/dist/dev.d.ts +1 -1
- package/dist/dev.js +1 -1
- package/dist/errors.d.ts +4 -1
- package/dist/errors.js +48 -0
- package/dist/i18n/catalog.d.ts +2 -2
- package/dist/i18n/catalog.js +4 -10
- package/dist/i18n/index.d.ts +2 -2
- package/dist/i18n/index.js +2 -2
- package/dist/i18n/keys.d.ts +2 -2
- package/dist/index.d.ts +44 -42
- package/dist/index.js +39 -37
- package/dist/lint.js +8 -15
- package/dist/provider.d.ts +11 -11
- package/dist/provider.js +9 -9
- package/dist/public-schema-field-lint.d.ts +1 -1
- package/dist/recipes/gov-api.js +1 -1
- package/dist/runtime/auth-flow.d.ts +1 -1
- package/dist/runtime/auth-flow.js +3 -3
- package/dist/runtime/browser.d.ts +1 -1
- package/dist/runtime/browser.js +15 -29
- package/dist/runtime/cache.d.ts +1 -1
- package/dist/runtime/cache.js +4 -8
- package/dist/runtime/choice.d.ts +1 -1
- package/dist/runtime/choice.js +31 -35
- package/dist/runtime/credential.d.ts +1 -1
- package/dist/runtime/credential.js +1 -1
- package/dist/runtime/env.d.ts +1 -1
- package/dist/runtime/executor.d.ts +1 -1
- package/dist/runtime/executor.js +15 -3
- package/dist/runtime/http.d.ts +2 -2
- package/dist/runtime/http.js +144 -348
- package/dist/runtime/insights.d.ts +1 -1
- package/dist/runtime/insights.js +6 -13
- package/dist/runtime/instrumentation.d.ts +2 -2
- package/dist/runtime/instrumentation.js +56 -19
- package/dist/runtime/keyring.js +1 -1
- package/dist/runtime/namespace.js +1 -1
- package/dist/runtime/otlp.d.ts +1 -1
- package/dist/runtime/perf.d.ts +1 -1
- package/dist/runtime/provider.d.ts +1 -1
- package/dist/runtime/provider.js +1 -2
- package/dist/runtime/proxy-errors.d.ts +1 -1
- package/dist/runtime/proxy-errors.js +9 -7
- package/dist/runtime/proxy-nodemaven.d.ts +35 -0
- package/dist/runtime/proxy-nodemaven.js +128 -0
- package/dist/runtime/proxy-retry-policy.d.ts +40 -0
- package/dist/runtime/proxy-retry-policy.js +326 -0
- package/dist/runtime/proxy-telemetry.d.ts +2 -1
- package/dist/runtime/proxy-telemetry.js +55 -52
- package/dist/runtime/redis.d.ts +1 -1
- package/dist/runtime/redis.js +2 -4
- package/dist/runtime/request-options.d.ts +1 -1
- package/dist/runtime/secrets.d.ts +27 -0
- package/dist/runtime/secrets.js +51 -0
- package/dist/runtime/state.d.ts +2 -2
- package/dist/runtime/state.js +15 -4
- package/dist/runtime/stealth.d.ts +2 -2
- package/dist/runtime/stealth.js +108 -212
- package/dist/runtime/stt.d.ts +1 -1
- package/dist/runtime/stt.js +11 -15
- package/dist/runtime/trace.d.ts +2 -2
- package/dist/runtime/trace.js +2 -4
- package/dist/runtime/waterfall.d.ts +1 -1
- package/dist/schema.d.ts +1 -1
- package/dist/schema.js +7 -15
- package/dist/serve.d.ts +1 -1
- package/dist/serve.js +1 -1
- package/dist/server/index.d.ts +7 -7
- package/dist/server/index.js +6 -6
- package/dist/server/self-test-redaction.d.ts +1 -1
- package/dist/server/self-test-redaction.js +1 -1
- package/dist/server/self-test.d.ts +104 -3
- package/dist/server/self-test.js +673 -115
- package/dist/server/serve.d.ts +8 -3
- package/dist/server/serve.js +105 -59
- package/dist/server/types.d.ts +10 -9
- package/dist/server/types.js +3 -7
- package/dist/stealth/profiles.d.ts +1 -1
- package/dist/stealth/profiles.js +5 -14
- package/dist/stream.d.ts +1 -1
- package/dist/testing/index.d.ts +2 -2
- package/dist/testing/index.js +2 -2
- package/dist/testing/run.d.ts +1 -1
- package/dist/testing/run.js +7 -14
- package/dist/types.d.ts +87 -1
- package/dist/user-input.d.ts +30 -0
- package/dist/user-input.js +66 -0
- package/package.json +9 -5
- package/src/auth-turn/index.ts +1 -1
- package/src/auth.ts +28 -86
- package/src/ceremonies/index.ts +24 -78
- package/src/cli/commands.ts +10 -0
- package/src/cli/create.ts +42 -35
- package/src/cli/prompt-assets.ts +865 -0
- package/src/cli/templates/provider/AGENTS.md.tpl +17 -8
- package/src/config/loader.ts +543 -208
- package/src/contract-serialization.ts +5 -11
- package/src/contract-types.ts +2 -2
- package/src/contract.ts +12 -28
- package/src/define.ts +247 -505
- package/src/dev.ts +4 -9
- package/src/errors.ts +58 -5
- package/src/i18n/catalog.ts +10 -32
- package/src/i18n/index.ts +2 -2
- package/src/i18n/keys.ts +5 -11
- package/src/index.ts +49 -42
- package/src/lint.ts +89 -159
- package/src/provider.ts +15 -11
- package/src/public-schema-field-lint.ts +7 -33
- package/src/recipes/gov-api.ts +2 -5
- package/src/runtime/auth-flow.ts +5 -7
- package/src/runtime/browser.ts +64 -187
- package/src/runtime/cache.ts +20 -67
- package/src/runtime/choice.ts +79 -132
- package/src/runtime/credential.ts +2 -2
- package/src/runtime/env.ts +1 -1
- package/src/runtime/executor.ts +23 -20
- package/src/runtime/http.ts +195 -559
- package/src/runtime/insights.ts +15 -53
- package/src/runtime/instrumentation.ts +78 -65
- package/src/runtime/keyring.ts +7 -19
- package/src/runtime/namespace.ts +2 -7
- package/src/runtime/otlp.ts +12 -23
- package/src/runtime/perf.ts +1 -1
- package/src/runtime/provider.ts +4 -9
- package/src/runtime/proxy-errors.ts +29 -42
- package/src/runtime/proxy-nodemaven.ts +178 -0
- package/src/runtime/proxy-retry-policy.ts +469 -0
- package/src/runtime/proxy-telemetry.ts +79 -77
- package/src/runtime/redis.ts +4 -12
- package/src/runtime/request-options.ts +4 -13
- package/src/runtime/secrets.ts +64 -0
- package/src/runtime/state.ts +41 -110
- package/src/runtime/stealth.ts +162 -377
- package/src/runtime/stt.ts +38 -94
- package/src/runtime/trace.ts +14 -44
- package/src/runtime/waterfall.ts +5 -18
- package/src/schema.ts +23 -84
- package/src/serve.ts +1 -1
- package/src/server/index.ts +12 -7
- package/src/server/self-test-redaction.ts +2 -2
- package/src/server/self-test.ts +857 -132
- package/src/server/serve.ts +172 -245
- package/src/server/types.ts +7 -19
- package/src/stealth/profiles.ts +10 -26
- package/src/stream.ts +8 -19
- package/src/testing/index.ts +2 -2
- package/src/testing/run.ts +19 -67
- package/src/types.ts +88 -1
- package/src/user-input.ts +118 -0
- 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
package/dist/runtime/http.js
CHANGED
|
@@ -1,316 +1,14 @@
|
|
|
1
|
-
import { resolveProxyConfigAsync } from "../config/loader";
|
|
2
|
-
import { ProviderError, TransportError } from "../errors";
|
|
3
|
-
import { parseSseStream, readableBytes, readableLines, readableTextChunks
|
|
4
|
-
import {
|
|
5
|
-
import { appendQueryParams, normalizeHttpRequestBody } from "./request-options";
|
|
1
|
+
import { policyRotatesTransportVendorChain, resolvePolicyTransportAttemptCap, resolveProxyConfigAsync, } from "../config/loader.js";
|
|
2
|
+
import { ProviderError, TransportError } from "../errors.js";
|
|
3
|
+
import { parseSseStream, readableBytes, readableLines, readableTextChunks } from "../stream.js";
|
|
4
|
+
import { computeProxyAttemptIndex, computeProxyTransportRetryDelayMs, createDefaultProxyTransportRetryOptions, isProxyTransportRetryMethod, normalizeProxyTransportRetryOptions, proxyTransportRetryErrorCode, proxyTransportRetryErrorStatus, shouldRetryProxyTransportAttempt, validateUnsafeProxyTransportRetryMethods, } from "./proxy-retry-policy.js";
|
|
5
|
+
import { appendQueryParams, normalizeHttpRequestBody } from "./request-options.js";
|
|
6
6
|
const DEFAULT_HTTP_BASE_URL = "http://localhost";
|
|
7
7
|
function isHttpStatusOutcome(outcome) {
|
|
8
8
|
return "kind" in outcome && outcome.kind === "http-status";
|
|
9
9
|
}
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
"transport_network_error",
|
|
13
|
-
"transport_timeout",
|
|
14
|
-
];
|
|
15
|
-
const SAFE_RETRY_STATUS_CODES = [408, 429, 500, 502, 503, 504];
|
|
16
|
-
const RATE_LIMIT_RETRY_STATUS_CODES = [429, 503];
|
|
17
|
-
const KNOWN_RETRY_METHODS = new Set([
|
|
18
|
-
"GET",
|
|
19
|
-
"HEAD",
|
|
20
|
-
"POST",
|
|
21
|
-
"PUT",
|
|
22
|
-
"DELETE",
|
|
23
|
-
"OPTIONS",
|
|
24
|
-
"TRACE",
|
|
25
|
-
"PATCH",
|
|
26
|
-
]);
|
|
27
|
-
const UNSAFE_RETRY_METHODS = new Set([
|
|
28
|
-
"POST",
|
|
29
|
-
"PUT",
|
|
30
|
-
"PATCH",
|
|
31
|
-
"DELETE",
|
|
32
|
-
"TRACE",
|
|
33
|
-
]);
|
|
34
|
-
const MAX_RETRY_ATTEMPTS = 8;
|
|
35
|
-
const MAX_RETRY_DELAY_MS = 30_000;
|
|
36
|
-
function hasOwnValue(values, value) {
|
|
37
|
-
if (typeof value !== "string")
|
|
38
|
-
return false;
|
|
39
|
-
return Object.values(values).some((candidate) => candidate === value);
|
|
40
|
-
}
|
|
41
|
-
function createInvalidRetryPolicyError(message) {
|
|
42
|
-
return new ProviderError(message, { code: "retry_invalid_policy" });
|
|
43
|
-
}
|
|
44
|
-
function createRetryOptions(preset) {
|
|
45
|
-
switch (preset) {
|
|
46
|
-
case HttpRetryPreset.Off:
|
|
47
|
-
return {
|
|
48
|
-
preset,
|
|
49
|
-
attempts: 1,
|
|
50
|
-
methods: DEFAULT_RETRY_METHODS,
|
|
51
|
-
statusCodes: [],
|
|
52
|
-
errorCodes: DEFAULT_RETRY_ERROR_CODES,
|
|
53
|
-
delayStrategy: HttpRetryDelayStrategy.Exponential,
|
|
54
|
-
baseDelayMs: 100,
|
|
55
|
-
maxDelayMs: 1_000,
|
|
56
|
-
jitter: HttpRetryJitter.Full,
|
|
57
|
-
retryAfter: HttpRetryAfterPolicy.Ignore,
|
|
58
|
-
unsafeMethodPolicy: HttpRetryUnsafeMethodPolicy.Reject,
|
|
59
|
-
};
|
|
60
|
-
case HttpRetryPreset.SafeRead:
|
|
61
|
-
return {
|
|
62
|
-
preset,
|
|
63
|
-
attempts: 3,
|
|
64
|
-
methods: DEFAULT_RETRY_METHODS,
|
|
65
|
-
statusCodes: SAFE_RETRY_STATUS_CODES,
|
|
66
|
-
errorCodes: DEFAULT_RETRY_ERROR_CODES,
|
|
67
|
-
delayStrategy: HttpRetryDelayStrategy.Exponential,
|
|
68
|
-
baseDelayMs: 100,
|
|
69
|
-
maxDelayMs: 2_000,
|
|
70
|
-
jitter: HttpRetryJitter.Full,
|
|
71
|
-
retryAfter: HttpRetryAfterPolicy.Cap,
|
|
72
|
-
unsafeMethodPolicy: HttpRetryUnsafeMethodPolicy.Reject,
|
|
73
|
-
};
|
|
74
|
-
case HttpRetryPreset.AggressiveRead:
|
|
75
|
-
return {
|
|
76
|
-
preset,
|
|
77
|
-
attempts: 4,
|
|
78
|
-
methods: DEFAULT_RETRY_METHODS,
|
|
79
|
-
statusCodes: SAFE_RETRY_STATUS_CODES,
|
|
80
|
-
errorCodes: DEFAULT_RETRY_ERROR_CODES,
|
|
81
|
-
delayStrategy: HttpRetryDelayStrategy.Exponential,
|
|
82
|
-
baseDelayMs: 150,
|
|
83
|
-
maxDelayMs: 5_000,
|
|
84
|
-
jitter: HttpRetryJitter.Full,
|
|
85
|
-
retryAfter: HttpRetryAfterPolicy.Cap,
|
|
86
|
-
unsafeMethodPolicy: HttpRetryUnsafeMethodPolicy.Reject,
|
|
87
|
-
};
|
|
88
|
-
case HttpRetryPreset.RateLimitAware:
|
|
89
|
-
return {
|
|
90
|
-
preset,
|
|
91
|
-
attempts: 3,
|
|
92
|
-
methods: DEFAULT_RETRY_METHODS,
|
|
93
|
-
statusCodes: RATE_LIMIT_RETRY_STATUS_CODES,
|
|
94
|
-
errorCodes: ["transport_timeout"],
|
|
95
|
-
delayStrategy: HttpRetryDelayStrategy.Exponential,
|
|
96
|
-
baseDelayMs: 250,
|
|
97
|
-
maxDelayMs: 5_000,
|
|
98
|
-
jitter: HttpRetryJitter.Equal,
|
|
99
|
-
retryAfter: HttpRetryAfterPolicy.Respect,
|
|
100
|
-
unsafeMethodPolicy: HttpRetryUnsafeMethodPolicy.Reject,
|
|
101
|
-
};
|
|
102
|
-
case HttpRetryPreset.TransportTransient:
|
|
103
|
-
return {
|
|
104
|
-
preset,
|
|
105
|
-
attempts: 3,
|
|
106
|
-
methods: DEFAULT_RETRY_METHODS,
|
|
107
|
-
statusCodes: [],
|
|
108
|
-
errorCodes: DEFAULT_RETRY_ERROR_CODES,
|
|
109
|
-
delayStrategy: HttpRetryDelayStrategy.Exponential,
|
|
110
|
-
baseDelayMs: 100,
|
|
111
|
-
maxDelayMs: 1_000,
|
|
112
|
-
jitter: HttpRetryJitter.Full,
|
|
113
|
-
retryAfter: HttpRetryAfterPolicy.Ignore,
|
|
114
|
-
unsafeMethodPolicy: HttpRetryUnsafeMethodPolicy.Reject,
|
|
115
|
-
};
|
|
116
|
-
}
|
|
117
|
-
throw createInvalidRetryPolicyError(`Unknown HTTP retry preset: ${preset}`);
|
|
118
|
-
}
|
|
119
|
-
function clampPositiveInteger(value, fallback, max) {
|
|
120
|
-
if (value === undefined)
|
|
121
|
-
return fallback;
|
|
122
|
-
if (!Number.isFinite(value) || value < 1)
|
|
123
|
-
return fallback;
|
|
124
|
-
return Math.min(Math.floor(value), max);
|
|
125
|
-
}
|
|
126
|
-
function clampDelay(value, fallback) {
|
|
127
|
-
if (value === undefined)
|
|
128
|
-
return fallback;
|
|
129
|
-
if (!Number.isFinite(value) || value < 0)
|
|
130
|
-
return fallback;
|
|
131
|
-
return Math.min(Math.floor(value), MAX_RETRY_DELAY_MS);
|
|
132
|
-
}
|
|
133
|
-
function normalizeRetryOptions(retry) {
|
|
134
|
-
if (retry === undefined || retry === false) {
|
|
135
|
-
return undefined;
|
|
136
|
-
}
|
|
137
|
-
if (retry === true) {
|
|
138
|
-
return createRetryOptions(HttpRetryPreset.TransportTransient);
|
|
139
|
-
}
|
|
140
|
-
if (typeof retry === "string") {
|
|
141
|
-
if (!hasOwnValue(HttpRetryPreset, retry)) {
|
|
142
|
-
throw createInvalidRetryPolicyError(`Unknown HTTP retry preset: ${retry}`);
|
|
143
|
-
}
|
|
144
|
-
return createRetryOptions(retry);
|
|
145
|
-
}
|
|
146
|
-
if (typeof retry !== "object" || retry === null) {
|
|
147
|
-
throw createInvalidRetryPolicyError("HTTP retry policy must be an object");
|
|
148
|
-
}
|
|
149
|
-
if (Array.isArray(retry)) {
|
|
150
|
-
throw createInvalidRetryPolicyError("HTTP retry policy must be a plain object");
|
|
151
|
-
}
|
|
152
|
-
validateRetryOptionsShape(retry);
|
|
153
|
-
const base = createRetryOptions(retry.preset ?? HttpRetryPreset.TransportTransient);
|
|
154
|
-
const maxDelayMs = clampDelay(retry.maxDelayMs, base.maxDelayMs);
|
|
155
|
-
const normalized = {
|
|
156
|
-
preset: retry.preset ?? base.preset,
|
|
157
|
-
attempts: clampPositiveInteger(retry.attempts, base.attempts, MAX_RETRY_ATTEMPTS),
|
|
158
|
-
methods: retry.methods?.map((method) => method.toUpperCase()) ?? base.methods,
|
|
159
|
-
statusCodes: retry.statusCodes?.filter((status) => Number.isInteger(status)) ??
|
|
160
|
-
base.statusCodes,
|
|
161
|
-
errorCodes: retry.errorCodes ?? base.errorCodes,
|
|
162
|
-
delayStrategy: retry.delayStrategy ?? base.delayStrategy,
|
|
163
|
-
baseDelayMs: clampDelay(retry.baseDelayMs, base.baseDelayMs),
|
|
164
|
-
maxDelayMs,
|
|
165
|
-
jitter: retry.jitter ?? base.jitter,
|
|
166
|
-
retryAfter: retry.retryAfter ?? base.retryAfter,
|
|
167
|
-
unsafeMethodPolicy: retry.unsafeMethodPolicy ?? base.unsafeMethodPolicy,
|
|
168
|
-
};
|
|
169
|
-
return normalized;
|
|
170
|
-
}
|
|
171
|
-
function validateRetryOptionsShape(retry) {
|
|
172
|
-
if (retry.preset !== undefined &&
|
|
173
|
-
!hasOwnValue(HttpRetryPreset, retry.preset)) {
|
|
174
|
-
throw createInvalidRetryPolicyError(`Unknown HTTP retry preset: ${String(retry.preset)}`);
|
|
175
|
-
}
|
|
176
|
-
if (retry.delayStrategy !== undefined &&
|
|
177
|
-
!hasOwnValue(HttpRetryDelayStrategy, retry.delayStrategy)) {
|
|
178
|
-
throw createInvalidRetryPolicyError(`Unknown HTTP retry delay strategy: ${String(retry.delayStrategy)}`);
|
|
179
|
-
}
|
|
180
|
-
if (retry.jitter !== undefined &&
|
|
181
|
-
!hasOwnValue(HttpRetryJitter, retry.jitter)) {
|
|
182
|
-
throw createInvalidRetryPolicyError(`Unknown HTTP retry jitter policy: ${String(retry.jitter)}`);
|
|
183
|
-
}
|
|
184
|
-
if (retry.retryAfter !== undefined &&
|
|
185
|
-
!hasOwnValue(HttpRetryAfterPolicy, retry.retryAfter)) {
|
|
186
|
-
throw createInvalidRetryPolicyError(`Unknown HTTP retry-after policy: ${String(retry.retryAfter)}`);
|
|
187
|
-
}
|
|
188
|
-
if (retry.unsafeMethodPolicy !== undefined &&
|
|
189
|
-
!hasOwnValue(HttpRetryUnsafeMethodPolicy, retry.unsafeMethodPolicy)) {
|
|
190
|
-
throw createInvalidRetryPolicyError(`Unknown HTTP retry unsafe method policy: ${String(retry.unsafeMethodPolicy)}`);
|
|
191
|
-
}
|
|
192
|
-
if (retry.methods !== undefined) {
|
|
193
|
-
if (!Array.isArray(retry.methods)) {
|
|
194
|
-
throw createInvalidRetryPolicyError("HTTP retry methods must be an array");
|
|
195
|
-
}
|
|
196
|
-
const nonStringMethods = retry.methods.filter((method) => typeof method !== "string");
|
|
197
|
-
if (nonStringMethods.length > 0) {
|
|
198
|
-
throw createInvalidRetryPolicyError("HTTP retry methods must contain only strings");
|
|
199
|
-
}
|
|
200
|
-
const unknownMethods = retry.methods
|
|
201
|
-
.map((method) => method.toUpperCase())
|
|
202
|
-
.filter((method) => !KNOWN_RETRY_METHODS.has(method));
|
|
203
|
-
if (unknownMethods.length > 0) {
|
|
204
|
-
throw createInvalidRetryPolicyError(`Unknown HTTP retry method(s): ${unknownMethods.join(", ")}`);
|
|
205
|
-
}
|
|
206
|
-
}
|
|
207
|
-
if (retry.statusCodes !== undefined) {
|
|
208
|
-
if (!Array.isArray(retry.statusCodes)) {
|
|
209
|
-
throw createInvalidRetryPolicyError("HTTP retry statusCodes must be an array");
|
|
210
|
-
}
|
|
211
|
-
const invalidStatusCodes = retry.statusCodes.filter((status) => !Number.isInteger(status) ||
|
|
212
|
-
Number(status) < 100 ||
|
|
213
|
-
Number(status) > 599);
|
|
214
|
-
if (invalidStatusCodes.length > 0) {
|
|
215
|
-
throw createInvalidRetryPolicyError("HTTP retry statusCodes must contain HTTP status integers in [100, 599]");
|
|
216
|
-
}
|
|
217
|
-
}
|
|
218
|
-
if (retry.errorCodes !== undefined) {
|
|
219
|
-
if (!Array.isArray(retry.errorCodes)) {
|
|
220
|
-
throw createInvalidRetryPolicyError("HTTP retry errorCodes must be an array");
|
|
221
|
-
}
|
|
222
|
-
const nonStringErrorCodes = retry.errorCodes.filter((errorCode) => typeof errorCode !== "string");
|
|
223
|
-
if (nonStringErrorCodes.length > 0) {
|
|
224
|
-
throw createInvalidRetryPolicyError("HTTP retry errorCodes must contain only strings");
|
|
225
|
-
}
|
|
226
|
-
}
|
|
227
|
-
if (retry.preset === HttpRetryPreset.Off &&
|
|
228
|
-
((retry.attempts !== undefined && retry.attempts > 1) ||
|
|
229
|
-
(retry.statusCodes !== undefined && retry.statusCodes.length > 0))) {
|
|
230
|
-
throw createInvalidRetryPolicyError("HTTP retry preset off cannot be combined with retry-enabling overrides");
|
|
231
|
-
}
|
|
232
|
-
}
|
|
233
|
-
function validateUnsafeRetryMethods(options) {
|
|
234
|
-
if (options.unsafeMethodPolicy ===
|
|
235
|
-
HttpRetryUnsafeMethodPolicy.AllowExplicitUnsafe) {
|
|
236
|
-
return;
|
|
237
|
-
}
|
|
238
|
-
const unsafeMethods = options.methods.filter((method) => UNSAFE_RETRY_METHODS.has(method.toUpperCase()));
|
|
239
|
-
if (unsafeMethods.length === 0)
|
|
240
|
-
return;
|
|
241
|
-
throw new ProviderError(`HTTP retry methods include unsafe method(s): ${unsafeMethods.join(", ")}`, { code: "retry_unsafe_method" });
|
|
242
|
-
}
|
|
243
|
-
function isMethodRetryable(method, options) {
|
|
244
|
-
return options.methods
|
|
245
|
-
.map((allowedMethod) => allowedMethod.toUpperCase())
|
|
246
|
-
.includes(method.toUpperCase());
|
|
247
|
-
}
|
|
248
|
-
function retryErrorCode(error) {
|
|
249
|
-
if (error instanceof TransportError) {
|
|
250
|
-
return error.code;
|
|
251
|
-
}
|
|
252
|
-
if (error && typeof error === "object" && "code" in error) {
|
|
253
|
-
const code = Reflect.get(error, "code");
|
|
254
|
-
return typeof code === "string" ? code : undefined;
|
|
255
|
-
}
|
|
256
|
-
return undefined;
|
|
257
|
-
}
|
|
258
|
-
function retryErrorStatus(error) {
|
|
259
|
-
if (error instanceof TransportError) {
|
|
260
|
-
return error.status ?? error.upstreamStatus;
|
|
261
|
-
}
|
|
262
|
-
return undefined;
|
|
263
|
-
}
|
|
264
|
-
function shouldRetryTransportError(error, options) {
|
|
265
|
-
const code = retryErrorCode(error);
|
|
266
|
-
return Boolean(code && options.errorCodes.includes(code));
|
|
267
|
-
}
|
|
268
|
-
function retryAfterHeader(headers) {
|
|
269
|
-
for (const [name, value] of Object.entries(headers)) {
|
|
270
|
-
if (name.toLowerCase() === "retry-after")
|
|
271
|
-
return value;
|
|
272
|
-
}
|
|
273
|
-
return undefined;
|
|
274
|
-
}
|
|
275
|
-
function parseRetryAfterMs(headers, now = Date.now()) {
|
|
276
|
-
const value = retryAfterHeader(headers);
|
|
277
|
-
if (!value)
|
|
278
|
-
return undefined;
|
|
279
|
-
const seconds = Number(value);
|
|
280
|
-
if (Number.isFinite(seconds)) {
|
|
281
|
-
return Math.max(0, Math.floor(seconds * 1_000));
|
|
282
|
-
}
|
|
283
|
-
const dateMs = Date.parse(value);
|
|
284
|
-
if (!Number.isNaN(dateMs)) {
|
|
285
|
-
return Math.max(0, dateMs - now);
|
|
286
|
-
}
|
|
287
|
-
return undefined;
|
|
288
|
-
}
|
|
289
|
-
function computeRetryDelayMs(options, attemptIndex, headers) {
|
|
290
|
-
const multiplier = options.delayStrategy === HttpRetryDelayStrategy.Exponential
|
|
291
|
-
? 2 ** Math.max(0, attemptIndex - 1)
|
|
292
|
-
: 1;
|
|
293
|
-
const configuredDelay = Math.min(options.baseDelayMs * multiplier, options.maxDelayMs);
|
|
294
|
-
const retryAfterMs = options.retryAfter === HttpRetryAfterPolicy.Ignore
|
|
295
|
-
? undefined
|
|
296
|
-
: headers
|
|
297
|
-
? parseRetryAfterMs(headers)
|
|
298
|
-
: undefined;
|
|
299
|
-
if (retryAfterMs !== undefined) {
|
|
300
|
-
const boundedRetryAfterMs = Math.min(retryAfterMs, options.maxDelayMs);
|
|
301
|
-
if (options.retryAfter === HttpRetryAfterPolicy.Cap) {
|
|
302
|
-
return Math.min(boundedRetryAfterMs, configuredDelay);
|
|
303
|
-
}
|
|
304
|
-
return boundedRetryAfterMs;
|
|
305
|
-
}
|
|
306
|
-
switch (options.jitter) {
|
|
307
|
-
case HttpRetryJitter.None:
|
|
308
|
-
return configuredDelay;
|
|
309
|
-
case HttpRetryJitter.Equal:
|
|
310
|
-
return Math.floor(configuredDelay / 2 + Math.random() * (configuredDelay / 2));
|
|
311
|
-
case HttpRetryJitter.Full:
|
|
312
|
-
return Math.floor(Math.random() * configuredDelay);
|
|
313
|
-
}
|
|
10
|
+
function isDedupeSkipOutcome(outcome) {
|
|
11
|
+
return "kind" in outcome && outcome.kind === "dedupe-skip";
|
|
314
12
|
}
|
|
315
13
|
async function sleep(ms) {
|
|
316
14
|
if (ms <= 0)
|
|
@@ -330,9 +28,7 @@ function hasHeader(headers, name) {
|
|
|
330
28
|
}
|
|
331
29
|
function withClientHeaders(options, clientOptions, body) {
|
|
332
30
|
const headers = {
|
|
333
|
-
...(clientOptions.userAgent
|
|
334
|
-
? { "User-Agent": clientOptions.userAgent }
|
|
335
|
-
: {}),
|
|
31
|
+
...(clientOptions.userAgent ? { "User-Agent": clientOptions.userAgent } : {}),
|
|
336
32
|
...options?.headers,
|
|
337
33
|
};
|
|
338
34
|
if (body !== undefined && !hasHeader(headers, "Content-Type")) {
|
|
@@ -344,9 +40,7 @@ function withClientHeaders(options, clientOptions, body) {
|
|
|
344
40
|
};
|
|
345
41
|
}
|
|
346
42
|
function parseHttpData(body, headers) {
|
|
347
|
-
const contentType = headers["content-type"] ??
|
|
348
|
-
headers["Content-Type"] ??
|
|
349
|
-
headers["CONTENT-TYPE"];
|
|
43
|
+
const contentType = headers["content-type"] ?? headers["Content-Type"] ?? headers["CONTENT-TYPE"];
|
|
350
44
|
if (contentType?.includes("application/json")) {
|
|
351
45
|
return body ? JSON.parse(body) : null;
|
|
352
46
|
}
|
|
@@ -403,12 +97,8 @@ async function toNativeHttpResponse(response) {
|
|
|
403
97
|
data,
|
|
404
98
|
headers,
|
|
405
99
|
json: async () => {
|
|
406
|
-
const contentType = headers["content-type"] ??
|
|
407
|
-
|
|
408
|
-
headers["CONTENT-TYPE"];
|
|
409
|
-
return parseJson(contentType?.includes("application/json") && !rawText
|
|
410
|
-
? "null"
|
|
411
|
-
: rawText);
|
|
100
|
+
const contentType = headers["content-type"] ?? headers["Content-Type"] ?? headers["CONTENT-TYPE"];
|
|
101
|
+
return parseJson(contentType?.includes("application/json") && !rawText ? "null" : rawText);
|
|
412
102
|
},
|
|
413
103
|
ok: response.status >= 200 && response.status < 300,
|
|
414
104
|
status: response.status,
|
|
@@ -478,17 +168,19 @@ function resolveHttpUrl(baseUrl, url) {
|
|
|
478
168
|
return new URL(url, baseUrl ?? DEFAULT_HTTP_BASE_URL).toString();
|
|
479
169
|
}
|
|
480
170
|
async function resolveNativeProxy(options, clientOptions, warn, proxyAttemptOffset = 0) {
|
|
481
|
-
const baseProxyAttempt = clientOptions.proxyAttempt === undefined ||
|
|
482
|
-
!Number.isFinite(clientOptions.proxyAttempt)
|
|
483
|
-
? 0
|
|
484
|
-
: Math.max(0, Math.floor(clientOptions.proxyAttempt));
|
|
485
171
|
const resolvedProxy = await resolveProxyConfigAsync({
|
|
486
172
|
proxy: options.proxy ?? clientOptions.proxy,
|
|
487
173
|
upstream: clientOptions.upstream,
|
|
488
174
|
apifuseConfig: clientOptions.apifuseConfig,
|
|
489
175
|
proxyPolicy: clientOptions.proxyPolicy,
|
|
490
176
|
affinityKey: clientOptions.affinityKey,
|
|
491
|
-
proxyAttempt:
|
|
177
|
+
proxyAttempt: computeProxyAttemptIndex({
|
|
178
|
+
baseProxyAttempt: clientOptions.proxyAttempt,
|
|
179
|
+
retryAttemptOffset: proxyAttemptOffset,
|
|
180
|
+
}),
|
|
181
|
+
// Bun's native fetch proxy option tunnels HTTP CONNECT only; SOCKS5 is not
|
|
182
|
+
// supported here, so a socks5 policy fails loudly rather than downgrading.
|
|
183
|
+
transportProtocols: ["http"],
|
|
492
184
|
telemetry: clientOptions.telemetry,
|
|
493
185
|
});
|
|
494
186
|
if (resolvedProxy.shouldWarn) {
|
|
@@ -510,7 +202,7 @@ function normalizeNativeFetchBody(body) {
|
|
|
510
202
|
copied.set(normalized);
|
|
511
203
|
return copied.buffer;
|
|
512
204
|
}
|
|
513
|
-
async function fetchNativeHttp(baseUrl, url, method, options, clientOptions, warn, statusRetryCodes, proxyAttemptOffset = 0) {
|
|
205
|
+
async function fetchNativeHttp(baseUrl, url, method, options, clientOptions, warn, statusRetryCodes, proxyAttemptOffset = 0, dedupe) {
|
|
514
206
|
const requestUrl = appendQueryParams(resolveHttpUrl(baseUrl, url), options.params);
|
|
515
207
|
const controller = options.timeout ? new AbortController() : undefined;
|
|
516
208
|
const timeoutHandle = options.timeout
|
|
@@ -518,7 +210,20 @@ async function fetchNativeHttp(baseUrl, url, method, options, clientOptions, war
|
|
|
518
210
|
: undefined;
|
|
519
211
|
let proxy;
|
|
520
212
|
try {
|
|
213
|
+
// Resolve inside the try (and after the timeout is armed) so allocator
|
|
214
|
+
// failures are branded as TransportErrors and count against the request
|
|
215
|
+
// deadline, exactly as an inline resolve would.
|
|
521
216
|
proxy = await resolveNativeProxy(options, clientOptions, warn, proxyAttemptOffset);
|
|
217
|
+
// For a registry allocator chain, skip an endpoint a prior attempt already
|
|
218
|
+
// tried rather than re-issuing the same request. Returning the sentinel
|
|
219
|
+
// (instead of breaking) lets the loop keep advancing the flat offset until
|
|
220
|
+
// it crosses into the fallback vendor's pool span.
|
|
221
|
+
if (dedupe && proxy) {
|
|
222
|
+
if (dedupe.attempted.has(proxy)) {
|
|
223
|
+
return { kind: "dedupe-skip" };
|
|
224
|
+
}
|
|
225
|
+
dedupe.attempted.add(proxy);
|
|
226
|
+
}
|
|
522
227
|
const requestInit = {
|
|
523
228
|
headers: options.headers,
|
|
524
229
|
method,
|
|
@@ -622,15 +327,15 @@ export function createHttpClient(baseUrl, clientOptions = {}) {
|
|
|
622
327
|
const headersOptions = withClientHeaders(options, clientOptions, options.body);
|
|
623
328
|
const methodName = normalizeHttpMethod(method);
|
|
624
329
|
const explicitRetry = headersOptions.retry !== undefined;
|
|
625
|
-
const retryOptions =
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
330
|
+
const retryOptions = normalizeProxyTransportRetryOptions(headersOptions.retry, {
|
|
331
|
+
label: "HTTP",
|
|
332
|
+
}) ??
|
|
333
|
+
(explicitRetry ? undefined : createDefaultProxyTransportRetryOptions({ label: "HTTP" }));
|
|
629
334
|
if (retryOptions)
|
|
630
|
-
|
|
335
|
+
validateUnsafeProxyTransportRetryMethods(retryOptions, "HTTP");
|
|
631
336
|
const retryEnabled = Boolean(retryOptions &&
|
|
632
337
|
retryOptions.attempts > 1 &&
|
|
633
|
-
|
|
338
|
+
isProxyTransportRetryMethod(methodName, retryOptions));
|
|
634
339
|
const statusRetryEnabled = Boolean(retryEnabled &&
|
|
635
340
|
explicitRetry &&
|
|
636
341
|
retryOptions &&
|
|
@@ -639,36 +344,112 @@ export function createHttpClient(baseUrl, clientOptions = {}) {
|
|
|
639
344
|
const attemptOptions = statusRetryEnabled
|
|
640
345
|
? { ...headersOptions, throwOnHttpError: false }
|
|
641
346
|
: headersOptions;
|
|
642
|
-
|
|
347
|
+
// Span the whole vendor chain on transport failures. Like ctx.stealth, a
|
|
348
|
+
// policy-managed proxy resolves a *different* endpoint/vendor per attempt
|
|
349
|
+
// (the flat proxyAttemptOffset rotates across the concatenated vendor pool
|
|
350
|
+
// spans), so a transport failure should advance to the next endpoint —
|
|
351
|
+
// potentially crossing into the fallback vendor — rather than stopping at
|
|
352
|
+
// the per-endpoint retry budget and stranding the request on the primary
|
|
353
|
+
// vendor. resolvePolicyTransportAttemptCap widens the cap to the chain span
|
|
354
|
+
// only for implicit, safe-method allocator requests; explicit retry
|
|
355
|
+
// policies (their documented `attempts` ceiling), unsafe methods, and
|
|
356
|
+
// static/non-registry vendors keep the retry budget. Status-code retries
|
|
357
|
+
// stay bounded by the retry budget regardless; only transport rotation gets
|
|
358
|
+
// the full span.
|
|
359
|
+
const policyProxy = (() => {
|
|
360
|
+
const policy = clientOptions.proxyPolicy ?? clientOptions.upstream?.proxy;
|
|
361
|
+
return policy && typeof policy === "object" ? policy : undefined;
|
|
362
|
+
})();
|
|
363
|
+
const usesPolicyAllocator = Boolean(policyProxy) && !options.proxy && !clientOptions.proxy;
|
|
364
|
+
const transportAttemptCap = retryOptions
|
|
365
|
+
? resolvePolicyTransportAttemptCap({
|
|
366
|
+
policy: policyProxy,
|
|
367
|
+
usesPolicyAllocator,
|
|
368
|
+
retryAttempts: retryOptions.attempts,
|
|
369
|
+
explicitRetry,
|
|
370
|
+
method: methodName,
|
|
371
|
+
})
|
|
372
|
+
: 1;
|
|
373
|
+
// Track resolved endpoints across a policy-allocator chain. Successive
|
|
374
|
+
// attempts rotate the flat offset across the concatenated vendor pool
|
|
375
|
+
// spans, but an under-filled allocation (fewer live endpoints than the
|
|
376
|
+
// configured pool size) makes the modulo mapping repeat endpoints before
|
|
377
|
+
// the offset reaches the next vendor. Rather than re-hammering an
|
|
378
|
+
// already-tried endpoint under backoff, fetchNativeHttp returns a skip
|
|
379
|
+
// sentinel for a duplicate; the loop then advances the flat offset without
|
|
380
|
+
// issuing the request, so it keeps walking toward — and into — the fallback
|
|
381
|
+
// vendor's pool span instead of stalling on the primary vendor.
|
|
382
|
+
// De-duplication is gated on the SAME predicate that widens the attempt cap
|
|
383
|
+
// (implicit, safe-method, registry-chain rotation). It must NOT engage for
|
|
384
|
+
// an explicit retry policy: there the caller's `attempts` count is the
|
|
385
|
+
// contract and each attempt must issue against whatever endpoint it resolves
|
|
386
|
+
// — even a repeat — instead of being silently skipped (which would collapse
|
|
387
|
+
// a `poolSize: 1` + `attempts: 3` request to a single fetch).
|
|
388
|
+
const dedupeAllocatorEndpoints = policyRotatesTransportVendorChain({
|
|
389
|
+
policy: policyProxy,
|
|
390
|
+
usesPolicyAllocator,
|
|
391
|
+
explicitRetry,
|
|
392
|
+
method: methodName,
|
|
393
|
+
});
|
|
394
|
+
const dedupeContext = dedupeAllocatorEndpoints
|
|
395
|
+
? { attempted: new Set() }
|
|
396
|
+
: undefined;
|
|
397
|
+
const executeOnce = (proxyAttemptOffset = 0) => fetchNativeHttp(baseUrl, url, methodName, attemptOptions, clientOptions, warnOnce, statusRetryEnabled ? retryOptions?.statusCodes : undefined, proxyAttemptOffset, dedupeContext);
|
|
643
398
|
if (!retryEnabled || !retryOptions) {
|
|
644
399
|
const outcome = await executeOnce();
|
|
400
|
+
if (isDedupeSkipOutcome(outcome)) {
|
|
401
|
+
// Single-shot path never de-duplicates (dedupeContext is undefined),
|
|
402
|
+
// but keep the union total.
|
|
403
|
+
throw new TransportError("HTTP request produced no terminal result", {
|
|
404
|
+
code: "retry_exhausted",
|
|
405
|
+
});
|
|
406
|
+
}
|
|
645
407
|
if (isHttpStatusOutcome(outcome)) {
|
|
646
408
|
throw toUpstreamHttpError(outcome.status);
|
|
647
409
|
}
|
|
648
410
|
return outcome;
|
|
649
411
|
}
|
|
412
|
+
let lastError;
|
|
650
413
|
let lastErrorCode;
|
|
651
414
|
let lastStatus;
|
|
652
|
-
|
|
415
|
+
// `attempt` walks the flat proxy offset across the full chain span; `issued`
|
|
416
|
+
// counts requests that were actually sent (skipped duplicate offsets do not
|
|
417
|
+
// increment it). Retry summaries and the status-retry budget must reflect
|
|
418
|
+
// issued requests, not the raw offset, so they stay accurate when partial
|
|
419
|
+
// allocations skip offsets.
|
|
420
|
+
let issued = 0;
|
|
421
|
+
for (let attempt = 1; attempt <= transportAttemptCap; attempt += 1) {
|
|
422
|
+
// Whether this offset actually issued a request (vs. a skipped duplicate),
|
|
423
|
+
// so the catch counts a thrown *transport* failure once without
|
|
424
|
+
// double-counting a status outcome that already incremented before it
|
|
425
|
+
// re-threw as an upstream HTTP error.
|
|
426
|
+
let issuedThisAttempt = false;
|
|
653
427
|
try {
|
|
654
428
|
const outcome = await executeOnce(attempt - 1);
|
|
429
|
+
if (isDedupeSkipOutcome(outcome)) {
|
|
430
|
+
// Duplicate endpoint from a partial allocation: advance the flat
|
|
431
|
+
// offset without issuing the request (no backoff, not a failure) so
|
|
432
|
+
// the loop keeps rotating toward the fallback vendor.
|
|
433
|
+
continue;
|
|
434
|
+
}
|
|
435
|
+
issued += 1;
|
|
436
|
+
issuedThisAttempt = true;
|
|
655
437
|
if (isHttpStatusOutcome(outcome)) {
|
|
656
438
|
lastStatus = outcome.status;
|
|
657
|
-
if (outcome.retryable &&
|
|
658
|
-
await sleep(
|
|
439
|
+
if (outcome.retryable && issued < retryOptions.attempts) {
|
|
440
|
+
await sleep(computeProxyTransportRetryDelayMs(retryOptions, attempt, outcome.headers));
|
|
659
441
|
continue;
|
|
660
442
|
}
|
|
661
443
|
throw toUpstreamHttpError(outcome.status);
|
|
662
444
|
}
|
|
663
445
|
const response = outcome;
|
|
664
|
-
if (response.status >= 400 &&
|
|
665
|
-
headersOptions.throwOnHttpError !== false) {
|
|
446
|
+
if (response.status >= 400 && headersOptions.throwOnHttpError !== false) {
|
|
666
447
|
throw toUpstreamHttpError(response.status);
|
|
667
448
|
}
|
|
668
|
-
if (
|
|
449
|
+
if (issued > 1) {
|
|
669
450
|
const summary = {
|
|
670
|
-
attempts:
|
|
671
|
-
retries:
|
|
451
|
+
attempts: issued,
|
|
452
|
+
retries: issued - 1,
|
|
672
453
|
...(retryOptions.preset ? { preset: retryOptions.preset } : {}),
|
|
673
454
|
transport: "native",
|
|
674
455
|
...(lastErrorCode ? { lastErrorCode } : {}),
|
|
@@ -679,18 +460,33 @@ export function createHttpClient(baseUrl, clientOptions = {}) {
|
|
|
679
460
|
return response;
|
|
680
461
|
}
|
|
681
462
|
catch (error) {
|
|
682
|
-
|
|
683
|
-
|
|
463
|
+
if (!issuedThisAttempt)
|
|
464
|
+
issued += 1;
|
|
465
|
+
lastError = error;
|
|
466
|
+
lastErrorCode = proxyTransportRetryErrorCode(error);
|
|
467
|
+
lastStatus = proxyTransportRetryErrorStatus(error);
|
|
684
468
|
const proxyUsed = Boolean(error.proxyUsed);
|
|
685
|
-
if (attempt <
|
|
686
|
-
(
|
|
687
|
-
|
|
688
|
-
|
|
469
|
+
if (attempt < transportAttemptCap &&
|
|
470
|
+
shouldRetryProxyTransportAttempt({
|
|
471
|
+
error,
|
|
472
|
+
explicitRetry,
|
|
473
|
+
method: methodName,
|
|
474
|
+
options: retryOptions,
|
|
475
|
+
proxyUsed,
|
|
476
|
+
})) {
|
|
477
|
+
await sleep(computeProxyTransportRetryDelayMs(retryOptions, attempt));
|
|
689
478
|
continue;
|
|
690
479
|
}
|
|
691
480
|
throw error;
|
|
692
481
|
}
|
|
693
482
|
}
|
|
483
|
+
// Reached when the attempt cap is consumed without a terminal outcome —
|
|
484
|
+
// e.g. the final offsets of a partial allocation all resolved to
|
|
485
|
+
// already-tried endpoints and were skipped. Surface the last real transport
|
|
486
|
+
// failure rather than a synthetic exhaustion error.
|
|
487
|
+
if (lastError !== undefined) {
|
|
488
|
+
throw lastError;
|
|
489
|
+
}
|
|
694
490
|
throw new TransportError("HTTP retry exhausted without a terminal result", {
|
|
695
491
|
code: "retry_exhausted",
|
|
696
492
|
});
|
package/dist/runtime/insights.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { computePercentile } from "./perf";
|
|
1
|
+
import { computePercentile } from "./perf.js";
|
|
2
2
|
const LARGE_RESPONSE_BYTES = 100_000;
|
|
3
3
|
const SLOW_TRANSFORM_MS = 10;
|
|
4
4
|
const DNS_WARN_MS = 5;
|
|
@@ -188,8 +188,7 @@ function getDnsRepeatedCandidate(spans) {
|
|
|
188
188
|
if (entry.totalCount < 2) {
|
|
189
189
|
continue;
|
|
190
190
|
}
|
|
191
|
-
const avgDnsMs = entry.dnsDurations.reduce((sum, value) => sum + value, 0) /
|
|
192
|
-
entry.dnsDurations.length;
|
|
191
|
+
const avgDnsMs = entry.dnsDurations.reduce((sum, value) => sum + value, 0) / entry.dnsDurations.length;
|
|
193
192
|
const reuseRate = entry.reuseCount / entry.totalCount;
|
|
194
193
|
if (avgDnsMs > DNS_WARN_MS && reuseRate < 0.2) {
|
|
195
194
|
if (!candidate || avgDnsMs > candidate.avgDnsMs) {
|
|
@@ -218,9 +217,7 @@ function getDnsRepeatedInsight(spans) {
|
|
|
218
217
|
}
|
|
219
218
|
function getProxyOverheadInsight(spans) {
|
|
220
219
|
const requestSpans = spans.filter(isRequestSpan);
|
|
221
|
-
const proxiedDurations = requestSpans
|
|
222
|
-
.filter(hasProxy)
|
|
223
|
-
.map((span) => span.duration_ms);
|
|
220
|
+
const proxiedDurations = requestSpans.filter(hasProxy).map((span) => span.duration_ms);
|
|
224
221
|
const directDurations = requestSpans
|
|
225
222
|
.filter((span) => !hasProxy(span))
|
|
226
223
|
.map((span) => span.duration_ms);
|
|
@@ -230,10 +227,8 @@ function getProxyOverheadInsight(spans) {
|
|
|
230
227
|
message: "✓ Proxy overhead: insufficient proxy/direct samples",
|
|
231
228
|
};
|
|
232
229
|
}
|
|
233
|
-
const proxyAvg = proxiedDurations.reduce((sum, value) => sum + value, 0) /
|
|
234
|
-
|
|
235
|
-
const directAvg = directDurations.reduce((sum, value) => sum + value, 0) /
|
|
236
|
-
directDurations.length;
|
|
230
|
+
const proxyAvg = proxiedDurations.reduce((sum, value) => sum + value, 0) / proxiedDurations.length;
|
|
231
|
+
const directAvg = directDurations.reduce((sum, value) => sum + value, 0) / directDurations.length;
|
|
237
232
|
if (directAvg > 0 && proxyAvg >= directAvg * 2) {
|
|
238
233
|
return {
|
|
239
234
|
triggered: true,
|
|
@@ -254,9 +249,7 @@ function getBrowserIdleInsight(spans) {
|
|
|
254
249
|
if (waitMs !== undefined) {
|
|
255
250
|
return waitMs;
|
|
256
251
|
}
|
|
257
|
-
return span.name.toLowerCase().includes("wait")
|
|
258
|
-
? span.duration_ms
|
|
259
|
-
: undefined;
|
|
252
|
+
return span.name.toLowerCase().includes("wait") ? span.duration_ms : undefined;
|
|
260
253
|
})
|
|
261
254
|
.filter((value) => value !== undefined);
|
|
262
255
|
if (waits.length === 0) {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type { ProviderContext } from "../types";
|
|
2
|
-
import { type CreateTraceContextOptions, type TraceContext } from "./trace";
|
|
1
|
+
import type { ProviderContext } from "../types.js";
|
|
2
|
+
import { type CreateTraceContextOptions, type TraceContext } from "./trace.js";
|
|
3
3
|
export interface InstrumentationOptions extends CreateTraceContextOptions {
|
|
4
4
|
}
|
|
5
5
|
export type InstrumentedProviderContext<T extends ProviderContext> = Omit<T, "trace"> & {
|