@superutils/fetch 1.5.9 → 1.5.11

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/index.cjs CHANGED
@@ -42,7 +42,7 @@ var import_promise5 = require("@superutils/promise");
42
42
  var import_promise3 = require("@superutils/promise");
43
43
 
44
44
  // src/fetch.ts
45
- var import_core4 = require("@superutils/core");
45
+ var import_core5 = require("@superutils/core");
46
46
  var import_promise2 = require("@superutils/promise");
47
47
 
48
48
  // src/executeInterceptors.ts
@@ -98,46 +98,8 @@ var getResponse = (url, options = {}) => {
98
98
  getResponse.fetch = globalThis.fetch;
99
99
  var getResponse_default = getResponse;
100
100
 
101
- // src/mergeOptions.ts
101
+ // src/getResult.ts
102
102
  var import_core3 = require("@superutils/core");
103
- var mergeOptions = (...allOptions) => allOptions.reduce(
104
- (merged, options) => {
105
- var _a;
106
- options = (0, import_core3.isObj)(options) ? options : {};
107
- const { headers, interceptors: ints1 = {} } = merged;
108
- const { interceptors: ints2 = {} } = options;
109
- options.headers && new Headers(options.headers).forEach(
110
- (value, key) => headers.set(key, value)
111
- );
112
- return {
113
- ...merged,
114
- ...options,
115
- errMsgs: (0, import_core3.objCopy)(
116
- options.errMsgs,
117
- merged.errMsgs,
118
- [],
119
- "empty"
120
- ),
121
- headers,
122
- interceptors: {
123
- error: [...toArr(ints1 == null ? void 0 : ints1.error), ...toArr(ints2 == null ? void 0 : ints2.error)],
124
- request: [
125
- ...toArr(ints1 == null ? void 0 : ints1.request),
126
- ...toArr(ints2 == null ? void 0 : ints2.request)
127
- ],
128
- response: [
129
- ...toArr(ints1 == null ? void 0 : ints1.response),
130
- ...toArr(ints2 == null ? void 0 : ints2.response)
131
- ],
132
- result: [...toArr(ints1 == null ? void 0 : ints1.result), ...toArr(ints2 == null ? void 0 : ints2.result)]
133
- },
134
- timeout: (_a = options.timeout) != null ? _a : merged.timeout
135
- };
136
- },
137
- { headers: new Headers() }
138
- );
139
- var mergeOptions_default = mergeOptions;
140
- var toArr = (x) => (0, import_core3.isArr)(x) ? x : (0, import_core3.isFn)(x) ? [x] : [];
141
103
 
142
104
  // src/types/constants.ts
143
105
  var ContentType = {
@@ -201,6 +163,170 @@ var FetchError = class _FetchError extends Error {
201
163
  }
202
164
  };
203
165
 
166
+ // src/getResult.ts
167
+ var getResult = async (response, as = "json" /* json */, onDownloadProgress) => {
168
+ var _a;
169
+ if (!(0, import_core3.isFn)(onDownloadProgress) || as === "response" /* response */) {
170
+ const parseFunc = response[as];
171
+ const result = !(0, import_core3.isFn)(parseFunc) ? response : parseFunc.bind(response)();
172
+ return result;
173
+ }
174
+ const reader = (_a = response == null ? void 0 : response.body) == null ? void 0 : _a.getReader();
175
+ const contentLength = response.headers.get("Content-Length");
176
+ const total = contentLength ? parseInt(contentLength, 10) : null;
177
+ const chunks = new Chunks(
178
+ [],
179
+ response.headers.get("content-type")
180
+ );
181
+ let received = 0;
182
+ while (reader) {
183
+ const { done, value } = await reader.read();
184
+ if (done) break;
185
+ chunks.value.push(value);
186
+ received += value.length;
187
+ (0, import_core3.fallbackIfFails)(
188
+ onDownloadProgress,
189
+ total ? [100 * received / total, received, total] : [null, received, null],
190
+ null
191
+ );
192
+ }
193
+ switch (as) {
194
+ case "arrayBuffer" /* arrayBuffer */:
195
+ return chunks.toArrayBuffer();
196
+ case "blob" /* blob */:
197
+ return chunks.toBlob();
198
+ case "bytes" /* bytes */:
199
+ return chunks.toBytes();
200
+ case "formData" /* formData */:
201
+ return chunks.toFormData();
202
+ case "text" /* text */:
203
+ return chunks.toText();
204
+ case "json" /* json */:
205
+ break;
206
+ }
207
+ return chunks.toJSON();
208
+ };
209
+ var getResult_default = getResult;
210
+ var Chunks = class {
211
+ /**
212
+ * @param value The initial array of chunks.
213
+ * @param contentType The MIME type of the content for Blob/FormData conversion.
214
+ * @param encoding The character encoding for text decoding. Defaults to 'utf-8'.
215
+ */
216
+ constructor(value, contentType = "", encoding = "utf-8") {
217
+ this.value = value;
218
+ this.contentType = contentType;
219
+ this.encoding = encoding;
220
+ /**
221
+ * Converts the accumulated data to an ArrayBuffer.
222
+ */
223
+ this.toArrayBuffer = () => this.concatChunks().buffer;
224
+ /**
225
+ * Converts the accumulated data to a Blob
226
+ *
227
+ * @param type content type. Default: `this.contentType`
228
+ */
229
+ this.toBlob = (type = this.contentType) => new Blob(this.value, { type });
230
+ /**
231
+ * Converts the accumulated data to a single Uint8Array.
232
+ */
233
+ this.toBytes = () => this.concatChunks();
234
+ /**
235
+ * Decodes the data as text and parses it as JSON.
236
+ *
237
+ * @template T The expected type of the JSON result.
238
+ * @param encoding Optional encoding override.
239
+ * @returns The parsed JSON object.
240
+ */
241
+ this.toJSON = (encoding = this.encoding) => JSON.parse(this.toText(encoding));
242
+ /**
243
+ * Decodes the accumulated chunks into a string.
244
+ *
245
+ * @param encoding The character encoding to use. Defaults to the instance's `encoding`.
246
+ */
247
+ this.toText = (encoding = this.encoding) => new TextDecoder(encoding).decode(this.concatChunks());
248
+ }
249
+ /**
250
+ * Concatenates all accumulated chunks into a single Uint8Array.
251
+ *
252
+ * @returns A single Uint8Array containing the merged data.
253
+ */
254
+ concatChunks() {
255
+ const total = this.value.reduce((s, c) => s + c.byteLength, 0);
256
+ const out = new Uint8Array(total);
257
+ let off = 0;
258
+ for (const u of this.value) {
259
+ out.set(u, off);
260
+ off += u.byteLength;
261
+ }
262
+ return out;
263
+ }
264
+ /**
265
+ * Attempts to convert the accumulated data to FormData.
266
+ *
267
+ * If the `contentType` is `application/x-www-form-urlencoded` or the content appears to be
268
+ * query parameters, it parses them into FormData. Otherwise, it appends the entire
269
+ * content as a Blob under the key 'file'.
270
+ *
271
+ * @param encoding Optional encoding override for text decoding.
272
+ * @returns A FormData instance containing the data.
273
+ */
274
+ toFormData(encoding = this.encoding) {
275
+ const text = this.toText(encoding);
276
+ const formData = new FormData();
277
+ if (this.contentType.includes(
278
+ ContentType.APPLICATION_X_WWW_FORM_URLENCODED
279
+ ) || text.includes("=")) {
280
+ const params = new URLSearchParams(text);
281
+ for (const [k, v] of params) formData.append(k, v);
282
+ return formData;
283
+ }
284
+ formData.append("file", new Blob([text]));
285
+ return formData;
286
+ }
287
+ };
288
+
289
+ // src/mergeOptions.ts
290
+ var import_core4 = require("@superutils/core");
291
+ var mergeOptions = (...allOptions) => allOptions.reduce(
292
+ (merged, options) => {
293
+ var _a;
294
+ options = (0, import_core4.isObj)(options) ? options : {};
295
+ const { headers, interceptors: ints1 = {} } = merged;
296
+ const { interceptors: ints2 = {} } = options;
297
+ options.headers && new Headers(options.headers).forEach(
298
+ (value, key) => headers.set(key, value)
299
+ );
300
+ return {
301
+ ...merged,
302
+ ...options,
303
+ errMsgs: (0, import_core4.objCopy)(
304
+ options.errMsgs,
305
+ merged.errMsgs,
306
+ [],
307
+ "empty"
308
+ ),
309
+ headers,
310
+ interceptors: {
311
+ error: [...toArr(ints1 == null ? void 0 : ints1.error), ...toArr(ints2 == null ? void 0 : ints2.error)],
312
+ request: [
313
+ ...toArr(ints1 == null ? void 0 : ints1.request),
314
+ ...toArr(ints2 == null ? void 0 : ints2.request)
315
+ ],
316
+ response: [
317
+ ...toArr(ints1 == null ? void 0 : ints1.response),
318
+ ...toArr(ints2 == null ? void 0 : ints2.response)
319
+ ],
320
+ result: [...toArr(ints1 == null ? void 0 : ints1.result), ...toArr(ints2 == null ? void 0 : ints2.result)]
321
+ },
322
+ timeout: (_a = options.timeout) != null ? _a : merged.timeout
323
+ };
324
+ },
325
+ { headers: new Headers() }
326
+ );
327
+ var mergeOptions_default = mergeOptions;
328
+ var toArr = (x) => (0, import_core4.isArr)(x) ? x : (0, import_core4.isFn)(x) ? [x] : [];
329
+
204
330
  // src/fetch.ts
205
331
  var defaultErrorMsgs = Object.freeze({
206
332
  aborted: "Request aborted",
@@ -211,7 +337,7 @@ var defaultErrorMsgs = Object.freeze({
211
337
  });
212
338
  var fetch = (url, options = {}) => {
213
339
  var _a, _b, _c;
214
- if (!(0, import_core4.isObj)(options)) options = {};
340
+ if (!(0, import_core5.isObj)(options)) options = {};
215
341
  let response;
216
342
  const opts = mergeOptions_default(
217
343
  { errMsgs: defaultErrorMsgs },
@@ -224,16 +350,16 @@ var fetch = (url, options = {}) => {
224
350
  (_c = opts.signal) != null ? _c : opts.signal = opts.abortCtrl.signal;
225
351
  const { abortCtrl, as: parseAs, headers, onAbort, onTimeout } = opts;
226
352
  opts.onAbort = async () => {
227
- const err = await (0, import_core4.fallbackIfFails)(onAbort, [], void 0);
353
+ const err = await (0, import_core5.fallbackIfFails)(onAbort, [], void 0);
228
354
  return await interceptErr(
229
- (0, import_core4.isError)(err) ? err : new Error(err),
355
+ (0, import_core5.isError)(err) ? err : new Error(err),
230
356
  url,
231
357
  opts,
232
358
  response
233
359
  );
234
360
  };
235
361
  opts.onTimeout = async () => {
236
- const err = await (0, import_core4.fallbackIfFails)(onTimeout, [], void 0);
362
+ const err = await (0, import_core5.fallbackIfFails)(onTimeout, [], void 0);
237
363
  return await interceptErr(
238
364
  err != null ? err : new Error(opts.errMsgs.timedout),
239
365
  url,
@@ -244,7 +370,7 @@ var fetch = (url, options = {}) => {
244
370
  return (0, import_promise2.timeout)(opts, async () => {
245
371
  var _a2, _b2, _c2, _d;
246
372
  try {
247
- opts.body = await (0, import_core4.fallbackIfFails)(
373
+ opts.body = await (0, import_core5.fallbackIfFails)(
248
374
  opts.body,
249
375
  [],
250
376
  (err) => Promise.reject(err)
@@ -257,11 +383,11 @@ var fetch = (url, options = {}) => {
257
383
  );
258
384
  const { body, errMsgs, validateUrl = false } = opts;
259
385
  (_b2 = opts.signal) != null ? _b2 : opts.signal = abortCtrl.signal;
260
- if (validateUrl && !(0, import_core4.isUrlValid)(url, false))
386
+ if (validateUrl && !(0, import_core5.isUrlValid)(url, false))
261
387
  throw new Error(errMsgs.invalidUrl);
262
388
  const stringify = ["delete", "patch", "post", "put"].includes(
263
389
  `${opts.method}`.toLowerCase()
264
- ) && !["undefined", "string"].includes(typeof body) && (0, import_core4.isObj)(body, true) && headers.get("content-type") === ContentType.APPLICATION_JSON;
390
+ ) && !["undefined", "string"].includes(typeof body) && (0, import_core5.isObj)(body, true) && headers.get("content-type") === ContentType.APPLICATION_JSON;
265
391
  if (stringify) opts.body = JSON.stringify(opts.body);
266
392
  response = await getResponse_default(url, opts);
267
393
  response = await executeInterceptors_default(
@@ -274,7 +400,7 @@ var fetch = (url, options = {}) => {
274
400
  const status = response == null ? void 0 : response.status;
275
401
  const isSuccess = status >= 200 && status < 300;
276
402
  if (!isSuccess) {
277
- const jsonError = await (0, import_core4.fallbackIfFails)(
403
+ const jsonError = await (0, import_core5.fallbackIfFails)(
278
404
  // try to parse error response as json first
279
405
  () => {
280
406
  var _a3;
@@ -285,9 +411,12 @@ var fetch = (url, options = {}) => {
285
411
  );
286
412
  throw new Error(jsonError, { cause: jsonError });
287
413
  }
288
- const parseFunc = response[parseAs];
289
- let result = !(0, import_core4.isFn)(parseFunc) ? response : parseFunc.bind(response)();
290
- if ((0, import_core4.isPromise)(result))
414
+ let result = getResult_default(
415
+ response,
416
+ parseAs,
417
+ opts.onDownloadProgress
418
+ );
419
+ if ((0, import_core5.isPromise)(result)) {
291
420
  result = await result.catch(
292
421
  (err) => Promise.reject(
293
422
  new Error(
@@ -296,6 +425,7 @@ var fetch = (url, options = {}) => {
296
425
  )
297
426
  )
298
427
  );
428
+ }
299
429
  result = await executeInterceptors_default(
300
430
  result,
301
431
  abortCtrl.signal,
package/dist/index.d.cts CHANGED
@@ -285,6 +285,7 @@ type FetchCustomOptions = {
285
285
  */
286
286
  abortCtrl?: AbortController;
287
287
  body?: PostArgs[1];
288
+ errMsgs?: FetchErrMsgs;
288
289
  /**
289
290
  * Custom fetch function to use instead of the global `fetch`.
290
291
  * Useful for testing or using a different fetch implementation (e.g. `node-fetch` in older Node versions).
@@ -292,12 +293,12 @@ type FetchCustomOptions = {
292
293
  * Default: `globalThis.fetch`
293
294
  */
294
295
  fetchFunc?: FetchFunc;
295
- errMsgs?: FetchErrMsgs;
296
296
  /**
297
297
  * Interceptor/transformer callback executed at different stages of the request.
298
298
  * See {@link FetchInterceptors} for more details.
299
299
  */
300
300
  interceptors?: FetchInterceptors;
301
+ onDownloadProgress?: OnDownloadProgress;
301
302
  /** Whether to validate URL before making the request. Default: `false` */
302
303
  validateUrl?: boolean;
303
304
  } & FetchRetryOptions & TimeoutOptions<[]>;
@@ -378,6 +379,37 @@ type FetchRetryOptions = Omit<Partial<RetryOptions>, 'retry' | 'retryIf'> & {
378
379
  retry?: number;
379
380
  retryIf?: RetryIfFunc<Response>;
380
381
  };
382
+ /**
383
+ * Callback function for monitoring download progress.
384
+ *
385
+ * @param percent The progress percentage (0-100), or `null` if Content-Length is unknown.
386
+ * @param received The total number of bytes received so far.
387
+ * @param total The total number of bytes expected, or `null` if Content-Length is unknown.
388
+ *
389
+ * @example
390
+ * #### Download a file as Blob
391
+ * ```javascript
392
+ * import fetch from '@superutils/fetch'
393
+ *
394
+ * fetch.get(
395
+ * 'https://dummyjson.com/image/4000x4000/008080/ffffff?text=Hello+@superutils', // dynamic image file
396
+ * {
397
+ * as: FetchAs.blob,
398
+ * onDownloadProgress: (parcent, received, total) =>
399
+ * console.log({
400
+ * percent: `${(parcent ?? 0).toFixed(2)}%`,
401
+ * received,
402
+ * total,
403
+ * }),
404
+ * },
405
+ * ).then(r => console.log('result', r), console.log)
406
+ * ```
407
+ */
408
+ type OnDownloadProgress = (percent: number | null, received: number, total: number | null) => ValueOrPromise<void>;
409
+ /**
410
+ * Possible types for the request body.
411
+ * Can be a plain object (which will be stringified if JSON), standard BodyInit, or null.
412
+ */
381
413
  type PostBody = Record<string, unknown> | BodyInit | null;
382
414
  type PostArgs = [
383
415
  url: FetchArgs[0],
@@ -899,4 +931,4 @@ declare const methods: {
899
931
  */
900
932
  declare const fetch: typeof fetch$1 & typeof methods;
901
933
 
902
- export { type ClientData, ContentType, type ExcludeOptions, type ExcludePostOptions, type ExtractAs, type FetchArgs, type FetchArgsInterceptor, FetchAs, type FetchCustomOptions, type FetchErrMsgs, FetchError, type FetchFunc, type FetchInterceptorError, type FetchInterceptorRequest, type FetchInterceptorResponse, type FetchInterceptorResult, type FetchInterceptors, type FetchInterceptorsMerged, type FetchOptions, type FetchOptionsDefault, type FetchOptionsInterceptor, type FetchResult, type FetchRetryOptions, type GetFetchResult, type IPromise_Fetch, type Interceptor, type PostArgs, type PostBody, type PostDeferredCbArgs, type PostOptions, createClient, createPostClient, fetch as default, executeInterceptors, fetch, mergeOptions };
934
+ export { type ClientData, ContentType, type ExcludeOptions, type ExcludePostOptions, type ExtractAs, type FetchArgs, type FetchArgsInterceptor, FetchAs, type FetchCustomOptions, type FetchErrMsgs, FetchError, type FetchFunc, type FetchInterceptorError, type FetchInterceptorRequest, type FetchInterceptorResponse, type FetchInterceptorResult, type FetchInterceptors, type FetchInterceptorsMerged, type FetchOptions, type FetchOptionsDefault, type FetchOptionsInterceptor, type FetchResult, type FetchRetryOptions, type GetFetchResult, type IPromise_Fetch, type Interceptor, type OnDownloadProgress, type PostArgs, type PostBody, type PostDeferredCbArgs, type PostOptions, createClient, createPostClient, fetch as default, executeInterceptors, fetch, mergeOptions };
package/dist/index.d.ts CHANGED
@@ -285,6 +285,7 @@ type FetchCustomOptions = {
285
285
  */
286
286
  abortCtrl?: AbortController;
287
287
  body?: PostArgs[1];
288
+ errMsgs?: FetchErrMsgs;
288
289
  /**
289
290
  * Custom fetch function to use instead of the global `fetch`.
290
291
  * Useful for testing or using a different fetch implementation (e.g. `node-fetch` in older Node versions).
@@ -292,12 +293,12 @@ type FetchCustomOptions = {
292
293
  * Default: `globalThis.fetch`
293
294
  */
294
295
  fetchFunc?: FetchFunc;
295
- errMsgs?: FetchErrMsgs;
296
296
  /**
297
297
  * Interceptor/transformer callback executed at different stages of the request.
298
298
  * See {@link FetchInterceptors} for more details.
299
299
  */
300
300
  interceptors?: FetchInterceptors;
301
+ onDownloadProgress?: OnDownloadProgress;
301
302
  /** Whether to validate URL before making the request. Default: `false` */
302
303
  validateUrl?: boolean;
303
304
  } & FetchRetryOptions & TimeoutOptions<[]>;
@@ -378,6 +379,37 @@ type FetchRetryOptions = Omit<Partial<RetryOptions>, 'retry' | 'retryIf'> & {
378
379
  retry?: number;
379
380
  retryIf?: RetryIfFunc<Response>;
380
381
  };
382
+ /**
383
+ * Callback function for monitoring download progress.
384
+ *
385
+ * @param percent The progress percentage (0-100), or `null` if Content-Length is unknown.
386
+ * @param received The total number of bytes received so far.
387
+ * @param total The total number of bytes expected, or `null` if Content-Length is unknown.
388
+ *
389
+ * @example
390
+ * #### Download a file as Blob
391
+ * ```javascript
392
+ * import fetch from '@superutils/fetch'
393
+ *
394
+ * fetch.get(
395
+ * 'https://dummyjson.com/image/4000x4000/008080/ffffff?text=Hello+@superutils', // dynamic image file
396
+ * {
397
+ * as: FetchAs.blob,
398
+ * onDownloadProgress: (parcent, received, total) =>
399
+ * console.log({
400
+ * percent: `${(parcent ?? 0).toFixed(2)}%`,
401
+ * received,
402
+ * total,
403
+ * }),
404
+ * },
405
+ * ).then(r => console.log('result', r), console.log)
406
+ * ```
407
+ */
408
+ type OnDownloadProgress = (percent: number | null, received: number, total: number | null) => ValueOrPromise<void>;
409
+ /**
410
+ * Possible types for the request body.
411
+ * Can be a plain object (which will be stringified if JSON), standard BodyInit, or null.
412
+ */
381
413
  type PostBody = Record<string, unknown> | BodyInit | null;
382
414
  type PostArgs = [
383
415
  url: FetchArgs[0],
@@ -899,4 +931,4 @@ declare const methods: {
899
931
  */
900
932
  declare const fetch: typeof fetch$1 & typeof methods;
901
933
 
902
- export { type ClientData, ContentType, type ExcludeOptions, type ExcludePostOptions, type ExtractAs, type FetchArgs, type FetchArgsInterceptor, FetchAs, type FetchCustomOptions, type FetchErrMsgs, FetchError, type FetchFunc, type FetchInterceptorError, type FetchInterceptorRequest, type FetchInterceptorResponse, type FetchInterceptorResult, type FetchInterceptors, type FetchInterceptorsMerged, type FetchOptions, type FetchOptionsDefault, type FetchOptionsInterceptor, type FetchResult, type FetchRetryOptions, type GetFetchResult, type IPromise_Fetch, type Interceptor, type PostArgs, type PostBody, type PostDeferredCbArgs, type PostOptions, createClient, createPostClient, fetch as default, executeInterceptors, fetch, mergeOptions };
934
+ export { type ClientData, ContentType, type ExcludeOptions, type ExcludePostOptions, type ExtractAs, type FetchArgs, type FetchArgsInterceptor, FetchAs, type FetchCustomOptions, type FetchErrMsgs, FetchError, type FetchFunc, type FetchInterceptorError, type FetchInterceptorRequest, type FetchInterceptorResponse, type FetchInterceptorResult, type FetchInterceptors, type FetchInterceptorsMerged, type FetchOptions, type FetchOptionsDefault, type FetchOptionsInterceptor, type FetchResult, type FetchRetryOptions, type GetFetchResult, type IPromise_Fetch, type Interceptor, type OnDownloadProgress, type PostArgs, type PostBody, type PostDeferredCbArgs, type PostOptions, createClient, createPostClient, fetch as default, executeInterceptors, fetch, mergeOptions };