foxts 4.0.0-beta.2 → 4.1.0

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.
@@ -0,0 +1,82 @@
1
+ interface AsyncRetryContext {
2
+ readonly error: unknown;
3
+ readonly attemptNumber: number;
4
+ readonly retriesLeft: number;
5
+ }
6
+ interface AsyncRetryOptions {
7
+ /**
8
+ Callback invoked on each retry. Receives a context object containing the error and retry state information.
9
+
10
+ The `onFailedAttempt` function can return a promise. For example, to add extra delay, especially useful reading `Retry-After` header.
11
+
12
+ If the `onFailedAttempt` function throws, all retries will be aborted and the original promise will reject with the thrown error.
13
+ */
14
+ onFailedAttempt?: (context: AsyncRetryContext) => void | Promise<void>;
15
+ /**
16
+ * @deprecated Use `onFailedAttempt` instead. This is added only to be compatible with `async-retry`
17
+ */
18
+ onRetry?: (error: unknown, attemptNumber: number) => void | Promise<void>;
19
+ /**
20
+ Decide if a retry should occur based on the context. Returning true triggers a retry, false aborts with the error.
21
+
22
+ It is only called if `retries` and `maxRetryTime` have not been exhausted.
23
+ It is not called for `TypeError` (except network errors) and `AbortError`.
24
+
25
+ In the example above, the operation will be retried unless the error is an instance of `CustomError`.
26
+ */
27
+ shouldRetry?: (context: AsyncRetryContext) => boolean | Promise<boolean>;
28
+ /**
29
+ * @deprecated Use `retries` w/ `Number.POSITIVE_INFINITY` instead
30
+ */
31
+ forever?: boolean;
32
+ /**
33
+ The maximum amount of times to retry the operation.
34
+ @default 10
35
+ */
36
+ retries?: number;
37
+ /**
38
+ The exponential factor to use.
39
+ @default 2
40
+ */
41
+ factor?: number;
42
+ /**
43
+ The number of milliseconds before starting the first retry.
44
+ @default 1000
45
+ */
46
+ minTimeout?: number;
47
+ /**
48
+ The maximum number of milliseconds between two retries.
49
+ @default Infinity
50
+ */
51
+ maxTimeout?: number;
52
+ /**
53
+ Randomizes the timeouts by multiplying with a factor between 1 and 2.
54
+ @default true
55
+ */
56
+ randomize?: boolean;
57
+ /**
58
+ The maximum time (in milliseconds) that the retried operation is allowed to run.
59
+ @default Infinity
60
+ */
61
+ maxRetryTime?: number;
62
+ /**
63
+ You can abort retrying using [`AbortController`](https://developer.mozilla.org/en-US/docs/Web/API/AbortController).
64
+ */
65
+ signal?: AbortSignal;
66
+ /**
67
+ Prevents retry timeouts from keeping the process alive.
68
+ Only affects platforms with a `.unref()` method on timeouts, such as Node.js.
69
+ @default false
70
+ */
71
+ unref?: boolean;
72
+ }
73
+ declare class AsyncRetryAbortError extends Error {
74
+ name: string;
75
+ cause: unknown;
76
+ constructor(message: string | Error | unknown);
77
+ }
78
+ declare function asyncRetry<T>(callback: (bail: (reason?: unknown) => void, attemptNumber: number) => PromiseLike<T> | T, retryOptions?: AsyncRetryOptions): Promise<T>;
79
+ declare function makeRetriable<Args extends unknown[], Result>(fn: (...args: Args) => PromiseLike<Result> | Result, options?: AsyncRetryOptions): (this: unknown, ...args: Args) => Promise<Result>;
80
+
81
+ export { AsyncRetryAbortError, asyncRetry, makeRetriable };
82
+ export type { AsyncRetryContext, AsyncRetryOptions };
@@ -0,0 +1 @@
1
+ "use strict";var t=require("../extract-error-message/index.js"),e=require("../is-network-error/index.js"),r=require("../noop/index.js");function o(t,e,{min:r=0,allowInfinity:i=!1}={}){if(Number.isNaN(e))throw TypeError(`Expected \`${t}\` to be a valid number${i?" or Infinity":""}, got NaN.`);if(!i&&!Number.isFinite(e))throw TypeError(`Expected \`${t}\` to be a finite number.`);if(e<r)throw TypeError(`Expected \`${t}\` to be \u2265 ${r}.`)}class i extends Error{name="AsyncRetryAbortError";cause;constructor(e){if(super("string"==typeof e?e:t.extractErrorMessage(e,!1)??"Aborted"),"string"==typeof e){const t=Error(e);t.stack=this.stack,this.cause=t}else e instanceof Error?(this.cause=e,this.message=e.message):this.cause=e}}async function n(r,o,n,a,s){if(r instanceof i)throw r.cause;if(r instanceof TypeError&&!e.isNetworkError(r)||t.isErrorLikeObject(r)&&"AbortError"===r.name)throw r;const c=n.retries-(o-1),m={error:r,attemptNumber:o,retriesLeft:c};await n.onFailedAttempt(m),o>1&&await n.onRetry(r,o-1);const u=Date.now();if(u-a>=s||o>=n.retries+1||!await n.shouldRetry(m))throw r;let f=Math.round((n.randomize?Math.random()+1:1)*n.minTimeout*n.factor**(o-1));f=Math.min(f,n.maxTimeout);const l=s-(u-a);if(l<=0)throw r;const w=Math.min(f,l);w>0&&await new Promise((t,e)=>{const r=setTimeout(()=>{n.signal?.removeEventListener("abort",o),t()},w);function o(){clearTimeout(r),n.signal?.removeEventListener("abort",o),e(n.signal?.reason)}n.unref&&"object"==typeof r&&"unref"in r&&"function"==typeof r.unref&&r.unref(),n.signal?.addEventListener("abort",o,{once:!0})}),n.signal?.throwIfAborted()}function a(t){throw new i(t??"Aborted")}async function s(t,e={}){const c={...e};c.retries??=10,c.forever??=!1,c.factor??=2,c.minTimeout??=1e3,c.maxTimeout??=1/0,c.randomize??=!0,c.onFailedAttempt??=r.noop,c.onRetry??=r.noop,c.shouldRetry??=r.trueFn,c.forever&&(c.retries=1/0),o("retries",c.retries,{min:0,allowInfinity:!0}),o("factor",c.factor,{min:0,allowInfinity:!1}),o("minTimeout",c.minTimeout,{min:0,allowInfinity:!1}),o("maxTimeout",c.maxTimeout,{min:0,allowInfinity:!0});const m=c.maxRetryTime??1/0;o("maxRetryTime",m,{min:0,allowInfinity:!0}),c.minTimeout=Math.max(c.minTimeout,1),c.factor<=0&&(c.factor=1),c.signal?.throwIfAborted();let u=0;const f=Date.now();for(;u<c.retries+1;){u++;try{c.signal?.throwIfAborted();const e=await t(a,u);return c.signal?.throwIfAborted(),e}catch(e){let t=e;"object"==typeof e&&e&&"bail"in e&&e.bail&&(t=new i(e)),await n(t,u,c,f,m)}}throw Error("Retry attempts exhausted without throwing an error.")}exports.AsyncRetryAbortError=i,exports.asyncRetry=s,exports.makeRetriable=function(t,e){return function(...r){return s(()=>t.apply(this,r),e)}};
@@ -0,0 +1 @@
1
+ import{extractErrorMessage as t,isErrorLikeObject as e}from"../extract-error-message/index.mjs";import{isNetworkError as r}from"../is-network-error/index.mjs";import{noop as o,trueFn as n}from"../noop/index.mjs";function i(t,e,{min:r=0,allowInfinity:o=!1}={}){if(Number.isNaN(e))throw TypeError(`Expected \`${t}\` to be a valid number${o?" or Infinity":""}, got NaN.`);if(!o&&!Number.isFinite(e))throw TypeError(`Expected \`${t}\` to be a finite number.`);if(e<r)throw TypeError(`Expected \`${t}\` to be \u2265 ${r}.`)}class a extends Error{name="AsyncRetryAbortError";cause;constructor(e){if(super("string"==typeof e?e:t(e,!1)??"Aborted"),"string"==typeof e){const t=Error(e);t.stack=this.stack,this.cause=t}else e instanceof Error?(this.cause=e,this.message=e.message):this.cause=e}}async function s(t,o,n,i,s){if(t instanceof a)throw t.cause;if(t instanceof TypeError&&!r(t)||e(t)&&"AbortError"===t.name)throw t;const m=n.retries-(o-1),c={error:t,attemptNumber:o,retriesLeft:m};await n.onFailedAttempt(c),o>1&&await n.onRetry(t,o-1);const f=Date.now();if(f-i>=s||o>=n.retries+1||!await n.shouldRetry(c))throw t;let u=Math.round((n.randomize?Math.random()+1:1)*n.minTimeout*n.factor**(o-1));u=Math.min(u,n.maxTimeout);const l=s-(f-i);if(l<=0)throw t;const w=Math.min(u,l);w>0&&await new Promise((t,e)=>{const r=setTimeout(()=>{n.signal?.removeEventListener("abort",o),t()},w);function o(){clearTimeout(r),n.signal?.removeEventListener("abort",o),e(n.signal?.reason)}n.unref&&"object"==typeof r&&"unref"in r&&"function"==typeof r.unref&&r.unref(),n.signal?.addEventListener("abort",o,{once:!0})}),n.signal?.throwIfAborted()}function m(t){throw new a(t??"Aborted")}async function c(t,e={}){const r={...e};r.retries??=10,r.forever??=!1,r.factor??=2,r.minTimeout??=1e3,r.maxTimeout??=1/0,r.randomize??=!0,r.onFailedAttempt??=o,r.onRetry??=o,r.shouldRetry??=n,r.forever&&(r.retries=1/0),i("retries",r.retries,{min:0,allowInfinity:!0}),i("factor",r.factor,{min:0,allowInfinity:!1}),i("minTimeout",r.minTimeout,{min:0,allowInfinity:!1}),i("maxTimeout",r.maxTimeout,{min:0,allowInfinity:!0});const f=r.maxRetryTime??1/0;i("maxRetryTime",f,{min:0,allowInfinity:!0}),r.minTimeout=Math.max(r.minTimeout,1),r.factor<=0&&(r.factor=1),r.signal?.throwIfAborted();let u=0;const l=Date.now();for(;u<r.retries+1;){u++;try{r.signal?.throwIfAborted();const e=await t(m,u);return r.signal?.throwIfAborted(),e}catch(e){let t=e;"object"==typeof e&&e&&"bail"in e&&e.bail&&(t=new a(e)),await s(t,u,r,l,f)}}throw Error("Retry attempts exhausted without throwing an error.")}function f(t,e){return function(...r){return c(()=>t.apply(this,r),e)}}export{a as AsyncRetryAbortError,c as asyncRetry,f as makeRetriable};
@@ -0,0 +1,3 @@
1
+ declare function isNetworkError(error: unknown): boolean;
2
+
3
+ export { isNetworkError };
@@ -0,0 +1 @@
1
+ "use strict";const e=Object.prototype.toString,r=new Set(["network error","Failed to fetch","NetworkError when attempting to fetch resource.","The Internet connection appears to be offline.","Network request failed","fetch failed","terminated"," A network error occurred.","Network connection lost"]),t=new Set(["ETIMEDOUT","ECONNRESET","ECONNREFUSED","ENOTFOUND","ENETDOWN","ENETUNREACH","EHOSTDOWN","EHOSTUNREACH","EPIPE","UND_ERR_SOCKET","UND_ERR_HEADERS_TIMEOUT"]);exports.isNetworkError=function(o){if("object"!=typeof o||null==o)return!1;if("code"in o&&"string"==typeof o.code){if("ERR_UNESCAPED_CHARACTERS"===o.code)return!1;if(t.has(o.code))return!0}if(!(o&&"[object Error]"===e.call(o)&&"TypeError"===o.name&&"string"==typeof o.message))return!1;const{message:n,stack:E}=o;return"Load failed"===n?void 0===E||"__sentry_captured__"in o:!!n.startsWith("error sending request for url")||r.has(n)};
@@ -0,0 +1 @@
1
+ const e=Object.prototype.toString,r=new Set(["network error","Failed to fetch","NetworkError when attempting to fetch resource.","The Internet connection appears to be offline.","Network request failed","fetch failed","terminated"," A network error occurred.","Network connection lost"]),t=new Set(["ETIMEDOUT","ECONNRESET","ECONNREFUSED","ENOTFOUND","ENETDOWN","ENETUNREACH","EHOSTDOWN","EHOSTUNREACH","EPIPE","UND_ERR_SOCKET","UND_ERR_HEADERS_TIMEOUT"]);function o(o){if("object"!=typeof o||null==o)return!1;if("code"in o&&"string"==typeof o.code){if("ERR_UNESCAPED_CHARACTERS"===o.code)return!1;if(t.has(o.code))return!0}if(!(o&&"[object Error]"===e.call(o)&&"TypeError"===o.name&&"string"==typeof o.message))return!1;const{message:n,stack:E}=o;return"Load failed"===n?void 0===E||"__sentry_captured__"in o:!!n.startsWith("error sending request for url")||r.has(n)}export{o as isNetworkError};
@@ -1,6 +1,8 @@
1
1
  declare function stringToUint8Array(str: string): Uint8Array;
