@internetarchive/fetch-handler 1.1.0-webdev-7731.5 → 1.1.0-webdev-7731.6

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.
@@ -5,8 +5,9 @@ export declare class DefaultRetryConfiguration implements RetryConfiguring {
5
5
  private readonly maxRetries;
6
6
  constructor(options?: {
7
7
  maxRetries?: number;
8
+ transientStatusCodes?: Set<number>;
8
9
  });
9
- private readonly transient4xxStatusCodes;
10
+ readonly transientStatusCodes: ReadonlySet<number>;
10
11
  shouldRetry(response: Response | null, retryNumber: number): boolean;
11
12
  retryDelay(retryNumber: number, response?: Response | null): Milliseconds;
12
13
  }
@@ -1,22 +1,29 @@
1
1
  export class DefaultRetryConfiguration {
2
2
  constructor(options) {
3
3
  this.maxRetries = 2;
4
- this.transient4xxStatusCodes = new Set([
4
+ this.transientStatusCodes = new Set([
5
5
  408, // Request Timeout
6
6
  429, // Too Many Requests
7
+ 500, // Internal Server Error
8
+ 502, // Bad Gateway
9
+ 503, // Service Unavailable
10
+ 504, // Gateway Timeout
11
+ 522, // Cloudflare Origin Server Connection Timed Out
7
12
  ]);
8
13
  if ((options === null || options === void 0 ? void 0 : options.maxRetries) !== undefined) {
9
14
  this.maxRetries = options.maxRetries;
10
15
  }
16
+ if ((options === null || options === void 0 ? void 0 : options.transientStatusCodes) !== undefined) {
17
+ this.transientStatusCodes = options.transientStatusCodes;
18
+ }
11
19
  }
12
20
  shouldRetry(response, retryNumber) {
13
21
  if (response === null)
14
22
  return false;
15
23
  if (retryNumber > this.maxRetries)
16
24
  return false;
17
- const is5xx = response.status >= 500 && response.status < 600;
18
- const isTransient4xx = this.transient4xxStatusCodes.has(response.status);
19
- return is5xx || isTransient4xx;
25
+ const isTransient = this.transientStatusCodes.has(response.status);
26
+ return isTransient;
20
27
  }
21
28
  retryDelay(retryNumber, response) {
22
29
  // If we have a Retry-After header, use that
@@ -1 +1 @@
1
- {"version":3,"file":"default-retry-configuration.js","sourceRoot":"","sources":["../../../../src/fetch-retry/configuration/default-retry-configuration.ts"],"names":[],"mappings":"AAGA,MAAM,OAAO,yBAAyB;IAMpC,YAAY,OAAiC;QAF5B,eAAU,GAAqB,CAAC,CAAC;QAQjC,4BAAuB,GAAwB,IAAI,GAAG,CAAC;YACtE,GAAG,EAAE,kBAAkB;YACvB,GAAG,EAAE,oBAAoB;SAC1B,CAAC,CAAC;QARD,IAAI,CAAA,OAAO,aAAP,OAAO,uBAAP,OAAO,CAAE,UAAU,MAAK,SAAS,EAAE,CAAC;YACtC,IAAI,CAAC,UAAU,GAAG,OAAO,CAAC,UAAU,CAAC;QACvC,CAAC;IACH,CAAC;IAOD,WAAW,CAAC,QAAyB,EAAE,WAAmB;QACxD,IAAI,QAAQ,KAAK,IAAI;YAAE,OAAO,KAAK,CAAC;QACpC,IAAI,WAAW,GAAG,IAAI,CAAC,UAAU;YAAE,OAAO,KAAK,CAAC;QAChD,MAAM,KAAK,GAAG,QAAQ,CAAC,MAAM,IAAI,GAAG,IAAI,QAAQ,CAAC,MAAM,GAAG,GAAG,CAAC;QAC9D,MAAM,cAAc,GAAG,IAAI,CAAC,uBAAuB,CAAC,GAAG,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;QACzE,OAAO,KAAK,IAAI,cAAc,CAAC;IACjC,CAAC;IAED,UAAU,CAAC,WAAmB,EAAE,QAA0B;QACxD,4CAA4C;QAC5C,MAAM,UAAU,GAAG,QAAQ,aAAR,QAAQ,uBAAR,QAAQ,CAAE,OAAO,CAAC,GAAG,CAAC,aAAa,CAAC,CAAC;QACxD,IAAI,UAAU,EAAE,CAAC;YACf,MAAM,iBAAiB,GAAG,QAAQ,CAAC,UAAU,EAAE,EAAE,CAAC,CAAC;YACnD,IAAI,CAAC,KAAK,CAAC,iBAAiB,CAAC,EAAE,CAAC;gBAC9B,OAAO,iBAAiB,GAAG,IAAI,CAAC;YAClC,CAAC;QACH,CAAC;QAED,uCAAuC;QACvC,OAAO,IAAI,CAAC,GAAG,CAAC,GAAG,GAAG,CAAC,IAAI,WAAW,EAAE,KAAK,CAAC,CAAC;IACjD,CAAC;;AApCe,gCAAM,GACpB,IAAI,yBAAyB,EAAE,AADX,CACY","sourcesContent":["import type { RetryConfiguring } from './retry-configuring';\nimport type { Milliseconds } from './milliseconds';\n\nexport class DefaultRetryConfiguration implements RetryConfiguring {\n static readonly shared: Readonly<RetryConfiguring> =\n new DefaultRetryConfiguration();\n\n private readonly maxRetries: Readonly<number> = 2;\n\n constructor(options?: { maxRetries?: number }) {\n if (options?.maxRetries !== undefined) {\n this.maxRetries = options.maxRetries;\n }\n }\n\n private readonly transient4xxStatusCodes: ReadonlySet<number> = new Set([\n 408, // Request Timeout\n 429, // Too Many Requests\n ]);\n\n shouldRetry(response: Response | null, retryNumber: number): boolean {\n if (response === null) return false;\n if (retryNumber > this.maxRetries) return false;\n const is5xx = response.status >= 500 && response.status < 600;\n const isTransient4xx = this.transient4xxStatusCodes.has(response.status);\n return is5xx || isTransient4xx;\n }\n\n retryDelay(retryNumber: number, response?: Response | null): Milliseconds {\n // If we have a Retry-After header, use that\n const retryAfter = response?.headers.get('Retry-After');\n if (retryAfter) {\n const retryAfterSeconds = parseInt(retryAfter, 10);\n if (!isNaN(retryAfterSeconds)) {\n return retryAfterSeconds * 1000;\n }\n }\n\n // Exponential backoff up to 10 seconds\n return Math.min(500 * 2 ** retryNumber, 10000);\n }\n}\n"]}
1
+ {"version":3,"file":"default-retry-configuration.js","sourceRoot":"","sources":["../../../../src/fetch-retry/configuration/default-retry-configuration.ts"],"names":[],"mappings":"AAGA,MAAM,OAAO,yBAAyB;IAMpC,YAAY,OAGX;QALgB,eAAU,GAAqB,CAAC,CAAC;QAczC,yBAAoB,GAAwB,IAAI,GAAG,CAAC;YAC3D,GAAG,EAAE,kBAAkB;YACvB,GAAG,EAAE,oBAAoB;YACzB,GAAG,EAAE,wBAAwB;YAC7B,GAAG,EAAE,cAAc;YACnB,GAAG,EAAE,sBAAsB;YAC3B,GAAG,EAAE,kBAAkB;YACvB,GAAG,EAAE,gDAAgD;SACtD,CAAC,CAAC;QAhBD,IAAI,CAAA,OAAO,aAAP,OAAO,uBAAP,OAAO,CAAE,UAAU,MAAK,SAAS,EAAE,CAAC;YACtC,IAAI,CAAC,UAAU,GAAG,OAAO,CAAC,UAAU,CAAC;QACvC,CAAC;QACD,IAAI,CAAA,OAAO,aAAP,OAAO,uBAAP,OAAO,CAAE,oBAAoB,MAAK,SAAS,EAAE,CAAC;YAChD,IAAI,CAAC,oBAAoB,GAAG,OAAO,CAAC,oBAAoB,CAAC;QAC3D,CAAC;IACH,CAAC;IAYD,WAAW,CAAC,QAAyB,EAAE,WAAmB;QACxD,IAAI,QAAQ,KAAK,IAAI;YAAE,OAAO,KAAK,CAAC;QACpC,IAAI,WAAW,GAAG,IAAI,CAAC,UAAU;YAAE,OAAO,KAAK,CAAC;QAChD,MAAM,WAAW,GAAG,IAAI,CAAC,oBAAoB,CAAC,GAAG,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;QACnE,OAAO,WAAW,CAAC;IACrB,CAAC;IAED,UAAU,CAAC,WAAmB,EAAE,QAA0B;QACxD,4CAA4C;QAC5C,MAAM,UAAU,GAAG,QAAQ,aAAR,QAAQ,uBAAR,QAAQ,CAAE,OAAO,CAAC,GAAG,CAAC,aAAa,CAAC,CAAC;QACxD,IAAI,UAAU,EAAE,CAAC;YACf,MAAM,iBAAiB,GAAG,QAAQ,CAAC,UAAU,EAAE,EAAE,CAAC,CAAC;YACnD,IAAI,CAAC,KAAK,CAAC,iBAAiB,CAAC,EAAE,CAAC;gBAC9B,OAAO,iBAAiB,GAAG,IAAI,CAAC;YAClC,CAAC;QACH,CAAC;QAED,uCAAuC;QACvC,OAAO,IAAI,CAAC,GAAG,CAAC,GAAG,GAAG,CAAC,IAAI,WAAW,EAAE,KAAK,CAAC,CAAC;IACjD,CAAC;;AA9Ce,gCAAM,GACpB,IAAI,yBAAyB,EAAE,AADX,CACY","sourcesContent":["import type { RetryConfiguring } from './retry-configuring';\nimport type { Milliseconds } from './milliseconds';\n\nexport class DefaultRetryConfiguration implements RetryConfiguring {\n static readonly shared: Readonly<RetryConfiguring> =\n new DefaultRetryConfiguration();\n\n private readonly maxRetries: Readonly<number> = 2;\n\n constructor(options?: {\n maxRetries?: number;\n transientStatusCodes?: Set<number>;\n }) {\n if (options?.maxRetries !== undefined) {\n this.maxRetries = options.maxRetries;\n }\n if (options?.transientStatusCodes !== undefined) {\n this.transientStatusCodes = options.transientStatusCodes;\n }\n }\n\n readonly transientStatusCodes: ReadonlySet<number> = new Set([\n 408, // Request Timeout\n 429, // Too Many Requests\n 500, // Internal Server Error\n 502, // Bad Gateway\n 503, // Service Unavailable\n 504, // Gateway Timeout\n 522, // Cloudflare Origin Server Connection Timed Out\n ]);\n\n shouldRetry(response: Response | null, retryNumber: number): boolean {\n if (response === null) return false;\n if (retryNumber > this.maxRetries) return false;\n const isTransient = this.transientStatusCodes.has(response.status);\n return isTransient;\n }\n\n retryDelay(retryNumber: number, response?: Response | null): Milliseconds {\n // If we have a Retry-After header, use that\n const retryAfter = response?.headers.get('Retry-After');\n if (retryAfter) {\n const retryAfterSeconds = parseInt(retryAfter, 10);\n if (!isNaN(retryAfterSeconds)) {\n return retryAfterSeconds * 1000;\n }\n }\n\n // Exponential backoff up to 10 seconds\n return Math.min(500 * 2 ** retryNumber, 10000);\n }\n}\n"]}
@@ -10,22 +10,21 @@ describe('DefaultRetryConfiguration', () => {
10
10
  const mockResponse = new Response(null, { status: 500 });
11
11
  expect(config.shouldRetry(mockResponse, 3)).to.be.false;
12
12
  });
13
- it('should retry on 5xx status codes', async () => {
14
- const config = new DefaultRetryConfiguration();
15
- const mockResponse = new Response(null, { status: 502 });
16
- expect(config.shouldRetry(mockResponse, 1)).to.be.true;
13
+ it('should retry transient status codes', async () => {
14
+ const transientStatuses = [408, 429, 500, 502, 503, 504];
15
+ const config = new DefaultRetryConfiguration({
16
+ transientStatusCodes: new Set(transientStatuses),
17
+ });
18
+ for (const status of transientStatuses) {
19
+ const mockResponse = new Response(null, { status });
20
+ expect(config.shouldRetry(mockResponse, 1)).to.be.true;
21
+ }
17
22
  });
18
- it('should not retry on non-5xx status codes', async () => {
23
+ it('should not retry non-transient status codes', async () => {
19
24
  const config = new DefaultRetryConfiguration();
20
25
  const mockResponse = new Response(null, { status: 404 });
21
26
  expect(config.shouldRetry(mockResponse, 1)).to.be.false;
22
27
  });
23
- it('has exponential backoff delay', async () => {
24
- const config = new DefaultRetryConfiguration();
25
- expect(config.retryDelay(0)).to.equal(500);
26
- expect(config.retryDelay(1)).to.equal(1000);
27
- expect(config.retryDelay(2)).to.equal(2000);
28
- });
29
28
  it('uses Retry-After header if present', async () => {
30
29
  const config = new DefaultRetryConfiguration();
31
30
  const headers = new Headers();
@@ -33,6 +32,12 @@ describe('DefaultRetryConfiguration', () => {
33
32
  const mockResponse = new Response(null, { status: 503, headers });
34
33
  expect(config.retryDelay(0, mockResponse)).to.equal(3000);
35
34
  });
35
+ it('has exponential backoff delay', async () => {
36
+ const config = new DefaultRetryConfiguration();
37
+ expect(config.retryDelay(0)).to.equal(500);
38
+ expect(config.retryDelay(1)).to.equal(1000);
39
+ expect(config.retryDelay(2)).to.equal(2000);
40
+ });
36
41
  it('caps retry delay at 10 seconds', async () => {
37
42
  const config = new DefaultRetryConfiguration();
38
43
  expect(config.retryDelay(10)).to.equal(10000);
@@ -1 +1 @@
1
- {"version":3,"file":"default-retry-config.test.js","sourceRoot":"","sources":["../../test/default-retry-config.test.ts"],"names":[],"mappings":"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","sourcesContent":["import { expect } from '@open-wc/testing';\nimport { DefaultRetryConfiguration } from '../src/fetch-retry/configuration/default-retry-configuration';\n\ndescribe('DefaultRetryConfiguration', () => {\n it('should not retry on null response', async () => {\n const config = new DefaultRetryConfiguration();\n expect(config.shouldRetry(null, 1)).to.be.false;\n });\n\n it('should not retry after max retries exceeded', async () => {\n const config = new DefaultRetryConfiguration({ maxRetries: 2 });\n const mockResponse = new Response(null, { status: 500 });\n expect(config.shouldRetry(mockResponse, 3)).to.be.false;\n });\n\n it('should retry on 5xx status codes', async () => {\n const config = new DefaultRetryConfiguration();\n const mockResponse = new Response(null, { status: 502 });\n expect(config.shouldRetry(mockResponse, 1)).to.be.true;\n });\n\n it('should not retry on non-5xx status codes', async () => {\n const config = new DefaultRetryConfiguration();\n const mockResponse = new Response(null, { status: 404 });\n expect(config.shouldRetry(mockResponse, 1)).to.be.false;\n });\n\n it('has exponential backoff delay', async () => {\n const config = new DefaultRetryConfiguration();\n expect(config.retryDelay(0)).to.equal(500);\n expect(config.retryDelay(1)).to.equal(1000);\n expect(config.retryDelay(2)).to.equal(2000);\n });\n\n it('uses Retry-After header if present', async () => {\n const config = new DefaultRetryConfiguration();\n const headers = new Headers();\n headers.append('Retry-After', '3');\n const mockResponse = new Response(null, { status: 503, headers });\n expect(config.retryDelay(0, mockResponse)).to.equal(3000);\n });\n\n it('caps retry delay at 10 seconds', async () => {\n const config = new DefaultRetryConfiguration();\n expect(config.retryDelay(10)).to.equal(10000);\n expect(config.retryDelay(20)).to.equal(10000);\n });\n});\n"]}
1
+ {"version":3,"file":"default-retry-config.test.js","sourceRoot":"","sources":["../../test/default-retry-config.test.ts"],"names":[],"mappings":"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","sourcesContent":["import { expect } from '@open-wc/testing';\nimport { DefaultRetryConfiguration } from '../src/fetch-retry/configuration/default-retry-configuration';\n\ndescribe('DefaultRetryConfiguration', () => {\n it('should not retry on null response', async () => {\n const config = new DefaultRetryConfiguration();\n expect(config.shouldRetry(null, 1)).to.be.false;\n });\n\n it('should not retry after max retries exceeded', async () => {\n const config = new DefaultRetryConfiguration({ maxRetries: 2 });\n const mockResponse = new Response(null, { status: 500 });\n expect(config.shouldRetry(mockResponse, 3)).to.be.false;\n });\n\n it('should retry transient status codes', async () => {\n const transientStatuses = [408, 429, 500, 502, 503, 504];\n const config = new DefaultRetryConfiguration({\n transientStatusCodes: new Set(transientStatuses),\n });\n for (const status of transientStatuses) {\n const mockResponse = new Response(null, { status });\n expect(config.shouldRetry(mockResponse, 1)).to.be.true;\n }\n });\n\n it('should not retry non-transient status codes', async () => {\n const config = new DefaultRetryConfiguration();\n const mockResponse = new Response(null, { status: 404 });\n expect(config.shouldRetry(mockResponse, 1)).to.be.false;\n });\n\n it('uses Retry-After header if present', async () => {\n const config = new DefaultRetryConfiguration();\n const headers = new Headers();\n headers.append('Retry-After', '3');\n const mockResponse = new Response(null, { status: 503, headers });\n expect(config.retryDelay(0, mockResponse)).to.equal(3000);\n });\n\n it('has exponential backoff delay', async () => {\n const config = new DefaultRetryConfiguration();\n expect(config.retryDelay(0)).to.equal(500);\n expect(config.retryDelay(1)).to.equal(1000);\n expect(config.retryDelay(2)).to.equal(2000);\n });\n\n it('caps retry delay at 10 seconds', async () => {\n const config = new DefaultRetryConfiguration();\n expect(config.retryDelay(10)).to.equal(10000);\n expect(config.retryDelay(20)).to.equal(10000);\n });\n});\n"]}
package/package.json CHANGED
@@ -7,7 +7,7 @@
7
7
  },
8
8
  "license": "AGPL-3.0-only",
9
9
  "author": "Internet Archive",
10
- "version": "1.1.0-webdev-7731.5",
10
+ "version": "1.1.0-webdev-7731.6",
11
11
  "main": "dist/index.js",
12
12
  "module": "dist/index.js",
13
13
  "scripts": {
@@ -7,23 +7,33 @@ export class DefaultRetryConfiguration implements RetryConfiguring {
7
7
 
8
8
  private readonly maxRetries: Readonly<number> = 2;
9
9
 
10
- constructor(options?: { maxRetries?: number }) {
10
+ constructor(options?: {
11
+ maxRetries?: number;
12
+ transientStatusCodes?: Set<number>;
13
+ }) {
11
14
  if (options?.maxRetries !== undefined) {
12
15
  this.maxRetries = options.maxRetries;
13
16
  }
17
+ if (options?.transientStatusCodes !== undefined) {
18
+ this.transientStatusCodes = options.transientStatusCodes;
19
+ }
14
20
  }
15
21
 
16
- private readonly transient4xxStatusCodes: ReadonlySet<number> = new Set([
22
+ readonly transientStatusCodes: ReadonlySet<number> = new Set([
17
23
  408, // Request Timeout
18
24
  429, // Too Many Requests
25
+ 500, // Internal Server Error
26
+ 502, // Bad Gateway
27
+ 503, // Service Unavailable
28
+ 504, // Gateway Timeout
29
+ 522, // Cloudflare Origin Server Connection Timed Out
19
30
  ]);
20
31
 
21
32
  shouldRetry(response: Response | null, retryNumber: number): boolean {
22
33
  if (response === null) return false;
23
34
  if (retryNumber > this.maxRetries) return false;
24
- const is5xx = response.status >= 500 && response.status < 600;
25
- const isTransient4xx = this.transient4xxStatusCodes.has(response.status);
26
- return is5xx || isTransient4xx;
35
+ const isTransient = this.transientStatusCodes.has(response.status);
36
+ return isTransient;
27
37
  }
28
38
 
29
39
  retryDelay(retryNumber: number, response?: Response | null): Milliseconds {
@@ -13,25 +13,23 @@ describe('DefaultRetryConfiguration', () => {
13
13
  expect(config.shouldRetry(mockResponse, 3)).to.be.false;
14
14
  });
15
15
 
16
- it('should retry on 5xx status codes', async () => {
17
- const config = new DefaultRetryConfiguration();
18
- const mockResponse = new Response(null, { status: 502 });
19
- expect(config.shouldRetry(mockResponse, 1)).to.be.true;
16
+ it('should retry transient status codes', async () => {
17
+ const transientStatuses = [408, 429, 500, 502, 503, 504];
18
+ const config = new DefaultRetryConfiguration({
19
+ transientStatusCodes: new Set(transientStatuses),
20
+ });
21
+ for (const status of transientStatuses) {
22
+ const mockResponse = new Response(null, { status });
23
+ expect(config.shouldRetry(mockResponse, 1)).to.be.true;
24
+ }
20
25
  });
21
26
 
22
- it('should not retry on non-5xx status codes', async () => {
27
+ it('should not retry non-transient status codes', async () => {
23
28
  const config = new DefaultRetryConfiguration();
24
29
  const mockResponse = new Response(null, { status: 404 });
25
30
  expect(config.shouldRetry(mockResponse, 1)).to.be.false;
26
31
  });
27
32
 
28
- it('has exponential backoff delay', async () => {
29
- const config = new DefaultRetryConfiguration();
30
- expect(config.retryDelay(0)).to.equal(500);
31
- expect(config.retryDelay(1)).to.equal(1000);
32
- expect(config.retryDelay(2)).to.equal(2000);
33
- });
34
-
35
33
  it('uses Retry-After header if present', async () => {
36
34
  const config = new DefaultRetryConfiguration();
37
35
  const headers = new Headers();
@@ -40,6 +38,13 @@ describe('DefaultRetryConfiguration', () => {
40
38
  expect(config.retryDelay(0, mockResponse)).to.equal(3000);
41
39
  });
42
40
 
41
+ it('has exponential backoff delay', async () => {
42
+ const config = new DefaultRetryConfiguration();
43
+ expect(config.retryDelay(0)).to.equal(500);
44
+ expect(config.retryDelay(1)).to.equal(1000);
45
+ expect(config.retryDelay(2)).to.equal(2000);
46
+ });
47
+
43
48
  it('caps retry delay at 10 seconds', async () => {
44
49
  const config = new DefaultRetryConfiguration();
45
50
  expect(config.retryDelay(10)).to.equal(10000);