@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.
Files changed (38) hide show
  1. package/dist/lib/adapters/fetch.d.ts +4 -1
  2. package/dist/lib/adapters/fetch.d.ts.map +1 -1
  3. package/dist/lib/adapters/fetch.js +134 -76
  4. package/dist/lib/adapters/fetch.js.map +1 -1
  5. package/dist/lib/core/FaxiosHeaders.d.ts.map +1 -1
  6. package/dist/lib/core/FaxiosHeaders.js +31 -7
  7. package/dist/lib/core/FaxiosHeaders.js.map +1 -1
  8. package/dist/lib/core/dispatchRequest.d.ts.map +1 -1
  9. package/dist/lib/core/dispatchRequest.js +6 -1
  10. package/dist/lib/core/dispatchRequest.js.map +1 -1
  11. package/dist/lib/env/data.d.ts +1 -1
  12. package/dist/lib/env/data.js +1 -1
  13. package/dist/lib/helpers/progressEventReducer.d.ts.map +1 -1
  14. package/dist/lib/helpers/progressEventReducer.js +14 -1
  15. package/dist/lib/helpers/progressEventReducer.js.map +1 -1
  16. package/dist/lib/helpers/toFormData.d.ts.map +1 -1
  17. package/dist/lib/helpers/toFormData.js +12 -18
  18. package/dist/lib/helpers/toFormData.js.map +1 -1
  19. package/dist/lib/helpers/trackStream.d.ts.map +1 -1
  20. package/dist/lib/helpers/trackStream.js +27 -3
  21. package/dist/lib/helpers/trackStream.js.map +1 -1
  22. package/dist/lib/types.d.ts +14 -0
  23. package/dist/lib/types.d.ts.map +1 -1
  24. package/dist/lib/utils.d.ts.map +1 -1
  25. package/dist/lib/utils.js +9 -3
  26. package/dist/lib/utils.js.map +1 -1
  27. package/package.json +1 -1
  28. package/skills/configure-requests/SKILL.md +6 -3
  29. package/skills/configure-requests/references/axios-differences.md +1 -1
  30. package/src/lib/adapters/fetch.ts +152 -94
  31. package/src/lib/core/FaxiosHeaders.ts +34 -7
  32. package/src/lib/core/dispatchRequest.ts +6 -1
  33. package/src/lib/env/data.ts +1 -1
  34. package/src/lib/helpers/progressEventReducer.ts +14 -1
  35. package/src/lib/helpers/toFormData.ts +15 -19
  36. package/src/lib/helpers/trackStream.ts +38 -10
  37. package/src/lib/types.ts +14 -0
  38. 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 TypeError("target must be an object");
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"] || (typeof (globalThis as Record<string, unknown>)["Blob"] !== "undefined" && (globalThis as Record<string, unknown>)["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 TypeError("visitor must be a function");
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("Blob is not supported. Use a Buffer instead.");
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
- const BlobCtor = (globalThis as Record<string, unknown>)["Blob"] as (new (parts: Array<unknown>) => unknown) | undefined;
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 = Object.assign(predicates, {
254
- defaultVisitor: defaultVisitor,
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 Error("Circular reference detected in " + (path ?? []).join("."));
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
- type ByteChunk = { byteLength: number; slice: (start: number, end?: number) => ByteChunk; };
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: ByteChunk, chunkSize?: number): Generator<ByteChunk> {
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.slice(pos, end);
59
+ yield chunk.subarray(pos, end);
35
60
  pos = end;
36
61
  }
37
62
  };
38
63
 
39
- const readBytes = async function* (iterable: AsyncIterable<ByteChunk> | unknown, chunkSize?: number): AsyncGenerator<ByteChunk> {
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: ByteChunk; }>;
72
+ read: () => Promise<{ done: boolean; value: unknown; }>;
48
73
  };
49
74
 
50
75
  type StreamLike = {
51
- [Symbol.asyncIterator]?: () => AsyncIterator<ByteChunk>;
76
+ [Symbol.asyncIterator]?: () => AsyncIterator<unknown>;
52
77
  getReader?: () => ReaderLike;
53
78
  };
54
79
 
55
- const readStream = async function* (stream: unknown): AsyncGenerator<ByteChunk> {
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<ByteChunk>;
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(new Uint8Array(value as unknown as ArrayBuffer));
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
- // findKey lowercases the key, so caseless lookup only applies to strings —
459
- // symbol keys are identity-matched.
460
- const targetKey: string | symbol = (caseless && typeof keyStr === "string" ? (findKey(result, keyStr) ?? (keyStr)) : (keyStr));
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++) {