@gcmdev/faxios 0.2.0 → 0.2.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.
Files changed (186) hide show
  1. package/LICENSE +8 -0
  2. package/dist/index.d.ts +4 -3
  3. package/dist/index.d.ts.map +1 -0
  4. package/dist/index.js +1 -1
  5. package/dist/index.js.map +1 -1
  6. package/dist/lib/adapters/fetch-helpers.d.ts +2 -1
  7. package/dist/lib/adapters/fetch-helpers.d.ts.map +1 -0
  8. package/dist/lib/adapters/fetch.d.ts +1 -0
  9. package/dist/lib/adapters/fetch.d.ts.map +1 -0
  10. package/dist/lib/adapters/fetch.js +86 -26
  11. package/dist/lib/adapters/fetch.js.map +1 -1
  12. package/dist/lib/cancel/CanceledError.d.ts +2 -1
  13. package/dist/lib/cancel/CanceledError.d.ts.map +1 -0
  14. package/dist/lib/cancel/isCancel.d.ts +1 -0
  15. package/dist/lib/cancel/isCancel.d.ts.map +1 -0
  16. package/dist/lib/core/Faxios.d.ts +4 -3
  17. package/dist/lib/core/Faxios.d.ts.map +1 -0
  18. package/dist/lib/core/Faxios.js +8 -4
  19. package/dist/lib/core/Faxios.js.map +1 -1
  20. package/dist/lib/core/FaxiosError.d.ts +3 -2
  21. package/dist/lib/core/FaxiosError.d.ts.map +1 -0
  22. package/dist/lib/core/FaxiosHeaders.d.ts +9 -8
  23. package/dist/lib/core/FaxiosHeaders.d.ts.map +1 -0
  24. package/dist/lib/core/buildFullPath.d.ts +1 -0
  25. package/dist/lib/core/buildFullPath.d.ts.map +1 -0
  26. package/dist/lib/core/define.d.ts +20 -3
  27. package/dist/lib/core/define.d.ts.map +1 -0
  28. package/dist/lib/core/define.js.map +1 -1
  29. package/dist/lib/core/dispatchRequest.d.ts +2 -1
  30. package/dist/lib/core/dispatchRequest.d.ts.map +1 -0
  31. package/dist/lib/core/mergeConfig.d.ts +3 -2
  32. package/dist/lib/core/mergeConfig.d.ts.map +1 -0
  33. package/dist/lib/core/mergeConfig.js +40 -2
  34. package/dist/lib/core/mergeConfig.js.map +1 -1
  35. package/dist/lib/core/prepareRequest.d.ts +1 -0
  36. package/dist/lib/core/prepareRequest.d.ts.map +1 -0
  37. package/dist/lib/core/route.d.ts +10 -1
  38. package/dist/lib/core/route.d.ts.map +1 -0
  39. package/dist/lib/core/route.js +2 -5
  40. package/dist/lib/core/route.js.map +1 -1
  41. package/dist/lib/core/settle.d.ts +1 -0
  42. package/dist/lib/core/settle.d.ts.map +1 -0
  43. package/dist/lib/core/transformData.d.ts +3 -2
  44. package/dist/lib/core/transformData.d.ts.map +1 -0
  45. package/dist/lib/core/validateSchema.d.ts +3 -2
  46. package/dist/lib/core/validateSchema.d.ts.map +1 -0
  47. package/dist/lib/defaults/index.d.ts +1 -0
  48. package/dist/lib/defaults/index.d.ts.map +1 -0
  49. package/dist/lib/defaults/transitional.d.ts +1 -0
  50. package/dist/lib/defaults/transitional.d.ts.map +1 -0
  51. package/dist/lib/env/data.d.ts +2 -1
  52. package/dist/lib/env/data.d.ts.map +1 -0
  53. package/dist/lib/env/data.js +1 -1
  54. package/dist/lib/faxios.d.ts +59 -38
  55. package/dist/lib/faxios.d.ts.map +1 -0
  56. package/dist/lib/faxios.js.map +1 -1
  57. package/dist/lib/helpers/FaxiosURLSearchParams.d.ts +1 -0
  58. package/dist/lib/helpers/FaxiosURLSearchParams.d.ts.map +1 -0
  59. package/dist/lib/helpers/HttpStatusCode.d.ts +1 -0
  60. package/dist/lib/helpers/HttpStatusCode.d.ts.map +1 -0
  61. package/dist/lib/helpers/buildURL.d.ts +1 -0
  62. package/dist/lib/helpers/buildURL.d.ts.map +1 -0
  63. package/dist/lib/helpers/cloneConfig.d.ts +1 -1
  64. package/dist/lib/helpers/cloneConfig.d.ts.map +1 -0
  65. package/dist/lib/helpers/cloneConfig.js +2 -2
  66. package/dist/lib/helpers/cloneConfig.js.map +1 -1
  67. package/dist/lib/helpers/combineURLs.d.ts +1 -0
  68. package/dist/lib/helpers/combineURLs.d.ts.map +1 -0
  69. package/dist/lib/helpers/composeSignals.d.ts +1 -0
  70. package/dist/lib/helpers/composeSignals.d.ts.map +1 -0
  71. package/dist/lib/helpers/cookies.d.ts +2 -1
  72. package/dist/lib/helpers/cookies.d.ts.map +1 -0
  73. package/dist/lib/helpers/estimateDataURLDecodedBytes.d.ts +1 -0
  74. package/dist/lib/helpers/estimateDataURLDecodedBytes.d.ts.map +1 -0
  75. package/dist/lib/helpers/formDataToJSON.d.ts +2 -1
  76. package/dist/lib/helpers/formDataToJSON.d.ts.map +1 -0
  77. package/dist/lib/helpers/formDataToJSON.js +1 -1
  78. package/dist/lib/helpers/formDataToJSON.js.map +1 -1
  79. package/dist/lib/helpers/isAbsoluteURL.d.ts +1 -0
  80. package/dist/lib/helpers/isAbsoluteURL.d.ts.map +1 -0
  81. package/dist/lib/helpers/isFaxiosError.d.ts +1 -0
  82. package/dist/lib/helpers/isFaxiosError.d.ts.map +1 -0
  83. package/dist/lib/helpers/isURLSameOrigin.d.ts +1 -0
  84. package/dist/lib/helpers/isURLSameOrigin.d.ts.map +1 -0
  85. package/dist/lib/helpers/parseHeaders.d.ts +2 -1
  86. package/dist/lib/helpers/parseHeaders.d.ts.map +1 -0
  87. package/dist/lib/helpers/progressEventReducer.d.ts +1 -0
  88. package/dist/lib/helpers/progressEventReducer.d.ts.map +1 -0
  89. package/dist/lib/helpers/sanitizeHeaderValue.d.ts +1 -0
  90. package/dist/lib/helpers/sanitizeHeaderValue.d.ts.map +1 -0
  91. package/dist/lib/helpers/speedometer.d.ts +1 -0
  92. package/dist/lib/helpers/speedometer.d.ts.map +1 -0
  93. package/dist/lib/helpers/substitutePathParams.d.ts +1 -0
  94. package/dist/lib/helpers/substitutePathParams.d.ts.map +1 -0
  95. package/dist/lib/helpers/substitutePathParams.js +11 -3
  96. package/dist/lib/helpers/substitutePathParams.js.map +1 -1
  97. package/dist/lib/helpers/throttle.d.ts +1 -0
  98. package/dist/lib/helpers/throttle.d.ts.map +1 -0
  99. package/dist/lib/helpers/toFormData.d.ts +1 -0
  100. package/dist/lib/helpers/toFormData.d.ts.map +1 -0
  101. package/dist/lib/helpers/toFormData.js +4 -2
  102. package/dist/lib/helpers/toFormData.js.map +1 -1
  103. package/dist/lib/helpers/toURLEncodedForm.d.ts +1 -0
  104. package/dist/lib/helpers/toURLEncodedForm.d.ts.map +1 -0
  105. package/dist/lib/helpers/trackStream.d.ts +1 -0
  106. package/dist/lib/helpers/trackStream.d.ts.map +1 -0
  107. package/dist/lib/helpers/validator.d.ts +2 -1
  108. package/dist/lib/helpers/validator.d.ts.map +1 -0
  109. package/dist/lib/platform.d.ts +1 -0
  110. package/dist/lib/platform.d.ts.map +1 -0
  111. package/dist/lib/plugins/attempt.d.ts +1 -0
  112. package/dist/lib/plugins/attempt.d.ts.map +1 -0
  113. package/dist/lib/plugins/authBearer.d.ts +10 -5
  114. package/dist/lib/plugins/authBearer.d.ts.map +1 -0
  115. package/dist/lib/plugins/authBearer.js +6 -0
  116. package/dist/lib/plugins/authBearer.js.map +1 -1
  117. package/dist/lib/plugins/definePlugin.d.ts +4 -3
  118. package/dist/lib/plugins/definePlugin.d.ts.map +1 -0
  119. package/dist/lib/plugins/retry.d.ts +24 -23
  120. package/dist/lib/plugins/retry.d.ts.map +1 -0
  121. package/dist/lib/plugins/timing.d.ts +9 -8
  122. package/dist/lib/plugins/timing.d.ts.map +1 -0
  123. package/dist/lib/types/standard-schema.d.ts +7 -6
  124. package/dist/lib/types/standard-schema.d.ts.map +1 -0
  125. package/dist/lib/types.d.ts +104 -92
  126. package/dist/lib/types.d.ts.map +1 -0
  127. package/dist/lib/utils.d.ts +5 -3
  128. package/dist/lib/utils.d.ts.map +1 -0
  129. package/dist/lib/utils.js +9 -2
  130. package/dist/lib/utils.js.map +1 -1
  131. package/package.json +8 -4
  132. package/skills/configure-requests/SKILL.md +53 -8
  133. package/skills/configure-requests/references/plugins.md +54 -6
  134. package/src/index.ts +57 -0
  135. package/src/lib/adapters/fetch-helpers.ts +167 -0
  136. package/src/lib/adapters/fetch.ts +871 -0
  137. package/src/lib/cancel/CanceledError.ts +23 -0
  138. package/src/lib/cancel/isCancel.ts +10 -0
  139. package/src/lib/core/Faxios.ts +517 -0
  140. package/src/lib/core/FaxiosError.ts +283 -0
  141. package/src/lib/core/FaxiosHeaders.ts +529 -0
  142. package/src/lib/core/buildFullPath.ts +51 -0
  143. package/src/lib/core/define.ts +127 -0
  144. package/src/lib/core/dispatchRequest.ts +171 -0
  145. package/src/lib/core/mergeConfig.ts +213 -0
  146. package/src/lib/core/prepareRequest.ts +155 -0
  147. package/src/lib/core/route.ts +65 -0
  148. package/src/lib/core/settle.ts +20 -0
  149. package/src/lib/core/transformData.ts +23 -0
  150. package/src/lib/core/validateSchema.ts +53 -0
  151. package/src/lib/defaults/index.ts +221 -0
  152. package/src/lib/defaults/transitional.ts +9 -0
  153. package/src/lib/env/data.ts +1 -0
  154. package/src/lib/faxios.ts +306 -0
  155. package/src/lib/helpers/FaxiosURLSearchParams.ts +52 -0
  156. package/src/lib/helpers/HttpStatusCode.ts +77 -0
  157. package/src/lib/helpers/buildURL.ts +77 -0
  158. package/src/lib/helpers/cloneConfig.ts +22 -0
  159. package/src/lib/helpers/combineURLs.ts +11 -0
  160. package/src/lib/helpers/composeSignals.ts +74 -0
  161. package/src/lib/helpers/cookies.ts +68 -0
  162. package/src/lib/helpers/deprecatedMethod.ts +32 -0
  163. package/src/lib/helpers/estimateDataURLDecodedBytes.ts +95 -0
  164. package/src/lib/helpers/formDataToJSON.ts +122 -0
  165. package/src/lib/helpers/isAbsoluteURL.ts +13 -0
  166. package/src/lib/helpers/isFaxiosError.ts +19 -0
  167. package/src/lib/helpers/isURLSameOrigin.ts +16 -0
  168. package/src/lib/helpers/parseHeaders.ts +76 -0
  169. package/src/lib/helpers/progressEventReducer.ts +57 -0
  170. package/src/lib/helpers/sanitizeHeaderValue.ts +61 -0
  171. package/src/lib/helpers/speedometer.ts +55 -0
  172. package/src/lib/helpers/substitutePathParams.ts +23 -0
  173. package/src/lib/helpers/throttle.ts +45 -0
  174. package/src/lib/helpers/toFormData.ts +298 -0
  175. package/src/lib/helpers/toURLEncodedForm.ts +14 -0
  176. package/src/lib/helpers/trackStream.ts +123 -0
  177. package/src/lib/helpers/validator.ts +114 -0
  178. package/src/lib/platform.ts +44 -0
  179. package/src/lib/plugins/attempt.ts +3 -0
  180. package/src/lib/plugins/authBearer.ts +129 -0
  181. package/src/lib/plugins/definePlugin.ts +34 -0
  182. package/src/lib/plugins/retry.ts +245 -0
  183. package/src/lib/plugins/timing.ts +76 -0
  184. package/src/lib/types/standard-schema.ts +58 -0
  185. package/src/lib/types.ts +496 -0
  186. package/src/lib/utils.ts +941 -0