2
- declare function uint8ArrayToString(array: Uint8Array, encoding?: string): string;
2
+ declare function uint8ArrayToString(array: ArrayBuffer | ArrayBufferView, encoding?: string): string;
3
3
  declare function base64ToUint8Array(base64String: string): Uint8Array;
4
4
  declare function uint8ArrayToBase64(array: Uint8Array, urlSafe?: boolean): string;
5
+ declare function concatUint8Arrays(arrays: Uint8Array[], totalLength?: number): Uint8Array;
6
+ declare function toUint8Array(value: ArrayBuffer | ArrayBufferView): Uint8Array<ArrayBufferLike>;
5
7
 
6
- export { base64ToUint8Array, stringToUint8Array, uint8ArrayToBase64, uint8ArrayToString };
8
+ export { base64ToUint8Array, concatUint8Arrays, stringToUint8Array, toUint8Array, uint8ArrayToBase64, uint8ArrayToString };
@@ -1 +1 @@
1
- "use strict";const e=new TextEncoder,r={utf8:new TextDecoder("utf8")};exports.base64ToUint8Array=function(e){return Uint8Array.from(atob(function(e){const r=e.replaceAll("-","+").replaceAll("_","/"),t=(4-r.length%4)%4;return r+"=".repeat(t)}(e)),e=>e.charCodeAt(0))},exports.stringToUint8Array=function(r){return e.encode(r)},exports.uint8ArrayToBase64=function(e,r=!1){let t="";for(let r=0;r<e.length;r+=65535){const n=e.subarray(r,r+65535);t+=globalThis.btoa(String.fromCharCode.apply(void 0,n))}return r?t.replaceAll("+","-").replaceAll("/","_").replace(/=+$/,""):t},exports.uint8ArrayToString=function(e,t="utf8"){return r[t]??=new TextDecoder(t),r[t].decode(e)};
1
+ "use strict";var r=require("../guard/index.js");const e=new TextEncoder,t={utf8:new TextDecoder("utf8")};exports.base64ToUint8Array=function(r){return Uint8Array.from(atob(function(r){const e=r.replaceAll("-","+").replaceAll("_","/"),t=(4-e.length%4)%4;return e+"=".repeat(t)}(r)),r=>r.charCodeAt(0))},exports.concatUint8Arrays=function(r,e){if(0===r.length)return new Uint8Array(0);const t=new Uint8Array(e??=r.reduce((r,e)=>r+e.length,0));let n=0;for(let e=0,o=r.length;e<o;e++){const o=r[e];t.set(o,n),n+=o.length}return t},exports.stringToUint8Array=function(r){return e.encode(r)},exports.toUint8Array=function(e){return e instanceof ArrayBuffer?new Uint8Array(e):ArrayBuffer.isView(e)?new Uint8Array(e.buffer,e.byteOffset,e.byteLength):void r.never(e,"value must be ArrayBuffer or ArrayBufferView")},exports.uint8ArrayToBase64=function(r,e=!1){let t="";for(let e=0;e<r.length;e+=65535){const n=r.subarray(e,e+65535);t+=globalThis.btoa(String.fromCharCode.apply(void 0,n))}return e?t.replaceAll("+","-").replaceAll("/","_").replace(/=+$/,""):t},exports.uint8ArrayToString=function(r,e="utf8"){return t[e]??=new TextDecoder(e),t[e].decode(r)};
@@ -1 +1 @@
1
- const e=new TextEncoder;function r(r){return e.encode(r)}const t={utf8:new TextDecoder("utf8")};function n(e,r="utf8"){return t[r]??=new TextDecoder(r),t[r].decode(e)}function o(e){return Uint8Array.from(atob(function(e){const r=e.replaceAll("-","+").replaceAll("_","/"),t=(4-r.length%4)%4;return r+"=".repeat(t)}(e)),e=>e.charCodeAt(0))}function a(e,r=!1){let t="";for(let r=0;r<e.length;r+=65535){const n=e.subarray(r,r+65535);t+=globalThis.btoa(String.fromCharCode.apply(void 0,n))}return r?t.replaceAll("+","-").replaceAll("/","_").replace(/=+$/,""):t}export{o as base64ToUint8Array,r as stringToUint8Array,a as uint8ArrayToBase64,n as uint8ArrayToString};
1
+ import{never as r}from"../guard/index.mjs";const e=new TextEncoder;function t(r){return e.encode(r)}const n={utf8:new TextDecoder("utf8")};function o(r,e="utf8"){return n[e]??=new TextDecoder(e),n[e].decode(r)}function a(r){return Uint8Array.from(atob(function(r){const e=r.replaceAll("-","+").replaceAll("_","/"),t=(4-e.length%4)%4;return e+"=".repeat(t)}(r)),r=>r.charCodeAt(0))}function i(r,e=!1){let t="";for(let e=0;e<r.length;e+=65535){const n=r.subarray(e,e+65535);t+=globalThis.btoa(String.fromCharCode.apply(void 0,n))}return e?t.replaceAll("+","-").replaceAll("/","_").replace(/=+$/,""):t}function u(r,e){if(0===r.length)return new Uint8Array(0);const t=new Uint8Array(e??=r.reduce((r,e)=>r+e.length,0));let n=0;for(let e=0,o=r.length;e<o;e++){const o=r[e];t.set(o,n),n+=o.length}return t}function f(e){return e instanceof ArrayBuffer?new Uint8Array(e):ArrayBuffer.isView(e)?new Uint8Array(e.buffer,e.byteOffset,e.byteLength):void r(e,"value must be ArrayBuffer or ArrayBufferView")}export{a as base64ToUint8Array,u as concatUint8Arrays,t as stringToUint8Array,f as toUint8Array,i as uint8ArrayToBase64,o as uint8ArrayToString};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "foxts",
3
- "version": "4.0.0-beta.2",
3
+ "version": "4.1.0",
4
4
  "description": "Opinionated collection of common TypeScript utils by @SukkaW",
