@to11ai/sdk 0.1.2 → 0.3.0
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/dist/behaviors/store.d.ts +62 -0
- package/dist/client.cjs +37 -4
- package/dist/client.d.ts +41 -2
- package/dist/client.js +45 -4
- package/dist/errors.cjs +90 -1
- package/dist/errors.d.ts +142 -0
- package/dist/errors.js +90 -1
- package/dist/gateway.cjs +12 -0
- package/dist/gateway.d.ts +26 -12
- package/dist/gateway.js +14 -0
- package/dist/generated/index.d.ts +1 -0
- package/dist/generated/types.gen.d.ts +22978 -0
- package/dist/generated/zod.gen.d.ts +13543 -0
- package/dist/projectEnvironments/resource.cjs +7 -2
- package/dist/projectEnvironments/resource.js +7 -2
- package/dist/projectEnvironments/types.d.ts +4 -75
- package/dist/prompts/cache.d.ts +24 -3
- package/dist/prompts/resource.cjs +101 -3
- package/dist/prompts/resource.d.ts +47 -3
- package/dist/prompts/resource.js +103 -5
- package/dist/prompts/types.d.ts +36 -177
- package/dist/routingRules/resource.cjs +9 -2
- package/dist/routingRules/resource.js +9 -2
- package/dist/routingRules/types.d.ts +12 -123
- package/dist/transport.d.ts +23 -0
- package/package.json +23 -12
- package/src/behaviors/store.ts +175 -0
- package/src/client.ts +115 -3
- package/src/errors.ts +261 -0
- package/src/gateway.ts +54 -12
- package/src/generated/index.ts +3 -0
- package/src/generated/types.gen.ts +24087 -0
- package/src/generated/zod.gen.ts +12660 -0
- package/src/projectEnvironments/resource.ts +10 -0
- package/src/projectEnvironments/types.ts +9 -91
- package/src/prompts/cache.ts +39 -4
- package/src/prompts/resource.ts +258 -5
- package/src/prompts/types.ts +56 -237
- package/src/routingRules/resource.ts +10 -1
- package/src/routingRules/types.ts +21 -155
- package/src/traceparent.ts +69 -4
- package/src/transport.ts +188 -3
package/src/traceparent.ts
CHANGED
|
@@ -19,6 +19,16 @@
|
|
|
19
19
|
* @see docs/product-specs/2026-05-20-to11-1016-sdk-traceparent-propagation.md
|
|
20
20
|
*/
|
|
21
21
|
|
|
22
|
+
// NOTE: deliberately NO top-level `import ... from "node:module"`. With the
|
|
23
|
+
// SDK's `--external '*'` build that import is emitted verbatim into the ESM
|
|
24
|
+
// `dist/client.js`, and a static `node:module` import is resolved at module
|
|
25
|
+
// initialization — *before* any try/catch in `resolveRequire()` can run. In
|
|
26
|
+
// edge runtimes that lack `node:module` (Cloudflare Workers without
|
|
27
|
+
// `nodejs_compat`, bare Service Workers) that is a hard module-load failure,
|
|
28
|
+
// not the spec-R2 graceful no-op. We instead reach `node:module` lazily and
|
|
29
|
+
// optionally via `process.getBuiltinModule`, which is absent (not throwing) in
|
|
30
|
+
// those runtimes.
|
|
31
|
+
|
|
22
32
|
// Minimal structural typing of the OTel API surface we actually use. Keeping
|
|
23
33
|
// this local — rather than importing types from `@opentelemetry/api` — is
|
|
24
34
|
// what keeps the SDK zero-dep at the TypeScript layer too. Source types:
|
|
@@ -52,16 +62,71 @@ let loader: OtelLoader = defaultLoader;
|
|
|
52
62
|
|
|
53
63
|
function defaultLoader(): OtelApi | null {
|
|
54
64
|
try {
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
// biome-ignore lint/security/noGlobalEval: runtime-detected optional peer dep; bundlers cannot statically resolve
|
|
58
|
-
const r = (g.require ?? eval("require")) as (id: string) => unknown;
|
|
65
|
+
const r = resolveRequire();
|
|
66
|
+
if (r === null) return null;
|
|
59
67
|
return r("@opentelemetry/api") as OtelApi;
|
|
60
68
|
} catch {
|
|
61
69
|
return null;
|
|
62
70
|
}
|
|
63
71
|
}
|
|
64
72
|
|
|
73
|
+
/**
|
|
74
|
+
* Resolve a CommonJS `require` usable for optionally loading `@opentelemetry/api`,
|
|
75
|
+
* across every runtime this package ships to. Order matters — it is the whole
|
|
76
|
+
* point of the TO11-1380 fix:
|
|
77
|
+
*
|
|
78
|
+
* 1. `globalThis.require` — present in some bundled / global contexts.
|
|
79
|
+
* 2. `eval("require")` — resolves the module-local `require` in the
|
|
80
|
+
* `dist/client.cjs` (CommonJS) build. In a native Node ESM module this
|
|
81
|
+
* throws `ReferenceError: require is not defined`, so we fall through.
|
|
82
|
+
* 3. `process.getBuiltinModule("node:module").createRequire(import.meta.url)`
|
|
83
|
+
* — the ESM-correct path. The published `dist/client.js` is loaded by Node
|
|
84
|
+
* via `import`, where `require` is not defined and not on `globalThis`. The
|
|
85
|
+
* previous loader stopped at step 2, cached `null` forever, and made
|
|
86
|
+
* `activeTraceContext()` a permanent no-op for the primary import build
|
|
87
|
+
* (TO11-1380). `import.meta` is only referenced here, in the ESM branch, so
|
|
88
|
+
* the CJS build never evaluates it.
|
|
89
|
+
*
|
|
90
|
+
* `node:module` is reached lazily via `process.getBuiltinModule` (not a static
|
|
91
|
+
* `import`) precisely so non-Node runtimes never resolve the builtin at module
|
|
92
|
+
* load. In a browser / edge runtime (Cloudflare Workers, Vercel Edge)
|
|
93
|
+
* `globalThis.require` is absent, `eval("require")` throws, and
|
|
94
|
+
* `process.getBuiltinModule` is absent — every step degrades to a strict no-op,
|
|
95
|
+
* preserving the zero-dep edge behavior (spec R2). `process.getBuiltinModule`
|
|
96
|
+
* is available on Node ≥ 20.16 / ≥ 22.3; older Node ESM (no global `require`,
|
|
97
|
+
* no `getBuiltinModule`) degrades to no-op rather than crashing.
|
|
98
|
+
*/
|
|
99
|
+
function resolveRequire(): ((id: string) => unknown) | null {
|
|
100
|
+
// biome-ignore lint/suspicious/noExplicitAny: dynamic require shim — see header comment
|
|
101
|
+
const g = globalThis as any;
|
|
102
|
+
if (typeof g.require === "function") {
|
|
103
|
+
return g.require as (id: string) => unknown;
|
|
104
|
+
}
|
|
105
|
+
try {
|
|
106
|
+
// biome-ignore lint/security/noGlobalEval: CJS build only; throws in native ESM and we fall through.
|
|
107
|
+
const cjsRequire = eval("require") as (id: string) => unknown;
|
|
108
|
+
if (typeof cjsRequire === "function") return cjsRequire;
|
|
109
|
+
} catch {
|
|
110
|
+
// Not a CommonJS context (native ESM or edge runtime). Try ESM next.
|
|
111
|
+
}
|
|
112
|
+
try {
|
|
113
|
+
// `getBuiltinModule` is optional: absent (returns undefined / not a
|
|
114
|
+
// function) in edge runtimes, so this never throws at module load.
|
|
115
|
+
const getBuiltinModule = g.process?.getBuiltinModule as
|
|
116
|
+
| ((id: string) => unknown)
|
|
117
|
+
| undefined;
|
|
118
|
+
if (typeof getBuiltinModule !== "function") return null;
|
|
119
|
+
const nodeModule = getBuiltinModule("node:module") as {
|
|
120
|
+
createRequire?: (path: string | URL) => (id: string) => unknown;
|
|
121
|
+
};
|
|
122
|
+
if (typeof nodeModule?.createRequire !== "function") return null;
|
|
123
|
+
return nodeModule.createRequire(import.meta.url);
|
|
124
|
+
} catch {
|
|
125
|
+
// No usable `node:module` (edge runtime) — strict no-op.
|
|
126
|
+
return null;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
65
130
|
/**
|
|
66
131
|
* Return the W3C `traceparent` (and optional `tracestate`) string pair for
|
|
67
132
|
* the currently active OpenTelemetry span in the host process, or `null` if
|
package/src/transport.ts
CHANGED
|
@@ -1,13 +1,20 @@
|
|
|
1
|
+
import type { BehaviorStore } from "./behaviors/store";
|
|
1
2
|
import {
|
|
3
|
+
AmbiguousEnvLabelsError,
|
|
2
4
|
EnvNotEnabledForProviderError,
|
|
5
|
+
EnvNotFoundError,
|
|
6
|
+
NoActiveReleaseError,
|
|
3
7
|
NoApplicableRouteError,
|
|
4
8
|
PromptLabelNotRegisteredError,
|
|
9
|
+
PromptNotFoundError,
|
|
10
|
+
PromptPolicyViolationError,
|
|
5
11
|
RuleNotFoundError,
|
|
6
12
|
RuleVersionInvalidSnapshotError,
|
|
7
13
|
To11aiApiError,
|
|
8
14
|
To11aiAuthError,
|
|
9
15
|
To11aiNetworkError,
|
|
10
16
|
To11aiRateLimitError,
|
|
17
|
+
To11aiResponseValidationError,
|
|
11
18
|
To11aiValidationError,
|
|
12
19
|
} from "./errors";
|
|
13
20
|
import { activeTraceContext } from "./traceparent";
|
|
@@ -28,6 +35,111 @@ const RULE_NOT_FOUND_DETAILS_ERRORS = new Set([
|
|
|
28
35
|
"rule_not_found_in_env",
|
|
29
36
|
]);
|
|
30
37
|
|
|
38
|
+
/**
|
|
39
|
+
* TO11-980 (S9) prompt-resolution canonical error codes.
|
|
40
|
+
*
|
|
41
|
+
* WIRE NOTE: the server (TO11-1925/1926) emits the generic HTTP class
|
|
42
|
+
* (`not_found` / `conflict` / `unprocessable_entity`) at the envelope's
|
|
43
|
+
* top-level `error.code`, and the canonical SCREAMING_SNAKE code below in
|
|
44
|
+
* `error.details.code` (matching the existing `PROMPT_NOT_FOUND` convention).
|
|
45
|
+
* The four prompt-resolve branches in `mapHttpError` therefore read
|
|
46
|
+
* `details.code`, NOT top-level `errorCode`.
|
|
47
|
+
*
|
|
48
|
+
* Two codes fold into `PromptNotFoundError`: `PROMPT_NOT_FOUND` (no prompt)
|
|
49
|
+
* and `PROMPT_LABELS_NOT_FOUND` (no version matched the env-label).
|
|
50
|
+
*/
|
|
51
|
+
const PROMPT_NOT_FOUND_DETAILS_CODES = new Set([
|
|
52
|
+
"PROMPT_NOT_FOUND",
|
|
53
|
+
"PROMPT_LABELS_NOT_FOUND",
|
|
54
|
+
]);
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Prompt-resolution policy violation codes (S5 / TO11-976). The generic
|
|
58
|
+
* `POLICY_VIOLATION` plus the concrete `ENV_POLICY_*` family the server
|
|
59
|
+
* emits. Any of these maps to `PromptPolicyViolationError`. This set is
|
|
60
|
+
* matched on `details.code` BEFORE the generic `401 || 403 → auth` guard so a
|
|
61
|
+
* 403 policy violation is not swallowed as an auth error (D3, AC6).
|
|
62
|
+
*/
|
|
63
|
+
const PROMPT_POLICY_VIOLATION_DETAILS_CODES = new Set([
|
|
64
|
+
"POLICY_VIOLATION",
|
|
65
|
+
"ENV_POLICY_REQUIRED_SOURCE_ENV",
|
|
66
|
+
"ENV_POLICY_MINIMUM_DWELL_TIME",
|
|
67
|
+
"ENV_POLICY_NO_ROLLBACK_IN_SOURCE",
|
|
68
|
+
"ENV_POLICY_REQUIRES_APPROVAL",
|
|
69
|
+
"ENV_POLICY_SEPARATION_OF_DUTIES",
|
|
70
|
+
"ENV_POLICY_SOURCE_ENV_UNAVAILABLE",
|
|
71
|
+
"ENV_POLICY_ENV_NOT_FOUND",
|
|
72
|
+
"ENV_POLICY_INVALID_CONFIG",
|
|
73
|
+
]);
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Map a prompt-resolution envelope (keyed on `details.code`) to a typed S9
|
|
77
|
+
* subclass, or `undefined` when the code is not one of the four S9 families.
|
|
78
|
+
*
|
|
79
|
+
* Returning `undefined` lets the caller fall through to the existing
|
|
80
|
+
* status-keyed dispatch (so unmapped codes stay base `To11aiApiError`, per
|
|
81
|
+
* D3). This function reads ONLY `details.code` — never top-level `error.code`
|
|
82
|
+
* — because the server puts the canonical code in `details.code` (see above).
|
|
83
|
+
*/
|
|
84
|
+
function mapPromptResolveError(
|
|
85
|
+
status: number,
|
|
86
|
+
message: string,
|
|
87
|
+
details: unknown,
|
|
88
|
+
): To11aiApiError | undefined {
|
|
89
|
+
const d = (details && typeof details === "object" ? details : {}) as Record<
|
|
90
|
+
string,
|
|
91
|
+
unknown
|
|
92
|
+
>;
|
|
93
|
+
const detailsCode = typeof d.code === "string" ? d.code : undefined;
|
|
94
|
+
if (detailsCode === undefined) return undefined;
|
|
95
|
+
|
|
96
|
+
const slug = typeof d.slug === "string" ? d.slug : undefined;
|
|
97
|
+
const projectId = typeof d.projectId === "string" ? d.projectId : undefined;
|
|
98
|
+
const env = typeof d.env === "string" ? d.env : undefined;
|
|
99
|
+
const promptId = typeof d.promptId === "string" ? d.promptId : undefined;
|
|
100
|
+
|
|
101
|
+
if (PROMPT_NOT_FOUND_DETAILS_CODES.has(detailsCode)) {
|
|
102
|
+
return new PromptNotFoundError(
|
|
103
|
+
message,
|
|
104
|
+
detailsCode,
|
|
105
|
+
{ slug, projectId },
|
|
106
|
+
details,
|
|
107
|
+
status,
|
|
108
|
+
);
|
|
109
|
+
}
|
|
110
|
+
if (detailsCode === "ENV_NOT_FOUND") {
|
|
111
|
+
// Server-side unknown-env. (The client-eager "missing" variant is
|
|
112
|
+
// raised in resource.ts before any request — never here.)
|
|
113
|
+
return new EnvNotFoundError(
|
|
114
|
+
message,
|
|
115
|
+
{ env, source: "unknown" },
|
|
116
|
+
details,
|
|
117
|
+
status,
|
|
118
|
+
);
|
|
119
|
+
}
|
|
120
|
+
if (detailsCode === "NO_ACTIVE_RELEASE") {
|
|
121
|
+
return new NoActiveReleaseError(
|
|
122
|
+
message,
|
|
123
|
+
{ slug, promptId, env },
|
|
124
|
+
details,
|
|
125
|
+
status,
|
|
126
|
+
);
|
|
127
|
+
}
|
|
128
|
+
if (detailsCode === "AMBIGUOUS_ENV_LABELS") {
|
|
129
|
+
return new AmbiguousEnvLabelsError(message, { env, slug }, details, status);
|
|
130
|
+
}
|
|
131
|
+
if (PROMPT_POLICY_VIOLATION_DETAILS_CODES.has(detailsCode)) {
|
|
132
|
+
const policy = typeof d.policy === "string" ? d.policy : undefined;
|
|
133
|
+
return new PromptPolicyViolationError(
|
|
134
|
+
message,
|
|
135
|
+
{ code: detailsCode, policy, detail: d.detail },
|
|
136
|
+
details,
|
|
137
|
+
status,
|
|
138
|
+
);
|
|
139
|
+
}
|
|
140
|
+
return undefined;
|
|
141
|
+
}
|
|
142
|
+
|
|
31
143
|
export type AuthConfig =
|
|
32
144
|
| { readonly apiKey: string; readonly getAccessToken?: never }
|
|
33
145
|
| { readonly getAccessToken: () => Promise<string>; readonly apiKey?: never };
|
|
@@ -44,6 +156,12 @@ export interface TransportConfig {
|
|
|
44
156
|
* attempt. Default: 3
|
|
45
157
|
*/
|
|
46
158
|
readonly maxRetries?: number;
|
|
159
|
+
/**
|
|
160
|
+
* Optional behavior store updated from each successful response's
|
|
161
|
+
* `sdkBehavior` field (TO11-2100). When omitted, server-delivered behavior
|
|
162
|
+
* values are simply not captured.
|
|
163
|
+
*/
|
|
164
|
+
readonly behaviorStore?: BehaviorStore;
|
|
47
165
|
}
|
|
48
166
|
|
|
49
167
|
/** Standard API envelope returned by the to11ai server. */
|
|
@@ -58,6 +176,15 @@ export interface ApiEnvelope<T = unknown> {
|
|
|
58
176
|
readonly total: number;
|
|
59
177
|
readonly totalPages: number;
|
|
60
178
|
} | null;
|
|
179
|
+
/**
|
|
180
|
+
* Optional, additively-versioned server-evaluated SDK-behavior values
|
|
181
|
+
* (TO11-2100). Present only when the server has behavior flags to deliver;
|
|
182
|
+
* an unknown `v` is ignored by the SDK. See `behaviors/store.ts`.
|
|
183
|
+
*/
|
|
184
|
+
readonly sdkBehavior?: {
|
|
185
|
+
readonly v: number;
|
|
186
|
+
readonly flags: Record<string, unknown>;
|
|
187
|
+
};
|
|
61
188
|
}
|
|
62
189
|
|
|
63
190
|
const DEFAULT_TIMEOUT_MS = 30_000;
|
|
@@ -99,6 +226,18 @@ function mapHttpError(
|
|
|
99
226
|
const errorCode = envelope?.code;
|
|
100
227
|
const details = envelope?.details;
|
|
101
228
|
|
|
229
|
+
// TO11-980 (S9): prompt-resolution typed errors are keyed on
|
|
230
|
+
// `details.code` (the canonical SCREAMING_SNAKE code), NOT top-level
|
|
231
|
+
// `error.code`. This MUST run BEFORE the generic `401 || 403 → auth`
|
|
232
|
+
// guard below so a 403 `POLICY_VIOLATION` is mapped to
|
|
233
|
+
// `PromptPolicyViolationError` rather than swallowed as `To11aiAuthError`
|
|
234
|
+
// (D3 branch-ordering, AC6). Unmapped `details.code` values return
|
|
235
|
+
// `undefined` and fall through to the existing status-keyed dispatch.
|
|
236
|
+
const promptResolveError = mapPromptResolveError(status, message, details);
|
|
237
|
+
if (promptResolveError) {
|
|
238
|
+
return promptResolveError;
|
|
239
|
+
}
|
|
240
|
+
|
|
102
241
|
if (status === 401 || status === 403) {
|
|
103
242
|
return new To11aiAuthError(
|
|
104
243
|
message,
|
|
@@ -253,12 +392,14 @@ export class Transport {
|
|
|
253
392
|
private readonly auth: AuthConfig;
|
|
254
393
|
private readonly timeoutMs: number;
|
|
255
394
|
private readonly maxRetries: number;
|
|
395
|
+
private readonly behaviorStore: BehaviorStore | undefined;
|
|
256
396
|
|
|
257
397
|
constructor(config: TransportConfig) {
|
|
258
398
|
this.baseUrl = config.baseUrl.replace(/\/$/, "");
|
|
259
399
|
this.auth = config.auth;
|
|
260
400
|
this.timeoutMs = config.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
261
401
|
this.maxRetries = config.maxRetries ?? DEFAULT_MAX_RETRIES;
|
|
402
|
+
this.behaviorStore = config.behaviorStore;
|
|
262
403
|
}
|
|
263
404
|
|
|
264
405
|
private async buildHeaders(): Promise<Record<string, string>> {
|
|
@@ -289,6 +430,12 @@ export class Transport {
|
|
|
289
430
|
options?: {
|
|
290
431
|
query?: Record<string, string | number | boolean | undefined>;
|
|
291
432
|
body?: unknown;
|
|
433
|
+
/**
|
|
434
|
+
* Runtime validator for the envelope `data` on a 2xx response. When
|
|
435
|
+
* supplied, the decoded value replaces `data`; a thrown decoder error
|
|
436
|
+
* surfaces as `To11aiResponseValidationError`.
|
|
437
|
+
*/
|
|
438
|
+
decode?: (data: unknown) => T;
|
|
292
439
|
},
|
|
293
440
|
): Promise<ApiEnvelope<T>> {
|
|
294
441
|
const url = new URL(this.baseUrl + path);
|
|
@@ -320,7 +467,15 @@ export class Transport {
|
|
|
320
467
|
signal: controller.signal,
|
|
321
468
|
});
|
|
322
469
|
|
|
323
|
-
|
|
470
|
+
// NOTE: the timeout is intentionally NOT cleared here. A server can
|
|
471
|
+
// send headers promptly (resolving `fetch`) but then stall the
|
|
472
|
+
// response body forever. The timeout must stay armed across
|
|
473
|
+
// `response.text()` and JSON parsing so a stalled body still aborts
|
|
474
|
+
// after `timeoutMs`; an abort mid-body surfaces as an AbortError that
|
|
475
|
+
// the catch block maps to `To11aiNetworkError`. Cleanup lives in the
|
|
476
|
+
// `finally` below so every exit path (success, retry `continue`,
|
|
477
|
+
// throw) clears the timer exactly once before the next attempt. See
|
|
478
|
+
// TO11-1402.
|
|
324
479
|
|
|
325
480
|
// 5xx — retry only if the method is idempotent (RFC 9110 §9.2.2).
|
|
326
481
|
// POST/PATCH may have already committed server-side, so replaying
|
|
@@ -361,10 +516,32 @@ export class Transport {
|
|
|
361
516
|
);
|
|
362
517
|
}
|
|
363
518
|
|
|
519
|
+
// Capture any server-delivered SDK-behavior values from the
|
|
520
|
+
// success envelope (TO11-2100). Behavior is delivered on success
|
|
521
|
+
// responses only — gate on the envelope's `success` flag, not just
|
|
522
|
+
// the 2xx status, so an unusual 2xx-with-`success:false` body does
|
|
523
|
+
// not update the store. An absent field leaves prior values intact.
|
|
524
|
+
// Done before any caller-supplied decode so the store reflects the
|
|
525
|
+
// raw envelope regardless of how the data is post-processed.
|
|
526
|
+
if (json.success === true) {
|
|
527
|
+
this.behaviorStore?.updateFromEnvelope(json.sdkBehavior);
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
if (options?.decode && json.data !== null && json.data !== undefined) {
|
|
531
|
+
try {
|
|
532
|
+
const decoded = options.decode(json.data);
|
|
533
|
+
return { ...json, data: decoded };
|
|
534
|
+
} catch (validationErr) {
|
|
535
|
+
throw new To11aiResponseValidationError(
|
|
536
|
+
path,
|
|
537
|
+
validationErr,
|
|
538
|
+
validationErr,
|
|
539
|
+
);
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
|
|
364
543
|
return json;
|
|
365
544
|
} catch (err) {
|
|
366
|
-
clearTimeout(timeoutId);
|
|
367
|
-
|
|
368
545
|
// Re-throw typed SDK errors immediately (no retry)
|
|
369
546
|
if (err instanceof To11aiApiError) {
|
|
370
547
|
throw err;
|
|
@@ -403,6 +580,14 @@ export class Transport {
|
|
|
403
580
|
|
|
404
581
|
// Re-throw unexpected errors (e.g. auth callback failures) as-is
|
|
405
582
|
throw err;
|
|
583
|
+
} finally {
|
|
584
|
+
// Clear the per-attempt timeout exactly once on every exit path:
|
|
585
|
+
// success (`return`), retry (`continue`), or throw. Running here —
|
|
586
|
+
// rather than after `fetch` resolves — keeps the timer armed across
|
|
587
|
+
// the response-body read so a stalled body still aborts (TO11-1402),
|
|
588
|
+
// while still freeing the timer before the loop starts a fresh
|
|
589
|
+
// attempt with a new AbortController.
|
|
590
|
+
clearTimeout(timeoutId);
|
|
406
591
|
}
|
|
407
592
|
}
|
|
408
593
|
}
|