lifecycleion 0.0.9 → 0.0.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/README.md +45 -36
- package/dist/lib/domain-utils/domain-utils.cjs +1154 -0
- package/dist/lib/domain-utils/domain-utils.cjs.map +1 -0
- package/dist/lib/domain-utils/domain-utils.d.cts +210 -0
- package/dist/lib/domain-utils/domain-utils.d.ts +210 -0
- package/dist/lib/domain-utils/domain-utils.js +1112 -0
- package/dist/lib/domain-utils/domain-utils.js.map +1 -0
- package/dist/lib/http-client/index.cjs +5254 -0
- package/dist/lib/http-client/index.cjs.map +1 -0
- package/dist/lib/http-client/index.d.cts +372 -0
- package/dist/lib/http-client/index.d.ts +372 -0
- package/dist/lib/http-client/index.js +5207 -0
- package/dist/lib/http-client/index.js.map +1 -0
- package/dist/lib/http-client-mock/index.cjs +525 -0
- package/dist/lib/http-client-mock/index.cjs.map +1 -0
- package/dist/lib/http-client-mock/index.d.cts +129 -0
- package/dist/lib/http-client-mock/index.d.ts +129 -0
- package/dist/lib/http-client-mock/index.js +488 -0
- package/dist/lib/http-client-mock/index.js.map +1 -0
- package/dist/lib/http-client-node/index.cjs +1112 -0
- package/dist/lib/http-client-node/index.cjs.map +1 -0
- package/dist/lib/http-client-node/index.d.cts +43 -0
- package/dist/lib/http-client-node/index.d.ts +43 -0
- package/dist/lib/http-client-node/index.js +1075 -0
- package/dist/lib/http-client-node/index.js.map +1 -0
- package/dist/lib/http-client-xhr/index.cjs +323 -0
- package/dist/lib/http-client-xhr/index.cjs.map +1 -0
- package/dist/lib/http-client-xhr/index.d.cts +23 -0
- package/dist/lib/http-client-xhr/index.d.ts +23 -0
- package/dist/lib/http-client-xhr/index.js +286 -0
- package/dist/lib/http-client-xhr/index.js.map +1 -0
- package/dist/lib/lru-cache/index.cjs +274 -0
- package/dist/lib/lru-cache/index.cjs.map +1 -0
- package/dist/lib/lru-cache/index.d.cts +84 -0
- package/dist/lib/lru-cache/index.d.ts +84 -0
- package/dist/lib/lru-cache/index.js +249 -0
- package/dist/lib/lru-cache/index.js.map +1 -0
- package/dist/lib/retry-utils/index.d.cts +3 -23
- package/dist/lib/retry-utils/index.d.ts +3 -23
- package/dist/types-CUPvmYQ8.d.cts +868 -0
- package/dist/types-D_MywcG0.d.cts +23 -0
- package/dist/types-D_MywcG0.d.ts +23 -0
- package/dist/types-Hw2PUTIT.d.ts +868 -0
- package/package.json +45 -3
|
@@ -0,0 +1,868 @@
|
|
|
1
|
+
import { R as RetryPolicyOptions } from './types-D_MywcG0.cjs';
|
|
2
|
+
|
|
3
|
+
interface Cookie {
|
|
4
|
+
name: string;
|
|
5
|
+
value: string;
|
|
6
|
+
domain?: string;
|
|
7
|
+
/** True when the cookie came from a Set-Cookie without Domain= and must not match subdomains. */
|
|
8
|
+
hostOnly?: boolean;
|
|
9
|
+
path?: string;
|
|
10
|
+
expires?: Date;
|
|
11
|
+
/** Seconds from {@link createdAt}; no synthetic `expires` when only Max-Age was sent (RFC 6265). */
|
|
12
|
+
maxAge?: number;
|
|
13
|
+
secure?: boolean;
|
|
14
|
+
httpOnly?: boolean;
|
|
15
|
+
sameSite?: 'Strict' | 'Lax' | 'None';
|
|
16
|
+
/** Epoch ms when the cookie was stored. Used with {@link maxAge} to compute expiry. */
|
|
17
|
+
createdAt: number;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Input shape for {@link CookieJar.setCookie}. Identical to {@link Cookie} except
|
|
21
|
+
* `createdAt` is optional — the jar injects `Date.now()` when omitted.
|
|
22
|
+
*/
|
|
23
|
+
type CookieInput = Omit<Cookie, 'createdAt'> & {
|
|
24
|
+
createdAt?: number;
|
|
25
|
+
};
|
|
26
|
+
interface CookieJarJSON {
|
|
27
|
+
cookies: Cookie[];
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Shareable, standalone cookie jar.
|
|
31
|
+
*
|
|
32
|
+
* Cookies are bucketed by apex domain (via tldts Public Suffix List) for efficient
|
|
33
|
+
* URL lookup — only the relevant bucket is scanned instead of all stored cookies.
|
|
34
|
+
*
|
|
35
|
+
* Validation applied when storing from Set-Cookie headers:
|
|
36
|
+
* - Rejects Domain= values that are recognized public suffixes (e.g. co.uk, com)
|
|
37
|
+
* - Rejects Domain= values that are not a suffix of the request host
|
|
38
|
+
* - Strips leading dots from Domain= per RFC 6265
|
|
39
|
+
*
|
|
40
|
+
* IPs and local hostnames like localhost are never treated as public suffixes.
|
|
41
|
+
*/
|
|
42
|
+
declare class CookieJar {
|
|
43
|
+
private buckets;
|
|
44
|
+
/**
|
|
45
|
+
* Stores or updates a cookie. Returns false if the domain is missing or
|
|
46
|
+
* not a valid hostname/IP (e.g. empty string, spaces, garbage input).
|
|
47
|
+
*
|
|
48
|
+
* Valid domains include: hostnames (example.com, localhost, myapp.test),
|
|
49
|
+
* IPv4 (127.0.0.1), and IPv6 ([::1]).
|
|
50
|
+
*
|
|
51
|
+
* For server responses use parseSetCookieHeader — it also enforces PSL
|
|
52
|
+
* validation and domain-suffix checks on top of the syntax check here.
|
|
53
|
+
*/
|
|
54
|
+
setCookie(cookie: CookieInput): boolean;
|
|
55
|
+
/**
|
|
56
|
+
* Returns all stored cookies (including possibly expired ones — call
|
|
57
|
+
* clearExpiredCookies first if needed).
|
|
58
|
+
*/
|
|
59
|
+
getAllCookies(): Cookie[];
|
|
60
|
+
/**
|
|
61
|
+
* Returns the named cookie applicable for the given URL (domain + path
|
|
62
|
+
* matching, unexpired), or undefined.
|
|
63
|
+
*
|
|
64
|
+
* Applies the same rules as getCookiesFor — domain, path, and expiry are all checked.
|
|
65
|
+
*/
|
|
66
|
+
getCookieFor(name: string, url: string): Cookie | undefined;
|
|
67
|
+
/**
|
|
68
|
+
* Returns all domains that have cookies stored, with a count per domain.
|
|
69
|
+
* Domains are stored in canonical form (lowercase hostnames; normalized IP literals).
|
|
70
|
+
*/
|
|
71
|
+
getStoredDomains(): Array<{
|
|
72
|
+
domain: string;
|
|
73
|
+
count: number;
|
|
74
|
+
}>;
|
|
75
|
+
/**
|
|
76
|
+
* Parses a Set-Cookie header string and stores the resulting cookie.
|
|
77
|
+
* The request URL is used to infer and validate the domain (PSL + suffix checks).
|
|
78
|
+
*/
|
|
79
|
+
parseSetCookieHeader(header: string, url: string): void;
|
|
80
|
+
/**
|
|
81
|
+
* Processes all Set-Cookie headers from a response headers object.
|
|
82
|
+
*
|
|
83
|
+
* Uses the same normalization as `HTTPClient` ({@link normalizeAdapterResponseHeaders}):
|
|
84
|
+
* lowercase keys and merged `set-cookie` lines, so mixed-case adapter output
|
|
85
|
+
* matches `FetchAdapter` / normalized responses.
|
|
86
|
+
*/
|
|
87
|
+
processResponseHeaders(headers: Record<string, string | string[]>, url: string): void;
|
|
88
|
+
/**
|
|
89
|
+
* Returns cookies applicable for the given URL (domain + path matching, unexpired).
|
|
90
|
+
* Cookies with the Secure attribute are omitted unless the URL uses the `https:` scheme
|
|
91
|
+
* (RFC 6265 §5.4).
|
|
92
|
+
*
|
|
93
|
+
* Only scans the apex-domain bucket for the URL — O(cookies in that domain)
|
|
94
|
+
* instead of O(all cookies).
|
|
95
|
+
*/
|
|
96
|
+
getCookiesFor(url: string): Cookie[];
|
|
97
|
+
/**
|
|
98
|
+
* Returns a `Cookie: name=value; name2=value2` string for the given URL.
|
|
99
|
+
* Uses `getCookiesFor`, so expired cookies are never included (same as RFC
|
|
100
|
+
* behavior on the wire).
|
|
101
|
+
*/
|
|
102
|
+
getCookieHeaderString(url: string): string;
|
|
103
|
+
/**
|
|
104
|
+
* Removes expired cookies from the jar. Returns the number of cookies removed.
|
|
105
|
+
*/
|
|
106
|
+
clearExpiredCookies(): number;
|
|
107
|
+
/**
|
|
108
|
+
* Removes cookies from the jar.
|
|
109
|
+
*
|
|
110
|
+
* - `clear()` — removes everything
|
|
111
|
+
* - `clear(host, 'hostname')` — removes cookies stored for exactly that hostname,
|
|
112
|
+
* leaving other subdomains untouched. e.g. `clear('api.example.com', 'hostname')`
|
|
113
|
+
* does not touch cookies stored for `example.com`.
|
|
114
|
+
* - `clear(host, 'domain')` — removes all cookies for the entire domain family
|
|
115
|
+
* (apex + all subdomains). e.g. `clear('api.example.com', 'domain')` clears
|
|
116
|
+
* everything in the `example.com` apex bucket.
|
|
117
|
+
*/
|
|
118
|
+
clear(): number;
|
|
119
|
+
clear(host: string, scope: 'hostname' | 'domain'): number;
|
|
120
|
+
/**
|
|
121
|
+
* Serializes the jar to JSON.
|
|
122
|
+
*/
|
|
123
|
+
toJSON(): CookieJarJSON;
|
|
124
|
+
/**
|
|
125
|
+
* Restores a jar from serialized JSON.
|
|
126
|
+
*/
|
|
127
|
+
fromJSON(data: CookieJarJSON): void;
|
|
128
|
+
private cookieKey;
|
|
129
|
+
/** Canonical form for stored cookie domains: lowercase DNS names; canonical IP literals. */
|
|
130
|
+
private normalizeStoredDomain;
|
|
131
|
+
private parseCookieString;
|
|
132
|
+
private pruneEmptyBucket;
|
|
133
|
+
private deleteCookieByIdentity;
|
|
134
|
+
/**
|
|
135
|
+
* RFC 6265 §5.1.4 — default-path from the path portion of the request-uri.
|
|
136
|
+
* E.g. `/admin/settings` → `/admin`; `/admin` or `/` → `/`.
|
|
137
|
+
*/
|
|
138
|
+
private defaultCookiePathFromPathname;
|
|
139
|
+
/** Path from Set-Cookie when present and non-empty; otherwise §5.1.4 default-path. */
|
|
140
|
+
private resolvedCookiePath;
|
|
141
|
+
/** Returns the apex (registered) domain for bucketing.
|
|
142
|
+
* Falls back to hostname for IPs and local hostnames like localhost. */
|
|
143
|
+
private apexFor;
|
|
144
|
+
private getOrCreateBucket;
|
|
145
|
+
private storeParsed;
|
|
146
|
+
/**
|
|
147
|
+
* Returns a canonical lowercase IP literal for bucketing and matching, or null if
|
|
148
|
+
* `host` is not syntactically a valid IPv4 or IPv6 host string (bracketed or not).
|
|
149
|
+
*
|
|
150
|
+
* Uses the URL parser for IPv6 literals so validation does not depend on tldts
|
|
151
|
+
* `isIp`, which is unreliable for bracketed IPv6 and false for unbracketed `::1`
|
|
152
|
+
* in some releases (see tldts#2288).
|
|
153
|
+
*/
|
|
154
|
+
private tryCanonicalIPLiteral;
|
|
155
|
+
private unbracketHost;
|
|
156
|
+
/** Validates domain syntax for setCookie. Accepts hostnames (example.com,
|
|
157
|
+
* localhost, myapp.test), IPv4, and bracketed IPv6 ([::1]). Rejects empty
|
|
158
|
+
* strings, spaces, and other garbage. Does NOT enforce PSL — use
|
|
159
|
+
* parseSetCookieHeader for server responses. */
|
|
160
|
+
private isSyntaxValidDomain;
|
|
161
|
+
/** Returns true if the domain is a recognized public suffix (e.g. co.uk, com).
|
|
162
|
+
* IPs and local hostnames like localhost are not rejected. */
|
|
163
|
+
private isPublicSuffix;
|
|
164
|
+
private isExpired;
|
|
165
|
+
private domainMatches;
|
|
166
|
+
private hostOnlyDomainMatches;
|
|
167
|
+
/**
|
|
168
|
+
* RFC 6265 §5.1.4 — request-path path-matches cookie-path when:
|
|
169
|
+
* (1) identical, (2) cookie-path is a prefix and ends with `/`, or
|
|
170
|
+
* (3) cookie-path is a prefix and the next request-path character is `/`.
|
|
171
|
+
*
|
|
172
|
+
* A single `startsWith(cookiePath + '/')` only covers (1)+(3) and breaks (2):
|
|
173
|
+
* e.g. cookie `/api/` must match `/api/users` without requiring `/api//`.
|
|
174
|
+
*/
|
|
175
|
+
private pathMatches;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
type AdapterType = 'fetch' | 'xhr' | 'node' | 'mock';
|
|
179
|
+
type HTTPMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE' | 'HEAD';
|
|
180
|
+
type ContentType = 'json' | 'text' | 'binary';
|
|
181
|
+
/**
|
|
182
|
+
* Public query value shape compatible with `qs.parse()` output, so consumers
|
|
183
|
+
* do not need `@types/qs` installed just to use this package's declarations.
|
|
184
|
+
*/
|
|
185
|
+
type QueryValue = string | QueryObject | QueryValue[] | undefined;
|
|
186
|
+
interface QueryObject {
|
|
187
|
+
[key: string]: QueryValue;
|
|
188
|
+
}
|
|
189
|
+
interface HTTPAdapter {
|
|
190
|
+
send(request: AdapterRequest): Promise<AdapterResponse>;
|
|
191
|
+
getType(): AdapterType;
|
|
192
|
+
}
|
|
193
|
+
interface AdapterRequest {
|
|
194
|
+
requestURL: string;
|
|
195
|
+
method: HTTPMethod;
|
|
196
|
+
/** Lowercase-keyed outbound headers for this attempt. Some adapters materialize string[] values at send time. */
|
|
197
|
+
headers: Record<string, string | string[]>;
|
|
198
|
+
body?: string | Uint8Array | FormData | null;
|
|
199
|
+
signal?: AbortSignal;
|
|
200
|
+
onUploadProgress?: (event: AdapterProgressEvent) => void;
|
|
201
|
+
onDownloadProgress?: (event: AdapterProgressEvent) => void;
|
|
202
|
+
/**
|
|
203
|
+
* NodeAdapter only. HTTPClient rejects non-node adapters before dispatch if
|
|
204
|
+
* this is set. Called after response headers arrive on a 200 response.
|
|
205
|
+
* Return a writable stream to pipe the body into it, or null to cancel.
|
|
206
|
+
*/
|
|
207
|
+
streamResponse?: StreamResponseFactory;
|
|
208
|
+
/** Passed by the client so NodeAdapter can populate StreamResponseInfo. */
|
|
209
|
+
attemptNumber?: number;
|
|
210
|
+
/** Passed by the client so NodeAdapter can populate StreamResponseInfo. */
|
|
211
|
+
requestID?: string;
|
|
212
|
+
}
|
|
213
|
+
interface AdapterResponse {
|
|
214
|
+
status: number;
|
|
215
|
+
/**
|
|
216
|
+
* Set by adapters when a redirect response was encountered.
|
|
217
|
+
*
|
|
218
|
+
* - **NodeAdapter / MockAdapter**: set on real 3xx responses. Location is
|
|
219
|
+
* accessible, so HTTPClient's redirect loop can follow normally.
|
|
220
|
+
*
|
|
221
|
+
* - **FetchAdapter**: uses `redirect: 'manual'`.
|
|
222
|
+
* - In server runtimes (Bun, Node) this returns the real 3xx with a Location
|
|
223
|
+
* header, so HTTPClient's redirect loop works normally.
|
|
224
|
+
*
|
|
225
|
+
* - In a browser, CORS constraints cause `redirect: 'manual'` to yield an
|
|
226
|
+
* opaque response (status 0, no Location). Browser adapters require
|
|
227
|
+
* `followRedirects: false`, so a detected redirect always results in
|
|
228
|
+
* `redirect_disabled`.
|
|
229
|
+
*
|
|
230
|
+
* - **XHRAdapter**: the browser always follows redirects with no opt-out.
|
|
231
|
+
* Detected after the fact by comparing `xhr.responseURL` to the original
|
|
232
|
+
* URL; resolves as status 0 with `wasRedirectDetected: true`. Same
|
|
233
|
+
* HTTPClient routing as FetchAdapter.
|
|
234
|
+
*/
|
|
235
|
+
wasRedirectDetected?: boolean;
|
|
236
|
+
/**
|
|
237
|
+
* Redirect target when the adapter can determine it, even if the client
|
|
238
|
+
* does not follow that redirect itself.
|
|
239
|
+
*/
|
|
240
|
+
detectedRedirectURL?: string;
|
|
241
|
+
/**
|
|
242
|
+
* True when the adapter wants the client to treat this as a transport-level
|
|
243
|
+
* failure. Adapters may still preserve a diagnostic status code such as 495,
|
|
244
|
+
* or use `status: 0` for generic transport failures. The client routes these
|
|
245
|
+
* through the failed/error path and sets `HTTPResponse.isFailed: true`.
|
|
246
|
+
*/
|
|
247
|
+
isTransportError?: boolean;
|
|
248
|
+
/**
|
|
249
|
+
* Set to `false` when the adapter knows the request/response progressed far
|
|
250
|
+
* enough that replay is unsafe, even if `status` would normally be retried
|
|
251
|
+
* by the client (for example a mid-upload socket write failure after some
|
|
252
|
+
* bytes may already have left the process).
|
|
253
|
+
*/
|
|
254
|
+
isRetryable?: boolean;
|
|
255
|
+
/**
|
|
256
|
+
* Final request headers actually used by the adapter after adapter-local
|
|
257
|
+
* mutations (for example multipart Content-Type/Content-Length added by the
|
|
258
|
+
* Node adapter). When present, HTTPClient merges these into the observer-
|
|
259
|
+
* facing AttemptRequest snapshot so response/error observers see the real
|
|
260
|
+
* outgoing headers rather than only the pre-adapter request shape.
|
|
261
|
+
*/
|
|
262
|
+
effectiveRequestHeaders?: Record<string, string | string[]>;
|
|
263
|
+
/**
|
|
264
|
+
* Most headers are `string`. `set-cookie` is always `string[]` (HTTP spec,
|
|
265
|
+
* each cookie is a separate header line).
|
|
266
|
+
*
|
|
267
|
+
* Adapters may use any header casing, `HTTPClient` normalizes keys to
|
|
268
|
+
* lowercase before redirect handling, cookies, and the public `HTTPResponse`.
|
|
269
|
+
*/
|
|
270
|
+
headers: Record<string, string | string[]>;
|
|
271
|
+
/** Raw response bytes. Higher layers decode/parse based on content-type. */
|
|
272
|
+
body: Uint8Array | null;
|
|
273
|
+
/**
|
|
274
|
+
* Set by NodeAdapter when the body was piped to a StreamResponseFactory writable.
|
|
275
|
+
* The client skips body parsing and sets isStreamed: true on HTTPResponse.
|
|
276
|
+
*/
|
|
277
|
+
isStreamed?: boolean;
|
|
278
|
+
/**
|
|
279
|
+
* Set by NodeAdapter when response-body delivery fails after headers arrive
|
|
280
|
+
* (for example buffered-download truncation, writable errors, or upstream
|
|
281
|
+
* response-stream errors). The client treats this as a terminal stream
|
|
282
|
+
* failure: isStreamError: true. The real HTTP status is preserved (not
|
|
283
|
+
* zeroed) so observers can tell the server responded but body delivery still
|
|
284
|
+
* failed locally/in-flight.
|
|
285
|
+
*/
|
|
286
|
+
isStreamError?: boolean;
|
|
287
|
+
/**
|
|
288
|
+
* Optional sub-classification for `isStreamError` so the client can surface a
|
|
289
|
+
* more precise `HTTPClientError.code` while keeping `HTTPResponse`
|
|
290
|
+
* transport-agnostic.
|
|
291
|
+
*/
|
|
292
|
+
streamErrorCode?: 'stream_write_error' | 'stream_response_error';
|
|
293
|
+
/**
|
|
294
|
+
* Underlying error for an absorbed adapter failure. Populated for transport
|
|
295
|
+
* failures the adapter resolves instead of throwing, and for streamed-body
|
|
296
|
+
* failures carried via `isStreamError`.
|
|
297
|
+
*/
|
|
298
|
+
errorCause?: Error;
|
|
299
|
+
}
|
|
300
|
+
/**
|
|
301
|
+
* Minimal write-capable stream interface. Structurally matches Node.js Writable
|
|
302
|
+
* without importing from 'node:stream', keeping this file isomorphic.
|
|
303
|
+
*/
|
|
304
|
+
interface WritableLike {
|
|
305
|
+
write(chunk: Uint8Array | string, cb?: (err?: Error | null) => void): boolean;
|
|
306
|
+
end(cb?: () => void): void;
|
|
307
|
+
on(event: 'error', listener: (err: Error) => void): this;
|
|
308
|
+
on(event: 'close', listener: () => void): this;
|
|
309
|
+
on(event: 'drain', listener: () => void): this;
|
|
310
|
+
once(event: 'drain', listener: () => void): this;
|
|
311
|
+
destroy(error?: Error): void;
|
|
312
|
+
}
|
|
313
|
+
/**
|
|
314
|
+
* Info passed to a StreamResponseFactory. Status is always 200 — the factory
|
|
315
|
+
* is never called for any other status code.
|
|
316
|
+
*/
|
|
317
|
+
interface StreamResponseInfo {
|
|
318
|
+
/** Always 200 — factory is not called for any other status. */
|
|
319
|
+
status: 200;
|
|
320
|
+
/** Response headers for this attempt. Most values are string; set-cookie is string[]. */
|
|
321
|
+
headers: Record<string, string | string[]>;
|
|
322
|
+
/** Fully resolved request URL for this attempt. */
|
|
323
|
+
url: string;
|
|
324
|
+
/** Which attempt this is (1-based). Increments on retry. */
|
|
325
|
+
attempt: number;
|
|
326
|
+
/** ULID assigned to this send() call. */
|
|
327
|
+
requestID: string;
|
|
328
|
+
}
|
|
329
|
+
/**
|
|
330
|
+
* Context passed alongside StreamResponseInfo. Provides an attempt-scoped
|
|
331
|
+
* AbortSignal that fires on cancel, timeout, or stream write failure — so
|
|
332
|
+
* cleanup logic (delete partial file, close import stream, etc.) can be
|
|
333
|
+
* co-located with the stream setup code instead of scattered across observers.
|
|
334
|
+
*/
|
|
335
|
+
interface StreamResponseContext {
|
|
336
|
+
/** Fires on cancel, timeout, or stream write failure for this attempt. */
|
|
337
|
+
signal: AbortSignal;
|
|
338
|
+
}
|
|
339
|
+
/**
|
|
340
|
+
* Returned from a StreamResponseFactory to cancel the stream with an optional reason.
|
|
341
|
+
* Equivalent to returning null, but allows attaching a reason to HTTPClientError.cancelReason.
|
|
342
|
+
*/
|
|
343
|
+
interface StreamResponseCancel {
|
|
344
|
+
cancel: true;
|
|
345
|
+
reason?: string;
|
|
346
|
+
}
|
|
347
|
+
/**
|
|
348
|
+
* Factory called by NodeAdapter after response headers arrive on a 200.
|
|
349
|
+
* Return a WritableLike to pipe the body into it, null to cancel, or
|
|
350
|
+
* `{ cancel: true, reason?: string }` to cancel with an optional reason string
|
|
351
|
+
* surfaced on HTTPClientError.cancelReason.
|
|
352
|
+
* May be async.
|
|
353
|
+
*/
|
|
354
|
+
type StreamResponseFactory = (info: StreamResponseInfo, context: StreamResponseContext) => WritableLike | null | StreamResponseCancel | Promise<WritableLike | null | StreamResponseCancel>;
|
|
355
|
+
/**
|
|
356
|
+
* Raw progress event emitted by adapters. Does not include `attemptNumber` or
|
|
357
|
+
* `hopNumber` — those are injected by `HTTPClient` when forwarding to consumers.
|
|
358
|
+
*/
|
|
359
|
+
interface AdapterProgressEvent {
|
|
360
|
+
loaded: number;
|
|
361
|
+
total: number;
|
|
362
|
+
/** 0–1, or -1 if total unknown */
|
|
363
|
+
progress: number;
|
|
364
|
+
}
|
|
365
|
+
interface HTTPProgressEvent extends AdapterProgressEvent {
|
|
366
|
+
attemptNumber: number;
|
|
367
|
+
/** Present only during redirect hops (1 = first redirect, 2 = second, etc.). */
|
|
368
|
+
hopNumber?: number;
|
|
369
|
+
}
|
|
370
|
+
interface AttemptStartEvent {
|
|
371
|
+
attemptNumber: number;
|
|
372
|
+
isRetry: boolean;
|
|
373
|
+
requestID: string;
|
|
374
|
+
/**
|
|
375
|
+
* The original resolved URL for this `send()` before redirect follow-ups.
|
|
376
|
+
*
|
|
377
|
+
* During `initial` interceptors this is the pre-interceptor resolved URL.
|
|
378
|
+
* In later phases it matches {@link HTTPResponse.initialURL}. This is not
|
|
379
|
+
* the URL of the current adapter attempt — see
|
|
380
|
+
* {@link InterceptedRequest.requestURL} and `redirect.to` for hop context.
|
|
381
|
+
*/
|
|
382
|
+
initialURL: string;
|
|
383
|
+
/**
|
|
384
|
+
* Set for adapter attempts that are part of a redirect follow-up (1 = first hop).
|
|
385
|
+
* Aligns with {@link HTTPProgressEvent.hopNumber}.
|
|
386
|
+
*/
|
|
387
|
+
hopNumber?: number;
|
|
388
|
+
/**
|
|
389
|
+
* When `hopNumber` is set, the redirect hop this attempt belongs to — same fields as
|
|
390
|
+
* `RequestPhase` `redirect` / `retry.redirect` for observers.
|
|
391
|
+
*/
|
|
392
|
+
redirect?: RedirectHopInfo;
|
|
393
|
+
}
|
|
394
|
+
interface AttemptEndEvent {
|
|
395
|
+
attemptNumber: number;
|
|
396
|
+
isRetry: boolean;
|
|
397
|
+
willRetry: boolean;
|
|
398
|
+
/** Present only when a retry has been scheduled after this attempt. */
|
|
399
|
+
nextRetryDelayMS?: number;
|
|
400
|
+
/** Epoch ms for the scheduled retry, when a retry has been scheduled. */
|
|
401
|
+
nextRetryAt?: number;
|
|
402
|
+
status: number;
|
|
403
|
+
requestID: string;
|
|
404
|
+
/** Same as {@link AttemptStartEvent.initialURL}. */
|
|
405
|
+
initialURL: string;
|
|
406
|
+
hopNumber?: number;
|
|
407
|
+
redirect?: RedirectHopInfo;
|
|
408
|
+
}
|
|
409
|
+
interface HTTPClientConfig {
|
|
410
|
+
adapter?: HTTPAdapter;
|
|
411
|
+
/**
|
|
412
|
+
* Origin / prefix for relative request paths (`/api/...`, `v1/...`, etc.).
|
|
413
|
+
*
|
|
414
|
+
* Omit or leave unset only when every {@link BaseHTTPClient.request} path is
|
|
415
|
+
* already absolute, or when you resolve URLs yourself.
|
|
416
|
+
*
|
|
417
|
+
* Relative paths are appended to this value. Full `http(s)://…` paths (and
|
|
418
|
+
* `//host/…`) skip concatenation with `baseURL`; see JSDoc on
|
|
419
|
+
* `BaseHTTPClient.request` and `buildURL` in `utils.ts`.
|
|
420
|
+
*/
|
|
421
|
+
baseURL?: string;
|
|
422
|
+
defaultHeaders?: Record<string, string | string[]>;
|
|
423
|
+
timeout?: number;
|
|
424
|
+
cookieJar?: CookieJar | null;
|
|
425
|
+
retryPolicy?: RetryPolicyOptions;
|
|
426
|
+
includeRequestID?: boolean;
|
|
427
|
+
includeAttemptHeader?: boolean;
|
|
428
|
+
userAgent?: string;
|
|
429
|
+
followRedirects?: boolean;
|
|
430
|
+
maxRedirects?: number;
|
|
431
|
+
}
|
|
432
|
+
interface SubClientConfig extends Partial<HTTPClientConfig> {
|
|
433
|
+
/**
|
|
434
|
+
* How sub-client `defaultHeaders` should interact with inherited defaults.
|
|
435
|
+
* Default: `'replace'`.
|
|
436
|
+
*
|
|
437
|
+
* Use `'merge'` to preserve inherited defaults and layer new headers on top.
|
|
438
|
+
*
|
|
439
|
+
* If no new `defaultHeaders` are provided, `'merge'` keeps the inherited
|
|
440
|
+
* defaults as-is.
|
|
441
|
+
*/
|
|
442
|
+
defaultHeadersStrategy?: 'replace' | 'merge';
|
|
443
|
+
}
|
|
444
|
+
interface HTTPRequestOptions {
|
|
445
|
+
headers?: Record<string, string | string[]>;
|
|
446
|
+
params?: Record<string, unknown>;
|
|
447
|
+
body?: unknown;
|
|
448
|
+
timeout?: number;
|
|
449
|
+
signal?: AbortSignal;
|
|
450
|
+
retryPolicy?: RetryPolicyOptions | null;
|
|
451
|
+
label?: string;
|
|
452
|
+
onUploadProgress?: (event: HTTPProgressEvent) => void;
|
|
453
|
+
onDownloadProgress?: (event: HTTPProgressEvent) => void;
|
|
454
|
+
onAttemptStart?: (event: AttemptStartEvent) => void;
|
|
455
|
+
onAttemptEnd?: (event: AttemptEndEvent) => void;
|
|
456
|
+
/**
|
|
457
|
+
* NodeAdapter only. Called after response headers arrive on a 200 response.
|
|
458
|
+
* Return a WritableLike to pipe the body to it, or null to cancel.
|
|
459
|
+
* HTTPClient rejects non-node adapters before dispatch if this is set.
|
|
460
|
+
*/
|
|
461
|
+
streamResponse?: StreamResponseFactory;
|
|
462
|
+
}
|
|
463
|
+
interface HTTPResponse<T = unknown> {
|
|
464
|
+
status: number;
|
|
465
|
+
/**
|
|
466
|
+
* Lowercase keys (normalized by `HTTPClient` from adapter output).
|
|
467
|
+
* Most values are `string`; `set-cookie` is `string[]`.
|
|
468
|
+
*/
|
|
469
|
+
headers: Record<string, string | string[]>;
|
|
470
|
+
body: T;
|
|
471
|
+
contentType: ContentType;
|
|
472
|
+
isJSON: boolean;
|
|
473
|
+
isText: boolean;
|
|
474
|
+
isCancelled: boolean;
|
|
475
|
+
isTimeout: boolean;
|
|
476
|
+
isNetworkError: boolean;
|
|
477
|
+
/**
|
|
478
|
+
* True when this response settled through the client's failed/error path.
|
|
479
|
+
*
|
|
480
|
+
* In practice this is true for responses flagged as `isCancelled`,
|
|
481
|
+
* `isTimeout`, `isNetworkError`, `isStreamError`, or adapter
|
|
482
|
+
* `isTransportError`, as well as other client-level failures that resolve to
|
|
483
|
+
* `status: 0` (for example request setup, interceptor, and redirect
|
|
484
|
+
* control-flow failures). For adapter-originated transport failures,
|
|
485
|
+
* `isTransportError` is the explicit signal; bare `status: 0` is also
|
|
486
|
+
* treated as a transport failure by the client.
|
|
487
|
+
*
|
|
488
|
+
* Ordinary HTTP responses, including HTTP error statuses like 4xx/5xx,
|
|
489
|
+
* remain `false`. This tracks client-level failure handling, not HTTP
|
|
490
|
+
* status class.
|
|
491
|
+
*/
|
|
492
|
+
isFailed: boolean;
|
|
493
|
+
isParseError: boolean;
|
|
494
|
+
/**
|
|
495
|
+
* Request URL for this `send()` after `initial` interceptors, before any
|
|
496
|
+
* redirect follow-ups. Same string as {@link AttemptStartEvent.initialURL} on attempt hooks.
|
|
497
|
+
* Real transport adapters use an absolute http(s) URL here. MockAdapter
|
|
498
|
+
* path-only requests without a client `baseURL` are materialized as
|
|
499
|
+
* `http://localhost/...` before interceptors run.
|
|
500
|
+
*/
|
|
501
|
+
initialURL: string;
|
|
502
|
+
/**
|
|
503
|
+
* URL of the request that produced this response (last adapter attempt after redirects).
|
|
504
|
+
* Equals {@link HTTPResponse.initialURL} when no redirect occurred.
|
|
505
|
+
*/
|
|
506
|
+
requestURL: string;
|
|
507
|
+
/** True when a redirect response was encountered at any point (followed or not). */
|
|
508
|
+
wasRedirectDetected: boolean;
|
|
509
|
+
/** True when HTTPClient's own redirect loop followed at least one hop. */
|
|
510
|
+
wasRedirectFollowed: boolean;
|
|
511
|
+
/** Redirect target when known but not followed by the client loop. */
|
|
512
|
+
detectedRedirectURL?: string;
|
|
513
|
+
/**
|
|
514
|
+
* Redirect target URLs recorded during redirect handling, in order.
|
|
515
|
+
* The current detected target may appear here before its follow-up request is
|
|
516
|
+
* dispatched. If a redirect-phase interceptor rewrites that target and
|
|
517
|
+
* redirect handling continues, later entries reflect the rewritten URL.
|
|
518
|
+
*/
|
|
519
|
+
redirectHistory: string[];
|
|
520
|
+
requestID: string;
|
|
521
|
+
adapterType: AdapterType;
|
|
522
|
+
/**
|
|
523
|
+
* True when the response body was piped to a StreamResponseFactory writable.
|
|
524
|
+
* body is null in this case — do not try to parse it.
|
|
525
|
+
*/
|
|
526
|
+
isStreamed: boolean;
|
|
527
|
+
/**
|
|
528
|
+
* True when streamed body delivery fails after headers arrive (disk full,
|
|
529
|
+
* writable destroyed, upstream response-stream error, etc.). The request
|
|
530
|
+
* resolves as a failed response, with the real HTTP status preserved so
|
|
531
|
+
* observers can distinguish a post-header streaming failure from a transport
|
|
532
|
+
* failure (status: 0). Stream setup/factory failures do not set
|
|
533
|
+
* `isStreamError`.
|
|
534
|
+
*/
|
|
535
|
+
isStreamError: boolean;
|
|
536
|
+
}
|
|
537
|
+
type ErrorCode = 'network_error' | 'timeout' | 'cancelled' | 'redirect_disabled' | 'redirect_loop' | 'request_setup_error' | 'adapter_error' | 'interceptor_error' | 'stream_write_error' | 'stream_response_error' | 'stream_setup_error';
|
|
538
|
+
interface HTTPClientError {
|
|
539
|
+
code: ErrorCode;
|
|
540
|
+
message: string;
|
|
541
|
+
cause?: Error;
|
|
542
|
+
/**
|
|
543
|
+
* Original resolved URL for this `send()` before redirects.
|
|
544
|
+
*
|
|
545
|
+
* During `initial` interceptors this is the pre-interceptor resolved URL.
|
|
546
|
+
* In later phases it matches {@link HTTPResponse.initialURL} and
|
|
547
|
+
* {@link AttemptStartEvent.initialURL}.
|
|
548
|
+
* Real transport adapters use an absolute http(s) URL here. MockAdapter
|
|
549
|
+
* path-only requests without a client `baseURL` are materialized as
|
|
550
|
+
* `http://localhost/...` before interceptors run.
|
|
551
|
+
*/
|
|
552
|
+
initialURL: string;
|
|
553
|
+
/** URL of the last request attempt when the error was produced. */
|
|
554
|
+
requestURL: string;
|
|
555
|
+
/** True when a redirect response was encountered at any point (followed or not). */
|
|
556
|
+
wasRedirectDetected: boolean;
|
|
557
|
+
/** True when HTTPClient's own redirect loop followed at least one hop before the error. */
|
|
558
|
+
wasRedirectFollowed: boolean;
|
|
559
|
+
/** Redirect target when known but not followed by the client loop. */
|
|
560
|
+
detectedRedirectURL?: string;
|
|
561
|
+
/**
|
|
562
|
+
* Redirect target URLs recorded during redirect handling, in order.
|
|
563
|
+
* The current detected target may appear here before its follow-up request is
|
|
564
|
+
* dispatched. If a redirect-phase interceptor rewrites that target and
|
|
565
|
+
* redirect handling continues, later entries reflect the rewritten URL.
|
|
566
|
+
*/
|
|
567
|
+
redirectHistory: string[];
|
|
568
|
+
requestID: string;
|
|
569
|
+
isTimeout: boolean;
|
|
570
|
+
/** True when a retry policy was active and all attempts were exhausted before a response was received. */
|
|
571
|
+
isRetriesExhausted: boolean;
|
|
572
|
+
/**
|
|
573
|
+
* Optional reason string when the request was cancelled with an explicit string reason.
|
|
574
|
+
* Set by: a request interceptor returning `{ cancel: true, reason: '...' }`, a
|
|
575
|
+
* StreamResponseFactory returning `{ cancel: true, reason: '...' }`, or an AbortSignal
|
|
576
|
+
* aborted via `controller.abort('reason')`. Undefined when no explicit string reason
|
|
577
|
+
* was provided (e.g. `controller.abort()` with no argument, `builder.cancel()`).
|
|
578
|
+
*/
|
|
579
|
+
cancelReason?: string;
|
|
580
|
+
}
|
|
581
|
+
/**
|
|
582
|
+
* **Phases vs hooks (one mental model)**
|
|
583
|
+
*
|
|
584
|
+
* - **`retry` and `redirect` are both phases** on the same `RequestPhaseName` list. They
|
|
585
|
+
* mean “something intermediate happened before this `send()` finishes,” not “only one
|
|
586
|
+
* kind of thing counts as a phase.” The names differ because the **payload** differs:
|
|
587
|
+
* retries are policy- and attempt-scoped (`attempt`, `maxAttempts`), while redirects are
|
|
588
|
+
* HTTP hop-scoped (`hop`, `from`, `to`, `statusCode`). Same idea as having both
|
|
589
|
+
* `click` and `keydown` in the DOM, different events but the same observer mechanism.
|
|
590
|
+
*
|
|
591
|
+
* - **Global (client-scoped) monitoring:** `addResponseObserver` / `addErrorObserver` on
|
|
592
|
+
* `HTTPClient`, with optional `phases` (plain arrays like `['retry', 'redirect', 'final']`).
|
|
593
|
+
* One callback with
|
|
594
|
+
* `phases: ['retry', 'redirect']` runs **once per matching event**, e.g. a `send()` that
|
|
595
|
+
* redirects once then retries once invokes the handler twice with different `phase.type`,
|
|
596
|
+
* never twice for the same adapter response (a status is either a redirect hop or a retry
|
|
597
|
+
* trigger, not both). The filter list is **OR** on `phase.type`.
|
|
598
|
+
*
|
|
599
|
+
* - **Policy retry (`type: ‘retry’`):** fires once per reattempt. Interceptors and observers
|
|
600
|
+
* always see the URL being retried, which is the redirect target when a redirect preceded
|
|
601
|
+
* the retry. The optional `phase.redirect` field is metadata that tells you which redirect
|
|
602
|
+
* hop led to this retry (`{ hop, from, to, statusCode }`), so you don’t have to track
|
|
603
|
+
* that yourself.
|
|
604
|
+
*
|
|
605
|
+
* - **Per-request (builder-scoped) monitoring:** `onAttemptStart`, `onAttemptEnd`, and
|
|
606
|
+
* progress callbacks fire only for that builder’s `send()`. They do not replace global
|
|
607
|
+
* observers. They are for when you want lifecycle tied to one call without registering
|
|
608
|
+
* on the client. Each **adapter** attempt on the initial URL and on every redirect
|
|
609
|
+
* follow-up (including policy retries on a hop) invokes start/end; `initialURL` on
|
|
610
|
+
* those events matches {@link HTTPResponse.initialURL}. Redirect attempts include
|
|
611
|
+
* `hopNumber` and `redirect` ({@link RedirectHopInfo}) matching observer phases.
|
|
612
|
+
*
|
|
613
|
+
* - **`initial`** is for **request** interceptors (mutate before send), not response/error
|
|
614
|
+
* observers. Settlement-only errors (interceptor throw mid-redirect/retry setup) still
|
|
615
|
+
* use **`final`** on error observers by design.
|
|
616
|
+
*
|
|
617
|
+
* **Per-handler phase subsets:**
|
|
618
|
+
* - Interceptors see: `initial`, `retry`, `redirect` (never `final`)
|
|
619
|
+
* - Response observers see: `retry`, `redirect`, `final` (never `initial`)
|
|
620
|
+
* - Error observers see: `retry`, `final` (never `initial` or `redirect` — redirect errors
|
|
621
|
+
* surface as `final`, retry errors on redirect hops carry `redirect` inside the `retry` phase)
|
|
622
|
+
*/
|
|
623
|
+
type RequestPhaseName = 'initial' | 'retry' | 'redirect' | 'final';
|
|
624
|
+
/** Phases that can reach a {@link RequestInterceptor}. */
|
|
625
|
+
type InterceptorPhaseName = 'initial' | 'retry' | 'redirect';
|
|
626
|
+
/** Phases that can reach a {@link ResponseObserver}. */
|
|
627
|
+
type ResponseObserverPhaseName = 'retry' | 'redirect' | 'final';
|
|
628
|
+
/** Phases that can reach an {@link ErrorObserver}. */
|
|
629
|
+
type ErrorObserverPhaseName = 'retry' | 'final';
|
|
630
|
+
/**
|
|
631
|
+
* One HTTP redirect hop. The **`redirect`** phase and **`retry.redirect`** (policy retry on a
|
|
632
|
+
* post-redirect URL) both use this shape.
|
|
633
|
+
*
|
|
634
|
+
* **`from` / `to`:** Intended to be absolute URLs (the {@link InterceptedRequest.requestURL}
|
|
635
|
+
* that received the redirect response, and the next {@link InterceptedRequest.requestURL}).
|
|
636
|
+
* `Location` is resolved against `from`, then normalized with the client `baseURL` when
|
|
637
|
+
* needed. Without a `baseURL`, path-only strings may remain as paths.
|
|
638
|
+
*/
|
|
639
|
+
type RedirectHopInfo = {
|
|
640
|
+
hop: number;
|
|
641
|
+
from: string;
|
|
642
|
+
to: string;
|
|
643
|
+
statusCode: number;
|
|
644
|
+
};
|
|
645
|
+
/** Discriminated union describing the current phase of a request lifecycle. */
|
|
646
|
+
type RequestPhase = {
|
|
647
|
+
type: 'initial';
|
|
648
|
+
} | {
|
|
649
|
+
type: 'retry';
|
|
650
|
+
attempt: number;
|
|
651
|
+
maxAttempts: number;
|
|
652
|
+
/**
|
|
653
|
+
* When set, this policy retry applies after this redirect hop (same fields as
|
|
654
|
+
* `type: 'redirect'`). Omit when the reattempt still uses the original request URL.
|
|
655
|
+
*/
|
|
656
|
+
redirect?: RedirectHopInfo;
|
|
657
|
+
} | ({
|
|
658
|
+
type: 'redirect';
|
|
659
|
+
} & RedirectHopInfo) | {
|
|
660
|
+
type: 'final';
|
|
661
|
+
};
|
|
662
|
+
/** Narrowed phase union for {@link RequestInterceptor} — excludes `final`. */
|
|
663
|
+
type InterceptorPhase = Extract<RequestPhase, {
|
|
664
|
+
type: InterceptorPhaseName;
|
|
665
|
+
}>;
|
|
666
|
+
/** Narrowed phase union for {@link ResponseObserver} — excludes `initial`. */
|
|
667
|
+
type ResponseObserverPhase = Extract<RequestPhase, {
|
|
668
|
+
type: ResponseObserverPhaseName;
|
|
669
|
+
}>;
|
|
670
|
+
/** Narrowed phase union for {@link ErrorObserver} — the phases actually delivered to the callback (excludes `initial` and `redirect`). */
|
|
671
|
+
type ErrorObserverPhase = Extract<RequestPhase, {
|
|
672
|
+
type: ErrorObserverPhaseName;
|
|
673
|
+
}>;
|
|
674
|
+
interface PhaseFilter {
|
|
675
|
+
methods?: HTTPMethod[];
|
|
676
|
+
hosts?: string[];
|
|
677
|
+
/**
|
|
678
|
+
* Match against the request scheme (`'http'` or `'https'`). In practice
|
|
679
|
+
* `requestURL` is absolute whenever the request could be resolved before
|
|
680
|
+
* dispatch. `MockAdapter` path-only requests without a client `baseURL`
|
|
681
|
+
* are materialized as `http://localhost/...`, browser adapters resolve
|
|
682
|
+
* against `window.location`, and non-mock adapters still require absolute
|
|
683
|
+
* URLs at validation time. The scheme check is skipped only when
|
|
684
|
+
* `requestURL` is absent.
|
|
685
|
+
*/
|
|
686
|
+
schemes?: ('http' | 'https')[];
|
|
687
|
+
}
|
|
688
|
+
/**
|
|
689
|
+
* Filter for request interceptors.
|
|
690
|
+
* Default phases: `['initial']`. Only runs before the first request attempt.
|
|
691
|
+
* Set `phases` to `['initial', 'retry', 'redirect']` to re-run before every attempt.
|
|
692
|
+
*
|
|
693
|
+
* On a redirect follow-up, the first adapter attempt uses phase `redirect`. Further
|
|
694
|
+
* attempts on that same URL (policy retry) use `retry` with the same `redirect` object
|
|
695
|
+
* shape nested under `retry.redirect`.
|
|
696
|
+
*/
|
|
697
|
+
interface RequestInterceptorFilter extends PhaseFilter {
|
|
698
|
+
/**
|
|
699
|
+
* Which events run this interceptor: **OR** over phase type. Valid values: `initial`,
|
|
700
|
+
* `retry`, `redirect`. (`final` never reaches interceptors.)
|
|
701
|
+
*
|
|
702
|
+
* Default: `['initial']`. Pass `['initial', 'retry', 'redirect']` to re-run on every attempt.
|
|
703
|
+
* Pass `[]` to match all phases.
|
|
704
|
+
*/
|
|
705
|
+
phases?: InterceptorPhaseName[];
|
|
706
|
+
/** Match against the outgoing request body. */
|
|
707
|
+
bodyContainsKeys?: string[];
|
|
708
|
+
}
|
|
709
|
+
/**
|
|
710
|
+
* Filter for response observers. See {@link RequestPhaseName} for how `retry` and
|
|
711
|
+
* `redirect` fit together as intermediate phases.
|
|
712
|
+
*
|
|
713
|
+
* Default phases: `[‘final’]`. Terminal **HTTP** response for this `send()` only.
|
|
714
|
+
*
|
|
715
|
+
* - **`’retry’`**: the adapter returned a **retryable HTTP status** (including `0`
|
|
716
|
+
* when it is in the client’s retryable set) and a further attempt will run. Phase is
|
|
717
|
+
* `{ type: ‘retry’, attempt, maxAttempts, redirect? }` (aligned with
|
|
718
|
+
* `onAttemptEnd.attemptNumber`, with `redirect` only for retries on a post-redirect URL).
|
|
719
|
+
* If the adapter **throws** but a retry will follow, that path uses **error**
|
|
720
|
+
* observers with the same phase shape, not response observers.
|
|
721
|
+
* - **`’redirect’`**: each redirect **response** before following `Location`. Phase is
|
|
722
|
+
* `{ type: ‘redirect’, hop, from, to, statusCode }` (see {@link RedirectHopInfo}).
|
|
723
|
+
*
|
|
724
|
+
* When `send()` **settles** with `status === 0`, **error** observers run with phase
|
|
725
|
+
* `final` only. **Before** that, each retryable `0` response in a retry cycle can
|
|
726
|
+
* still invoke **response** observers with phase `retry` when your filter includes it.
|
|
727
|
+
*/
|
|
728
|
+
interface ResponseObserverFilter extends PhaseFilter {
|
|
729
|
+
/**
|
|
730
|
+
* Which events run this observer: **OR** over phase type. Valid values: `retry`,
|
|
731
|
+
* `redirect`, `final`. (`initial` never reaches response observers.)
|
|
732
|
+
*
|
|
733
|
+
* Default: `['final']`. Pass `[]` to match all phases.
|
|
734
|
+
*/
|
|
735
|
+
phases?: ResponseObserverPhaseName[];
|
|
736
|
+
statusCodes?: number[];
|
|
737
|
+
/**
|
|
738
|
+
* Match against the parsed response content type category (`json`, `text`, `binary`).
|
|
739
|
+
*/
|
|
740
|
+
contentTypes?: ContentType[];
|
|
741
|
+
/**
|
|
742
|
+
* Match against the raw `content-type` header, with optional wildcard subtype patterns
|
|
743
|
+
* such as `image/*`.
|
|
744
|
+
*/
|
|
745
|
+
contentTypeHeaders?: string[];
|
|
746
|
+
/**
|
|
747
|
+
* Match against the decoded response body.
|
|
748
|
+
*/
|
|
749
|
+
bodyContainsKeys?: string[];
|
|
750
|
+
}
|
|
751
|
+
/**
|
|
752
|
+
* Filter for error observers. Response-only fields like `statusCodes` and
|
|
753
|
+
* `bodyContainsKeys` are excluded.
|
|
754
|
+
*
|
|
755
|
+
* Default phases: `['final']`. Fires when the request settles with an error,
|
|
756
|
+
* including failures before any adapter call (for example an interceptor
|
|
757
|
+
* throw or interceptor cancel). Interceptor failures during `retry` or
|
|
758
|
+
* `redirect` interceptor phases are also reported as `final`, not as those
|
|
759
|
+
* phase names. That matches settlement semantics, not “which interceptor
|
|
760
|
+
* phase threw.”
|
|
761
|
+
*
|
|
762
|
+
* Include `'retry'` in `phases` to also run when the **adapter** throws but a retry
|
|
763
|
+
* will follow. Same `HTTPClientError` shape as terminal adapter errors,
|
|
764
|
+
* with `isRetriesExhausted: false` and phase
|
|
765
|
+
* `{ type: 'retry', attempt, maxAttempts, redirect? }`. **Interceptor** throws
|
|
766
|
+
* (any phase, including `retry` / `redirect`) never emit this: they abort the chain
|
|
767
|
+
* and only **`final`** error observers run, as in the paragraph above. Retryable **HTTP
|
|
768
|
+
* status** responses (including `0` when retried) use **response** observers for this
|
|
769
|
+
* phase name, not error observers.
|
|
770
|
+
*/
|
|
771
|
+
interface ErrorObserverFilter extends PhaseFilter {
|
|
772
|
+
/**
|
|
773
|
+
* Which events run this observer: **OR** over phase type. Only `retry` and
|
|
774
|
+
* `final` are valid here — `initial` and `redirect` never reach error
|
|
775
|
+
* observers (redirect errors surface as `final`; retry errors on redirect
|
|
776
|
+
* hops carry `redirect` context inside the `retry` phase).
|
|
777
|
+
*
|
|
778
|
+
* Default: `['final']`. Pass `[]` to match all phases.
|
|
779
|
+
*/
|
|
780
|
+
phases?: ErrorObserverPhaseName[];
|
|
781
|
+
}
|
|
782
|
+
/**
|
|
783
|
+
* Signal returned by an interceptor to cancel the request.
|
|
784
|
+
* When returned, the request is aborted and a 'cancelled' error is produced.
|
|
785
|
+
*/
|
|
786
|
+
interface InterceptorCancel {
|
|
787
|
+
cancel: true;
|
|
788
|
+
reason?: string;
|
|
789
|
+
}
|
|
790
|
+
/**
|
|
791
|
+
* Additional context passed to request interceptors so they can see the full
|
|
792
|
+
* request chain without having to track it themselves.
|
|
793
|
+
*/
|
|
794
|
+
interface RequestInterceptorContext {
|
|
795
|
+
/**
|
|
796
|
+
* Original resolved URL for this `send()` before redirects.
|
|
797
|
+
*
|
|
798
|
+
* During `initial` interceptors this is the pre-interceptor resolved URL.
|
|
799
|
+
* In later phases it matches {@link HTTPResponse.initialURL} and
|
|
800
|
+
* {@link AttemptStartEvent.initialURL}.
|
|
801
|
+
*/
|
|
802
|
+
initialURL: string;
|
|
803
|
+
/**
|
|
804
|
+
* Redirect targets already recorded for this send, in order.
|
|
805
|
+
* During redirect-phase interceptors this includes the current detected
|
|
806
|
+
* target before any rewrite returned from that interceptor.
|
|
807
|
+
*/
|
|
808
|
+
redirectHistory: string[];
|
|
809
|
+
/**
|
|
810
|
+
* ULID assigned to this `send()` call.
|
|
811
|
+
* Matches {@link HTTPResponse.requestID} and {@link HTTPClientError.requestID}.
|
|
812
|
+
*/
|
|
813
|
+
requestID: string;
|
|
814
|
+
/**
|
|
815
|
+
* The adapter attempt number that will be dispatched after this interceptor
|
|
816
|
+
* chain completes (1-based). Increments on each retry across all redirect hops.
|
|
817
|
+
*/
|
|
818
|
+
attemptNumber: number;
|
|
819
|
+
}
|
|
820
|
+
type RequestInterceptor = (request: InterceptedRequest, phase: InterceptorPhase, context: RequestInterceptorContext) => InterceptedRequest | InterceptorCancel | null | Promise<InterceptedRequest | InterceptorCancel | null>;
|
|
821
|
+
interface InterceptedRequest {
|
|
822
|
+
/**
|
|
823
|
+
* Pending request shape seen by request interceptors before body serialization.
|
|
824
|
+
* `requestURL` changes on redirect follow-ups and is absolute before dispatch.
|
|
825
|
+
*/
|
|
826
|
+
requestURL: string;
|
|
827
|
+
method: HTTPMethod;
|
|
828
|
+
headers: Record<string, string | string[]>;
|
|
829
|
+
body?: unknown;
|
|
830
|
+
}
|
|
831
|
+
/**
|
|
832
|
+
* Finalized observer-facing snapshot for one adapter attempt, after
|
|
833
|
+
* headers/body have been prepared for dispatch.
|
|
834
|
+
*/
|
|
835
|
+
type AttemptRequest = Omit<AdapterRequest, 'headers' | 'signal' | 'onUploadProgress' | 'onDownloadProgress' | 'streamResponse'> & {
|
|
836
|
+
/**
|
|
837
|
+
* Final request headers visible to response/error observers. These are
|
|
838
|
+
* lowercase-keyed. Most values are `string`, but adapter-local snapshots may
|
|
839
|
+
* preserve repeated request headers as `string[]` when the underlying runtime
|
|
840
|
+
* supports them.
|
|
841
|
+
*/
|
|
842
|
+
headers: Record<string, string | string[]>;
|
|
843
|
+
/**
|
|
844
|
+
* Post-interceptor body before attempt materialization. This is useful for
|
|
845
|
+
* observers that need the semantic payload as well as the adapter-facing body.
|
|
846
|
+
*/
|
|
847
|
+
rawBody?: unknown;
|
|
848
|
+
/** Effective per-attempt timeout in ms. */
|
|
849
|
+
timeout?: number;
|
|
850
|
+
/**
|
|
851
|
+
* 1-based attempt number for this adapter call. Increments on each retry.
|
|
852
|
+
* Undefined on best-effort snapshots produced before any adapter attempt is
|
|
853
|
+
* dispatched (e.g. interceptor errors, pre-send cancellations).
|
|
854
|
+
*/
|
|
855
|
+
attemptNumber?: number;
|
|
856
|
+
/**
|
|
857
|
+
* ULID assigned to this {@link BaseHTTPClient.request} `send()` call.
|
|
858
|
+
* Matches {@link HTTPResponse.requestID} and {@link HTTPClientError.requestID}.
|
|
859
|
+
* Undefined on best-effort snapshots produced before the request ID is assigned
|
|
860
|
+
* (should not occur in practice).
|
|
861
|
+
*/
|
|
862
|
+
requestID?: string;
|
|
863
|
+
};
|
|
864
|
+
type ResponseObserver = (response: HTTPResponse, request: AttemptRequest, phase: ResponseObserverPhase) => void | Promise<void>;
|
|
865
|
+
type ErrorObserver = (error: HTTPClientError, request: AttemptRequest, phase: ErrorObserverPhase) => void | Promise<void>;
|
|
866
|
+
type RequestState = 'pending' | 'sending' | 'waiting_for_retry' | 'completed' | 'cancelled' | 'failed';
|
|
867
|
+
|
|
868
|
+
export { type AdapterType as A, type ErrorObserverPhaseName as B, type ContentType as C, type InterceptorPhase as D, type ErrorObserver as E, type ResponseObserverPhase as F, type ErrorObserverPhase as G, type HTTPAdapter as H, type InterceptorCancel as I, type RedirectHopInfo as J, type StreamResponseInfo as K, type StreamResponseContext as L, type StreamResponseCancel as M, type Cookie as N, type CookieInput as O, type CookieJarJSON as P, type QueryObject as Q, type RequestState as R, type StreamResponseFactory as S, type WritableLike as W, type AdapterRequest as a, type AdapterResponse as b, type HTTPMethod as c, type HTTPRequestOptions as d, type HTTPResponse as e, type HTTPClientError as f, type HTTPProgressEvent as g, type AttemptStartEvent as h, type AttemptEndEvent as i, type HTTPClientConfig as j, type RequestInterceptor as k, type RequestInterceptorFilter as l, type ResponseObserver as m, type ResponseObserverFilter as n, type ErrorObserverFilter as o, type SubClientConfig as p, CookieJar as q, type QueryValue as r, type ErrorCode as s, type RequestInterceptorContext as t, type InterceptedRequest as u, type AttemptRequest as v, type RequestPhase as w, type RequestPhaseName as x, type InterceptorPhaseName as y, type ResponseObserverPhaseName as z };
|