5
5
  "repository": {
6
6
  "url": "https://github.com/SukkaW/foxts"
@@ -34,6 +34,12 @@
34
34
  "require": "./dist/append-set-elements-to-array/index.js",
35
35
  "default": "./dist/append-set-elements-to-array/index.js"
36
36
  },
37
+ "./async-retry": {
38
+ "types": "./dist/async-retry/index.d.ts",
39
+ "import": "./dist/async-retry/index.mjs",
40
+ "require": "./dist/async-retry/index.js",
41
+ "default": "./dist/async-retry/index.js"
42
+ },
37
43
  "./async-write-to-stream": {
38
44
  "types": "./dist/async-write-to-stream/index.d.ts",
39
45
  "import": "./dist/async-write-to-stream/index.mjs",
@@ -166,6 +172,12 @@
166
172
  "require": "./dist/is-function/index.js",
167
173
  "default": "./dist/is-function/index.js"
168
174
  },
175
+ "./is-network-error": {
176
+ "types": "./dist/is-network-error/index.d.ts",
177
+ "import": "./dist/is-network-error/index.mjs",
178
+ "require": "./dist/is-network-error/index.js",
179
+ "default": "./dist/is-network-error/index.js"
180
+ },
169
181
  "./is-probably-ip": {
170
182
  "types": "./dist/is-probably-ip/index.d.ts",
171
183
  "import": "./dist/is-probably-ip/index.mjs",
@@ -287,23 +299,26 @@
287
299
  "@mitata/counters": "^0.0.8",
288
300
  "@monyone/aho-corasick": "^1.0.4",
289
301
  "@package-json/types": "^0.0.12",
302
+ "@rollup/plugin-node-resolve": "^16.0.1",
290
303
  "@swc-node/register": "^1.11.1",
291
304
  "@swc/core": "^1.13.20",
292
305
  "@types/mocha": "^10.0.10",
293
- "@types/node": "^22.18.6",
306
+ "@types/node": "^22.18.8",
294
307
  "@types/sinon": "^17.0.4",
295
308
  "bumpp": "^10.2.3",
296
309
  "devalue": "^5.3.2",
297
- "eslint": "^9.36.0",
310
+ "eslint": "^9.37.0",
298
311
  "eslint-config-sukka": "^7.2.1",
299
312
  "eslint-formatter-sukka": "^7.2.1",
300
313
  "expect": "^30.2.0",
301
314
  "fastscan": "^1.0.6",
315
+ "is-ci": "^4.1.0",
316
+ "is-network-error": "^1.3.0",
302
317
  "mitata": "^1.0.34",
303
- "mocha": "^11.7.2",
318
+ "mocha": "^11.7.4",
304
319
  "modern-ahocorasick": "^2.0.4",
305
320
  "nyc": "^17.1.0",
306
- "rollup": "^4.52.3",
321
+ "rollup": "^4.52.4",
307
322
  "rollup-plugin-dts": "^6.2.3",
308
323
  "rollup-plugin-swc3": "^0.12.1",
309
324
  "sinon": "^21.0.0",