@apifuse/provider-sdk 2.1.0-beta.9 → 2.2.0-beta.2
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 +134 -0
- package/CHANGELOG.md +61 -0
- package/README.md +21 -9
- package/SUBMISSION.md +10 -11
- package/bin/apifuse-pack-check.ts +22 -0
- package/bin/apifuse-submit-check.ts +1747 -388
- package/dist/auth-turn/auth-turn.v1.schema.json +89 -0
- package/dist/auth-turn/fixtures/invalid/empty-kind.json +4 -0
- package/dist/auth-turn/fixtures/invalid/expires-at-not-string.json +5 -0
- package/dist/auth-turn/fixtures/invalid/missing-kind.json +3 -0
- package/dist/auth-turn/fixtures/invalid/missing-turn-id.json +3 -0
- package/dist/auth-turn/fixtures/invalid/timing-unknown-field.json +7 -0
- package/dist/auth-turn/fixtures/invalid/turn-id-snake-case.json +4 -0
- package/dist/auth-turn/fixtures/invalid/unknown-top-level-field.json +5 -0
- package/dist/auth-turn/fixtures/valid/abort.json +8 -0
- package/dist/auth-turn/fixtures/valid/challenge.json +17 -0
- package/dist/auth-turn/fixtures/valid/complete.json +13 -0
- package/dist/auth-turn/fixtures/valid/form.json +14 -0
- package/dist/auth-turn/fixtures/valid/message.json +13 -0
- package/dist/auth-turn/fixtures/valid/multi_choice.json +15 -0
- package/dist/auth-turn/fixtures/valid/pending.json +5 -0
- package/dist/auth-turn/fixtures/valid/poll.json +9 -0
- package/dist/auth-turn/fixtures/valid/redirect.json +16 -0
- package/dist/auth-turn/fixtures/valid/retry.json +8 -0
- package/dist/auth-turn/fixtures/valid/unknown-kind.json +7 -0
- package/dist/auth-turn/index.d.ts +195 -0
- package/dist/auth-turn/index.js +133 -0
- package/dist/auth.d.ts +76 -0
- package/dist/auth.js +436 -0
- package/dist/ceremonies/index.js +7 -31
- package/dist/cli/create.js +45 -30
- package/dist/cli/templates/provider/.dockerignore.tpl +22 -0
- package/dist/cli/templates/provider/.gitignore.tpl +22 -0
- package/dist/cli/templates/provider/AGENTS.md.tpl +87 -0
- package/dist/cli/templates/provider/CLAUDE.md.tpl +1 -0
- package/dist/cli/templates/provider/Dockerfile.tpl +7 -0
- package/dist/cli/templates/provider/README.md.tpl +163 -0
- package/dist/cli/templates/provider/dev.ts.tpl +5 -0
- package/dist/cli/templates/provider/domain/README.md.tpl +3 -0
- package/dist/cli/templates/provider/index.test.ts.tpl +13 -0
- package/dist/cli/templates/provider/index.ts.tpl +15 -0
- package/dist/cli/templates/provider/mappers/README.md.tpl +3 -0
- package/dist/cli/templates/provider/meta.ts.tpl +7 -0
- package/dist/cli/templates/provider/operations/index.ts.tpl +5 -0
- package/dist/cli/templates/provider/operations/ping.ts.tpl +24 -0
- package/dist/cli/templates/provider/schemas/ping.ts.tpl +24 -0
- package/dist/cli/templates/provider/skills/fixtures-and-recording/SKILL.md.tpl +58 -0
- package/dist/cli/templates/provider/skills/health-checks-and-fail-closed/SKILL.md.tpl +65 -0
- package/dist/cli/templates/provider/skills/normalization-standards/SKILL.md.tpl +57 -0
- package/dist/cli/templates/provider/skills/pagination-and-counts/SKILL.md.tpl +52 -0
- package/dist/cli/templates/provider/skills/upstream-contract-verification/SKILL.md.tpl +45 -0
- package/dist/cli/templates/provider/skills/upstream-notes/README.md.tpl +13 -0
- package/dist/cli/templates/provider/start.ts.tpl +5 -0
- package/dist/cli/templates/provider/upstream/README.md.tpl +3 -0
- package/dist/contract.js +1 -0
- package/dist/define.d.ts +6 -1
- package/dist/define.js +140 -70
- package/dist/index.d.ts +3 -2
- package/dist/index.js +2 -1
- package/dist/lint.d.ts +1 -0
- package/dist/lint.js +27 -0
- package/dist/provider.d.ts +4 -2
- package/dist/provider.js +2 -1
- package/dist/runtime/auth-flow.js +2 -0
- package/dist/runtime/browser.js +203 -0
- package/dist/runtime/http.js +46 -336
- package/dist/runtime/proxy-retry-policy.d.ts +40 -0
- package/dist/runtime/proxy-retry-policy.js +326 -0
- package/dist/runtime/stealth.d.ts +5 -2
- package/dist/runtime/stealth.js +206 -206
- package/dist/server/index.d.ts +4 -0
- package/dist/server/index.js +4 -0
- package/dist/server/self-test-input-tokens.d.ts +1 -0
- package/dist/server/self-test-input-tokens.js +37 -0
- package/dist/server/self-test-redaction.d.ts +20 -0
- package/dist/server/self-test-redaction.js +70 -0
- package/dist/server/self-test-token.d.ts +30 -0
- package/dist/server/self-test-token.js +50 -0
- package/dist/server/self-test.d.ts +98 -0
- package/dist/server/self-test.js +555 -0
- package/dist/server/serve.d.ts +6 -0
- package/dist/server/serve.js +38 -10
- package/dist/server/types.d.ts +1 -0
- package/dist/server/types.js +1 -0
- package/dist/testing/run.js +5 -1
- package/dist/types.d.ts +150 -0
- package/package.json +9 -2
- package/src/auth-turn/auth-turn.v1.schema.json +89 -0
- package/src/auth-turn/fixtures/invalid/empty-kind.json +4 -0
- package/src/auth-turn/fixtures/invalid/expires-at-not-string.json +5 -0
- package/src/auth-turn/fixtures/invalid/missing-kind.json +3 -0
- package/src/auth-turn/fixtures/invalid/missing-turn-id.json +3 -0
- package/src/auth-turn/fixtures/invalid/timing-unknown-field.json +7 -0
- package/src/auth-turn/fixtures/invalid/turn-id-snake-case.json +4 -0
- package/src/auth-turn/fixtures/invalid/unknown-top-level-field.json +5 -0
- package/src/auth-turn/fixtures/valid/abort.json +8 -0
- package/src/auth-turn/fixtures/valid/challenge.json +17 -0
- package/src/auth-turn/fixtures/valid/complete.json +13 -0
- package/src/auth-turn/fixtures/valid/form.json +14 -0
- package/src/auth-turn/fixtures/valid/message.json +13 -0
- package/src/auth-turn/fixtures/valid/multi_choice.json +15 -0
- package/src/auth-turn/fixtures/valid/pending.json +5 -0
- package/src/auth-turn/fixtures/valid/poll.json +9 -0
- package/src/auth-turn/fixtures/valid/redirect.json +16 -0
- package/src/auth-turn/fixtures/valid/retry.json +8 -0
- package/src/auth-turn/fixtures/valid/unknown-kind.json +7 -0
- package/src/auth-turn/index.ts +177 -0
- package/src/auth.ts +786 -0
- package/src/ceremonies/index.ts +9 -43
- package/src/cli/create.ts +60 -97
- package/src/cli/templates/provider/AGENTS.md.tpl +87 -0
- package/src/cli/templates/provider/CLAUDE.md.tpl +1 -0
- package/src/cli/templates/provider/README.md.tpl +7 -4
- package/src/cli/templates/provider/skills/fixtures-and-recording/SKILL.md.tpl +58 -0
- package/src/cli/templates/provider/skills/health-checks-and-fail-closed/SKILL.md.tpl +65 -0
- package/src/cli/templates/provider/skills/normalization-standards/SKILL.md.tpl +57 -0
- package/src/cli/templates/provider/skills/pagination-and-counts/SKILL.md.tpl +52 -0
- package/src/cli/templates/provider/skills/upstream-contract-verification/SKILL.md.tpl +45 -0
- package/src/cli/templates/provider/skills/upstream-notes/README.md.tpl +13 -0
- package/src/contract.ts +1 -0
- package/src/define.ts +198 -71
- package/src/index.ts +16 -0
- package/src/lint.ts +33 -0
- package/src/provider.ts +27 -0
- package/src/runtime/auth-flow.ts +2 -0
- package/src/runtime/browser.ts +293 -1
- package/src/runtime/http.ts +84 -530
- package/src/runtime/proxy-retry-policy.ts +469 -0
- package/src/runtime/stealth.ts +269 -353
- package/src/server/index.ts +36 -0
- package/src/server/self-test-input-tokens.ts +46 -0
- package/src/server/self-test-redaction.ts +97 -0
- package/src/server/self-test-token.ts +70 -0
- package/src/server/self-test.ts +725 -0
- package/src/server/serve.ts +75 -6
- package/src/server/types.ts +1 -0
- package/src/testing/run.ts +9 -1
- package/src/types.ts +186 -0
package/src/runtime/http.ts
CHANGED
|
@@ -1,17 +1,11 @@
|
|
|
1
1
|
import type { ProxyResolutionOptions } from "../config/loader";
|
|
2
2
|
import { resolveProxyConfigAsync } from "../config/loader";
|
|
3
3
|
import { ProviderError, TransportError } from "../errors";
|
|
4
|
-
import {
|
|
5
|
-
parseSseStream,
|
|
6
|
-
readableBytes,
|
|
7
|
-
readableLines,
|
|
8
|
-
readableTextChunks,
|
|
9
|
-
} from "../stream";
|
|
4
|
+
import { parseSseStream, readableBytes, readableLines, readableTextChunks } from "../stream";
|
|
10
5
|
import type {
|
|
11
6
|
HttpClient,
|
|
12
7
|
HttpMethod,
|
|
13
8
|
HttpResponse,
|
|
14
|
-
HttpRetryOptions,
|
|
15
9
|
HttpRetrySummary,
|
|
16
10
|
HttpStreamResponse,
|
|
17
11
|
RequestOptions,
|
|
@@ -19,12 +13,16 @@ import type {
|
|
|
19
13
|
SseMessage,
|
|
20
14
|
} from "../types";
|
|
21
15
|
import {
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
16
|
+
computeProxyAttemptIndex,
|
|
17
|
+
computeProxyTransportRetryDelayMs,
|
|
18
|
+
createDefaultProxyTransportRetryOptions,
|
|
19
|
+
isProxyTransportRetryMethod,
|
|
20
|
+
normalizeProxyTransportRetryOptions,
|
|
21
|
+
proxyTransportRetryErrorCode,
|
|
22
|
+
proxyTransportRetryErrorStatus,
|
|
23
|
+
shouldRetryProxyTransportAttempt,
|
|
24
|
+
validateUnsafeProxyTransportRetryMethods,
|
|
25
|
+
} from "./proxy-retry-policy";
|
|
28
26
|
import { appendQueryParams, normalizeHttpRequestBody } from "./request-options";
|
|
29
27
|
|
|
30
28
|
const DEFAULT_HTTP_BASE_URL = "http://localhost";
|
|
@@ -35,447 +33,24 @@ export type HttpClientOptions = ProxyResolutionOptions & {
|
|
|
35
33
|
onRetrySummary?: (summary: HttpRetrySummary) => void;
|
|
36
34
|
};
|
|
37
35
|
|
|
38
|
-
type NormalizedRetryOptions = Required<
|
|
39
|
-
Pick<
|
|
40
|
-
HttpRetryOptions,
|
|
41
|
-
| "attempts"
|
|
42
|
-
| "delayStrategy"
|
|
43
|
-
| "baseDelayMs"
|
|
44
|
-
| "maxDelayMs"
|
|
45
|
-
| "jitter"
|
|
46
|
-
| "retryAfter"
|
|
47
|
-
| "unsafeMethodPolicy"
|
|
48
|
-
>
|
|
49
|
-
> & {
|
|
50
|
-
preset?: HttpRetryPreset;
|
|
51
|
-
methods: readonly string[];
|
|
52
|
-
statusCodes: readonly number[];
|
|
53
|
-
errorCodes: readonly string[];
|
|
54
|
-
};
|
|
55
|
-
|
|
56
36
|
type HttpStatusOutcome = {
|
|
57
37
|
kind: "http-status";
|
|
58
38
|
status: number;
|
|
59
39
|
headers: Record<string, string>;
|
|
60
40
|
retryable: boolean;
|
|
41
|
+
proxyUsed: boolean;
|
|
61
42
|
};
|
|
62
43
|
|
|
44
|
+
type NativeHttpAttemptOutcome = HttpResponse | HttpStatusOutcome;
|
|
45
|
+
|
|
46
|
+
type NativeHttpAttemptError = TransportError & { proxyUsed?: boolean };
|
|
47
|
+
|
|
63
48
|
function isHttpStatusOutcome(
|
|
64
49
|
outcome: HttpResponse | HttpStatusOutcome,
|
|
65
50
|
): outcome is HttpStatusOutcome {
|
|
66
51
|
return "kind" in outcome && outcome.kind === "http-status";
|
|
67
52
|
}
|
|
68
53
|
|
|
69
|
-
const DEFAULT_RETRY_METHODS = ["GET", "HEAD", "OPTIONS"] as const;
|
|
70
|
-
const DEFAULT_RETRY_ERROR_CODES = [
|
|
71
|
-
"transport_network_error",
|
|
72
|
-
"transport_timeout",
|
|
73
|
-
] as const;
|
|
74
|
-
const SAFE_RETRY_STATUS_CODES = [408, 429, 500, 502, 503, 504] as const;
|
|
75
|
-
const RATE_LIMIT_RETRY_STATUS_CODES = [429, 503] as const;
|
|
76
|
-
const KNOWN_RETRY_METHODS = new Set([
|
|
77
|
-
"GET",
|
|
78
|
-
"HEAD",
|
|
79
|
-
"POST",
|
|
80
|
-
"PUT",
|
|
81
|
-
"DELETE",
|
|
82
|
-
"OPTIONS",
|
|
83
|
-
"TRACE",
|
|
84
|
-
"PATCH",
|
|
85
|
-
]);
|
|
86
|
-
const UNSAFE_RETRY_METHODS = new Set([
|
|
87
|
-
"POST",
|
|
88
|
-
"PUT",
|
|
89
|
-
"PATCH",
|
|
90
|
-
"DELETE",
|
|
91
|
-
"TRACE",
|
|
92
|
-
]);
|
|
93
|
-
const MAX_RETRY_ATTEMPTS = 8;
|
|
94
|
-
const MAX_RETRY_DELAY_MS = 30_000;
|
|
95
|
-
|
|
96
|
-
function hasOwnValue<T extends string>(
|
|
97
|
-
values: Record<string, T>,
|
|
98
|
-
value: unknown,
|
|
99
|
-
): value is T {
|
|
100
|
-
if (typeof value !== "string") return false;
|
|
101
|
-
return Object.values(values).some((candidate) => candidate === value);
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
function createInvalidRetryPolicyError(message: string): ProviderError {
|
|
105
|
-
return new ProviderError(message, { code: "retry_invalid_policy" });
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
function createRetryOptions(preset: HttpRetryPreset): NormalizedRetryOptions {
|
|
109
|
-
switch (preset) {
|
|
110
|
-
case HttpRetryPreset.Off:
|
|
111
|
-
return {
|
|
112
|
-
preset,
|
|
113
|
-
attempts: 1,
|
|
114
|
-
methods: DEFAULT_RETRY_METHODS,
|
|
115
|
-
statusCodes: [],
|
|
116
|
-
errorCodes: DEFAULT_RETRY_ERROR_CODES,
|
|
117
|
-
delayStrategy: HttpRetryDelayStrategy.Exponential,
|
|
118
|
-
baseDelayMs: 100,
|
|
119
|
-
maxDelayMs: 1_000,
|
|
120
|
-
jitter: HttpRetryJitter.Full,
|
|
121
|
-
retryAfter: HttpRetryAfterPolicy.Ignore,
|
|
122
|
-
unsafeMethodPolicy: HttpRetryUnsafeMethodPolicy.Reject,
|
|
123
|
-
};
|
|
124
|
-
case HttpRetryPreset.SafeRead:
|
|
125
|
-
return {
|
|
126
|
-
preset,
|
|
127
|
-
attempts: 3,
|
|
128
|
-
methods: DEFAULT_RETRY_METHODS,
|
|
129
|
-
statusCodes: SAFE_RETRY_STATUS_CODES,
|
|
130
|
-
errorCodes: DEFAULT_RETRY_ERROR_CODES,
|
|
131
|
-
delayStrategy: HttpRetryDelayStrategy.Exponential,
|
|
132
|
-
baseDelayMs: 100,
|
|
133
|
-
maxDelayMs: 2_000,
|
|
134
|
-
jitter: HttpRetryJitter.Full,
|
|
135
|
-
retryAfter: HttpRetryAfterPolicy.Cap,
|
|
136
|
-
unsafeMethodPolicy: HttpRetryUnsafeMethodPolicy.Reject,
|
|
137
|
-
};
|
|
138
|
-
case HttpRetryPreset.AggressiveRead:
|
|
139
|
-
return {
|
|
140
|
-
preset,
|
|
141
|
-
attempts: 4,
|
|
142
|
-
methods: DEFAULT_RETRY_METHODS,
|
|
143
|
-
statusCodes: SAFE_RETRY_STATUS_CODES,
|
|
144
|
-
errorCodes: DEFAULT_RETRY_ERROR_CODES,
|
|
145
|
-
delayStrategy: HttpRetryDelayStrategy.Exponential,
|
|
146
|
-
baseDelayMs: 150,
|
|
147
|
-
maxDelayMs: 5_000,
|
|
148
|
-
jitter: HttpRetryJitter.Full,
|
|
149
|
-
retryAfter: HttpRetryAfterPolicy.Cap,
|
|
150
|
-
unsafeMethodPolicy: HttpRetryUnsafeMethodPolicy.Reject,
|
|
151
|
-
};
|
|
152
|
-
case HttpRetryPreset.RateLimitAware:
|
|
153
|
-
return {
|
|
154
|
-
preset,
|
|
155
|
-
attempts: 3,
|
|
156
|
-
methods: DEFAULT_RETRY_METHODS,
|
|
157
|
-
statusCodes: RATE_LIMIT_RETRY_STATUS_CODES,
|
|
158
|
-
errorCodes: ["transport_timeout"],
|
|
159
|
-
delayStrategy: HttpRetryDelayStrategy.Exponential,
|
|
160
|
-
baseDelayMs: 250,
|
|
161
|
-
maxDelayMs: 5_000,
|
|
162
|
-
jitter: HttpRetryJitter.Equal,
|
|
163
|
-
retryAfter: HttpRetryAfterPolicy.Respect,
|
|
164
|
-
unsafeMethodPolicy: HttpRetryUnsafeMethodPolicy.Reject,
|
|
165
|
-
};
|
|
166
|
-
case HttpRetryPreset.TransportTransient:
|
|
167
|
-
return {
|
|
168
|
-
preset,
|
|
169
|
-
attempts: 3,
|
|
170
|
-
methods: DEFAULT_RETRY_METHODS,
|
|
171
|
-
statusCodes: [],
|
|
172
|
-
errorCodes: DEFAULT_RETRY_ERROR_CODES,
|
|
173
|
-
delayStrategy: HttpRetryDelayStrategy.Exponential,
|
|
174
|
-
baseDelayMs: 100,
|
|
175
|
-
maxDelayMs: 1_000,
|
|
176
|
-
jitter: HttpRetryJitter.Full,
|
|
177
|
-
retryAfter: HttpRetryAfterPolicy.Ignore,
|
|
178
|
-
unsafeMethodPolicy: HttpRetryUnsafeMethodPolicy.Reject,
|
|
179
|
-
};
|
|
180
|
-
}
|
|
181
|
-
throw createInvalidRetryPolicyError(`Unknown HTTP retry preset: ${preset}`);
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
function clampPositiveInteger(
|
|
185
|
-
value: number | undefined,
|
|
186
|
-
fallback: number,
|
|
187
|
-
max: number,
|
|
188
|
-
): number {
|
|
189
|
-
if (value === undefined) return fallback;
|
|
190
|
-
if (!Number.isFinite(value) || value < 1) return fallback;
|
|
191
|
-
return Math.min(Math.floor(value), max);
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
function clampDelay(value: number | undefined, fallback: number): number {
|
|
195
|
-
if (value === undefined) return fallback;
|
|
196
|
-
if (!Number.isFinite(value) || value < 0) return fallback;
|
|
197
|
-
return Math.min(Math.floor(value), MAX_RETRY_DELAY_MS);
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
function normalizeRetryOptions(
|
|
201
|
-
retry: RequestOptions["retry"],
|
|
202
|
-
): NormalizedRetryOptions | undefined {
|
|
203
|
-
if (retry === undefined || retry === false) {
|
|
204
|
-
return undefined;
|
|
205
|
-
}
|
|
206
|
-
if (retry === true) {
|
|
207
|
-
return createRetryOptions(HttpRetryPreset.TransportTransient);
|
|
208
|
-
}
|
|
209
|
-
if (typeof retry === "string") {
|
|
210
|
-
if (!hasOwnValue(HttpRetryPreset, retry)) {
|
|
211
|
-
throw createInvalidRetryPolicyError(
|
|
212
|
-
`Unknown HTTP retry preset: ${retry}`,
|
|
213
|
-
);
|
|
214
|
-
}
|
|
215
|
-
return createRetryOptions(retry);
|
|
216
|
-
}
|
|
217
|
-
if (typeof retry !== "object" || retry === null) {
|
|
218
|
-
throw createInvalidRetryPolicyError("HTTP retry policy must be an object");
|
|
219
|
-
}
|
|
220
|
-
if (Array.isArray(retry)) {
|
|
221
|
-
throw createInvalidRetryPolicyError(
|
|
222
|
-
"HTTP retry policy must be a plain object",
|
|
223
|
-
);
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
validateRetryOptionsShape(retry);
|
|
227
|
-
const base = createRetryOptions(
|
|
228
|
-
retry.preset ?? HttpRetryPreset.TransportTransient,
|
|
229
|
-
);
|
|
230
|
-
const maxDelayMs = clampDelay(retry.maxDelayMs, base.maxDelayMs);
|
|
231
|
-
const normalized: NormalizedRetryOptions = {
|
|
232
|
-
preset: retry.preset ?? base.preset,
|
|
233
|
-
attempts: clampPositiveInteger(
|
|
234
|
-
retry.attempts,
|
|
235
|
-
base.attempts,
|
|
236
|
-
MAX_RETRY_ATTEMPTS,
|
|
237
|
-
),
|
|
238
|
-
methods:
|
|
239
|
-
retry.methods?.map((method) => method.toUpperCase()) ?? base.methods,
|
|
240
|
-
statusCodes:
|
|
241
|
-
retry.statusCodes?.filter((status) => Number.isInteger(status)) ??
|
|
242
|
-
base.statusCodes,
|
|
243
|
-
errorCodes: retry.errorCodes ?? base.errorCodes,
|
|
244
|
-
delayStrategy: retry.delayStrategy ?? base.delayStrategy,
|
|
245
|
-
baseDelayMs: clampDelay(retry.baseDelayMs, base.baseDelayMs),
|
|
246
|
-
maxDelayMs,
|
|
247
|
-
jitter: retry.jitter ?? base.jitter,
|
|
248
|
-
retryAfter: retry.retryAfter ?? base.retryAfter,
|
|
249
|
-
unsafeMethodPolicy: retry.unsafeMethodPolicy ?? base.unsafeMethodPolicy,
|
|
250
|
-
};
|
|
251
|
-
|
|
252
|
-
return normalized;
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
function validateRetryOptionsShape(retry: HttpRetryOptions): void {
|
|
256
|
-
if (
|
|
257
|
-
retry.preset !== undefined &&
|
|
258
|
-
!hasOwnValue(HttpRetryPreset, retry.preset)
|
|
259
|
-
) {
|
|
260
|
-
throw createInvalidRetryPolicyError(
|
|
261
|
-
`Unknown HTTP retry preset: ${String(retry.preset)}`,
|
|
262
|
-
);
|
|
263
|
-
}
|
|
264
|
-
if (
|
|
265
|
-
retry.delayStrategy !== undefined &&
|
|
266
|
-
!hasOwnValue(HttpRetryDelayStrategy, retry.delayStrategy)
|
|
267
|
-
) {
|
|
268
|
-
throw createInvalidRetryPolicyError(
|
|
269
|
-
`Unknown HTTP retry delay strategy: ${String(retry.delayStrategy)}`,
|
|
270
|
-
);
|
|
271
|
-
}
|
|
272
|
-
if (
|
|
273
|
-
retry.jitter !== undefined &&
|
|
274
|
-
!hasOwnValue(HttpRetryJitter, retry.jitter)
|
|
275
|
-
) {
|
|
276
|
-
throw createInvalidRetryPolicyError(
|
|
277
|
-
`Unknown HTTP retry jitter policy: ${String(retry.jitter)}`,
|
|
278
|
-
);
|
|
279
|
-
}
|
|
280
|
-
if (
|
|
281
|
-
retry.retryAfter !== undefined &&
|
|
282
|
-
!hasOwnValue(HttpRetryAfterPolicy, retry.retryAfter)
|
|
283
|
-
) {
|
|
284
|
-
throw createInvalidRetryPolicyError(
|
|
285
|
-
`Unknown HTTP retry-after policy: ${String(retry.retryAfter)}`,
|
|
286
|
-
);
|
|
287
|
-
}
|
|
288
|
-
if (
|
|
289
|
-
retry.unsafeMethodPolicy !== undefined &&
|
|
290
|
-
!hasOwnValue(HttpRetryUnsafeMethodPolicy, retry.unsafeMethodPolicy)
|
|
291
|
-
) {
|
|
292
|
-
throw createInvalidRetryPolicyError(
|
|
293
|
-
`Unknown HTTP retry unsafe method policy: ${String(retry.unsafeMethodPolicy)}`,
|
|
294
|
-
);
|
|
295
|
-
}
|
|
296
|
-
if (retry.methods !== undefined) {
|
|
297
|
-
if (!Array.isArray(retry.methods)) {
|
|
298
|
-
throw createInvalidRetryPolicyError(
|
|
299
|
-
"HTTP retry methods must be an array",
|
|
300
|
-
);
|
|
301
|
-
}
|
|
302
|
-
const nonStringMethods = retry.methods.filter(
|
|
303
|
-
(method) => typeof method !== "string",
|
|
304
|
-
);
|
|
305
|
-
if (nonStringMethods.length > 0) {
|
|
306
|
-
throw createInvalidRetryPolicyError(
|
|
307
|
-
"HTTP retry methods must contain only strings",
|
|
308
|
-
);
|
|
309
|
-
}
|
|
310
|
-
const unknownMethods = retry.methods
|
|
311
|
-
.map((method) => method.toUpperCase())
|
|
312
|
-
.filter((method) => !KNOWN_RETRY_METHODS.has(method));
|
|
313
|
-
if (unknownMethods.length > 0) {
|
|
314
|
-
throw createInvalidRetryPolicyError(
|
|
315
|
-
`Unknown HTTP retry method(s): ${unknownMethods.join(", ")}`,
|
|
316
|
-
);
|
|
317
|
-
}
|
|
318
|
-
}
|
|
319
|
-
if (retry.statusCodes !== undefined) {
|
|
320
|
-
if (!Array.isArray(retry.statusCodes)) {
|
|
321
|
-
throw createInvalidRetryPolicyError(
|
|
322
|
-
"HTTP retry statusCodes must be an array",
|
|
323
|
-
);
|
|
324
|
-
}
|
|
325
|
-
const invalidStatusCodes = retry.statusCodes.filter(
|
|
326
|
-
(status) =>
|
|
327
|
-
!Number.isInteger(status) ||
|
|
328
|
-
Number(status) < 100 ||
|
|
329
|
-
Number(status) > 599,
|
|
330
|
-
);
|
|
331
|
-
if (invalidStatusCodes.length > 0) {
|
|
332
|
-
throw createInvalidRetryPolicyError(
|
|
333
|
-
"HTTP retry statusCodes must contain HTTP status integers in [100, 599]",
|
|
334
|
-
);
|
|
335
|
-
}
|
|
336
|
-
}
|
|
337
|
-
if (retry.errorCodes !== undefined) {
|
|
338
|
-
if (!Array.isArray(retry.errorCodes)) {
|
|
339
|
-
throw createInvalidRetryPolicyError(
|
|
340
|
-
"HTTP retry errorCodes must be an array",
|
|
341
|
-
);
|
|
342
|
-
}
|
|
343
|
-
const nonStringErrorCodes = retry.errorCodes.filter(
|
|
344
|
-
(errorCode) => typeof errorCode !== "string",
|
|
345
|
-
);
|
|
346
|
-
if (nonStringErrorCodes.length > 0) {
|
|
347
|
-
throw createInvalidRetryPolicyError(
|
|
348
|
-
"HTTP retry errorCodes must contain only strings",
|
|
349
|
-
);
|
|
350
|
-
}
|
|
351
|
-
}
|
|
352
|
-
if (
|
|
353
|
-
retry.preset === HttpRetryPreset.Off &&
|
|
354
|
-
((retry.attempts !== undefined && retry.attempts > 1) ||
|
|
355
|
-
(retry.statusCodes !== undefined && retry.statusCodes.length > 0))
|
|
356
|
-
) {
|
|
357
|
-
throw createInvalidRetryPolicyError(
|
|
358
|
-
"HTTP retry preset off cannot be combined with retry-enabling overrides",
|
|
359
|
-
);
|
|
360
|
-
}
|
|
361
|
-
}
|
|
362
|
-
|
|
363
|
-
function validateUnsafeRetryMethods(options: NormalizedRetryOptions): void {
|
|
364
|
-
if (
|
|
365
|
-
options.unsafeMethodPolicy ===
|
|
366
|
-
HttpRetryUnsafeMethodPolicy.AllowExplicitUnsafe
|
|
367
|
-
) {
|
|
368
|
-
return;
|
|
369
|
-
}
|
|
370
|
-
const unsafeMethods = options.methods.filter((method) =>
|
|
371
|
-
UNSAFE_RETRY_METHODS.has(method.toUpperCase()),
|
|
372
|
-
);
|
|
373
|
-
if (unsafeMethods.length === 0) return;
|
|
374
|
-
|
|
375
|
-
throw new ProviderError(
|
|
376
|
-
`HTTP retry methods include unsafe method(s): ${unsafeMethods.join(", ")}`,
|
|
377
|
-
{ code: "retry_unsafe_method" },
|
|
378
|
-
);
|
|
379
|
-
}
|
|
380
|
-
|
|
381
|
-
function isMethodRetryable(
|
|
382
|
-
method: HttpMethod,
|
|
383
|
-
options: NormalizedRetryOptions,
|
|
384
|
-
): boolean {
|
|
385
|
-
return options.methods
|
|
386
|
-
.map((allowedMethod) => allowedMethod.toUpperCase())
|
|
387
|
-
.includes(method.toUpperCase());
|
|
388
|
-
}
|
|
389
|
-
|
|
390
|
-
function retryErrorCode(error: unknown): string | undefined {
|
|
391
|
-
if (error instanceof TransportError) {
|
|
392
|
-
return error.code;
|
|
393
|
-
}
|
|
394
|
-
if (error && typeof error === "object" && "code" in error) {
|
|
395
|
-
const code = Reflect.get(error, "code");
|
|
396
|
-
return typeof code === "string" ? code : undefined;
|
|
397
|
-
}
|
|
398
|
-
return undefined;
|
|
399
|
-
}
|
|
400
|
-
|
|
401
|
-
function retryErrorStatus(error: unknown): number | undefined {
|
|
402
|
-
if (error instanceof TransportError) {
|
|
403
|
-
return error.status ?? error.upstreamStatus;
|
|
404
|
-
}
|
|
405
|
-
return undefined;
|
|
406
|
-
}
|
|
407
|
-
|
|
408
|
-
function shouldRetryTransportError(
|
|
409
|
-
error: unknown,
|
|
410
|
-
options: NormalizedRetryOptions,
|
|
411
|
-
): boolean {
|
|
412
|
-
const code = retryErrorCode(error);
|
|
413
|
-
return Boolean(code && options.errorCodes.includes(code));
|
|
414
|
-
}
|
|
415
|
-
|
|
416
|
-
function retryAfterHeader(headers: Record<string, string>): string | undefined {
|
|
417
|
-
for (const [name, value] of Object.entries(headers)) {
|
|
418
|
-
if (name.toLowerCase() === "retry-after") return value;
|
|
419
|
-
}
|
|
420
|
-
return undefined;
|
|
421
|
-
}
|
|
422
|
-
|
|
423
|
-
function parseRetryAfterMs(
|
|
424
|
-
headers: Record<string, string>,
|
|
425
|
-
now: number = Date.now(),
|
|
426
|
-
): number | undefined {
|
|
427
|
-
const value = retryAfterHeader(headers);
|
|
428
|
-
if (!value) return undefined;
|
|
429
|
-
const seconds = Number(value);
|
|
430
|
-
if (Number.isFinite(seconds)) {
|
|
431
|
-
return Math.max(0, Math.floor(seconds * 1_000));
|
|
432
|
-
}
|
|
433
|
-
const dateMs = Date.parse(value);
|
|
434
|
-
if (!Number.isNaN(dateMs)) {
|
|
435
|
-
return Math.max(0, dateMs - now);
|
|
436
|
-
}
|
|
437
|
-
return undefined;
|
|
438
|
-
}
|
|
439
|
-
|
|
440
|
-
function computeRetryDelayMs(
|
|
441
|
-
options: NormalizedRetryOptions,
|
|
442
|
-
attemptIndex: number,
|
|
443
|
-
headers?: Record<string, string>,
|
|
444
|
-
): number {
|
|
445
|
-
const multiplier =
|
|
446
|
-
options.delayStrategy === HttpRetryDelayStrategy.Exponential
|
|
447
|
-
? 2 ** Math.max(0, attemptIndex - 1)
|
|
448
|
-
: 1;
|
|
449
|
-
const configuredDelay = Math.min(
|
|
450
|
-
options.baseDelayMs * multiplier,
|
|
451
|
-
options.maxDelayMs,
|
|
452
|
-
);
|
|
453
|
-
const retryAfterMs =
|
|
454
|
-
options.retryAfter === HttpRetryAfterPolicy.Ignore
|
|
455
|
-
? undefined
|
|
456
|
-
: headers
|
|
457
|
-
? parseRetryAfterMs(headers)
|
|
458
|
-
: undefined;
|
|
459
|
-
if (retryAfterMs !== undefined) {
|
|
460
|
-
const boundedRetryAfterMs = Math.min(retryAfterMs, options.maxDelayMs);
|
|
461
|
-
if (options.retryAfter === HttpRetryAfterPolicy.Cap) {
|
|
462
|
-
return Math.min(boundedRetryAfterMs, configuredDelay);
|
|
463
|
-
}
|
|
464
|
-
return boundedRetryAfterMs;
|
|
465
|
-
}
|
|
466
|
-
|
|
467
|
-
switch (options.jitter) {
|
|
468
|
-
case HttpRetryJitter.None:
|
|
469
|
-
return configuredDelay;
|
|
470
|
-
case HttpRetryJitter.Equal:
|
|
471
|
-
return Math.floor(
|
|
472
|
-
configuredDelay / 2 + Math.random() * (configuredDelay / 2),
|
|
473
|
-
);
|
|
474
|
-
case HttpRetryJitter.Full:
|
|
475
|
-
return Math.floor(Math.random() * configuredDelay);
|
|
476
|
-
}
|
|
477
|
-
}
|
|
478
|
-
|
|
479
54
|
async function sleep(ms: number): Promise<void> {
|
|
480
55
|
if (ms <= 0) return;
|
|
481
56
|
await new Promise((resolve) => setTimeout(resolve, ms));
|
|
@@ -500,9 +75,7 @@ function withClientHeaders(
|
|
|
500
75
|
body: unknown,
|
|
501
76
|
): RequestOptions {
|
|
502
77
|
const headers: Record<string, string> = {
|
|
503
|
-
...(clientOptions.userAgent
|
|
504
|
-
? { "User-Agent": clientOptions.userAgent }
|
|
505
|
-
: {}),
|
|
78
|
+
...(clientOptions.userAgent ? { "User-Agent": clientOptions.userAgent } : {}),
|
|
506
79
|
...options?.headers,
|
|
507
80
|
};
|
|
508
81
|
|
|
@@ -517,10 +90,7 @@ function withClientHeaders(
|
|
|
517
90
|
}
|
|
518
91
|
|
|
519
92
|
function parseHttpData(body: string, headers: Record<string, string>): unknown {
|
|
520
|
-
const contentType =
|
|
521
|
-
headers["content-type"] ??
|
|
522
|
-
headers["Content-Type"] ??
|
|
523
|
-
headers["CONTENT-TYPE"];
|
|
93
|
+
const contentType = headers["content-type"] ?? headers["Content-Type"] ?? headers["CONTENT-TYPE"];
|
|
524
94
|
|
|
525
95
|
if (contentType?.includes("application/json")) {
|
|
526
96
|
return body ? JSON.parse(body) : null;
|
|
@@ -582,7 +152,8 @@ function toHttpTransportError(error: unknown): TransportError {
|
|
|
582
152
|
|
|
583
153
|
async function toNativeHttpResponse(response: Response): Promise<HttpResponse> {
|
|
584
154
|
const headers = Object.fromEntries(response.headers.entries());
|
|
585
|
-
const
|
|
155
|
+
const bodyBytes = new Uint8Array(await response.arrayBuffer());
|
|
156
|
+
const rawText = new TextDecoder().decode(bodyBytes);
|
|
586
157
|
const data = parseHttpData(rawText, headers);
|
|
587
158
|
|
|
588
159
|
return {
|
|
@@ -590,18 +161,15 @@ async function toNativeHttpResponse(response: Response): Promise<HttpResponse> {
|
|
|
590
161
|
headers,
|
|
591
162
|
json: async <T = unknown>() => {
|
|
592
163
|
const contentType =
|
|
593
|
-
headers["content-type"] ??
|
|
594
|
-
|
|
595
|
-
headers["CONTENT-TYPE"];
|
|
596
|
-
return parseJson<T>(
|
|
597
|
-
contentType?.includes("application/json") && !rawText
|
|
598
|
-
? "null"
|
|
599
|
-
: rawText,
|
|
600
|
-
);
|
|
164
|
+
headers["content-type"] ?? headers["Content-Type"] ?? headers["CONTENT-TYPE"];
|
|
165
|
+
return parseJson<T>(contentType?.includes("application/json") && !rawText ? "null" : rawText);
|
|
601
166
|
},
|
|
602
167
|
ok: response.status >= 200 && response.status < 300,
|
|
603
168
|
status: response.status,
|
|
604
169
|
text: async () => rawText,
|
|
170
|
+
arrayBuffer: async () =>
|
|
171
|
+
bodyBytes.buffer.slice(bodyBytes.byteOffset, bodyBytes.byteOffset + bodyBytes.byteLength),
|
|
172
|
+
bytes: async () => bodyBytes.slice(0),
|
|
605
173
|
};
|
|
606
174
|
}
|
|
607
175
|
|
|
@@ -613,9 +181,7 @@ async function drainNativeResponseBody(response: Response): Promise<void> {
|
|
|
613
181
|
}
|
|
614
182
|
}
|
|
615
183
|
|
|
616
|
-
function requireNativeResponseBody(
|
|
617
|
-
response: Response,
|
|
618
|
-
): ReadableStream<Uint8Array> {
|
|
184
|
+
function requireNativeResponseBody(response: Response): ReadableStream<Uint8Array> {
|
|
619
185
|
if (!response.body) {
|
|
620
186
|
throw new TransportError("Response body stream is unavailable", {
|
|
621
187
|
code: "transport_stream_unavailable",
|
|
@@ -678,12 +244,18 @@ async function resolveNativeProxy(
|
|
|
678
244
|
options: RequestOptions,
|
|
679
245
|
clientOptions: HttpClientOptions,
|
|
680
246
|
warn: (message: string) => void,
|
|
247
|
+
proxyAttemptOffset = 0,
|
|
681
248
|
): Promise<string | undefined> {
|
|
682
249
|
const resolvedProxy = await resolveProxyConfigAsync({
|
|
683
250
|
proxy: options.proxy ?? clientOptions.proxy,
|
|
684
251
|
upstream: clientOptions.upstream,
|
|
685
252
|
apifuseConfig: clientOptions.apifuseConfig,
|
|
253
|
+
proxyPolicy: clientOptions.proxyPolicy,
|
|
686
254
|
affinityKey: clientOptions.affinityKey,
|
|
255
|
+
proxyAttempt: computeProxyAttemptIndex({
|
|
256
|
+
baseProxyAttempt: clientOptions.proxyAttempt,
|
|
257
|
+
retryAttemptOffset: proxyAttemptOffset,
|
|
258
|
+
}),
|
|
687
259
|
telemetry: clientOptions.telemetry,
|
|
688
260
|
});
|
|
689
261
|
if (resolvedProxy.shouldWarn) {
|
|
@@ -703,9 +275,7 @@ function assertNoHttpTransportOverrides(options: RequestOptions): void {
|
|
|
703
275
|
}
|
|
704
276
|
}
|
|
705
277
|
|
|
706
|
-
function normalizeNativeFetchBody(
|
|
707
|
-
body: unknown,
|
|
708
|
-
): string | ArrayBuffer | undefined {
|
|
278
|
+
function normalizeNativeFetchBody(body: unknown): string | ArrayBuffer | undefined {
|
|
709
279
|
const normalized = normalizeHttpRequestBody(body);
|
|
710
280
|
if (!Buffer.isBuffer(normalized)) {
|
|
711
281
|
return normalized;
|
|
@@ -723,18 +293,17 @@ async function fetchNativeHttp(
|
|
|
723
293
|
clientOptions: HttpClientOptions,
|
|
724
294
|
warn: (message: string) => void,
|
|
725
295
|
statusRetryCodes?: readonly number[],
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
options.params,
|
|
730
|
-
);
|
|
296
|
+
proxyAttemptOffset = 0,
|
|
297
|
+
): Promise<NativeHttpAttemptOutcome> {
|
|
298
|
+
const requestUrl = appendQueryParams(resolveHttpUrl(baseUrl, url), options.params);
|
|
731
299
|
const controller = options.timeout ? new AbortController() : undefined;
|
|
732
300
|
const timeoutHandle = options.timeout
|
|
733
301
|
? setTimeout(() => controller?.abort(), options.timeout)
|
|
734
302
|
: undefined;
|
|
735
303
|
|
|
304
|
+
let proxy: string | undefined;
|
|
736
305
|
try {
|
|
737
|
-
|
|
306
|
+
proxy = await resolveNativeProxy(options, clientOptions, warn, proxyAttemptOffset);
|
|
738
307
|
const requestInit: NativeFetchInit = {
|
|
739
308
|
headers: options.headers,
|
|
740
309
|
method,
|
|
@@ -756,18 +325,16 @@ async function fetchNativeHttp(
|
|
|
756
325
|
status: response.status,
|
|
757
326
|
headers,
|
|
758
327
|
retryable: statusRetryCodes.includes(response.status),
|
|
328
|
+
proxyUsed: Boolean(proxy),
|
|
759
329
|
};
|
|
760
330
|
}
|
|
761
331
|
|
|
762
332
|
if (response.status >= 400 && options.throwOnHttpError !== false) {
|
|
763
333
|
await drainNativeResponseBody(response);
|
|
764
|
-
throw new TransportError(
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
status: response.status,
|
|
769
|
-
},
|
|
770
|
-
);
|
|
334
|
+
throw new TransportError(`Upstream request failed with status ${response.status}`, {
|
|
335
|
+
code: "upstream_http_error",
|
|
336
|
+
status: response.status,
|
|
337
|
+
});
|
|
771
338
|
}
|
|
772
339
|
|
|
773
340
|
return toNativeHttpResponse(response);
|
|
@@ -775,7 +342,9 @@ async function fetchNativeHttp(
|
|
|
775
342
|
if (error instanceof SyntaxError) {
|
|
776
343
|
throw error;
|
|
777
344
|
}
|
|
778
|
-
|
|
345
|
+
const transportError = toHttpTransportError(error) as NativeHttpAttemptError;
|
|
346
|
+
transportError.proxyUsed = Boolean(proxy);
|
|
347
|
+
throw transportError;
|
|
779
348
|
} finally {
|
|
780
349
|
if (timeoutHandle) clearTimeout(timeoutHandle);
|
|
781
350
|
}
|
|
@@ -789,10 +358,7 @@ async function fetchNativeHttpStream(
|
|
|
789
358
|
clientOptions: HttpClientOptions,
|
|
790
359
|
warn: (message: string) => void,
|
|
791
360
|
): Promise<HttpStreamResponse> {
|
|
792
|
-
const requestUrl = appendQueryParams(
|
|
793
|
-
resolveHttpUrl(baseUrl, url),
|
|
794
|
-
options.params,
|
|
795
|
-
);
|
|
361
|
+
const requestUrl = appendQueryParams(resolveHttpUrl(baseUrl, url), options.params);
|
|
796
362
|
const controller = options.timeout ? new AbortController() : undefined;
|
|
797
363
|
const timeoutHandle = options.timeout
|
|
798
364
|
? setTimeout(() => controller?.abort(), options.timeout)
|
|
@@ -815,13 +381,10 @@ async function fetchNativeHttpStream(
|
|
|
815
381
|
|
|
816
382
|
if (response.status >= 400 && options.throwOnHttpError !== false) {
|
|
817
383
|
await drainNativeResponseBody(response);
|
|
818
|
-
throw new TransportError(
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
status: response.status,
|
|
823
|
-
},
|
|
824
|
-
);
|
|
384
|
+
throw new TransportError(`Upstream request failed with status ${response.status}`, {
|
|
385
|
+
code: "upstream_http_error",
|
|
386
|
+
status: response.status,
|
|
387
|
+
});
|
|
825
388
|
}
|
|
826
389
|
|
|
827
390
|
return toNativeHttpStreamResponse(response);
|
|
@@ -861,31 +424,32 @@ export function createHttpClient(
|
|
|
861
424
|
);
|
|
862
425
|
}
|
|
863
426
|
assertNoHttpTransportOverrides(options);
|
|
864
|
-
const headersOptions = withClientHeaders(
|
|
865
|
-
options,
|
|
866
|
-
clientOptions,
|
|
867
|
-
options.body,
|
|
868
|
-
);
|
|
427
|
+
const headersOptions = withClientHeaders(options, clientOptions, options.body);
|
|
869
428
|
const methodName = normalizeHttpMethod(method);
|
|
870
|
-
const
|
|
871
|
-
|
|
429
|
+
const explicitRetry = headersOptions.retry !== undefined;
|
|
430
|
+
const retryOptions =
|
|
431
|
+
normalizeProxyTransportRetryOptions(headersOptions.retry, {
|
|
432
|
+
label: "HTTP",
|
|
433
|
+
}) ??
|
|
434
|
+
(explicitRetry ? undefined : createDefaultProxyTransportRetryOptions({ label: "HTTP" }));
|
|
435
|
+
if (retryOptions) validateUnsafeProxyTransportRetryMethods(retryOptions, "HTTP");
|
|
872
436
|
const retryEnabled = Boolean(
|
|
873
437
|
retryOptions &&
|
|
874
438
|
retryOptions.attempts > 1 &&
|
|
875
|
-
|
|
439
|
+
isProxyTransportRetryMethod(methodName, retryOptions),
|
|
876
440
|
);
|
|
877
441
|
const statusRetryEnabled = Boolean(
|
|
878
442
|
retryEnabled &&
|
|
443
|
+
explicitRetry &&
|
|
879
444
|
retryOptions &&
|
|
880
445
|
retryOptions.statusCodes.length > 0 &&
|
|
881
446
|
headersOptions.throwOnHttpError !== false,
|
|
882
447
|
);
|
|
883
|
-
const attemptOptions: RequestOptions & { body?: unknown } =
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
: headersOptions;
|
|
448
|
+
const attemptOptions: RequestOptions & { body?: unknown } = statusRetryEnabled
|
|
449
|
+
? { ...headersOptions, throwOnHttpError: false }
|
|
450
|
+
: headersOptions;
|
|
887
451
|
|
|
888
|
-
const executeOnce = (): Promise<
|
|
452
|
+
const executeOnce = (proxyAttemptOffset = 0): Promise<NativeHttpAttemptOutcome> =>
|
|
889
453
|
fetchNativeHttp(
|
|
890
454
|
baseUrl,
|
|
891
455
|
url,
|
|
@@ -894,6 +458,7 @@ export function createHttpClient(
|
|
|
894
458
|
clientOptions,
|
|
895
459
|
warnOnce,
|
|
896
460
|
statusRetryEnabled ? retryOptions?.statusCodes : undefined,
|
|
461
|
+
proxyAttemptOffset,
|
|
897
462
|
);
|
|
898
463
|
|
|
899
464
|
if (!retryEnabled || !retryOptions) {
|
|
@@ -908,23 +473,18 @@ export function createHttpClient(
|
|
|
908
473
|
let lastStatus: number | undefined;
|
|
909
474
|
for (let attempt = 1; attempt <= retryOptions.attempts; attempt += 1) {
|
|
910
475
|
try {
|
|
911
|
-
const outcome = await executeOnce();
|
|
476
|
+
const outcome = await executeOnce(attempt - 1);
|
|
912
477
|
if (isHttpStatusOutcome(outcome)) {
|
|
913
478
|
lastStatus = outcome.status;
|
|
914
479
|
if (outcome.retryable && attempt < retryOptions.attempts) {
|
|
915
|
-
await sleep(
|
|
916
|
-
computeRetryDelayMs(retryOptions, attempt, outcome.headers),
|
|
917
|
-
);
|
|
480
|
+
await sleep(computeProxyTransportRetryDelayMs(retryOptions, attempt, outcome.headers));
|
|
918
481
|
continue;
|
|
919
482
|
}
|
|
920
483
|
throw toUpstreamHttpError(outcome.status);
|
|
921
484
|
}
|
|
922
485
|
|
|
923
486
|
const response = outcome;
|
|
924
|
-
if (
|
|
925
|
-
response.status >= 400 &&
|
|
926
|
-
headersOptions.throwOnHttpError !== false
|
|
927
|
-
) {
|
|
487
|
+
if (response.status >= 400 && headersOptions.throwOnHttpError !== false) {
|
|
928
488
|
throw toUpstreamHttpError(response.status);
|
|
929
489
|
}
|
|
930
490
|
|
|
@@ -941,13 +501,20 @@ export function createHttpClient(
|
|
|
941
501
|
}
|
|
942
502
|
return response;
|
|
943
503
|
} catch (error) {
|
|
944
|
-
lastErrorCode =
|
|
945
|
-
lastStatus =
|
|
504
|
+
lastErrorCode = proxyTransportRetryErrorCode(error);
|
|
505
|
+
lastStatus = proxyTransportRetryErrorStatus(error);
|
|
506
|
+
const proxyUsed = Boolean((error as NativeHttpAttemptError).proxyUsed);
|
|
946
507
|
if (
|
|
947
508
|
attempt < retryOptions.attempts &&
|
|
948
|
-
|
|
509
|
+
shouldRetryProxyTransportAttempt({
|
|
510
|
+
error,
|
|
511
|
+
explicitRetry,
|
|
512
|
+
method: methodName,
|
|
513
|
+
options: retryOptions,
|
|
514
|
+
proxyUsed,
|
|
515
|
+
})
|
|
949
516
|
) {
|
|
950
|
-
await sleep(
|
|
517
|
+
await sleep(computeProxyTransportRetryDelayMs(retryOptions, attempt));
|
|
951
518
|
continue;
|
|
952
519
|
}
|
|
953
520
|
throw error;
|
|
@@ -971,30 +538,17 @@ export function createHttpClient(
|
|
|
971
538
|
);
|
|
972
539
|
}
|
|
973
540
|
assertNoHttpTransportOverrides(options);
|
|
974
|
-
const headersOptions = withClientHeaders(
|
|
975
|
-
options,
|
|
976
|
-
clientOptions,
|
|
977
|
-
options.body,
|
|
978
|
-
);
|
|
541
|
+
const headersOptions = withClientHeaders(options, clientOptions, options.body);
|
|
979
542
|
const methodName = normalizeHttpMethod(method);
|
|
980
|
-
return fetchNativeHttpStream(
|
|
981
|
-
baseUrl,
|
|
982
|
-
url,
|
|
983
|
-
methodName,
|
|
984
|
-
headersOptions,
|
|
985
|
-
clientOptions,
|
|
986
|
-
warnOnce,
|
|
987
|
-
);
|
|
543
|
+
return fetchNativeHttpStream(baseUrl, url, methodName, headersOptions, clientOptions, warnOnce);
|
|
988
544
|
}
|
|
989
545
|
|
|
990
546
|
return {
|
|
991
547
|
request: async (url, options: RequestWithMethodOptions = {}) =>
|
|
992
548
|
request(url, options.method ?? "GET", options),
|
|
993
549
|
get: async (url, options) => request(url, "GET", options),
|
|
994
|
-
post: async (url, body, options) =>
|
|
995
|
-
|
|
996
|
-
put: async (url, body, options) =>
|
|
997
|
-
request(url, "PUT", { ...options, body }),
|
|
550
|
+
post: async (url, body, options) => request(url, "POST", { ...options, body }),
|
|
551
|
+
put: async (url, body, options) => request(url, "PUT", { ...options, body }),
|
|
998
552
|
delete: async (url, options) => request(url, "DELETE", options),
|
|
999
553
|
stream: async (url, options: RequestWithMethodOptions = {}) =>
|
|
1000
554
|
streamRequest(url, options.method ?? "GET", options),
|