@shware/http 1.2.7 → 1.2.9

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
@@ -40,6 +40,7 @@ __export(index_exports, {
40
40
  PurchaseError: () => import_error.PurchaseError,
41
41
  TokenBucket: () => import_token_bucket.TokenBucket,
42
42
  base62: () => import_base62.base62,
43
+ fetch: () => import_fetch.fetch,
43
44
  getErrorMessage: () => import_parse.getErrorMessage,
44
45
  getNextPageParam: () => import_response.getNextPageParam,
45
46
  getPreviousPageParam: () => import_response.getPreviousPageParam,
@@ -65,6 +66,7 @@ var import_timing = require("./utils/timing.cjs");
65
66
  var import_promise = require("./utils/promise.cjs");
66
67
  var import_invariant = require("./utils/invariant.cjs");
67
68
  var import_token_bucket = require("./utils/token-bucket.cjs");
69
+ var import_fetch = require("./utils/fetch.cjs");
68
70
  var import_iso_3601_1 = require("./iso/iso_3601_1.cjs");
69
71
  // Annotate the CommonJS export names for ESM import in node:
70
72
  0 && (module.exports = {
@@ -77,6 +79,7 @@ var import_iso_3601_1 = require("./iso/iso_3601_1.cjs");
77
79
  PurchaseError,
78
80
  TokenBucket,
79
81
  base62,
82
+ fetch,
80
83
  getErrorMessage,
81
84
  getNextPageParam,
82
85
  getPreviousPageParam,
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts"],"sourcesContent":["/**\n * @example\n * import { Details, Status } from '@repo/error';\n *\n * Status.adapter = () => new Error('Error');\n *\n * const details = Details.new()\n * .requestInfo({ requestId: '1234567890', servingData: '/v1/tests' })\n * .errorInfo({ reason: 'ACCOUNT_LOCKED' });\n *\n * throw Status.alreadyExists('xxx').error(details);\n */\n\nexport {\n LoginTimeoutError,\n LoginCanceledError,\n CheckoutCreateError,\n PurchaseError,\n} from './error/index';\nexport type {\n NetworkErrorReason,\n StatusErrorReason,\n AuthenticationErrorReason,\n ModerationErrorReason,\n MultipartErrorReason,\n AppErrorReason,\n ErrorReason,\n} from './error/reason';\nexport * from './error/detail';\nexport * from './error/status';\nexport { getErrorMessage } from './error/parse';\nexport {\n pageParamsSchema,\n Cursor,\n initialPageParam,\n getPreviousPageParam,\n getNextPageParam,\n} from './response';\nexport type {\n Empty,\n EntityId,\n Entity,\n Response,\n InitParams,\n NextParams,\n PrevParams,\n PageParams,\n ParentPageParams,\n Page,\n} from './response';\n\nexport * from './vaild';\nexport * from './snowflake';\n\nexport * as MAX_LENGTH from './max-length/index';\nexport { base62 } from './utils/base62';\nexport { hasText } from './utils/string';\nexport { timing } from './utils/timing';\nexport { once } from './utils/promise';\nexport { invariant } from './utils/invariant';\nexport { TokenBucket, type TokenBucketOptions } from './utils/token-bucket';\n\nexport { ISO_3601_1, type ISO3166CountryCode } from './iso/iso_3601_1';\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAaA,mBAKO;AAUP,0BAAc,+BA5Bd;AA6BA,0BAAc,+BA7Bd;AA8BA,mBAAgC;AAChC,sBAMO;AAcP,0BAAc,wBAnDd;AAoDA,0BAAc,4BApDd;AAsDA,iBAA4B;AAC5B,oBAAuB;AACvB,oBAAwB;AACxB,oBAAuB;AACvB,qBAAqB;AACrB,uBAA0B;AAC1B,0BAAqD;AAErD,wBAAoD;","names":[]}
1
+ {"version":3,"sources":["../src/index.ts"],"sourcesContent":["/**\n * @example\n * import { Details, Status } from '@repo/error';\n *\n * Status.adapter = () => new Error('Error');\n *\n * const details = Details.new()\n * .requestInfo({ requestId: '1234567890', servingData: '/v1/tests' })\n * .errorInfo({ reason: 'ACCOUNT_LOCKED' });\n *\n * throw Status.alreadyExists('xxx').error(details);\n */\n\nexport {\n LoginTimeoutError,\n LoginCanceledError,\n CheckoutCreateError,\n PurchaseError,\n} from './error/index';\nexport type {\n NetworkErrorReason,\n StatusErrorReason,\n AuthenticationErrorReason,\n ModerationErrorReason,\n MultipartErrorReason,\n AppErrorReason,\n ErrorReason,\n} from './error/reason';\nexport * from './error/detail';\nexport * from './error/status';\nexport { getErrorMessage } from './error/parse';\nexport {\n pageParamsSchema,\n Cursor,\n initialPageParam,\n getPreviousPageParam,\n getNextPageParam,\n} from './response';\nexport type {\n Empty,\n EntityId,\n Entity,\n Response,\n InitParams,\n NextParams,\n PrevParams,\n PageParams,\n ParentPageParams,\n Page,\n} from './response';\n\nexport * from './vaild';\nexport * from './snowflake';\n\nexport * as MAX_LENGTH from './max-length/index';\nexport { base62 } from './utils/base62';\nexport { hasText } from './utils/string';\nexport { timing } from './utils/timing';\nexport { once } from './utils/promise';\nexport { invariant } from './utils/invariant';\nexport { TokenBucket, type TokenBucketOptions } from './utils/token-bucket';\nexport { fetch, type RetryOptions } from './utils/fetch';\nexport { ISO_3601_1, type ISO3166CountryCode } from './iso/iso_3601_1';\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAaA,mBAKO;AAUP,0BAAc,+BA5Bd;AA6BA,0BAAc,+BA7Bd;AA8BA,mBAAgC;AAChC,sBAMO;AAcP,0BAAc,wBAnDd;AAoDA,0BAAc,4BApDd;AAsDA,iBAA4B;AAC5B,oBAAuB;AACvB,oBAAwB;AACxB,oBAAuB;AACvB,qBAAqB;AACrB,uBAA0B;AAC1B,0BAAqD;AACrD,mBAAyC;AACzC,wBAAoD;","names":[]}
package/dist/index.d.cts CHANGED
@@ -13,6 +13,7 @@ export { timing } from './utils/timing.cjs';
13
13
  export { once } from './utils/promise.cjs';
14
14
  export { invariant } from './utils/invariant.cjs';
15
15
  export { TokenBucket, TokenBucketOptions } from './utils/token-bucket.cjs';
16
+ export { RetryOptions, fetch } from './utils/fetch.cjs';
16
17
  export { ISO3166CountryCode, ISO_3601_1 } from './iso/iso_3601_1.cjs';
17
18
  import 'i18next';
18
19
  import 'zod/v4/core';
package/dist/index.d.ts CHANGED
@@ -13,6 +13,7 @@ export { timing } from './utils/timing.js';
13
13
  export { once } from './utils/promise.js';
14
14
  export { invariant } from './utils/invariant.js';
15
15
  export { TokenBucket, TokenBucketOptions } from './utils/token-bucket.js';
16
+ export { RetryOptions, fetch } from './utils/fetch.js';
16
17
  export { ISO3166CountryCode, ISO_3601_1 } from './iso/iso_3601_1.js';
17
18
  import 'i18next';
18
19
  import 'zod/v4/core';
package/dist/index.mjs CHANGED
@@ -24,6 +24,7 @@ import { timing } from "./utils/timing.mjs";
24
24
  import { once } from "./utils/promise.mjs";
25
25
  import { invariant } from "./utils/invariant.mjs";
26
26
  import { TokenBucket } from "./utils/token-bucket.mjs";
27
+ import { fetch } from "./utils/fetch.mjs";
27
28
  import { ISO_3601_1 } from "./iso/iso_3601_1.mjs";
28
29
  export {
29
30
  CheckoutCreateError,
@@ -35,6 +36,7 @@ export {
35
36
  PurchaseError,
36
37
  TokenBucket,
37
38
  base62,
39
+ fetch,
38
40
  getErrorMessage,
39
41
  getNextPageParam,
40
42
  getPreviousPageParam,
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts"],"sourcesContent":["/**\n * @example\n * import { Details, Status } from '@repo/error';\n *\n * Status.adapter = () => new Error('Error');\n *\n * const details = Details.new()\n * .requestInfo({ requestId: '1234567890', servingData: '/v1/tests' })\n * .errorInfo({ reason: 'ACCOUNT_LOCKED' });\n *\n * throw Status.alreadyExists('xxx').error(details);\n */\n\nexport {\n LoginTimeoutError,\n LoginCanceledError,\n CheckoutCreateError,\n PurchaseError,\n} from './error/index';\nexport type {\n NetworkErrorReason,\n StatusErrorReason,\n AuthenticationErrorReason,\n ModerationErrorReason,\n MultipartErrorReason,\n AppErrorReason,\n ErrorReason,\n} from './error/reason';\nexport * from './error/detail';\nexport * from './error/status';\nexport { getErrorMessage } from './error/parse';\nexport {\n pageParamsSchema,\n Cursor,\n initialPageParam,\n getPreviousPageParam,\n getNextPageParam,\n} from './response';\nexport type {\n Empty,\n EntityId,\n Entity,\n Response,\n InitParams,\n NextParams,\n PrevParams,\n PageParams,\n ParentPageParams,\n Page,\n} from './response';\n\nexport * from './vaild';\nexport * from './snowflake';\n\nexport * as MAX_LENGTH from './max-length/index';\nexport { base62 } from './utils/base62';\nexport { hasText } from './utils/string';\nexport { timing } from './utils/timing';\nexport { once } from './utils/promise';\nexport { invariant } from './utils/invariant';\nexport { TokenBucket, type TokenBucketOptions } from './utils/token-bucket';\n\nexport { ISO_3601_1, type ISO3166CountryCode } from './iso/iso_3601_1';\n"],"mappings":";AAaA;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AAUP,cAAc;AACd,cAAc;AACd,SAAS,uBAAuB;AAChC;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AAcP,cAAc;AACd,cAAc;AAEd,YAAY,gBAAgB;AAC5B,SAAS,cAAc;AACvB,SAAS,eAAe;AACxB,SAAS,cAAc;AACvB,SAAS,YAAY;AACrB,SAAS,iBAAiB;AAC1B,SAAS,mBAA4C;AAErD,SAAS,kBAA2C;","names":[]}
1
+ {"version":3,"sources":["../src/index.ts"],"sourcesContent":["/**\n * @example\n * import { Details, Status } from '@repo/error';\n *\n * Status.adapter = () => new Error('Error');\n *\n * const details = Details.new()\n * .requestInfo({ requestId: '1234567890', servingData: '/v1/tests' })\n * .errorInfo({ reason: 'ACCOUNT_LOCKED' });\n *\n * throw Status.alreadyExists('xxx').error(details);\n */\n\nexport {\n LoginTimeoutError,\n LoginCanceledError,\n CheckoutCreateError,\n PurchaseError,\n} from './error/index';\nexport type {\n NetworkErrorReason,\n StatusErrorReason,\n AuthenticationErrorReason,\n ModerationErrorReason,\n MultipartErrorReason,\n AppErrorReason,\n ErrorReason,\n} from './error/reason';\nexport * from './error/detail';\nexport * from './error/status';\nexport { getErrorMessage } from './error/parse';\nexport {\n pageParamsSchema,\n Cursor,\n initialPageParam,\n getPreviousPageParam,\n getNextPageParam,\n} from './response';\nexport type {\n Empty,\n EntityId,\n Entity,\n Response,\n InitParams,\n NextParams,\n PrevParams,\n PageParams,\n ParentPageParams,\n Page,\n} from './response';\n\nexport * from './vaild';\nexport * from './snowflake';\n\nexport * as MAX_LENGTH from './max-length/index';\nexport { base62 } from './utils/base62';\nexport { hasText } from './utils/string';\nexport { timing } from './utils/timing';\nexport { once } from './utils/promise';\nexport { invariant } from './utils/invariant';\nexport { TokenBucket, type TokenBucketOptions } from './utils/token-bucket';\nexport { fetch, type RetryOptions } from './utils/fetch';\nexport { ISO_3601_1, type ISO3166CountryCode } from './iso/iso_3601_1';\n"],"mappings":";AAaA;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AAUP,cAAc;AACd,cAAc;AACd,SAAS,uBAAuB;AAChC;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AAcP,cAAc;AACd,cAAc;AAEd,YAAY,gBAAgB;AAC5B,SAAS,cAAc;AACvB,SAAS,eAAe;AACxB,SAAS,cAAc;AACvB,SAAS,YAAY;AACrB,SAAS,iBAAiB;AAC1B,SAAS,mBAA4C;AACrD,SAAS,aAAgC;AACzC,SAAS,kBAA2C;","names":[]}
@@ -0,0 +1,78 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/utils/fetch.ts
21
+ var fetch_exports = {};
22
+ __export(fetch_exports, {
23
+ fetch: () => fetch
24
+ });
25
+ module.exports = __toCommonJS(fetch_exports);
26
+ function defaultRetryCondition(response) {
27
+ return response.status === 408 || response.status === 429 || response.status >= 500;
28
+ }
29
+ function parseRetryAfter(response) {
30
+ if (!response) return null;
31
+ const header = response.headers.get("retry-after");
32
+ if (!header) return null;
33
+ const seconds = Number(header);
34
+ if (!Number.isNaN(seconds)) return Math.max(0, seconds * 1e3);
35
+ const dateMs = Date.parse(header);
36
+ if (!Number.isNaN(dateMs)) return Math.max(0, dateMs - Date.now());
37
+ return null;
38
+ }
39
+ async function fetch(input, {
40
+ retries = 3,
41
+ delayFactor = 500,
42
+ maxDelay = 3e4,
43
+ retryCondition = defaultRetryCondition,
44
+ ...init
45
+ } = {}) {
46
+ let retryCount = 0;
47
+ let lastError = null;
48
+ let lastResponse = null;
49
+ while (retryCount <= retries) {
50
+ try {
51
+ const response = await globalThis.fetch(input, init);
52
+ lastResponse = response;
53
+ if (response.ok || !retryCondition(response) || retryCount === retries) {
54
+ return response;
55
+ }
56
+ const retryAfter = parseRetryAfter(response);
57
+ const delay = delayFactor * Math.pow(2, retryCount);
58
+ const jitter = delay * 0.25 * (Math.random() * 2 - 1);
59
+ const timeout = Math.min(retryAfter ?? delay + jitter, maxDelay);
60
+ await new Promise((resolve) => setTimeout(resolve, timeout));
61
+ } catch (error) {
62
+ lastError = error;
63
+ if (retryCount === retries) throw error;
64
+ const delay = delayFactor * Math.pow(2, retryCount);
65
+ const jitter = delay * 0.25 * (Math.random() * 2 - 1);
66
+ const timeout = Math.min(delay + jitter, maxDelay);
67
+ await new Promise((resolve) => setTimeout(resolve, timeout));
68
+ }
69
+ retryCount++;
70
+ }
71
+ if (lastResponse) return lastResponse;
72
+ throw lastError ?? new Error("Fetch failed");
73
+ }
74
+ // Annotate the CommonJS export names for ESM import in node:
75
+ 0 && (module.exports = {
76
+ fetch
77
+ });
78
+ //# sourceMappingURL=fetch.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/utils/fetch.ts"],"sourcesContent":["export interface RetryOptions {\n /** The number of times to retry before failing, default: 3 */\n retries?: number;\n\n /** The delay factor in milliseconds, default: 500 */\n delayFactor?: number;\n\n /** The maximum delay in milliseconds, default: 30_000 (30s) */\n maxDelay?: number;\n\n /**\n * A callback to further control if a request should be retried.\n * default: 408 (Request Timeout) or 429 (Too Many Requests) or 5xx (Server Error).\n */\n retryCondition?: (response: Response) => boolean;\n}\n\nfunction defaultRetryCondition(response: Response): boolean {\n return response.status === 408 || response.status === 429 || response.status >= 500;\n}\n\nfunction parseRetryAfter(response: Response | null): number | null {\n if (!response) return null;\n const header = response.headers.get('retry-after');\n if (!header) return null;\n\n // if the retry after header is a number, convert it to milliseconds\n const seconds = Number(header);\n if (!Number.isNaN(seconds)) return Math.max(0, seconds * 1000);\n\n // if the retry after header is a date, get the number of milliseconds until that date\n const dateMs = Date.parse(header);\n if (!Number.isNaN(dateMs)) return Math.max(0, dateMs - Date.now());\n\n return null;\n}\n\nexport async function fetch(\n input: RequestInfo,\n {\n retries = 3,\n delayFactor = 500,\n maxDelay = 30_000,\n retryCondition = defaultRetryCondition,\n ...init\n }: RequestInit & RetryOptions = {}\n): Promise<Response> {\n let retryCount = 0;\n let lastError: unknown | null = null;\n let lastResponse: Response | null = null;\n\n while (retryCount <= retries) {\n try {\n const response = await globalThis.fetch(input, init);\n lastResponse = response;\n if (response.ok || !retryCondition(response) || retryCount === retries) {\n return response;\n }\n\n const retryAfter = parseRetryAfter(response);\n const delay = delayFactor * Math.pow(2, retryCount);\n const jitter = delay * 0.25 * (Math.random() * 2 - 1); // 25% jitter\n\n const timeout = Math.min(retryAfter ?? delay + jitter, maxDelay);\n await new Promise((resolve) => setTimeout(resolve, timeout));\n } catch (error) {\n lastError = error;\n if (retryCount === retries) throw error;\n\n const delay = delayFactor * Math.pow(2, retryCount);\n const jitter = delay * 0.25 * (Math.random() * 2 - 1); // 25% jitter\n\n const timeout = Math.min(delay + jitter, maxDelay);\n await new Promise((resolve) => setTimeout(resolve, timeout));\n }\n\n retryCount++;\n }\n\n if (lastResponse) return lastResponse;\n throw lastError ?? new Error('Fetch failed');\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAiBA,SAAS,sBAAsB,UAA6B;AAC1D,SAAO,SAAS,WAAW,OAAO,SAAS,WAAW,OAAO,SAAS,UAAU;AAClF;AAEA,SAAS,gBAAgB,UAA0C;AACjE,MAAI,CAAC,SAAU,QAAO;AACtB,QAAM,SAAS,SAAS,QAAQ,IAAI,aAAa;AACjD,MAAI,CAAC,OAAQ,QAAO;AAGpB,QAAM,UAAU,OAAO,MAAM;AAC7B,MAAI,CAAC,OAAO,MAAM,OAAO,EAAG,QAAO,KAAK,IAAI,GAAG,UAAU,GAAI;AAG7D,QAAM,SAAS,KAAK,MAAM,MAAM;AAChC,MAAI,CAAC,OAAO,MAAM,MAAM,EAAG,QAAO,KAAK,IAAI,GAAG,SAAS,KAAK,IAAI,CAAC;AAEjE,SAAO;AACT;AAEA,eAAsB,MACpB,OACA;AAAA,EACE,UAAU;AAAA,EACV,cAAc;AAAA,EACd,WAAW;AAAA,EACX,iBAAiB;AAAA,EACjB,GAAG;AACL,IAAgC,CAAC,GACd;AACnB,MAAI,aAAa;AACjB,MAAI,YAA4B;AAChC,MAAI,eAAgC;AAEpC,SAAO,cAAc,SAAS;AAC5B,QAAI;AACF,YAAM,WAAW,MAAM,WAAW,MAAM,OAAO,IAAI;AACnD,qBAAe;AACf,UAAI,SAAS,MAAM,CAAC,eAAe,QAAQ,KAAK,eAAe,SAAS;AACtE,eAAO;AAAA,MACT;AAEA,YAAM,aAAa,gBAAgB,QAAQ;AAC3C,YAAM,QAAQ,cAAc,KAAK,IAAI,GAAG,UAAU;AAClD,YAAM,SAAS,QAAQ,QAAQ,KAAK,OAAO,IAAI,IAAI;AAEnD,YAAM,UAAU,KAAK,IAAI,cAAc,QAAQ,QAAQ,QAAQ;AAC/D,YAAM,IAAI,QAAQ,CAAC,YAAY,WAAW,SAAS,OAAO,CAAC;AAAA,IAC7D,SAAS,OAAO;AACd,kBAAY;AACZ,UAAI,eAAe,QAAS,OAAM;AAElC,YAAM,QAAQ,cAAc,KAAK,IAAI,GAAG,UAAU;AAClD,YAAM,SAAS,QAAQ,QAAQ,KAAK,OAAO,IAAI,IAAI;AAEnD,YAAM,UAAU,KAAK,IAAI,QAAQ,QAAQ,QAAQ;AACjD,YAAM,IAAI,QAAQ,CAAC,YAAY,WAAW,SAAS,OAAO,CAAC;AAAA,IAC7D;AAEA;AAAA,EACF;AAEA,MAAI,aAAc,QAAO;AACzB,QAAM,aAAa,IAAI,MAAM,cAAc;AAC7C;","names":[]}
@@ -0,0 +1,16 @@
1
+ interface RetryOptions {
2
+ /** The number of times to retry before failing, default: 3 */
3
+ retries?: number;
4
+ /** The delay factor in milliseconds, default: 500 */
5
+ delayFactor?: number;
6
+ /** The maximum delay in milliseconds, default: 30_000 (30s) */
7
+ maxDelay?: number;
8
+ /**
9
+ * A callback to further control if a request should be retried.
10
+ * default: 408 (Request Timeout) or 429 (Too Many Requests) or 5xx (Server Error).
11
+ */
12
+ retryCondition?: (response: Response) => boolean;
13
+ }
14
+ declare function fetch(input: RequestInfo, { retries, delayFactor, maxDelay, retryCondition, ...init }?: RequestInit & RetryOptions): Promise<Response>;
15
+
16
+ export { type RetryOptions, fetch };
@@ -0,0 +1,16 @@
1
+ interface RetryOptions {
2
+ /** The number of times to retry before failing, default: 3 */
3
+ retries?: number;
4
+ /** The delay factor in milliseconds, default: 500 */
5
+ delayFactor?: number;
6
+ /** The maximum delay in milliseconds, default: 30_000 (30s) */
7
+ maxDelay?: number;
8
+ /**
9
+ * A callback to further control if a request should be retried.
10
+ * default: 408 (Request Timeout) or 429 (Too Many Requests) or 5xx (Server Error).
11
+ */
12
+ retryCondition?: (response: Response) => boolean;
13
+ }
14
+ declare function fetch(input: RequestInfo, { retries, delayFactor, maxDelay, retryCondition, ...init }?: RequestInit & RetryOptions): Promise<Response>;
15
+
16
+ export { type RetryOptions, fetch };
@@ -0,0 +1,53 @@
1
+ // src/utils/fetch.ts
2
+ function defaultRetryCondition(response) {
3
+ return response.status === 408 || response.status === 429 || response.status >= 500;
4
+ }
5
+ function parseRetryAfter(response) {
6
+ if (!response) return null;
7
+ const header = response.headers.get("retry-after");
8
+ if (!header) return null;
9
+ const seconds = Number(header);
10
+ if (!Number.isNaN(seconds)) return Math.max(0, seconds * 1e3);
11
+ const dateMs = Date.parse(header);
12
+ if (!Number.isNaN(dateMs)) return Math.max(0, dateMs - Date.now());
13
+ return null;
14
+ }
15
+ async function fetch(input, {
16
+ retries = 3,
17
+ delayFactor = 500,
18
+ maxDelay = 3e4,
19
+ retryCondition = defaultRetryCondition,
20
+ ...init
21
+ } = {}) {
22
+ let retryCount = 0;
23
+ let lastError = null;
24
+ let lastResponse = null;
25
+ while (retryCount <= retries) {
26
+ try {
27
+ const response = await globalThis.fetch(input, init);
28
+ lastResponse = response;
29
+ if (response.ok || !retryCondition(response) || retryCount === retries) {
30
+ return response;
31
+ }
32
+ const retryAfter = parseRetryAfter(response);
33
+ const delay = delayFactor * Math.pow(2, retryCount);
34
+ const jitter = delay * 0.25 * (Math.random() * 2 - 1);
35
+ const timeout = Math.min(retryAfter ?? delay + jitter, maxDelay);
36
+ await new Promise((resolve) => setTimeout(resolve, timeout));
37
+ } catch (error) {
38
+ lastError = error;
39
+ if (retryCount === retries) throw error;
40
+ const delay = delayFactor * Math.pow(2, retryCount);
41
+ const jitter = delay * 0.25 * (Math.random() * 2 - 1);
42
+ const timeout = Math.min(delay + jitter, maxDelay);
43
+ await new Promise((resolve) => setTimeout(resolve, timeout));
44
+ }
45
+ retryCount++;
46
+ }
47
+ if (lastResponse) return lastResponse;
48
+ throw lastError ?? new Error("Fetch failed");
49
+ }
50
+ export {
51
+ fetch
52
+ };
53
+ //# sourceMappingURL=fetch.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/utils/fetch.ts"],"sourcesContent":["export interface RetryOptions {\n /** The number of times to retry before failing, default: 3 */\n retries?: number;\n\n /** The delay factor in milliseconds, default: 500 */\n delayFactor?: number;\n\n /** The maximum delay in milliseconds, default: 30_000 (30s) */\n maxDelay?: number;\n\n /**\n * A callback to further control if a request should be retried.\n * default: 408 (Request Timeout) or 429 (Too Many Requests) or 5xx (Server Error).\n */\n retryCondition?: (response: Response) => boolean;\n}\n\nfunction defaultRetryCondition(response: Response): boolean {\n return response.status === 408 || response.status === 429 || response.status >= 500;\n}\n\nfunction parseRetryAfter(response: Response | null): number | null {\n if (!response) return null;\n const header = response.headers.get('retry-after');\n if (!header) return null;\n\n // if the retry after header is a number, convert it to milliseconds\n const seconds = Number(header);\n if (!Number.isNaN(seconds)) return Math.max(0, seconds * 1000);\n\n // if the retry after header is a date, get the number of milliseconds until that date\n const dateMs = Date.parse(header);\n if (!Number.isNaN(dateMs)) return Math.max(0, dateMs - Date.now());\n\n return null;\n}\n\nexport async function fetch(\n input: RequestInfo,\n {\n retries = 3,\n delayFactor = 500,\n maxDelay = 30_000,\n retryCondition = defaultRetryCondition,\n ...init\n }: RequestInit & RetryOptions = {}\n): Promise<Response> {\n let retryCount = 0;\n let lastError: unknown | null = null;\n let lastResponse: Response | null = null;\n\n while (retryCount <= retries) {\n try {\n const response = await globalThis.fetch(input, init);\n lastResponse = response;\n if (response.ok || !retryCondition(response) || retryCount === retries) {\n return response;\n }\n\n const retryAfter = parseRetryAfter(response);\n const delay = delayFactor * Math.pow(2, retryCount);\n const jitter = delay * 0.25 * (Math.random() * 2 - 1); // 25% jitter\n\n const timeout = Math.min(retryAfter ?? delay + jitter, maxDelay);\n await new Promise((resolve) => setTimeout(resolve, timeout));\n } catch (error) {\n lastError = error;\n if (retryCount === retries) throw error;\n\n const delay = delayFactor * Math.pow(2, retryCount);\n const jitter = delay * 0.25 * (Math.random() * 2 - 1); // 25% jitter\n\n const timeout = Math.min(delay + jitter, maxDelay);\n await new Promise((resolve) => setTimeout(resolve, timeout));\n }\n\n retryCount++;\n }\n\n if (lastResponse) return lastResponse;\n throw lastError ?? new Error('Fetch failed');\n}\n"],"mappings":";AAiBA,SAAS,sBAAsB,UAA6B;AAC1D,SAAO,SAAS,WAAW,OAAO,SAAS,WAAW,OAAO,SAAS,UAAU;AAClF;AAEA,SAAS,gBAAgB,UAA0C;AACjE,MAAI,CAAC,SAAU,QAAO;AACtB,QAAM,SAAS,SAAS,QAAQ,IAAI,aAAa;AACjD,MAAI,CAAC,OAAQ,QAAO;AAGpB,QAAM,UAAU,OAAO,MAAM;AAC7B,MAAI,CAAC,OAAO,MAAM,OAAO,EAAG,QAAO,KAAK,IAAI,GAAG,UAAU,GAAI;AAG7D,QAAM,SAAS,KAAK,MAAM,MAAM;AAChC,MAAI,CAAC,OAAO,MAAM,MAAM,EAAG,QAAO,KAAK,IAAI,GAAG,SAAS,KAAK,IAAI,CAAC;AAEjE,SAAO;AACT;AAEA,eAAsB,MACpB,OACA;AAAA,EACE,UAAU;AAAA,EACV,cAAc;AAAA,EACd,WAAW;AAAA,EACX,iBAAiB;AAAA,EACjB,GAAG;AACL,IAAgC,CAAC,GACd;AACnB,MAAI,aAAa;AACjB,MAAI,YAA4B;AAChC,MAAI,eAAgC;AAEpC,SAAO,cAAc,SAAS;AAC5B,QAAI;AACF,YAAM,WAAW,MAAM,WAAW,MAAM,OAAO,IAAI;AACnD,qBAAe;AACf,UAAI,SAAS,MAAM,CAAC,eAAe,QAAQ,KAAK,eAAe,SAAS;AACtE,eAAO;AAAA,MACT;AAEA,YAAM,aAAa,gBAAgB,QAAQ;AAC3C,YAAM,QAAQ,cAAc,KAAK,IAAI,GAAG,UAAU;AAClD,YAAM,SAAS,QAAQ,QAAQ,KAAK,OAAO,IAAI,IAAI;AAEnD,YAAM,UAAU,KAAK,IAAI,cAAc,QAAQ,QAAQ,QAAQ;AAC/D,YAAM,IAAI,QAAQ,CAAC,YAAY,WAAW,SAAS,OAAO,CAAC;AAAA,IAC7D,SAAS,OAAO;AACd,kBAAY;AACZ,UAAI,eAAe,QAAS,OAAM;AAElC,YAAM,QAAQ,cAAc,KAAK,IAAI,GAAG,UAAU;AAClD,YAAM,SAAS,QAAQ,QAAQ,KAAK,OAAO,IAAI,IAAI;AAEnD,YAAM,UAAU,KAAK,IAAI,QAAQ,QAAQ,QAAQ;AACjD,YAAM,IAAI,QAAQ,CAAC,YAAY,WAAW,SAAS,OAAO,CAAC;AAAA,IAC7D;AAEA;AAAA,EACF;AAEA,MAAI,aAAc,QAAO;AACzB,QAAM,aAAa,IAAI,MAAM,cAAc;AAC7C;","names":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@shware/http",
3
- "version": "1.2.7",
3
+ "version": "1.2.9",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "repository": {