@@ -0,0 +1,871 @@
1
+ import FaxiosError from "../core/FaxiosError.js";
2
+ import FaxiosHeaders from "../core/FaxiosHeaders.js";
3
+ import prepareRequest from "../core/prepareRequest.js";
4
+ import { settle } from "../core/settle.js";
5
+ import { VERSION } from "../env/data.js";
6
+ import composeSignals from "../helpers/composeSignals.js";
7
+ import estimateDataURLDecodedBytes from "../helpers/estimateDataURLDecodedBytes.js";
8
+ import {
9
+ asyncDecorator,
10
+ progressEventDecorator,
11
+ progressEventReducer
12
+ } from "../helpers/progressEventReducer.js";
13
+ import { toByteStringHeaderObject } from "../helpers/sanitizeHeaderValue.js";
14
+ import { trackStream } from "../helpers/trackStream.js";
15
+ import platform from "../platform.js";
16
+ import type {
17
+ FaxiosRequestHeaders,
18
+ InternalFaxiosRequestConfig
19
+ } from "../types.js";
20
+ import utils from "../utils.js";
21
+ import { checkDeclaredContentLength, checkMaterializedSize, cleanFormDataContentType, encodeBodyIfNeeded, handleFetchCaughtError } from "./fetch-helpers.js";
22
+ import type { ComposedSignal, PendingBodyErrorRef } from "./fetch-helpers.js";
23
+
24
+ // btoa is a global in Node 16+ and browsers; accessed via globalThis for no-DOM lib compat
25
+ const _btoa: (data: string) => string = (
26
+ globalThis as unknown as Record<string, unknown>
27
+ )["btoa"] as (data: string) => string;
28
+ type AnyConstructor = new (...args: Array<unknown>) => unknown;
29
+ type FetchFn = (input: unknown, init?: unknown) => Promise<AnyResponse>;
30
+ type AnyResponse = {
31
+ [key: string]: unknown;
32
+ arrayBuffer: () => Promise<ArrayBuffer>;
33
+ body: unknown;
34
+ headers: {
35
+ [key: string]: unknown;
36
+ get: (name: string) => null | string;
37
+ has: (name: string) => boolean;
38
+ };
39
+ status: number;
40
+ statusText: string;
41
+ };
42
+ type AnyRequest = {
43
+ [key: string]: unknown;
44
+ arrayBuffer: () => Promise<ArrayBuffer>;
45
+ body: null | { cancel: () => Promise<void>; };
46
+ headers: {
47
+ get: (name: string) => null | string;
48
+ has: (name: string) => boolean;
49
+ };
50
+ };
51
+ type AnyReadableStream = {
52
+ [key: string]: unknown;
53
+ cancel: () => Promise<void>;
54
+ };
55
+ type AnyTextEncoder = { encode: (str: string) => Uint8Array; };
56
+
57
+ const DEFAULT_CHUNK_SIZE = 64 * 1024;
58
+
59
+ const { isFunction } = utils;
60
+
61
+ // Web stream bodies this adapter has sent. fetch can read a stream without leaving it locked,
62
+ // and there's no portable "disturbed" check, so a second send is caught here.
63
+ const sentStreamBodies = new WeakSet<object>();
64
+
65
+ // A stream body can be read once. Middleware that calls next() again after a try read it
66
+ // would send an empty body (Node stream) or get an uncoded TypeError from fetch (web stream).
67
+ const rejectUsedStreamBody = (data: unknown, config: InternalFaxiosRequestConfig): void => {
68
+ const body = data as { bodyUsed?: unknown; destroyed?: unknown; locked?: unknown; readableDidRead?: unknown; readableEnded?: unknown; };
69
+ const isWebStream = utils.isReadableStream!(data);
70
+ if (
71
+ (isWebStream && (body.locked === true || sentStreamBodies.has(body))) ||
72
+ ((utils.isRequest!(data) || utils.isResponse!(data)) && body.bodyUsed === true) ||
73
+ (utils.isStream(data) && (body.readableDidRead === true || body.readableEnded === true || body.destroyed === true))
74
+ ) {
75
+ throw new FaxiosError(
76
+ "Request body stream was already used and can't be sent again; pass a new body for each try",
77
+ FaxiosError.ERR_BAD_REQUEST,
78
+ config
79
+ );
80
+ }
81
+ if (isWebStream) sentStreamBodies.add(body);
82
+ };
83
+
84
+ // Server runtimes default User-Agent to their own (e.g. "node"), so name faxios there.
85
+ // Browsers send their own User-Agent; setting one makes every cross-origin request
86
+ // need a CORS preflight that most APIs don't allow.
87
+ const setsUserAgent = !platform.hasStandardBrowserEnv && !platform.hasStandardBrowserWebWorkerEnv;
88
+ const applyDefaultUserAgent = (headers: FaxiosRequestHeaders): void => {
89
+ if (setsUserAgent) headers.set("User-Agent", "faxios/" + VERSION, false);
90
+ };
91
+
92
+ /**
93
+ * Encode a UTF-8 string to a Latin-1 byte string for use with btoa().
94
+ * This is a modern replacement for the deprecated unescape(encodeURIComponent(str)) pattern.
95
+ *
96
+ * @param {string} str The string to encode
97
+ *
98
+ * @returns {string} UTF-8 bytes as a Latin-1 string
99
+ */
100
+ const encodeUTF8 = (str: string): string =>
101
+ encodeURIComponent(str).replace(
102
+ /%([0-9A-F]{2})/gi,
103
+ (_: string, hex: string) => String.fromCharCode(parseInt(hex, 16))
104
+ );
105
+
106
+ // Node's WHATWG URL parser returns `username` and `password` percent-encoded.
107
+ // Decode before composing the `auth` option so credentials such as
108
+ // `my%40email.com:pass` are sent as `my@email.com:pass`. Falls back to the
109
+ // original value for malformed input so a bad encoding never throws.
110
+ const decodeURIComponentSafe = (value: unknown): unknown => {
111
+ if (!utils.isString(value)) {
112
+ return value;
113
+ }
114
+
115
+ try {
116
+ return decodeURIComponent(value as string);
117
+ }
118
+ catch {
119
+ return value;
120
+ }
121
+ };
122
+
123
+ const test = (
124
+ fn: (...args: Array<unknown>) => unknown,
125
+ ...args: Array<unknown>
126
+ ): boolean => {
127
+ try {
128
+ return !!fn(...args);
129
+ }
130
+ catch {
131
+ return false;
132
+ }
133
+ };
134
+
135
+ const maybeWithAuthCredentials = (url: string): boolean => {
136
+ const protocolIndex = url.indexOf("://");
137
+ const urlToCheck = protocolIndex !== -1 ? url.slice(protocolIndex + 3) : url;
138
+ // Only `@` indicates credentials in the authority; `:` alone is a port separator.
139
+ return urlToCheck.includes("@");
140
+ };
141
+
142
+ // RequestInit members faxios sets itself; the same keys in `fetchOptions` are ignored.
143
+ const FAXIOS_INIT_KEYS: Record<string, true> = {
144
+ body: true,
145
+ credentials: true,
146
+ duplex: true,
147
+ headers: true,
148
+ method: true,
149
+ signal: true,
150
+ };
151
+
152
+ // Spec defaults written as own init members when the caller sets none. undici converts the init
153
+ // into a plain dictionary and reads absent members from it, so an absent member still picks up a
154
+ // polluted Object.prototype value; an own default leaves nothing to inherit.
155
+ const REQUEST_INIT_DEFAULTS: Record<string, unknown> = {
156
+ cache: "default",
157
+ integrity: "",
158
+ keepalive: false,
159
+ mode: "cors",
160
+ redirect: "follow",
161
+ referrer: "about:client",
162
+ referrerPolicy: "",
163
+ };
164
+
165
+ // Where undici (Node's fetch) keeps its global dispatcher.
166
+ const UNDICI_GLOBAL_DISPATCHER = Symbol.for("undici.globalDispatcher.1");
167
+
168
+ type FetchEnv = {
169
+ fetch?: unknown;
170
+ Request?: unknown;
171
+ Response?: unknown;
172
+ };
173
+
174
+ // eslint-disable-next-line sonarjs/function-return-type
175
+ const factory = (env: FetchEnv) => {
176
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
177
+ const globalObject: Record<string, unknown> = utils.global ?? globalThis;
178
+ const ReadableStream = globalObject["ReadableStream"] as
179
+ | (AnyConstructor & { prototype: AnyReadableStream; })
180
+ | undefined;
181
+ const TextEncoder = globalObject["TextEncoder"] as
182
+ | (new () => AnyTextEncoder)
183
+ | undefined;
184
+
185
+ // `undefined` falls back to the global constructor; `null` disables it.
186
+ const envFetch = env.fetch as FetchFn | undefined;
187
+ const Request = (env.Request === undefined ? globalObject["Request"] : env.Request) as AnyConstructor | undefined;
188
+ const Response = (env.Response === undefined ? globalObject["Response"] : env.Response) as AnyConstructor | undefined;
189
+ const _globalFetch = (globalThis as unknown as Record<string, unknown>)[
190
+ "fetch"
191
+ ];
192
+ const isFetchSupported = envFetch
193
+ ? isFunction(envFetch)
194
+ : isFunction(_globalFetch);
195
+ const isRequestSupported = isFunction(Request);
196
+ const isResponseSupported = isFunction(Response);
197
+
198
+ if (!isFetchSupported) {
199
+ return false;
200
+ }
201
+
202
+ // Only members this runtime's Request exposes, so an engine that rejects members it doesn't
203
+ // implement (Cloudflare Workers) is never sent one.
204
+ const requestInitDefaults = Object.entries(REQUEST_INIT_DEFAULTS).filter(([ key ]) =>
205
+ isRequestSupported && utils.hasOwnInPrototypeChain((Request as AnyConstructor).prototype, key));
206
+
207
+ // Null prototype: WebIDL reads every RequestInit member with Get, which walks the prototype
208
+ // chain, so a plain object literal would let a polluted Object.prototype.redirect, .integrity,
209
+ // .mode or undici .dispatcher apply to every request. Every own enumerable key of fetchOptions
210
+ // is copied, so runtime extensions (Next.js `next`, Workers `cf`, Bun `tls`) still pass through;
211
+ // inherited keys never do. The caller sets signal, method, headers, body, duplex, credentials.
212
+ const initFromFetchOptions = (fetchOptions: unknown): Record<string, unknown> => {
213
+ const init: Record<string, unknown> = Object.create(null);
214
+ for (const [ key, value ] of requestInitDefaults) init[key] = value;
215
+ // Node's global fetch reads `dispatcher` the same way; pin it to undici's own global one.
216
+ if (!envFetch && utils.hasOwnProp(globalThis, UNDICI_GLOBAL_DISPATCHER)) {
217
+ init["dispatcher"] = (globalThis as Record<PropertyKey, unknown>)[UNDICI_GLOBAL_DISPATCHER];
218
+ }
219
+ if (utils.isObject(fetchOptions)) {
220
+ for (const key of Object.keys(fetchOptions)) {
221
+ if (!utils.hasOwnProp(FAXIOS_INIT_KEYS, key)) init[key] = (fetchOptions as Record<string, unknown>)[key];
222
+ }
223
+ }
224
+ return init;
225
+ };
226
+
227
+ const isReadableStreamSupported = isFunction(ReadableStream);
228
+
229
+ const encodeText: (str: string) => Promise<Uint8Array> | Uint8Array =
230
+ typeof TextEncoder === "function"
231
+ ? (
232
+ (encoder: AnyTextEncoder) => (str: string) =>
233
+ encoder.encode(str)
234
+ )(new TextEncoder())
235
+ : async (str: string) =>
236
+ new Uint8Array(
237
+ await (
238
+ new (Request as AnyConstructor)(str) as AnyRequest
239
+ ).arrayBuffer()
240
+ );
241
+
242
+ // A Node stream body reaches fetch twice: once in `new Request(url, init)` and again in
243
+ // `fetch(request, init)`. undici (Node 26.11+) starts reading an async iterable in both,
244
+ // so the first chunk goes to the discarded copy. One web stream is read only once.
245
+ // Web stream bodies must yield bytes, so string chunks (e.g. `Readable.from(["a"])`) are encoded.
246
+ const from = (ReadableStream as undefined | { from?: (source: unknown) => unknown; })?.from;
247
+ const streamFrom = typeof from === "function" ? from.bind(ReadableStream) : undefined;
248
+ async function* toByteChunks(source: AsyncIterable<unknown>) {
249
+ for await (const chunk of source) yield typeof chunk === "string" ? await encodeText(chunk) : chunk;
250
+ }
251
+ const toWebStream = (body: unknown): unknown =>
252
+ (streamFrom && utils.isStream(body) && !utils.isFormData(body)
253
+ ? streamFrom(toByteChunks(body as AsyncIterable<unknown>))
254
+ : body);
255
+
256
+ const supportsRequestStream =
257
+ isRequestSupported &&
258
+ isReadableStreamSupported &&
259
+ test(() => {
260
+ let duplexAccessed = false;
261
+
262
+ const request = new (Request as AnyConstructor)(platform.origin, {
263
+ body: new (ReadableStream as AnyConstructor)(),
264
+ method: "POST",
265
+ get duplex() {
266
+ duplexAccessed = true;
267
+ return "half";
268
+ },
269
+ }) as AnyRequest;
270
+
271
+ const hasContentType = request.headers.has("Content-Type");
272
+
273
+ if (request.body != null) {
274
+ request.body.cancel().catch(() => {});
275
+ }
276
+
277
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
278
+ return duplexAccessed && !hasContentType;
279
+ });
280
+
281
+ const supportsResponseStream =
282
+ isResponseSupported &&
283
+ isReadableStreamSupported &&
284
+ test(() =>
285
+ utils.isReadableStream!(
286
+ (new (Response as AnyConstructor)("") as AnyResponse).body
287
+ )
288
+ );
289
+
290
+ const resolvers: Record<
291
+ string,
292
+ ((res: AnyResponse, config?: unknown) => unknown) | false
293
+ > = {
294
+ stream: supportsResponseStream && ((res: AnyResponse) => res.body),
295
+ };
296
+
297
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
298
+ isFetchSupported &&
299
+ (() => {
300
+ [ "text", "arrayBuffer", "blob", "formData", "stream" ].forEach(type => {
301
+ !resolvers[type] &&
302
+ (resolvers[type] = (res: AnyResponse, config?: unknown) => {
303
+ const method = (res as Record<string, unknown>)[type];
304
+
305
+ if (method) {
306
+ return (method as (this: AnyResponse) => unknown).call(res);
307
+ }
308
+
309
+ throw new FaxiosError(
310
+ `Response type '${type}' is not supported`,
311
+ FaxiosError.ERR_NOT_SUPPORT,
312
+ config as InternalFaxiosRequestConfig
313
+ );
314
+ });
315
+ });
316
+ })();
317
+
318
+ const getBodyLength = async (body: unknown): Promise<number | undefined> => {
319
+ if (body == null) {
320
+ return 0;
321
+ }
322
+
323
+ if (utils.isBlob(body)) {
324
+ return (body as { size: number; }).size;
325
+ }
326
+
327
+ if (utils.isSpecCompliantForm(body)) {
328
+ const _request = new (Request as AnyConstructor)(platform.origin, {
329
+ method: "POST",
330
+ body,
331
+ }) as AnyRequest;
332
+ return (await _request.arrayBuffer()).byteLength;
333
+ }
334
+
335
+ if (utils.isArrayBufferView(body) || utils.isArrayBuffer(body)) {
336
+ return (body as ArrayBuffer | ArrayBufferView).byteLength;
337
+ }
338
+
339
+ if (utils.isURLSearchParams(body)) {
340
+ body = body + "";
341
+ }
342
+
343
+ if (utils.isString(body)) {
344
+ return (await encodeText(body as string)).byteLength;
345
+ }
346
+ return undefined;
347
+ };
348
+
349
+ const resolveBodyLength = async (
350
+ headers: FaxiosRequestHeaders,
351
+ body: unknown
352
+ ): Promise<number | undefined> => {
353
+ const length = utils.toFiniteNumber(headers.getContentLength());
354
+
355
+ return length == null ? getBodyLength(body) : length;
356
+ };
357
+
358
+ const normalizeCredentials = (wc: boolean | string): string => {
359
+ if (utils.isString(wc)) return wc as string;
360
+ return wc ? "include" : "omit";
361
+ };
362
+
363
+ // HTTP basic authentication: extract auth from config or URL, set Authorization header,
364
+ // strip credentials from URL. Returns the (possibly updated) URL.
365
+ const applyAuthToRequest = (
366
+ url: string,
367
+ headers: FaxiosRequestHeaders,
368
+ own: (key: string) => unknown
369
+ ): string => {
370
+ let auth: undefined | { password: unknown; username: unknown; };
371
+ const configAuth = own("auth");
372
+ if (configAuth) {
373
+ auth = {
374
+ username: utils.getSafeProp(configAuth, "username") || "",
375
+ password: utils.getSafeProp(configAuth, "password") || "",
376
+ };
377
+ }
378
+ if (maybeWithAuthCredentials(url)) {
379
+ const parsedURL = new URL(url, platform.origin);
380
+ if (!auth && (parsedURL.username || parsedURL.password)) {
381
+ auth = {
382
+ username: decodeURIComponentSafe(parsedURL.username),
383
+ password: decodeURIComponentSafe(parsedURL.password),
384
+ };
385
+ }
386
+ if (parsedURL.username || parsedURL.password) {
387
+ parsedURL.username = "";
388
+ parsedURL.password = "";
389
+ url = parsedURL.href;
390
+ }
391
+ }
392
+ if (auth) {
393
+ headers.delete("authorization");
394
+ headers.set(
395
+ "Authorization",
396
+ "Basic " +
397
+ _btoa(
398
+ encodeUTF8(
399
+ String(auth.username || "") + ":" + String(auth.password || "")
400
+ )
401
+ )
402
+ );
403
+ }
404
+ return url;
405
+ };
406
+
407
+ // Pre-flight size checks: data: URL content-length and known-size body maxBodyLength.
408
+ // Returns requestContentLength if computed, else undefined.
409
+ const checkPreflightLimits = async (
410
+ url: string,
411
+ data: unknown,
412
+ method: string,
413
+ hasMaxContentLength: boolean,
414
+ maxContentLength: number,
415
+ hasMaxBodyLength: boolean,
416
+ maxBodyLength: number,
417
+ config: InternalFaxiosRequestConfig,
418
+ request: unknown
419
+ ): Promise<number | undefined> => {
420
+ // Enforce maxContentLength for data: URLs up-front so we never materialize
421
+ // an oversized payload. The HTTP adapter applies the same check.
422
+ if (
423
+ hasMaxContentLength &&
424
+ typeof url === "string" &&
425
+ url.startsWith("data:")
426
+ ) {
427
+ const estimated = estimateDataURLDecodedBytes(url);
428
+ if (estimated > maxContentLength) {
429
+ throw new FaxiosError(
430
+ "maxContentLength size of " + maxContentLength + " exceeded",
431
+ FaxiosError.ERR_BAD_RESPONSE,
432
+ config,
433
+ request
434
+ );
435
+ }
436
+ }
437
+ // Enforce maxBodyLength against known-size bodies before dispatch using
438
+ // the body's actual size — never a caller-declared Content-Length.
439
+ if (hasMaxBodyLength && method !== "get" && method !== "head") {
440
+ const outboundLength = await getBodyLength(data);
441
+ if (typeof outboundLength === "number" && isFinite(outboundLength)) {
442
+ if (outboundLength > maxBodyLength) {
443
+ throw new FaxiosError(
444
+ "Request body larger than maxBodyLength limit",
445
+ FaxiosError.ERR_BAD_REQUEST,
446
+ config,
447
+ request
448
+ );
449
+ }
450
+ return outboundLength;
451
+ }
452
+ }
453
+ return undefined;
454
+ };
455
+
456
+ // Wrap a Request body with upload progress tracking and optional maxBodyLength enforcement.
457
+ const applyUploadProgress = (
458
+ _request: AnyRequest,
459
+ data: unknown,
460
+ headers: FaxiosRequestHeaders,
461
+ requestContentLength: number | undefined,
462
+ onUploadProgress: unknown,
463
+ _trackStream: (stream: unknown, onProgress?: (bytes: number) => void, flush?: () => void) => unknown
464
+ ): unknown => {
465
+ let contentTypeHeader: null | string;
466
+ if (
467
+ utils.isFormData(data) &&
468
+ (contentTypeHeader = _request.headers.get("content-type"))
469
+ ) {
470
+ headers.setContentType(contentTypeHeader);
471
+ }
472
+ if (_request.body) {
473
+ const [ onProgress, flush ] = onUploadProgress
474
+ ? progressEventDecorator(
475
+ requestContentLength,
476
+ progressEventReducer(
477
+ asyncDecorator(
478
+ onUploadProgress as (...args: Array<unknown>) => unknown
479
+ ),
480
+ false
481
+ )
482
+ )
483
+ : [];
484
+ return _trackStream(_request.body, onProgress, flush);
485
+ }
486
+ return data;
487
+ };
488
+
489
+ // Set up request body streaming with upload progress and/or maxBodyLength enforcement.
490
+ const buildUploadStream = async (
491
+ data: unknown,
492
+ url: string,
493
+ method: string,
494
+ headers: FaxiosRequestHeaders,
495
+ onUploadProgress: unknown,
496
+ mustEnforceStreamBody: boolean,
497
+ requestContentLength: number | undefined,
498
+ hasMaxBodyLength: boolean,
499
+ maxBodyLength: number,
500
+ config: InternalFaxiosRequestConfig,
501
+ request: unknown,
502
+ pendingBodyErrorRef: PendingBodyErrorRef
503
+ ): Promise<{ data: unknown; requestContentLength: number | undefined; }> => {
504
+ const _makeBodyLengthError = () =>
505
+ new FaxiosError(
506
+ "Request body larger than maxBodyLength limit",
507
+ FaxiosError.ERR_BAD_REQUEST,
508
+ config,
509
+ request
510
+ );
511
+
512
+ const _trackStream = (
513
+ stream: unknown,
514
+ onProgress?: (bytes: number) => void,
515
+ flush?: () => void
516
+ ) =>
517
+ trackStream(
518
+ stream,
519
+ DEFAULT_CHUNK_SIZE,
520
+ (loadedBytes: number) => {
521
+ if (hasMaxBodyLength && loadedBytes > maxBodyLength) {
522
+ pendingBodyErrorRef.value = _makeBodyLengthError();
523
+ throw pendingBodyErrorRef.value;
524
+ }
525
+ onProgress && onProgress(loadedBytes);
526
+ },
527
+ flush
528
+ );
529
+
530
+ if (
531
+ supportsRequestStream &&
532
+ method !== "get" &&
533
+ method !== "head" &&
534
+ (onUploadProgress || mustEnforceStreamBody)
535
+ ) {
536
+ requestContentLength =
537
+ requestContentLength == null
538
+ ? await resolveBodyLength(headers, data)
539
+ : requestContentLength;
540
+
541
+ // A declared length of 0 is only trusted to skip the wrap when we are
542
+ // not enforcing a stream limit (which must not rely on that header).
543
+ if (requestContentLength !== 0 || mustEnforceStreamBody) {
544
+ const _request = new (Request as AnyConstructor)(url, {
545
+ method: "POST",
546
+ body: data,
547
+ duplex: "half",
548
+ }) as AnyRequest;
549
+ data = applyUploadProgress(_request, data, headers, requestContentLength, onUploadProgress, _trackStream);
550
+ }
551
+ }
552
+ else if (
553
+ mustEnforceStreamBody &&
554
+ !isRequestSupported &&
555
+ isReadableStreamSupported &&
556
+ method !== "get" &&
557
+ method !== "head"
558
+ ) {
559
+ data = _trackStream(data);
560
+ }
561
+ else if (
562
+ mustEnforceStreamBody &&
563
+ isRequestSupported &&
564
+ !supportsRequestStream &&
565
+ method !== "get" &&
566
+ method !== "head"
567
+ ) {
568
+ throw new FaxiosError(
569
+ "Stream request bodies are not supported by the current fetch implementation",
570
+ FaxiosError.ERR_NOT_SUPPORT,
571
+ config,
572
+ request
573
+ );
574
+ }
575
+
576
+ return { data, requestContentLength };
577
+ };
578
+
579
+ // Wrap the response body stream with download progress tracking and maxContentLength enforcement.
580
+ const buildDownloadStream = (
581
+ response: AnyResponse,
582
+ responseHeaders: FaxiosHeaders,
583
+ onDownloadProgress: unknown,
584
+ hasMaxContentLength: boolean,
585
+ maxContentLength: number,
586
+ isStreamResponse: boolean,
587
+ unsubscribe: (() => void) | false | undefined,
588
+ config: InternalFaxiosRequestConfig,
589
+ request: unknown
590
+ ): AnyResponse => {
591
+ const needsWrapping = onDownloadProgress || hasMaxContentLength || (isStreamResponse && unsubscribe);
592
+ if (!supportsResponseStream || !response.body || !needsWrapping) {
593
+ return response;
594
+ }
595
+
596
+ const options: Record<string, unknown> = {};
597
+ [ "status", "statusText", "headers" ].forEach(prop => {
598
+ options[prop] = response[prop];
599
+ });
600
+
601
+ const responseContentLength = utils.toFiniteNumber(
602
+ (responseHeaders.getContentLength as () => unknown)()
603
+ );
604
+
605
+ const [ onProgress, flush ] = onDownloadProgress
606
+ ? progressEventDecorator(
607
+ responseContentLength,
608
+ progressEventReducer(
609
+ asyncDecorator(
610
+ onDownloadProgress as (...args: Array<unknown>) => unknown
611
+ ),
612
+ true
613
+ )
614
+ )
615
+ : [];
616
+
617
+ let bytesRead = 0;
618
+ const onChunkProgress = (loadedBytes: number): void => {
619
+ if (hasMaxContentLength) {
620
+ bytesRead = loadedBytes;
621
+ if (bytesRead > maxContentLength) {
622
+ throw new FaxiosError(
623
+ "maxContentLength size of " + maxContentLength + " exceeded",
624
+ FaxiosError.ERR_BAD_RESPONSE,
625
+ config,
626
+ request
627
+ );
628
+ }
629
+ }
630
+ onProgress && onProgress(loadedBytes);
631
+ };
632
+
633
+ return new (Response as AnyConstructor)(
634
+ trackStream(
635
+ response.body,
636
+ DEFAULT_CHUNK_SIZE,
637
+ onChunkProgress,
638
+ () => {
639
+ flush && flush();
640
+ unsubscribe && unsubscribe();
641
+ }
642
+ ),
643
+ options
644
+ ) as AnyResponse;
645
+ };
646
+
647
+ const unsubscribeOnComplete = (isStreamResponse: boolean, unsubscribe: (() => void) | undefined) => {
648
+ if (!isStreamResponse) unsubscribe?.();
649
+ };
650
+
651
+ return async (config: InternalFaxiosRequestConfig) => {
652
+ const _resolved = prepareRequest(config) as InternalFaxiosRequestConfig & {
653
+ fetchOptions?: Record<string, unknown>;
654
+ withCredentials?: boolean | string;
655
+ };
656
+ let {
657
+ url,
658
+ method,
659
+ data,
660
+ signal,
661
+ timeout,
662
+ timeoutErrorMessage,
663
+ onDownloadProgress,
664
+ onUploadProgress,
665
+ responseType,
666
+ fetchOptions,
667
+ maxContentLength,
668
+ maxBodyLength,
669
+ } = _resolved;
670
+ let headers = _resolved.headers;
671
+ let withCredentials: boolean | string =
672
+ _resolved.withCredentials ?? "same-origin";
673
+
674
+ const hasMaxContentLength =
675
+ utils.isNumber(maxContentLength) && maxContentLength! > -1;
676
+ const hasMaxBodyLength =
677
+ utils.isNumber(maxBodyLength) && maxBodyLength! > -1;
678
+ const own = (key: string): unknown =>
679
+ utils.hasOwnProp(config, key)
680
+ ? (config as unknown as Record<string, unknown>)[key]
681
+ : undefined;
682
+
683
+ const _fetch: FetchFn = (envFetch || (globalThis as unknown as Record<string, unknown>)["fetch"]) as FetchFn;
684
+
685
+ if (timeout != null && !Number.isFinite(Number(timeout))) {
686
+ throw new FaxiosError(
687
+ "error trying to parse `config.timeout` to int",
688
+ FaxiosError.ERR_BAD_OPTION_VALUE,
689
+ config
690
+ );
691
+ }
692
+
693
+ responseType = (
694
+ responseType ? (responseType + "").toLowerCase() : "text"
695
+ ) as typeof responseType;
696
+
697
+ const composedSignal = composeSignals(
698
+ [ signal ],
699
+ timeout,
700
+ timeoutErrorMessage
701
+ ) as ComposedSignal | undefined;
702
+
703
+ const unsubscribe = composedSignal?.unsubscribe?.bind(composedSignal);
704
+
705
+ let request: null | Request = null;
706
+ const pendingBodyErrorRef: PendingBodyErrorRef = { value: null };
707
+
708
+ try {
709
+ rejectUsedStreamBody(data, config);
710
+
711
+ url = applyAuthToRequest(url!, headers, own);
712
+
713
+ const preflightContentLength = await checkPreflightLimits(
714
+ url,
715
+ data,
716
+ method!,
717
+ hasMaxContentLength,
718
+ maxContentLength!,
719
+ hasMaxBodyLength,
720
+ maxBodyLength!,
721
+ config,
722
+ request
723
+ );
724
+
725
+ // A streamed body under maxBodyLength must be counted as fetch consumes
726
+ // it; its size is never trusted from a caller-declared Content-Length.
727
+ const isStreamLike = utils.isReadableStream!(data) || utils.isStream(data);
728
+ const mustEnforceStreamBody = hasMaxBodyLength && isStreamLike;
729
+
730
+ ({ data } = await buildUploadStream(
731
+ data,
732
+ url,
733
+ method!,
734
+ headers,
735
+ onUploadProgress,
736
+ mustEnforceStreamBody,
737
+ preflightContentLength,
738
+ hasMaxBodyLength,
739
+ maxBodyLength!,
740
+ config,
741
+ request,
742
+ pendingBodyErrorRef
743
+ ));
744
+
745
+ withCredentials = normalizeCredentials(withCredentials);
746
+
747
+ // Cloudflare Workers throws when credentials are defined
748
+ // see https://github.com/cloudflare/workerd/issues/902
749
+ const isCredentialsSupported =
750
+ isRequestSupported &&
751
+ Request != null &&
752
+ "credentials" in (Request.prototype as object);
753
+
754
+ cleanFormDataContentType(data, headers);
755
+
756
+ applyDefaultUserAgent(headers);
757
+
758
+ const serializedHeaders = toByteStringHeaderObject(headers.normalize(false));
759
+
760
+ // Browsers auto-add "text/plain;charset=UTF-8" for string bodies when no Content-Type is set.
761
+ // Encoding as Uint8Array prevents the browser from injecting a content-type.
762
+ // Case-insensitive check: toByteStringHeaderObject may normalize to lowercase.
763
+ data = toWebStream(await encodeBodyIfNeeded(data, serializedHeaders, encodeText));
764
+
765
+ const resolvedOptions = initFromFetchOptions(fetchOptions);
766
+ resolvedOptions["signal"] = composedSignal;
767
+ resolvedOptions["method"] = (method as string).toUpperCase();
768
+ resolvedOptions["headers"] = serializedHeaders;
769
+ resolvedOptions["body"] = data;
770
+ resolvedOptions["duplex"] = "half";
771
+ resolvedOptions["credentials"] = isCredentialsSupported ? withCredentials : undefined;
772
+
773
+ request = isRequestSupported
774
+ ? new (Request as AnyConstructor)(url, resolvedOptions) as Request
775
+ : null;
776
+
777
+ // Pass resolvedOptions as the fetch init even when a Request object is supplied, so the
778
+ // same null-prototype members apply to both calls.
779
+ let response = await (isRequestSupported
780
+ ? _fetch(request, resolvedOptions)
781
+ : _fetch(url, resolvedOptions));
782
+
783
+ const responseHeaders = FaxiosHeaders.from(response.headers);
784
+
785
+ checkDeclaredContentLength(responseHeaders, hasMaxContentLength, maxContentLength!, config, request);
786
+
787
+ const isStreamResponse =
788
+ supportsResponseStream &&
789
+ (responseType === "stream" || responseType === "response");
790
+
791
+ response = buildDownloadStream(
792
+ response,
793
+ responseHeaders,
794
+ onDownloadProgress,
795
+ hasMaxContentLength,
796
+ maxContentLength!,
797
+ isStreamResponse,
798
+ unsubscribe,
799
+ config,
800
+ request
801
+ );
802
+
803
+ responseType = responseType || "text";
804
+
805
+ const resolverKey = utils.findKey(resolvers, responseType) || "text";
806
+ const resolver = resolvers[resolverKey];
807
+ const responseData = await (resolver && resolver(response, config));
808
+
809
+ checkMaterializedSize(
810
+ responseData,
811
+ hasMaxContentLength,
812
+ maxContentLength!,
813
+ isStreamResponse,
814
+ supportsResponseStream,
815
+ config,
816
+ request
817
+ );
818
+
819
+ unsubscribeOnComplete(isStreamResponse, unsubscribe);
820
+
821
+ return await new Promise((resolve, reject) => {
822
+ settle(resolve, reject, {
823
+ data: responseData,
824
+ headers: FaxiosHeaders.from(response.headers),
825
+ status: response.status,
826
+ statusText: response.statusText,
827
+ config,
828
+ request,
829
+ });
830
+ });
831
+ }
832
+ catch (err) {
833
+ unsubscribe && unsubscribe();
834
+ throw handleFetchCaughtError(err, composedSignal, pendingBodyErrorRef, config, request);
835
+ }
836
+ };
837
+ };
838
+
839
+ const seedCache = new Map<unknown, unknown>();
840
+
841
+ export const getFetch = (config?: { env?: Record<string, unknown>; }) => {
842
+ // Own reads only: a polluted Object.prototype.fetch must never become the transport.
843
+ const env = config && utils.hasOwnProp(config, "env") ? config.env : undefined;
844
+ const own = (key: keyof FetchEnv): unknown => (env && utils.hasOwnProp(env, key) ? env[key] : undefined);
845
+ const fetch = own("fetch");
846
+ const Request = own("Request");
847
+ const Response = own("Response");
848
+ const seeds: Array<unknown> = [ Request, Response, fetch ];
849
+
850
+ const len = seeds.length;
851
+ let i = len;
852
+ let seed: unknown;
853
+ let target: unknown;
854
+ let map: Map<unknown, unknown> = seedCache;
855
+
856
+ while (i--) {
857
+ seed = seeds[i];
858
+ target = map.get(seed);
859
+
860
+ if (target === undefined) {
861
+ target = i ? new Map<unknown, unknown>() : factory({ fetch, Request, Response });
862
+ map.set(seed, target);
863
+ }
864
+
865
+ map = target as Map<unknown, unknown>;
866
+ }
867
+
868
+ return target;
869
+ };
870
+
871
+ getFetch();