From 205fb18785a7b133cb7ec0f4c4d3cc6c9343ff8f Mon Sep 17 00:00:00 2001 From: Harvmaster Date: Thu, 23 Jul 2026 07:56:50 +0000 Subject: [PATCH 01/12] Use Custom Error Classes for Validation --- source/errors.ts | 40 +++++++++++++++ source/exponential-backoff.ts | 86 +++++++++++++++++++------------- test/exponential-backoff.test.ts | 26 ++++++++-- 3 files changed, 112 insertions(+), 40 deletions(-) diff --git a/source/errors.ts b/source/errors.ts index 02b32f7..be4d05b 100644 --- a/source/errors.ts +++ b/source/errors.ts @@ -20,3 +20,43 @@ export class ExponentialBackoffStoppedRetriesError extends Error { this.name = 'ExponentialBackoffStoppedRetriesError'; } } + +/** + * Error thrown when an exponential backoff option is too small + */ +export class ExponentialBackoffNumberTooSmallError extends Error { + constructor(option: string, value: number, min: number) { + super(`Exponential backoff option "${option}" is too small. Must be at least ${min}`); + this.name = 'ExponentialBackoffNumberTooSmallError'; + } +} + +/** + * Error thrown when an exponential backoff option is out of bounds + */ +export class ExponentialBackoffNumberOutOfBoundsError extends Error { + constructor(option: string, value: number, min: number, max: number) { + super(`Exponential backoff option "${option}" is out of bounds. Must be between ${min} and ${max}`); + this.name = 'ExponentialBackoffNumberOutOfBoundsError'; + } +} + +/** + * Error thrown when an exponential backoff option is an invalid infinite integer + */ +export class ExponentialBackoffInvalidInfiniteIntegerError extends Error { + constructor(option: string) { + super(`Exponential backoff option "${option}" is invalid. Must be a finite number`); + this.name = 'ExponentialBackoffInvalidInfiniteIntegerError'; + } +} + +/** + * Error thrown when an exponential backoff option is not an integer + */ +export class ExponentialBackoffNonIntegerError extends Error { + constructor(option: string) { + super(`Exponential backoff option "${option}" is invalid. Must be an integer`); + this.name = 'ExponentialBackoffNonIntegerError'; + } +} diff --git a/source/exponential-backoff.ts b/source/exponential-backoff.ts index 8b7b8a8..563d35e 100644 --- a/source/exponential-backoff.ts +++ b/source/exponential-backoff.ts @@ -1,4 +1,11 @@ -import { ExponentialBackoffStoppedRetriesError, ExponentialBackoffMaxRetriesHitError } from './errors.ts'; +import { + ExponentialBackoffStoppedRetriesError, + ExponentialBackoffMaxRetriesHitError, + ExponentialBackoffInvalidInfiniteIntegerError, + ExponentialBackoffNonIntegerError, + ExponentialBackoffNumberTooSmallError, + ExponentialBackoffNumberOutOfBoundsError, +} from './errors.ts'; /** * Exponential backoff is a technique used to retry a function after a delay. @@ -106,50 +113,57 @@ export class ExponentialBackoff { * @throws An error if the options are invalid */ public static validateOptions(options: ExponentialBackoffOptions): void { - // Validate the max delay is a finite number not less than 0 - if (!Number.isFinite(options.maxDelay)) { - throw new Error('maxDelay must be a finite number'); - } + /** Validate the value is finite, throwing an {@link ExponentialBackoffInvalidInfiniteIntegerError} if the value is infinite */ + const isFinite = (key: string, value: number): void => { + if (!Number.isFinite(value)) { + throw new ExponentialBackoffInvalidInfiniteIntegerError(key); + } + }; - if (options.maxDelay < 0) { - throw new Error('maxDelay must be not less than 0'); - } + /** Validate the value is an integer, throwing a {@link ExponentialBackoffNonIntegerError} if it is not an integer */ + const isInteger = (key: string, value: number): void => { + if (!Number.isInteger(value)) { + throw new ExponentialBackoffNonIntegerError(key); + } + }; - // Validate the max attempts is a finite number not less than 0 - if (!Number.isFinite(options.maxAttempts)) { - throw new Error('maxAttempts must be a finite number'); - } + /** Validate the value is within the bounds, throwing a {@link ExponentialBackoffNumberOutOfBoundsError} if it is not within the bounds */ + const isWithinBounds = (key: string, value: number, min: number, max?: number): void => { + // If both the min and max are defined, validate the value, throwing a number out of bounds error if it is not within the bounds + if (min !== undefined && max !== undefined) { + if (value < min || value > max) { + throw new ExponentialBackoffNumberOutOfBoundsError(key, value, min, max); + } - if (options.maxAttempts < 0) { - throw new Error('maxAttempts must be not less than 0'); - } + return; + } - // Validate the base delay is a finite number not less than 0 - if (!Number.isFinite(options.baseDelay)) { - throw new Error('baseDelay must be a finite number'); - } + // If only the min is defined, validate the value, throwing a number too small error if it is less than the min + if (value < min) { + throw new ExponentialBackoffNumberTooSmallError(key, value, min); + } + }; - if (options.baseDelay < 0) { - throw new Error('baseDelay must be not less than 0'); - } + // Validate the max delay + isFinite('maxDelay', options.maxDelay); + isWithinBounds('maxDelay', options.maxDelay, 0); - // Validate the growth rate is a finite number not less than 0 - if (!Number.isFinite(options.growthRate)) { - throw new Error('growthRate must be a finite number'); - } + // Validate the max attempts + isFinite('maxAttempts', options.maxAttempts); + isInteger('maxAttempts', options.maxAttempts); + isWithinBounds('maxAttempts', options.maxAttempts, 0); - if (options.growthRate < 0) { - throw new Error('growthRate must be not less than 0'); - } + // Validate the base delay + isFinite('baseDelay', options.baseDelay); + isWithinBounds('baseDelay', options.baseDelay, 0); - // Validate the jitter is a finite number not less than 0 or greater than 1 - if (!Number.isFinite(options.jitter)) { - throw new Error('jitter must be a finite number'); - } + // Validate the growth rate + isFinite('growthRate', options.growthRate); + isWithinBounds('growthRate', options.growthRate, 0); - if (options.jitter < 0 || options.jitter > 1) { - throw new Error('jitter must be not less than 0 or greater than 1'); - } + // Validate the jitter + isFinite('jitter', options.jitter); + isWithinBounds('jitter', options.jitter, 0, 1); } /** diff --git a/test/exponential-backoff.test.ts b/test/exponential-backoff.test.ts index 7c29a96..556ef11 100644 --- a/test/exponential-backoff.test.ts +++ b/test/exponential-backoff.test.ts @@ -498,7 +498,7 @@ const testExponentialBackoffValidateOptionsRejectsNegativeValues = (): void => { ExponentialBackoff.validateOptions({ ...validExponentialBackoffOptions, [field]: value, - })).toThrow(`${field} must be not less than 0`); + })).toThrow(`Exponential backoff option "${field}" is too small. Must be at least 0`); } }; @@ -515,7 +515,7 @@ const testExponentialBackoffValidateOptionsRejectsInvalidJitter = (): void => { ExponentialBackoff.validateOptions({ ...validExponentialBackoffOptions, jitter: value, - })).toThrow('jitter must be not less than 0 or greater than 1'); + })).toThrow('Exponential backoff option "jitter" is out of bounds. Must be between 0 and 1'); } }; @@ -538,7 +538,24 @@ const testExponentialBackoffValidateOptionsRejectsNonFiniteValues = (): void => ExponentialBackoff.validateOptions({ ...validExponentialBackoffOptions, [field]: value, - })).toThrow(`${field} must be a finite number`); + })).toThrow(`Exponential backoff option "${field}" is invalid. Must be a finite number`); + } +}; + +/** + * Tests that {@link ExponentialBackoff.validateOptions} rejects non-integer values. + */ +const testExponentialBackoffValidateOptionsRejectsNonIntegerValues = (): void => { + // Define our test cases with each value being a non-integer + const nonIntegerCases = [{ field: 'maxAttempts', value: 1.5 }] as const; + + // Iterate through the test cases and expect an error to be thrown + for (const { field, value } of nonIntegerCases) { + expect(() => + ExponentialBackoff.validateOptions({ + ...validExponentialBackoffOptions, + [field]: value, + })).toThrow(`Exponential backoff option "${field}" is invalid. Must be an integer`); } }; @@ -561,7 +578,7 @@ const testExponentialBackoffValidateOptionsRejectsNaN = (): void => { ExponentialBackoff.validateOptions({ ...validExponentialBackoffOptions, [field]: value, - })).toThrow(`${field} must be a finite number`); + })).toThrow(`Exponential backoff option "${field}" is invalid. Must be a finite number`); } }; @@ -589,6 +606,7 @@ const runTests = async (): Promise => { test('ExponentialBackoff.validateOptions: rejects negative values', testExponentialBackoffValidateOptionsRejectsNegativeValues); test('ExponentialBackoff.validateOptions: rejects invalid jitter', testExponentialBackoffValidateOptionsRejectsInvalidJitter); test('ExponentialBackoff.validateOptions: rejects Infinity', testExponentialBackoffValidateOptionsRejectsNonFiniteValues); + test('ExponentialBackoff.validateOptions: rejects non-integer values', testExponentialBackoffValidateOptionsRejectsNonIntegerValues); test('ExponentialBackoff.validateOptions: rejects NaN', testExponentialBackoffValidateOptionsRejectsNaN); }; From a2d7c723ad8f5a886eebfe437bcad8d10ef81355 Mon Sep 17 00:00:00 2001 From: Harvmaster Date: Thu, 23 Jul 2026 08:00:53 +0000 Subject: [PATCH 02/12] Audit Fix --- package-lock.json | 60 +++++++++++++++++++++++------------------------ 1 file changed, 30 insertions(+), 30 deletions(-) diff --git a/package-lock.json b/package-lock.json index 2d1a9ee..813ede6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1105,9 +1105,9 @@ "license": "MIT" }, "node_modules/@eslint/config-array/node_modules/brace-expansion": { - "version": "1.1.15", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.15.tgz", - "integrity": "sha512-EwOCDEex4quD37XhqM3omwtMoJjr//isUZz1JopUNWms+4Z2ViyM/k1YIRePpoVNnQhENnxtFjLaxNHrT7xIUg==", + "version": "1.1.16", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.16.tgz", + "integrity": "sha512-IDw48K2/2kRkg9LdJxurvq3lV3aBgq0REY89duEqFRthjlPdXHKMj7EnQOXVckxzgisinf3nHfrcE2FufFLXMw==", "dev": true, "license": "MIT", "dependencies": { @@ -1186,9 +1186,9 @@ "license": "MIT" }, "node_modules/@eslint/eslintrc/node_modules/brace-expansion": { - "version": "1.1.15", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.15.tgz", - "integrity": "sha512-EwOCDEex4quD37XhqM3omwtMoJjr//isUZz1JopUNWms+4Z2ViyM/k1YIRePpoVNnQhENnxtFjLaxNHrT7xIUg==", + "version": "1.1.16", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.16.tgz", + "integrity": "sha512-IDw48K2/2kRkg9LdJxurvq3lV3aBgq0REY89duEqFRthjlPdXHKMj7EnQOXVckxzgisinf3nHfrcE2FufFLXMw==", "dev": true, "license": "MIT", "dependencies": { @@ -3435,9 +3435,9 @@ } }, "node_modules/body-parser": { - "version": "1.20.5", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.5.tgz", - "integrity": "sha512-3grm+/2tUOvu2cjJkvsIxrv/wVpfXQW4PsQHYm7yk4vfpu7Ekl6nEsYBoJUL6qDwZUx8wUhQ8tR2qz+ad9c9OA==", + "version": "1.20.6", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.6.tgz", + "integrity": "sha512-p5tAzS57i5MV9fZFDj9LeIiTZEufbSe2eDozP+ElheSUq1m74CRq1jI4mYNDdVs9vQztXFLuk/Gd6BWTdwRJ5g==", "dev": true, "license": "MIT", "dependencies": { @@ -3503,9 +3503,9 @@ } }, "node_modules/brace-expansion": { - "version": "5.0.6", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.6.tgz", - "integrity": "sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==", + "version": "5.0.7", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.7.tgz", + "integrity": "sha512-7oFy703dxfY3/NLxC1fh2SUCQ0H9rmAY+5EpDVfXjUTTs+HEwR2nYaqLv+GWcTsumwxPfiz6CzCNkwXwBUwqCA==", "dev": true, "license": "MIT", "dependencies": { @@ -5292,9 +5292,9 @@ "peer": true }, "node_modules/eslint-plugin-import/node_modules/brace-expansion": { - "version": "1.1.15", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.15.tgz", - "integrity": "sha512-EwOCDEex4quD37XhqM3omwtMoJjr//isUZz1JopUNWms+4Z2ViyM/k1YIRePpoVNnQhENnxtFjLaxNHrT7xIUg==", + "version": "1.1.16", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.16.tgz", + "integrity": "sha512-IDw48K2/2kRkg9LdJxurvq3lV3aBgq0REY89duEqFRthjlPdXHKMj7EnQOXVckxzgisinf3nHfrcE2FufFLXMw==", "dev": true, "license": "MIT", "peer": true, @@ -5379,9 +5379,9 @@ "peer": true }, "node_modules/eslint-plugin-jsx-a11y/node_modules/brace-expansion": { - "version": "1.1.15", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.15.tgz", - "integrity": "sha512-EwOCDEex4quD37XhqM3omwtMoJjr//isUZz1JopUNWms+4Z2ViyM/k1YIRePpoVNnQhENnxtFjLaxNHrT7xIUg==", + "version": "1.1.16", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.16.tgz", + "integrity": "sha512-IDw48K2/2kRkg9LdJxurvq3lV3aBgq0REY89duEqFRthjlPdXHKMj7EnQOXVckxzgisinf3nHfrcE2FufFLXMw==", "dev": true, "license": "MIT", "peer": true, @@ -5468,9 +5468,9 @@ "peer": true }, "node_modules/eslint-plugin-react/node_modules/brace-expansion": { - "version": "1.1.15", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.15.tgz", - "integrity": "sha512-EwOCDEex4quD37XhqM3omwtMoJjr//isUZz1JopUNWms+4Z2ViyM/k1YIRePpoVNnQhENnxtFjLaxNHrT7xIUg==", + "version": "1.1.16", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.16.tgz", + "integrity": "sha512-IDw48K2/2kRkg9LdJxurvq3lV3aBgq0REY89duEqFRthjlPdXHKMj7EnQOXVckxzgisinf3nHfrcE2FufFLXMw==", "dev": true, "license": "MIT", "peer": true, @@ -5542,9 +5542,9 @@ "license": "MIT" }, "node_modules/eslint/node_modules/brace-expansion": { - "version": "1.1.15", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.15.tgz", - "integrity": "sha512-EwOCDEex4quD37XhqM3omwtMoJjr//isUZz1JopUNWms+4Z2ViyM/k1YIRePpoVNnQhENnxtFjLaxNHrT7xIUg==", + "version": "1.1.16", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.16.tgz", + "integrity": "sha512-IDw48K2/2kRkg9LdJxurvq3lV3aBgq0REY89duEqFRthjlPdXHKMj7EnQOXVckxzgisinf3nHfrcE2FufFLXMw==", "dev": true, "license": "MIT", "dependencies": { @@ -7129,9 +7129,9 @@ "license": "MIT" }, "node_modules/js-yaml": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.2.0.tgz", - "integrity": "sha512-ePWsvanv0DWuDRsW8dnt+R4jQ31SCRCQ7hhNcPXZPsoBZiemuZNYGf7adZdqX2D86j6rvKp3RpCxVTSb8WQlOw==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.3.0.tgz", + "integrity": "sha512-1td788aAnnZ5qs7V2QIRl1owjtYpbKt749Y3xauqQgwIIGF/xXWz1wMTEBx5O3LK3lXLVuqXPdPxj2BoFHaW9Q==", "dev": true, "funding": [ { @@ -7524,9 +7524,9 @@ } }, "node_modules/linkify-it": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-5.0.1.tgz", - "integrity": "sha512-wVoTjP4Q6R0NW5hiZkVJaFZPWgtXfoGF+6LucL3/FtiNjmcHhYjEr5f1Kqjirc1nBW07J/ZuRFumqr2oqccEWg==", + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-5.0.2.tgz", + "integrity": "sha512-ONTm2jCMAVZjgQa/Fy1kScXsuOoF5NPTsoFBdE1KVIZ2vAh/r9+Bqo+0jINCBYnavTPQZz38QzFTme79ENoN3Q==", "dev": true, "funding": [ { From 5e8b0a1ea8656f06c0b57c44b1b9ad7f8dc78f3d Mon Sep 17 00:00:00 2001 From: Harvmaster Date: Sun, 26 Jul 2026 04:24:11 +0000 Subject: [PATCH 03/12] Change comment: aborted -> activated --- source/exponential-backoff.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/source/exponential-backoff.ts b/source/exponential-backoff.ts index 563d35e..68726ae 100644 --- a/source/exponential-backoff.ts +++ b/source/exponential-backoff.ts @@ -225,9 +225,9 @@ export class ExponentialBackoff { break; } - // Check if the abort signal has been aborted + // Check if the abort signal has been activated if (abortController.signal.aborted) { - // Throw an error if the abort signal has been aborted + // Throw an error if the abort signal has been activated throw new ExponentialBackoffStoppedRetriesError(abortController.signal.reason); } From 604cd3633440ca132910122586090b682ad8f6a5 Mon Sep 17 00:00:00 2001 From: Harvmaster Date: Thu, 6 Aug 2026 10:06:04 +0000 Subject: [PATCH 04/12] Improve validation code. Add throws tsdocs to validateOptions --- source/exponential-backoff.ts | 59 ++++++++++++++++++----------------- source/misc.ts | 16 ++++++++++ 2 files changed, 46 insertions(+), 29 deletions(-) create mode 100644 source/misc.ts diff --git a/source/exponential-backoff.ts b/source/exponential-backoff.ts index 68726ae..59fe07e 100644 --- a/source/exponential-backoff.ts +++ b/source/exponential-backoff.ts @@ -1,11 +1,12 @@ import { ExponentialBackoffStoppedRetriesError, ExponentialBackoffMaxRetriesHitError, - ExponentialBackoffInvalidInfiniteIntegerError, ExponentialBackoffNonIntegerError, ExponentialBackoffNumberTooSmallError, ExponentialBackoffNumberOutOfBoundsError, + ExponentialBackoffNumberNotFiniteError, } from './errors.ts'; +import { isWithinBounds } from './misc.ts'; /** * Exponential backoff is a technique used to retry a function after a delay. @@ -110,60 +111,60 @@ export class ExponentialBackoff { * * @param options - The options to validate * - * @throws An error if the options are invalid + * @throws {@link ExponentialBackoffNumberNotFiniteError} if a provided option is not a finite number + * @throws {@link ExponentialBackoffNonIntegerError} if a provided option is not an integer + * @throws {@link ExponentialBackoffNumberOutOfBoundsError} if a provided option is out of bounds + * @throws {@link ExponentialBackoffNumberTooSmallError} if a provided option is too small */ public static validateOptions(options: ExponentialBackoffOptions): void { /** Validate the value is finite, throwing an {@link ExponentialBackoffInvalidInfiniteIntegerError} if the value is infinite */ - const isFinite = (key: string, value: number): void => { + const assertIsFinite = (key: string, value: number): void => { if (!Number.isFinite(value)) { - throw new ExponentialBackoffInvalidInfiniteIntegerError(key); + throw new ExponentialBackoffNumberNotFiniteError(key, value); } }; /** Validate the value is an integer, throwing a {@link ExponentialBackoffNonIntegerError} if it is not an integer */ - const isInteger = (key: string, value: number): void => { + const assertIsInteger = (key: string, value: number): void => { if (!Number.isInteger(value)) { - throw new ExponentialBackoffNonIntegerError(key); + throw new ExponentialBackoffNonIntegerError(key, value); } }; - /** Validate the value is within the bounds, throwing a {@link ExponentialBackoffNumberOutOfBoundsError} if it is not within the bounds */ - const isWithinBounds = (key: string, value: number, min: number, max?: number): void => { - // If both the min and max are defined, validate the value, throwing a number out of bounds error if it is not within the bounds - if (min !== undefined && max !== undefined) { - if (value < min || value > max) { - throw new ExponentialBackoffNumberOutOfBoundsError(key, value, min, max); - } - - return; - } - - // If only the min is defined, validate the value, throwing a number too small error if it is less than the min + /** Validate the value is greater than the minimum, throwing a {@link ExponentialBackoffNumberTooSmallError} if it is not */ + const assertIsHigherThan = (key: string, value: number, min: number): void => { if (value < min) { throw new ExponentialBackoffNumberTooSmallError(key, value, min); } }; + /** Validate the value is within the bounds, throwing a {@link ExponentialBackoffNumberOutOfBoundsError} if it is not within the bounds */ + const assertIsWithinBounds = (key: string, value: number, min: number, max: number): void => { + if (!isWithinBounds(value, min, max)) { + throw new ExponentialBackoffNumberOutOfBoundsError(key, value, min, max); + } + }; + // Validate the max delay - isFinite('maxDelay', options.maxDelay); - isWithinBounds('maxDelay', options.maxDelay, 0); + assertIsFinite('maxDelay', options.maxDelay); + assertIsHigherThan('maxDelay', options.maxDelay, 0); // Validate the max attempts - isFinite('maxAttempts', options.maxAttempts); - isInteger('maxAttempts', options.maxAttempts); - isWithinBounds('maxAttempts', options.maxAttempts, 0); + assertIsFinite('maxAttempts', options.maxAttempts); + assertIsInteger('maxAttempts', options.maxAttempts); + assertIsHigherThan('maxAttempts', options.maxAttempts, 0); // Validate the base delay - isFinite('baseDelay', options.baseDelay); - isWithinBounds('baseDelay', options.baseDelay, 0); + assertIsFinite('baseDelay', options.baseDelay); + assertIsHigherThan('baseDelay', options.baseDelay, 0); // Validate the growth rate - isFinite('growthRate', options.growthRate); - isWithinBounds('growthRate', options.growthRate, 0); + assertIsFinite('growthRate', options.growthRate); + assertIsHigherThan('growthRate', options.growthRate, 0); // Validate the jitter - isFinite('jitter', options.jitter); - isWithinBounds('jitter', options.jitter, 0, 1); + assertIsFinite('jitter', options.jitter); + assertIsWithinBounds('jitter', options.jitter, 0, 1); } /** diff --git a/source/misc.ts b/source/misc.ts new file mode 100644 index 0000000..7ac6c43 --- /dev/null +++ b/source/misc.ts @@ -0,0 +1,16 @@ +/** + * Validate the value is within the bounds, returning true if it is within the bounds, false otherwise + * + * @param value - The value to validate + * @param min - The minimum value + * @param max - The maximum value + * + * @returns True if the value is within the bounds, false otherwise + */ +export const isWithinBounds = (value: number, min: number, max: number): boolean => { + if (value < min || value > max) { + return false; + } + + return true; +}; \ No newline at end of file From 9f020df754b79a2e81b396520949908981fae839 Mon Sep 17 00:00:00 2001 From: Harvmaster Date: Thu, 6 Aug 2026 10:07:38 +0000 Subject: [PATCH 05/12] Fix errors --- source/errors.ts | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/source/errors.ts b/source/errors.ts index be4d05b..36afa6a 100644 --- a/source/errors.ts +++ b/source/errors.ts @@ -26,7 +26,7 @@ export class ExponentialBackoffStoppedRetriesError extends Error { */ export class ExponentialBackoffNumberTooSmallError extends Error { constructor(option: string, value: number, min: number) { - super(`Exponential backoff option "${option}" is too small. Must be at least ${min}`); + super(`Exponential backoff option "${option}" is too small. Must be at least ${min}. Received value: ${value}`); this.name = 'ExponentialBackoffNumberTooSmallError'; } } @@ -36,7 +36,7 @@ export class ExponentialBackoffNumberTooSmallError extends Error { */ export class ExponentialBackoffNumberOutOfBoundsError extends Error { constructor(option: string, value: number, min: number, max: number) { - super(`Exponential backoff option "${option}" is out of bounds. Must be between ${min} and ${max}`); + super(`Exponential backoff option "${option}" is out of bounds. Must be between ${min} and ${max}. Received value: ${value}`); this.name = 'ExponentialBackoffNumberOutOfBoundsError'; } } @@ -44,10 +44,10 @@ export class ExponentialBackoffNumberOutOfBoundsError extends Error { /** * Error thrown when an exponential backoff option is an invalid infinite integer */ -export class ExponentialBackoffInvalidInfiniteIntegerError extends Error { - constructor(option: string) { - super(`Exponential backoff option "${option}" is invalid. Must be a finite number`); - this.name = 'ExponentialBackoffInvalidInfiniteIntegerError'; +export class ExponentialBackoffNumberNotFiniteError extends Error { + constructor(option: string, value: number) { + super(`Exponential backoff option "${option}" is invalid. Must be a finite number. Received value: ${value}`); + this.name = 'ExponentialBackoffNumberNotFiniteError'; } } @@ -55,8 +55,8 @@ export class ExponentialBackoffInvalidInfiniteIntegerError extends Error { * Error thrown when an exponential backoff option is not an integer */ export class ExponentialBackoffNonIntegerError extends Error { - constructor(option: string) { - super(`Exponential backoff option "${option}" is invalid. Must be an integer`); + constructor(option: string, value: number) { + super(`Exponential backoff option "${option}" is invalid. Must be an integer. Received value: ${value}`); this.name = 'ExponentialBackoffNonIntegerError'; } } From 67c0239106463309ad26ace5378375c5ae2a9222 Mon Sep 17 00:00:00 2001 From: Harvmaster Date: Thu, 6 Aug 2026 10:08:00 +0000 Subject: [PATCH 06/12] Move Types --- source/exponential-backoff.ts | 90 +++++++++++++++++------------------ 1 file changed, 45 insertions(+), 45 deletions(-) diff --git a/source/exponential-backoff.ts b/source/exponential-backoff.ts index 59fe07e..89c3056 100644 --- a/source/exponential-backoff.ts +++ b/source/exponential-backoff.ts @@ -8,6 +8,51 @@ import { } from './errors.ts'; import { isWithinBounds } from './misc.ts'; +export type ExponentialBackoffOptions = { + + /** + * The maximum delay between attempts in milliseconds + */ + maxDelay: number; + + /** + * The maximum number of attempts. Passing 0 will result in infinite attempts. + */ + maxAttempts: number; + + /** + * The base delay between attempts in milliseconds + */ + baseDelay: number; + + /** + * The growth rate of the delay + */ + growthRate: number; + + /** + * The jitter of the delay as a percentage of growthRate. The jitter is subtracted from the delay. + */ + jitter: number; +}; + +/** + * The function to call to stop the retries. + * This mimics the AbortSignal.abort function by taking in a reason for stopping + * + * @param reason - The reason for stopping the retries. + */ +export type ExponentialBackoffStopRetriesFunction = (reason: unknown) => void; + +/** + * The parameters for the task function + * + * @param stopRetries - The function to call to stop the retries + */ +export type ExponentialBackoffCallbackParameters = { + stopRetries: ExponentialBackoffStopRetriesFunction; +}; + /** * Exponential backoff is a technique used to retry a function after a delay. * @@ -243,48 +288,3 @@ export class ExponentialBackoff { throw new ExponentialBackoffMaxRetriesHitError(errors); } } - -export type ExponentialBackoffOptions = { - - /** - * The maximum delay between attempts in milliseconds - */ - maxDelay: number; - - /** - * The maximum number of attempts. Passing 0 will result in infinite attempts. - */ - maxAttempts: number; - - /** - * The base delay between attempts in milliseconds - */ - baseDelay: number; - - /** - * The growth rate of the delay - */ - growthRate: number; - - /** - * The jitter of the delay as a percentage of growthRate. The jitter is subtracted from the delay. - */ - jitter: number; -}; - -/** - * The function to call to stop the retries. - * This mimics the AbortSignal.abort function by taking in a reason for stopping - * - * @param reason - The reason for stopping the retries. - */ -export type ExponentialBackoffStopRetriesFunction = (reason: unknown) => void; - -/** - * The parameters for the task function - * - * @param stopRetries - The function to call to stop the retries - */ -export type ExponentialBackoffCallbackParameters = { - stopRetries: ExponentialBackoffStopRetriesFunction; -}; From 44981839f8532b8a6167708f9a692808974b9bd8 Mon Sep 17 00:00:00 2001 From: Harvmaster Date: Thu, 6 Aug 2026 10:08:13 +0000 Subject: [PATCH 07/12] Add TS Docs throws to constructor --- source/exponential-backoff.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/source/exponential-backoff.ts b/source/exponential-backoff.ts index 89c3056..580b50f 100644 --- a/source/exponential-backoff.ts +++ b/source/exponential-backoff.ts @@ -76,6 +76,11 @@ export class ExponentialBackoff { * @param options.baseDelay - Delay used as the basis for the first retry. Default: `1_000` ms. * @param options.growthRate - Multiplier applied to the delay after each attempt. Default: `2`. * @param options.jitter - Maximum proportional reduction subtracted from each delay (0–1). Default: `0.1`. + * + * @throws An {@link ExponentialBackoffNumberNotFiniteError} if a provided option is not a finite number + * @throws An {@link ExponentialBackoffNumberOutOfBoundsError} if a provided option is out of bounds + * @throws An {@link ExponentialBackoffNumberTooSmallError} if a provided option is too small + * @throws An {@link ExponentialBackoffNonIntegerError} if a provided option is not an integer */ constructor(options: Partial = {}) { this.#options = { From 254aee021eae9dd708a3a063c671657baec39b81 Mon Sep 17 00:00:00 2001 From: Harvmaster Date: Thu, 6 Aug 2026 10:08:56 +0000 Subject: [PATCH 08/12] Add public to statics --- source/exponential-backoff.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/source/exponential-backoff.ts b/source/exponential-backoff.ts index 580b50f..f68b707 100644 --- a/source/exponential-backoff.ts +++ b/source/exponential-backoff.ts @@ -101,7 +101,7 @@ export class ExponentialBackoff { * @param config - The configuration for the exponential backoff * @returns The ExponentialBackoff instance */ - static from(config?: Partial): ExponentialBackoff { + public static from(config?: Partial): ExponentialBackoff { const backoff = new ExponentialBackoff(config); return backoff; @@ -119,7 +119,7 @@ export class ExponentialBackoff { * * @returns The result of the function */ - static run( + public static run( taskFn: (callbackParameters: ExponentialBackoffCallbackParameters) => Promise, onError = (_error: Error): void => {}, options?: Partial, @@ -233,7 +233,7 @@ export class ExponentialBackoff { * * @returns The result of the function */ - async run( + public async run( taskFn: (callbackParameters: ExponentialBackoffCallbackParameters) => Promise, onError = (_error: Error): void => {}, ): Promise { From 11d3aca5200e32ae369c966b15e6d8b7d4ad78ae Mon Sep 17 00:00:00 2001 From: Harvmaster Date: Thu, 6 Aug 2026 10:09:34 +0000 Subject: [PATCH 09/12] Add tests. Use exactOptionalPropertyTypes in tsconfig --- test/exponential-backoff.test.ts | 37 +++++++++++++++++++++++++++++++- tsconfig.json | 1 + 2 files changed, 37 insertions(+), 1 deletion(-) diff --git a/test/exponential-backoff.test.ts b/test/exponential-backoff.test.ts index 556ef11..34c8663 100644 --- a/test/exponential-backoff.test.ts +++ b/test/exponential-backoff.test.ts @@ -1,6 +1,6 @@ import { expect, test, vi } from 'vitest'; import { ExponentialBackoff } from '../source/exponential-backoff.ts'; -import { ExponentialBackoffMaxRetriesHitError, ExponentialBackoffStoppedRetriesError } from '../source/errors.ts'; +import { ExponentialBackoffMaxRetriesHitError, ExponentialBackoffNumberNotFiniteError, ExponentialBackoffStoppedRetriesError } from '../source/errors.ts'; /** * A valid options object that satisfies {@link ExponentialBackoff.validateOptions}. @@ -582,6 +582,39 @@ const testExponentialBackoffValidateOptionsRejectsNaN = (): void => { } }; +/** Tests that calculateDelay will not result in NaN from extremely large growth rates and attempts */ +const testExponentialBackoffCalculateDelayDoesNotResultInNaN = (): void => { + // Large number, 1 trillion. + // Theory being that 1 trillion to the power of 1 trillion should be a very large number and cause either an unsafe value or a NaN. + const largeNumber = 1_000_000_000_000; + + // Test the calculateDelay function + const result = ExponentialBackoff.calculateDelay({ + baseDelay: 10000, + growthRate: largeNumber, + jitter: 0, + maxDelay: 10_000, + maxAttempts: largeNumber, + }, largeNumber); + + // Test to ensure it was bounded to the max delay + expect(result).toBe(10_000); +}; + +/** Tests that passing undefined into the constructor does not cause an error during spread */ +const testExponentialBackoffConstructorDoesNotCauseErrorDuringSpread = async (): Promise => { + const options = { + baseDelay: undefined, + growthRate: undefined, + jitter: undefined, + maxDelay: undefined, + maxAttempts: undefined, + }; + + // We expect an error during validation as undefined is not a finite number, not an issue with the spread operator + // @ts-expect-error - Passing undefined is allowed if the exactOptionalPropertyTypes option is set to false in TS Compiler options. + expect(() => new ExponentialBackoff(options)).toThrow(ExponentialBackoffNumberNotFiniteError); +}; const runTests = async (): Promise => { test('ExponentialBackoff.run: delegates to a new instance using default options', testExponentialBackoffRunUsesDefaultOptions); test('ExponentialBackoff.run: retries and succeeds with partial options', testExponentialBackoffRunWithPartialOptions); @@ -608,6 +641,8 @@ const runTests = async (): Promise => { test('ExponentialBackoff.validateOptions: rejects Infinity', testExponentialBackoffValidateOptionsRejectsNonFiniteValues); test('ExponentialBackoff.validateOptions: rejects non-integer values', testExponentialBackoffValidateOptionsRejectsNonIntegerValues); test('ExponentialBackoff.validateOptions: rejects NaN', testExponentialBackoffValidateOptionsRejectsNaN); + test('ExponentialBackoff: calculateDelay does not result in NaN from extremely large growth rates and attempts', testExponentialBackoffCalculateDelayDoesNotResultInNaN); + test('ExponentialBackoff: constructor does not cause an error during spread', testExponentialBackoffConstructorDoesNotCauseErrorDuringSpread); }; await runTests(); diff --git a/tsconfig.json b/tsconfig.json index 106b15f..e6b7c1a 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -7,6 +7,7 @@ "moduleResolution": "bundler", "resolveJsonModule": true, "allowImportingTsExtensions": true, + "exactOptionalPropertyTypes": true, "noEmit": true, "declaration": true, "declarationMap": true From f1bdb1c9ed80c6f293fb22fd6da3d554792302d9 Mon Sep 17 00:00:00 2001 From: Harvmaster Date: Thu, 6 Aug 2026 13:31:47 +0000 Subject: [PATCH 10/12] Export errors --- source/index.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/source/index.ts b/source/index.ts index 899b941..9d70abf 100644 --- a/source/index.ts +++ b/source/index.ts @@ -1,3 +1,4 @@ +export * from './errors.ts'; export * from './exponential-backoff.ts'; export * from './extended-json.ts'; export * from './script.ts'; From 6306cc3380ba6a40aad0ccc9e46595f69ed3dd84 Mon Sep 17 00:00:00 2001 From: Harvmaster Date: Thu, 6 Aug 2026 13:32:16 +0000 Subject: [PATCH 11/12] Formatting --- source/exponential-backoff.ts | 8 +++++++- source/misc.ts | 10 +++++----- test/exponential-backoff.test.ts | 29 ++++++++++++++++++++--------- 3 files changed, 32 insertions(+), 15 deletions(-) diff --git a/source/exponential-backoff.ts b/source/exponential-backoff.ts index f68b707..f359f8c 100644 --- a/source/exponential-backoff.ts +++ b/source/exponential-backoff.ts @@ -76,7 +76,7 @@ export class ExponentialBackoff { * @param options.baseDelay - Delay used as the basis for the first retry. Default: `1_000` ms. * @param options.growthRate - Multiplier applied to the delay after each attempt. Default: `2`. * @param options.jitter - Maximum proportional reduction subtracted from each delay (0–1). Default: `0.1`. - * + * * @throws An {@link ExponentialBackoffNumberNotFiniteError} if a provided option is not a finite number * @throws An {@link ExponentialBackoffNumberOutOfBoundsError} if a provided option is out of bounds * @throws An {@link ExponentialBackoffNumberTooSmallError} if a provided option is too small @@ -99,6 +99,12 @@ export class ExponentialBackoff { * Create a new ExponentialBackoff instance * * @param config - The configuration for the exponential backoff + * + * @throws An {@link ExponentialBackoffNumberNotFiniteError} if a provided option is not a finite number + * @throws An {@link ExponentialBackoffNumberOutOfBoundsError} if a provided option is out of bounds + * @throws An {@link ExponentialBackoffNumberTooSmallError} if a provided option is too small + * @throws An {@link ExponentialBackoffNonIntegerError} if a provided option is not an integer + * * @returns The ExponentialBackoff instance */ public static from(config?: Partial): ExponentialBackoff { diff --git a/source/misc.ts b/source/misc.ts index 7ac6c43..7ec3b4c 100644 --- a/source/misc.ts +++ b/source/misc.ts @@ -8,9 +8,9 @@ * @returns True if the value is within the bounds, false otherwise */ export const isWithinBounds = (value: number, min: number, max: number): boolean => { - if (value < min || value > max) { - return false; - } + if (value < min || value > max) { + return false; + } - return true; -}; \ No newline at end of file + return true; +}; diff --git a/test/exponential-backoff.test.ts b/test/exponential-backoff.test.ts index 34c8663..a27b374 100644 --- a/test/exponential-backoff.test.ts +++ b/test/exponential-backoff.test.ts @@ -1,6 +1,10 @@ import { expect, test, vi } from 'vitest'; import { ExponentialBackoff } from '../source/exponential-backoff.ts'; -import { ExponentialBackoffMaxRetriesHitError, ExponentialBackoffNumberNotFiniteError, ExponentialBackoffStoppedRetriesError } from '../source/errors.ts'; +import { + ExponentialBackoffMaxRetriesHitError, + ExponentialBackoffNumberNotFiniteError, + ExponentialBackoffStoppedRetriesError, +} from '../source/errors.ts'; /** * A valid options object that satisfies {@link ExponentialBackoff.validateOptions}. @@ -589,13 +593,16 @@ const testExponentialBackoffCalculateDelayDoesNotResultInNaN = (): void => { const largeNumber = 1_000_000_000_000; // Test the calculateDelay function - const result = ExponentialBackoff.calculateDelay({ - baseDelay: 10000, - growthRate: largeNumber, - jitter: 0, - maxDelay: 10_000, - maxAttempts: largeNumber, - }, largeNumber); + const result = ExponentialBackoff.calculateDelay( + { + baseDelay: 10000, + growthRate: largeNumber, + jitter: 0, + maxDelay: 10_000, + maxAttempts: largeNumber, + }, + largeNumber, + ); // Test to ensure it was bounded to the max delay expect(result).toBe(10_000); @@ -615,6 +622,7 @@ const testExponentialBackoffConstructorDoesNotCauseErrorDuringSpread = async (): // @ts-expect-error - Passing undefined is allowed if the exactOptionalPropertyTypes option is set to false in TS Compiler options. expect(() => new ExponentialBackoff(options)).toThrow(ExponentialBackoffNumberNotFiniteError); }; + const runTests = async (): Promise => { test('ExponentialBackoff.run: delegates to a new instance using default options', testExponentialBackoffRunUsesDefaultOptions); test('ExponentialBackoff.run: retries and succeeds with partial options', testExponentialBackoffRunWithPartialOptions); @@ -641,7 +649,10 @@ const runTests = async (): Promise => { test('ExponentialBackoff.validateOptions: rejects Infinity', testExponentialBackoffValidateOptionsRejectsNonFiniteValues); test('ExponentialBackoff.validateOptions: rejects non-integer values', testExponentialBackoffValidateOptionsRejectsNonIntegerValues); test('ExponentialBackoff.validateOptions: rejects NaN', testExponentialBackoffValidateOptionsRejectsNaN); - test('ExponentialBackoff: calculateDelay does not result in NaN from extremely large growth rates and attempts', testExponentialBackoffCalculateDelayDoesNotResultInNaN); + test( + 'ExponentialBackoff: calculateDelay does not result in NaN from extremely large growth rates and attempts', + testExponentialBackoffCalculateDelayDoesNotResultInNaN, + ); test('ExponentialBackoff: constructor does not cause an error during spread', testExponentialBackoffConstructorDoesNotCauseErrorDuringSpread); }; From 941719e4e6b41ded4e2c82b8ee0dbd55880798fc Mon Sep 17 00:00:00 2001 From: Harvmaster Date: Thu, 6 Aug 2026 13:44:20 +0000 Subject: [PATCH 12/12] Audit fix --- package-lock.json | 58 +++++++++++++++++++++++------------------------ 1 file changed, 29 insertions(+), 29 deletions(-) diff --git a/package-lock.json b/package-lock.json index 813ede6..36f1cb4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1105,9 +1105,9 @@ "license": "MIT" }, "node_modules/@eslint/config-array/node_modules/brace-expansion": { - "version": "1.1.16", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.16.tgz", - "integrity": "sha512-IDw48K2/2kRkg9LdJxurvq3lV3aBgq0REY89duEqFRthjlPdXHKMj7EnQOXVckxzgisinf3nHfrcE2FufFLXMw==", + "version": "1.1.18", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.18.tgz", + "integrity": "sha512-Edep/X9fGqVNmzKBVsDYIOtD+z1tuezV70LBjdCst9Tqu76lsnvRiZ6oTic1n+/BIwX6QDGAO94PN4N2SADvtw==", "dev": true, "license": "MIT", "dependencies": { @@ -1186,9 +1186,9 @@ "license": "MIT" }, "node_modules/@eslint/eslintrc/node_modules/brace-expansion": { - "version": "1.1.16", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.16.tgz", - "integrity": "sha512-IDw48K2/2kRkg9LdJxurvq3lV3aBgq0REY89duEqFRthjlPdXHKMj7EnQOXVckxzgisinf3nHfrcE2FufFLXMw==", + "version": "1.1.18", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.18.tgz", + "integrity": "sha512-Edep/X9fGqVNmzKBVsDYIOtD+z1tuezV70LBjdCst9Tqu76lsnvRiZ6oTic1n+/BIwX6QDGAO94PN4N2SADvtw==", "dev": true, "license": "MIT", "dependencies": { @@ -3503,16 +3503,16 @@ } }, "node_modules/brace-expansion": { - "version": "5.0.7", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.7.tgz", - "integrity": "sha512-7oFy703dxfY3/NLxC1fh2SUCQ0H9rmAY+5EpDVfXjUTTs+HEwR2nYaqLv+GWcTsumwxPfiz6CzCNkwXwBUwqCA==", + "version": "5.0.9", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.9.tgz", + "integrity": "sha512-ScQ4IuvIEF1TMlP7Zt+vjJ//9zlPb2SDcxWxM3bk8s6t6GGdJ7KO1dCcTidOPJKePW30LE/2cT7wCyPho9/Wxg==", "dev": true, "license": "MIT", "dependencies": { "balanced-match": "^4.0.2" }, "engines": { - "node": "18 || 20 || >=22" + "node": "20 || >=22" } }, "node_modules/brotli-size": { @@ -5292,9 +5292,9 @@ "peer": true }, "node_modules/eslint-plugin-import/node_modules/brace-expansion": { - "version": "1.1.16", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.16.tgz", - "integrity": "sha512-IDw48K2/2kRkg9LdJxurvq3lV3aBgq0REY89duEqFRthjlPdXHKMj7EnQOXVckxzgisinf3nHfrcE2FufFLXMw==", + "version": "1.1.18", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.18.tgz", + "integrity": "sha512-Edep/X9fGqVNmzKBVsDYIOtD+z1tuezV70LBjdCst9Tqu76lsnvRiZ6oTic1n+/BIwX6QDGAO94PN4N2SADvtw==", "dev": true, "license": "MIT", "peer": true, @@ -5379,9 +5379,9 @@ "peer": true }, "node_modules/eslint-plugin-jsx-a11y/node_modules/brace-expansion": { - "version": "1.1.16", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.16.tgz", - "integrity": "sha512-IDw48K2/2kRkg9LdJxurvq3lV3aBgq0REY89duEqFRthjlPdXHKMj7EnQOXVckxzgisinf3nHfrcE2FufFLXMw==", + "version": "1.1.18", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.18.tgz", + "integrity": "sha512-Edep/X9fGqVNmzKBVsDYIOtD+z1tuezV70LBjdCst9Tqu76lsnvRiZ6oTic1n+/BIwX6QDGAO94PN4N2SADvtw==", "dev": true, "license": "MIT", "peer": true, @@ -5468,9 +5468,9 @@ "peer": true }, "node_modules/eslint-plugin-react/node_modules/brace-expansion": { - "version": "1.1.16", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.16.tgz", - "integrity": "sha512-IDw48K2/2kRkg9LdJxurvq3lV3aBgq0REY89duEqFRthjlPdXHKMj7EnQOXVckxzgisinf3nHfrcE2FufFLXMw==", + "version": "1.1.18", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.18.tgz", + "integrity": "sha512-Edep/X9fGqVNmzKBVsDYIOtD+z1tuezV70LBjdCst9Tqu76lsnvRiZ6oTic1n+/BIwX6QDGAO94PN4N2SADvtw==", "dev": true, "license": "MIT", "peer": true, @@ -5542,9 +5542,9 @@ "license": "MIT" }, "node_modules/eslint/node_modules/brace-expansion": { - "version": "1.1.16", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.16.tgz", - "integrity": "sha512-IDw48K2/2kRkg9LdJxurvq3lV3aBgq0REY89duEqFRthjlPdXHKMj7EnQOXVckxzgisinf3nHfrcE2FufFLXMw==", + "version": "1.1.18", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.18.tgz", + "integrity": "sha512-Edep/X9fGqVNmzKBVsDYIOtD+z1tuezV70LBjdCst9Tqu76lsnvRiZ6oTic1n+/BIwX6QDGAO94PN4N2SADvtw==", "dev": true, "license": "MIT", "dependencies": { @@ -7810,9 +7810,9 @@ "license": "MIT" }, "node_modules/nanoid": { - "version": "3.3.12", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.12.tgz", - "integrity": "sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==", + "version": "3.3.17", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.17.tgz", + "integrity": "sha512-xQLf0A3HOMlgHq0n247/LRuAOYmB7dXJ/DvAxGvsSBij45XtBSmQycu+F8ODbHwns/XyFZagyL1+J0Offw1E0g==", "dev": true, "funding": [ { @@ -8288,9 +8288,9 @@ } }, "node_modules/postcss": { - "version": "8.5.15", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.15.tgz", - "integrity": "sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A==", + "version": "8.5.26", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.26.tgz", + "integrity": "sha512-u82N74LFzG8ca+dD8puPnplTXoGH4fTPpVGuIbt36G3qvNlkvfD0lEAZSxaly3KX8TS/L1A1gsCEmvKmBcVbkQ==", "dev": true, "funding": [ { @@ -8308,7 +8308,7 @@ ], "license": "MIT", "dependencies": { - "nanoid": "^3.3.12", + "nanoid": "^3.3.17", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" },