@gcmdev/faxios 0.2.2 → 0.2.3
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/lib/adapters/fetch.d.ts +4 -1
- package/dist/lib/adapters/fetch.d.ts.map +1 -1
- package/dist/lib/adapters/fetch.js +134 -76
- package/dist/lib/adapters/fetch.js.map +1 -1
- package/dist/lib/core/FaxiosHeaders.d.ts.map +1 -1
- package/dist/lib/core/FaxiosHeaders.js +31 -7
- package/dist/lib/core/FaxiosHeaders.js.map +1 -1
- package/dist/lib/core/dispatchRequest.d.ts.map +1 -1
- package/dist/lib/core/dispatchRequest.js +6 -1
- package/dist/lib/core/dispatchRequest.js.map +1 -1
- package/dist/lib/env/data.d.ts +1 -1
- package/dist/lib/env/data.js +1 -1
- package/dist/lib/helpers/progressEventReducer.d.ts.map +1 -1
- package/dist/lib/helpers/progressEventReducer.js +14 -1
- package/dist/lib/helpers/progressEventReducer.js.map +1 -1
- package/dist/lib/helpers/toFormData.d.ts.map +1 -1
- package/dist/lib/helpers/toFormData.js +12 -18
- package/dist/lib/helpers/toFormData.js.map +1 -1
- package/dist/lib/helpers/trackStream.d.ts.map +1 -1
- package/dist/lib/helpers/trackStream.js +27 -3
- package/dist/lib/helpers/trackStream.js.map +1 -1
- package/dist/lib/types.d.ts +14 -0
- package/dist/lib/types.d.ts.map +1 -1
- package/dist/lib/utils.d.ts.map +1 -1
- package/dist/lib/utils.js +9 -3
- package/dist/lib/utils.js.map +1 -1
- package/package.json +1 -1
- package/skills/configure-requests/SKILL.md +6 -3
- package/skills/configure-requests/references/axios-differences.md +1 -1
- package/src/lib/adapters/fetch.ts +152 -94
- package/src/lib/core/FaxiosHeaders.ts +34 -7
- package/src/lib/core/dispatchRequest.ts +6 -1
- package/src/lib/env/data.ts +1 -1
- package/src/lib/helpers/progressEventReducer.ts +14 -1
- package/src/lib/helpers/toFormData.ts +15 -19
- package/src/lib/helpers/trackStream.ts +38 -10
- package/src/lib/types.ts +14 -0
- package/src/lib/utils.ts +9 -3
|
@@ -91,7 +91,7 @@ const predicates = utils.toFlatObject(utils, {}, null as unknown as false, funct
|
|
|
91
91
|
*/
|
|
92
92
|
function toFormData(obj: unknown, formData?: GenericFormData | null, options?: Record<string, unknown>): GenericFormData {
|
|
93
93
|
if (!utils.isObject(obj)) {
|
|
94
|
-
throw new
|
|
94
|
+
throw new FaxiosError("target must be an object", FaxiosError.ERR_BAD_REQUEST);
|
|
95
95
|
}
|
|
96
96
|
|
|
97
97
|
const FormDataCtor = (globalThis as Record<string, unknown>)["FormData"] as (new () => GenericFormData) | undefined;
|
|
@@ -120,16 +120,16 @@ function toFormData(obj: unknown, formData?: GenericFormData | null, options?: R
|
|
|
120
120
|
const visitor = (options["visitor"] || defaultVisitor) as SerializerVisitor;
|
|
121
121
|
const dots = options["dots"];
|
|
122
122
|
const indexes = options["indexes"];
|
|
123
|
-
const _Blob = options["Blob"] || (
|
|
123
|
+
const _Blob = (options["Blob"] || (globalThis as Record<string, unknown>)["Blob"]) as (new (parts: Array<unknown>) => unknown) | undefined;
|
|
124
124
|
const maxDepth =
|
|
125
125
|
options["maxDepth"] === undefined
|
|
126
126
|
? DEFAULT_FORM_DATA_MAX_DEPTH
|
|
127
127
|
: (options["maxDepth"] as number);
|
|
128
|
-
const useBlob = _Blob && utils.isSpecCompliantForm(formData);
|
|
128
|
+
const useBlob = typeof _Blob === "function" && utils.isSpecCompliantForm(formData);
|
|
129
129
|
const stack: Array<unknown> = [];
|
|
130
130
|
|
|
131
131
|
if (!utils.isFunction(visitor)) {
|
|
132
|
-
throw new
|
|
132
|
+
throw new FaxiosError("visitor must be a function", FaxiosError.ERR_BAD_OPTION_VALUE);
|
|
133
133
|
}
|
|
134
134
|
|
|
135
135
|
function convertValue(value: unknown): unknown {
|
|
@@ -143,16 +143,15 @@ function toFormData(obj: unknown, formData?: GenericFormData | null, options?: R
|
|
|
143
143
|
return String(value);
|
|
144
144
|
}
|
|
145
145
|
|
|
146
|
-
if (!useBlob && utils.isBlob(value)) {
|
|
147
|
-
throw new FaxiosError(
|
|
146
|
+
if (!useBlob && (utils.isBlob(value) || utils.isArrayBuffer(value) || utils.isTypedArray(value))) {
|
|
147
|
+
throw new FaxiosError(
|
|
148
|
+
"Binary FormData values need a spec-compliant FormData and a Blob constructor (set the `Blob` option)",
|
|
149
|
+
FaxiosError.ERR_NOT_SUPPORT
|
|
150
|
+
);
|
|
148
151
|
}
|
|
149
152
|
|
|
150
153
|
if (utils.isArrayBuffer(value) || utils.isTypedArray(value)) {
|
|
151
|
-
|
|
152
|
-
if (useBlob && typeof BlobCtor === "function") {
|
|
153
|
-
return new BlobCtor([ value ]);
|
|
154
|
-
}
|
|
155
|
-
throw new FaxiosError("Blob is required for binary FormData values in this environment", FaxiosError.ERR_NOT_SUPPORT);
|
|
154
|
+
return new (_Blob as new (parts: Array<unknown>) => unknown)([ value ]);
|
|
156
155
|
}
|
|
157
156
|
|
|
158
157
|
return value;
|
|
@@ -250,11 +249,12 @@ function toFormData(obj: unknown, formData?: GenericFormData | null, options?: R
|
|
|
250
249
|
return false;
|
|
251
250
|
}
|
|
252
251
|
|
|
253
|
-
const exposedHelpers: FormDataVisitorHelpers =
|
|
254
|
-
|
|
252
|
+
const exposedHelpers: FormDataVisitorHelpers = {
|
|
253
|
+
...predicates,
|
|
254
|
+
defaultVisitor,
|
|
255
255
|
convertValue,
|
|
256
256
|
isVisitable,
|
|
257
|
-
}
|
|
257
|
+
};
|
|
258
258
|
|
|
259
259
|
function build(value: unknown, path?: Array<number | string>, depth = 0): void {
|
|
260
260
|
if (utils.isUndefined(value)) return;
|
|
@@ -262,7 +262,7 @@ function toFormData(obj: unknown, formData?: GenericFormData | null, options?: R
|
|
|
262
262
|
throwIfMaxDepthExceeded(depth);
|
|
263
263
|
|
|
264
264
|
if (stack.indexOf(value) !== -1) {
|
|
265
|
-
throw new
|
|
265
|
+
throw new FaxiosError("Circular reference detected in " + (path ?? []).join("."), FaxiosError.ERR_BAD_REQUEST);
|
|
266
266
|
}
|
|
267
267
|
|
|
268
268
|
stack.push(value);
|
|
@@ -286,10 +286,6 @@ function toFormData(obj: unknown, formData?: GenericFormData | null, options?: R
|
|
|
286
286
|
stack.pop();
|
|
287
287
|
}
|
|
288
288
|
|
|
289
|
-
if (!utils.isObject(obj)) {
|
|
290
|
-
throw new TypeError("data must be an object");
|
|
291
|
-
}
|
|
292
|
-
|
|
293
289
|
build(obj);
|
|
294
290
|
|
|
295
291
|
return formData;
|
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import FaxiosError from "../core/FaxiosError.js";
|
|
2
|
+
import utils from "../utils.js";
|
|
3
|
+
|
|
1
4
|
type ReadableStreamController = {
|
|
2
5
|
close: () => void;
|
|
3
6
|
enqueue: (chunk: Uint8Array) => void;
|
|
@@ -16,9 +19,31 @@ type ReadableStreamCtor = new (source: ReadableStreamSource, init?: ReadableStre
|
|
|
16
19
|
|
|
17
20
|
const ReadableStreamGlobal = (globalThis as Record<string, unknown>)["ReadableStream"] as ReadableStreamCtor | undefined;
|
|
18
21
|
|
|
19
|
-
|
|
22
|
+
const textEncoder = typeof TextEncoder === "function" ? new TextEncoder() : undefined;
|
|
23
|
+
|
|
24
|
+
// View the chunk's bytes without copying them. Strings are UTF-8 encoded; anything else is not
|
|
25
|
+
// a byte chunk and is rejected rather than silently converted.
|
|
26
|
+
const toBytes = (chunk: unknown): Uint8Array => {
|
|
27
|
+
if (chunk instanceof Uint8Array) {
|
|
28
|
+
return chunk;
|
|
29
|
+
}
|
|
30
|
+
if (utils.isArrayBufferView(chunk)) {
|
|
31
|
+
const view = chunk as ArrayBufferView;
|
|
32
|
+
return new Uint8Array(view.buffer, view.byteOffset, view.byteLength);
|
|
33
|
+
}
|
|
34
|
+
if (utils.isArrayBuffer(chunk)) {
|
|
35
|
+
return new Uint8Array(chunk as ArrayBuffer);
|
|
36
|
+
}
|
|
37
|
+
if (utils.isString(chunk) && textEncoder) {
|
|
38
|
+
return textEncoder.encode(chunk as string);
|
|
39
|
+
}
|
|
40
|
+
throw new FaxiosError(
|
|
41
|
+
"Stream chunk must be a Uint8Array, another ArrayBufferView, an ArrayBuffer or a string, got " + utils.kindOf(chunk),
|
|
42
|
+
FaxiosError.ERR_BAD_REQUEST
|
|
43
|
+
);
|
|
44
|
+
};
|
|
20
45
|
|
|
21
|
-
const streamChunk = function* (chunk:
|
|
46
|
+
const streamChunk = function* (chunk: Uint8Array, chunkSize?: number): Generator<Uint8Array> {
|
|
22
47
|
const len = chunk.byteLength;
|
|
23
48
|
|
|
24
49
|
if (!chunkSize || len < chunkSize) {
|
|
@@ -31,31 +56,31 @@ const streamChunk = function* (chunk: ByteChunk, chunkSize?: number): Generator<
|
|
|
31
56
|
|
|
32
57
|
while (pos < len) {
|
|
33
58
|
end = pos + chunkSize;
|
|
34
|
-
yield chunk.
|
|
59
|
+
yield chunk.subarray(pos, end);
|
|
35
60
|
pos = end;
|
|
36
61
|
}
|
|
37
62
|
};
|
|
38
63
|
|
|
39
|
-
const readBytes = async function* (iterable:
|
|
64
|
+
const readBytes = async function* (iterable: unknown, chunkSize?: number): AsyncGenerator<Uint8Array> {
|
|
40
65
|
for await (const chunk of readStream(iterable)) {
|
|
41
|
-
yield* streamChunk(chunk, chunkSize);
|
|
66
|
+
yield* streamChunk(toBytes(chunk), chunkSize);
|
|
42
67
|
}
|
|
43
68
|
};
|
|
44
69
|
|
|
45
70
|
type ReaderLike = {
|
|
46
71
|
cancel: () => Promise<void>;
|
|
47
|
-
read: () => Promise<{ done: boolean; value:
|
|
72
|
+
read: () => Promise<{ done: boolean; value: unknown; }>;
|
|
48
73
|
};
|
|
49
74
|
|
|
50
75
|
type StreamLike = {
|
|
51
|
-
[Symbol.asyncIterator]?: () => AsyncIterator<
|
|
76
|
+
[Symbol.asyncIterator]?: () => AsyncIterator<unknown>;
|
|
52
77
|
getReader?: () => ReaderLike;
|
|
53
78
|
};
|
|
54
79
|
|
|
55
|
-
const readStream = async function* (stream: unknown): AsyncGenerator<
|
|
80
|
+
const readStream = async function* (stream: unknown): AsyncGenerator<unknown> {
|
|
56
81
|
const s = stream as StreamLike;
|
|
57
82
|
if (s[Symbol.asyncIterator]) {
|
|
58
|
-
yield* s as AsyncIterable<
|
|
83
|
+
yield* s as AsyncIterable<unknown>;
|
|
59
84
|
return;
|
|
60
85
|
}
|
|
61
86
|
|
|
@@ -104,9 +129,12 @@ export const trackStream = (stream: unknown, chunkSize: number | undefined, onPr
|
|
|
104
129
|
const loadedBytes = (bytes += len);
|
|
105
130
|
onProgress(loadedBytes);
|
|
106
131
|
}
|
|
107
|
-
controller.enqueue(
|
|
132
|
+
controller.enqueue(value);
|
|
108
133
|
}
|
|
109
134
|
catch (err) {
|
|
135
|
+
// Cancel the source so its lock and connection are released. The original error wins
|
|
136
|
+
// over a failure to cancel.
|
|
137
|
+
await iterator.return(undefined).catch(() => {});
|
|
110
138
|
_onFinish(err);
|
|
111
139
|
throw err;
|
|
112
140
|
}
|
package/src/lib/types.ts
CHANGED
|
@@ -152,6 +152,8 @@ export interface SerializerVisitor {
|
|
|
152
152
|
}
|
|
153
153
|
|
|
154
154
|
export interface SerializerOptions {
|
|
155
|
+
/** FormData only: Blob constructor used to wrap `ArrayBuffer` and typed-array values. Defaults to `globalThis.Blob`. */
|
|
156
|
+
Blob?: new (parts: Array<BlobPart>) => Blob;
|
|
155
157
|
dots?: boolean;
|
|
156
158
|
indexes?: boolean | null;
|
|
157
159
|
/** Maximum nesting depth to serialize; deeper objects throw `ERR_FORM_DATA_DEPTH_EXCEEDED`. `Infinity` disables the check. */
|
|
@@ -279,7 +281,19 @@ export interface FaxiosRequestConfig<D = unknown> {
|
|
|
279
281
|
maxBodyLength?: number;
|
|
280
282
|
maxContentLength?: number;
|
|
281
283
|
method?: StringLiteralsOrString<Method>;
|
|
284
|
+
/**
|
|
285
|
+
* Called with download progress. For a content-encoded response (any `Content-Encoding`
|
|
286
|
+
* other than `identity`) `total` is `undefined` and `lengthComputable` is `false`.
|
|
287
|
+
* An error thrown (or a rejected promise returned) by the callback is caught and ignored;
|
|
288
|
+
* the request settles normally.
|
|
289
|
+
*/
|
|
282
290
|
onDownloadProgress?: (progressEvent: FaxiosProgressEvent) => void;
|
|
291
|
+
/**
|
|
292
|
+
* Called with upload progress where faxios can stream the request body, which today is
|
|
293
|
+
* Node.js. It is not called in browsers or web workers (see #209).
|
|
294
|
+
* An error thrown (or a rejected promise returned) by the callback is caught and ignored;
|
|
295
|
+
* the request settles normally.
|
|
296
|
+
*/
|
|
283
297
|
onUploadProgress?: (progressEvent: FaxiosProgressEvent) => void;
|
|
284
298
|
params?: Record<string, unknown> | URLSearchParams;
|
|
285
299
|
paramsSchema?: StandardSchemaV1;
|
package/src/lib/utils.ts
CHANGED
|
@@ -448,6 +448,9 @@ function merge(this: unknown, ...objs: Array<unknown>): Record<string, unknown>
|
|
|
448
448
|
const caseless = ctx && ctx.caseless;
|
|
449
449
|
const skipUndefined = ctx && ctx.skipUndefined;
|
|
450
450
|
const result: Record<string | symbol, unknown> = {};
|
|
451
|
+
// Caseless mode: lowercased key -> the key already written to `result`. Only assignValue
|
|
452
|
+
// writes to `result`, so this index is complete.
|
|
453
|
+
const caselessKeys = new Map<string, string>();
|
|
451
454
|
const assignValue = (val: unknown, key: unknown) => {
|
|
452
455
|
const keyStr = key as string | symbol;
|
|
453
456
|
// Skip dangerous property names to prevent prototype pollution
|
|
@@ -455,9 +458,9 @@ function merge(this: unknown, ...objs: Array<unknown>): Record<string, unknown>
|
|
|
455
458
|
return;
|
|
456
459
|
}
|
|
457
460
|
|
|
458
|
-
//
|
|
459
|
-
|
|
460
|
-
const targetKey: string | symbol = (
|
|
461
|
+
// Caseless lookup only applies to strings — symbol keys are identity-matched.
|
|
462
|
+
const lowerKey = caseless && typeof keyStr === "string" ? keyStr.toLowerCase() : undefined;
|
|
463
|
+
const targetKey: string | symbol = (lowerKey === undefined ? keyStr : (caselessKeys.get(lowerKey) ?? keyStr));
|
|
461
464
|
// Read via own-prop only — a bare `result[targetKey]` walks the prototype
|
|
462
465
|
// chain, so a polluted Object.prototype value could surface here and get
|
|
463
466
|
// copied into the merged result.
|
|
@@ -474,6 +477,9 @@ function merge(this: unknown, ...objs: Array<unknown>): Record<string, unknown>
|
|
|
474
477
|
else if (!skipUndefined || !isUndefined(val)) {
|
|
475
478
|
result[targetKey] = val;
|
|
476
479
|
}
|
|
480
|
+
if (lowerKey !== undefined && hasOwnProperty(result, targetKey)) {
|
|
481
|
+
caselessKeys.set(lowerKey, targetKey as string);
|
|
482
|
+
}
|
|
477
483
|
};
|
|
478
484
|
|
|
479
485
|
for (let i = 0, l = objs.length; i < l; i++) {
|