From 684fbcc77ee7551ea898cb08ed0aa78fa85964a3 Mon Sep 17 00:00:00 2001 From: ivan k Date: Sun, 7 Apr 2024 16:30:22 -0700 Subject: [PATCH] 2.0 rewrite (typescript, etc.) (#34) * replace npm with pnpm * replace standard with eslint and prettier * replace ava with vitest * use typescript * replace travis ci with github actions * clean up README * document code with tsdoc * new default (retries: 2) --- .eslintrc.cjs | 13 + .github/actions/common-setup/action.yml | 13 + .github/workflows/main.yml | 52 + .gitignore | 3 +- .prettierignore | 3 + .prettierrc | 1 + .travis.yml | 7 - README.md | 119 +- lib/index.js | 185 -- package.json | 45 +- pnpm-lock.yaml | 2055 +++++++++++++++++++++++ src/index.test.ts | 409 +++++ src/index.ts | 371 ++++ test.js | 451 ----- tsconfig.json | 18 + vitest.config.ts | 10 + 16 files changed, 3029 insertions(+), 726 deletions(-) create mode 100644 .eslintrc.cjs create mode 100644 .github/actions/common-setup/action.yml create mode 100644 .github/workflows/main.yml create mode 100644 .prettierignore create mode 100644 .prettierrc delete mode 100644 .travis.yml delete mode 100644 lib/index.js create mode 100644 pnpm-lock.yaml create mode 100644 src/index.test.ts create mode 100644 src/index.ts delete mode 100644 test.js create mode 100644 tsconfig.json create mode 100644 vitest.config.ts diff --git a/.eslintrc.cjs b/.eslintrc.cjs new file mode 100644 index 0000000..f1f8ee4 --- /dev/null +++ b/.eslintrc.cjs @@ -0,0 +1,13 @@ +module.exports = { + root: true, + env: { "shared-node-browser": true, es2020: true }, + extends: ["eslint:recommended", "plugin:@typescript-eslint/recommended"], + ignorePatterns: ["lib", ".eslintrc.cjs"], + parser: "@typescript-eslint/parser", + plugins: ["eslint-plugin-tsdoc"], + rules: { + "@typescript-eslint/no-unused-vars": ["error", { argsIgnorePattern: "^_" }], + "no-constant-condition": ["error", { checkLoops: false }], + "tsdoc/syntax": "error", + }, +}; diff --git a/.github/actions/common-setup/action.yml b/.github/actions/common-setup/action.yml new file mode 100644 index 0000000..1ba14b8 --- /dev/null +++ b/.github/actions/common-setup/action.yml @@ -0,0 +1,13 @@ +name: "Common setup" +runs: + using: "composite" + steps: + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: 18 + + - name: Setup pnpm + uses: pnpm/action-setup@v3 + with: + run_install: true diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml new file mode 100644 index 0000000..d0c4efb --- /dev/null +++ b/.github/workflows/main.yml @@ -0,0 +1,52 @@ +name: CI +on: + - push +jobs: + test-style: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - uses: ./.github/actions/common-setup + + - name: test:style + run: pnpm run test:style + + test-lint: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - uses: ./.github/actions/common-setup + + - name: test:lint + run: pnpm run test:lint + + test-types: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - uses: ./.github/actions/common-setup + + - name: test:types + run: pnpm run test:types + + test-vitest: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - uses: ./.github/actions/common-setup + + - name: test:vitest + run: pnpm run test:vitest + + - name: Upload coverage reports to Codecov + uses: codecov/codecov-action@v4.0.1 + with: + token: ${{ secrets.CODECOV_TOKEN }} diff --git a/.gitignore b/.gitignore index 7591a8c..f417452 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,3 @@ node_modules/ -npm-debug*.log -.nyc_output/ coverage/ +lib/ diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 0000000..0f22083 --- /dev/null +++ b/.prettierignore @@ -0,0 +1,3 @@ +lib/ +coverage/ +pnpm-lock.yaml diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/.prettierrc @@ -0,0 +1 @@ +{} diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 3005fc6..0000000 --- a/.travis.yml +++ /dev/null @@ -1,7 +0,0 @@ -language: node_js -node_js: - - "6" - - "4" -after_success: - - './node_modules/.bin/nyc report --reporter=text-lcov | ./node_modules/.bin/coveralls' - diff --git a/README.md b/README.md index 65bffde..a237b54 100644 --- a/README.md +++ b/README.md @@ -1,20 +1,11 @@ -recaller -=== - -[![Greenkeeper badge](https://badges.greenkeeper.io/SEAPUNK/recaller.svg)](https://greenkeeper.io/) - -[![npm version](https://img.shields.io/npm/v/recaller.svg?style=flat-square)](https://npmjs.com/package/recaller) -[![javascript standard style](https://img.shields.io/badge/code%20style-standard-blue.svg?style=flat-square)](http://standardjs.com/) -[![travis build](https://img.shields.io/travis/SEAPUNK/recaller/master.svg?style=flat-square)](https://travis-ci.org/SEAPUNK/recaller) -[![coveralls coverage](https://img.shields.io/coveralls/SEAPUNK/recaller.svg?style=flat-square)](https://coveralls.io/github/SEAPUNK/recaller) -[![david dependencies](https://david-dm.org/SEAPUNK/recaller.svg?style=flat-square)](https://david-dm.org/SEAPUNK/recaller) -[![david dev dependencies](https://david-dm.org/SEAPUNK/recaller/dev-status.svg?style=flat-square)](https://david-dm.org/SEAPUNK/recaller) +# recaller +[![npm version](https://img.shields.io/npm/v/recaller.svg)](https://npmjs.com/package/recaller) +[![CI](https://github.com/SEAPUNK/recaller/actions/workflows/main.yml/badge.svg)](https://github.com/SEAPUNK/recaller/actions/workflows/main.yml) +[![codecov](https://codecov.io/gh/SEAPUNK/recaller/graph/badge.svg?token=UnnvO670f2)](https://codecov.io/gh/SEAPUNK/recaller) Promise-based function retry utility. Designed for `async/await`. -**Requires node v4 or above** - `npm install recaller` - [usage](#usage) @@ -24,58 +15,53 @@ Promise-based function retry utility. Designed for `async/await`. --- -usage ---- +## usage -*example partially stolen from [async-retry](https://github.com/zeit/async-retry)'s example* +_example partially stolen from [async-retry](https://github.com/zeit/async-retry)'s example_ ```js -import recaller from 'recaller' -import fetch from 'node-fetch' - -export default async function fetchSomething () { - return await recaller(async (bail, attempt) => { - const res = await fetch('https://google.com') - - if (403 === res.status) { - // we're not going to retry - return bail(new Error('Unauthorized')) - } - - const data = await res.text() - return data.substr(0, 500) - }, {retries: 5}) +import recaller, { constantBackoff } from "recaller"; +import fetch from "node-fetch"; + +export default async function fetchSomething() { + return await recaller( + async (bail, attempt) => { + const res = await fetch("https://google.com"); + + if (403 === res.status) { + // we're not going to retry + return bail(new Error("Unauthorized")); + } + + const data = await res.text(); + return data.substr(0, 500); + }, + { + // default: 2 retries + retries: 10, + // default: no backoff, retry immediately + backoff: constantBackoff(1000), + }, + ); } ``` +## api -api ---- - -`recaller(fn, opts)` - -Calls provided (async or regular) function, and retries on failure. - -`fn` is called with two arguments: -- `bail(err)` Stops and rejects the retryer's promise with given error. - + Note that this does not stop execution, so you have to return manually, allowing you to do some cleanup before returning -- `attempt` Current attempt. First call to function = attempt 1. - -`opts` is an object, with the following properties: +The code is fully TSDoc'd. See `src/index.ts` for documentation of the main functions, listed below: -- `opts.retries` (default `10`) How many times to retry before giving up, and rejecting with the error. -- `opts.backoff` (default `null`) Backoff generator to use. If null, there is no backoff, and on fail, the function is retried immediately. See: [backoffs](#backoffs) -- `opts.onretry` (default `null`) Retry event handler. See: [handling retries](#handling-retries) +- `recaller(fn, opts)` +- `constantBackoff(ms)` +- `fullJitterBackoff(opts)` -backoffs ---- +## backoffs -`recaller` doesn't backoff (wait before retrying) by default. To specify backoff, you must give it a "backoff generator" in the options (`opts.backoff`). +`recaller` doesn't backoff (wait before retrying) by default. To specify backoff, you must give it a backoff function in the options (`opts.backoff`). example: ```js -import recaller, {constantBackoff} from 'recaller' +import recaller, { constantBackoff } from 'recaller' export default function doSomething () { return await recaller(async () => { @@ -87,27 +73,32 @@ export default function doSomething () { } ``` -A backoff generator is a function that returns the next delay to wait in milliseconds. For example, the full `constantBackoff(ms)` generator is below: +A backoff function, given an attempt count, returns the next delay to wait in milliseconds. +For example, `constantBackoff(ms)` below: ```js -function constantBackoff (ms) { - ms = ms || 5000 - return (attempt) => ms +function constantBackoff(ms) { + ms = ms ?? 5000; + return (attempt) => ms; } ``` `recaller` comes with 5 backoff generator functions, inspired by [AWS's exponential backoff blog post](https://www.awsarchitectureblog.com/2015/03/backoff.html). -- [`constantBackoff(ms)`](https://github.com/SEAPUNK/recaller/blob/56f9d7b29a0459e1c4f4d40b1de9cd53be589405/lib/index.js#L86-L97) -- [`exponentialBackoff({base, cap, factor})`](https://github.com/SEAPUNK/recaller/blob/56f9d7b29a0459e1c4f4d40b1de9cd53be589405/lib/index.js#L99-L123) -- [`fullJitterBackoff({base, cap, factor})`](https://github.com/SEAPUNK/recaller/blob/56f9d7b29a0459e1c4f4d40b1de9cd53be589405/lib/index.js#L125-L136) -- [`equalJitterBackoff({base, cap, factor})`](https://github.com/SEAPUNK/recaller/blob/56f9d7b29a0459e1c4f4d40b1de9cd53be589405/lib/index.js#L138-L153) -- [`decorrelatedJitterBackoff({base, cap, times})`](https://github.com/SEAPUNK/recaller/blob/56f9d7b29a0459e1c4f4d40b1de9cd53be589405/lib/index.js#L154-L178) +Use `fullJitterBackoff` for most cases, as it generally gives you the best results. You only really have to tweak the `base` and `cap` with it. See code for more documentation. -handling retries ---- +- `constantBackoff(ms)` +- `fullJitterBackoff({base, cap, factor})` + +the following aren't recommended, and only exist for completeness: + +- `exponentialBackoff({base, cap, factor})` +- `equalJitterBackoff({base, cap, factor})` +- `decorrelatedJitterBackoff({base, cap, times})` + +## handling retries -You can intercept each retry attempt, by providing a middleware function in `opts.onretry`. +You can intercept each retry attempt, by providing a function in `opts.onretry`. ```js import recaller from 'recaller' @@ -126,7 +117,7 @@ export default function doSomething () { logger.warn(`doSomething attempt ${attempt} failed; will wait ${delayTime} ms before trying again. - error: ${err.stack} + error: ${err} `) } }) diff --git a/lib/index.js b/lib/index.js deleted file mode 100644 index 3fc0e2a..0000000 --- a/lib/index.js +++ /dev/null @@ -1,185 +0,0 @@ -'use strict' - -const minico = require('minico') -const delay = require('delay') - -// Gets a number between min (inclusive) and max (inclusive) -function randomBetween (min, max) { - return Math.floor(Math.random() * (max - min + 1) + min) -} - -// Main recaller function. -// fn: Function to retry. -// -// Function is called with 2 arguments: -// - bail: A function that stops the retry operation, and rejects -// the retrier promise with a given error instance. -// - attempt: Current attempt. First attempt is attempt 1. -// -// opts: Options object. -// -// opts.retries: How many times to retry before giving up. -// If retry is 1, then the function will be called twice total -// before giving up. -// Defaults to 10 retries. -// -// opts.backoff: Function that returns the next backoff delay on call. -// Recaller comes with a few standard backoff functions, -// see below. -// -// Function is called with 1 argument: -// - attempt: Current attempt. -// -// opts.onretry: Function that gets called when the function has thrown, -// and we will retry (potentially after waiting). -// You can also prevent a retry by having the onretry function -// throw, if you want to prevent retries under certain conditions. -// -// Function is called with 3 arguments: -// - err: Error thrown by the current attempt -// - attempt: Which attempt that was. -// - delayTime: How long we will wait in ms before trying again -// (0 if we are not waiting) -function recaller (fn, opts) { - return new Promise((resolve, reject) => { - if (typeof fn !== 'function') throw new Error('fn is not a function') - opts = opts || {} - if (opts.onretry && typeof opts.onretry !== 'function') throw new Error('onretry handler is not a function') - - const retries = opts.retries || 10 - - let bailed = false - let attempt = 0 - - function bail (err) { - bailed = true - return reject(err || new Error('Bailed without giving a reason.')) - } - - const runner = minico(function * () { - const backoff = opts.backoff - while (true) { - try { - attempt++ - return yield fn(bail, attempt) - } catch (err) { - if (bailed) return - let delayTime = 0 - if (backoff) { - delayTime = backoff(attempt) - } - if (attempt > retries) { - throw err - } else { - if (opts.onretry) opts.onretry(err, attempt, delayTime) - if (delayTime) yield delay(delayTime) - continue - } - } - } - }) - - runner().then(resolve).catch(reject) - }) -} - -// Backoff generator functions -// These are functions that you can conveniently use when setting the backoff -// options for the retrier. - -// Constant backoff delay generator. Consistently waits for "ms" milliseconds -// before trying again. -// -// ms: (default 5000) Delay in ms. -function constantBackoff (ms) { - ms = ms || 5000 - return () => ms -} - -// Below functions use algorithms and formulas taken from: -// https://www.awsarchitectureblog.com/2015/03/backoff.html -// https://github.com/awslabs/aws-arch-backoff-simulator - -// Exponential backoff delay generator. -// -// opts: options object -// opts.base: (default 1000) Base delay in ms to calculate the next delay with -// The first attempt will be the base delay. -// For example, if the base is 1000 (1s) and factor is 2, then the -// delays will go in this order: 1s, 2s, 4s, 8s, etc. -// opts.cap: (default 60000) Max allowed delay in ms. -// opts.factor: (default 2) Exponential factor. -function exponentialBackoff (opts) { - opts = opts || {} - const base = opts.base || 1000 - const cap = opts.cap || 60000 - const factor = opts.factor || 2 - - return (attempt) => Math.min(cap, ( - base * Math.pow( - factor, (attempt - 1) - ) - )) -} - -// Exponential backoff generator, with full jitter. -// It's more-or-less the same as the exponential backoff generator, except -// the delay returned will be between 0 and the generated delay. -// -// opts: options object -// opts.base: see exponentialBackoff(opts.base) -// opts.cap: see exponentialBackoff(opts.cap) -// opts.factor: see exponentialBackoff(opts.factor) -function fullJitterBackoff (opts) { - const gen = exponentialBackoff(opts) - return (attempt) => randomBetween(0, gen(attempt)) -} - -// Exponential backoff generator, with equal jitter. -// See https://www.awsarchitectureblog.com/2015/03/backoff.html's -// explanation of "equal jitter" for more information. -// -// opts: options object -// opts.base: see exponentialBackoff(opts.base) -// opts.cap: see exponentialBackoff(opts.cap) -// opts.factor: see exponentialBackoff(opts.factor) -function equalJitterBackoff (opts) { - const gen = exponentialBackoff(opts) - return (attempt) => { - const halfDelay = gen(attempt) / 2 - return halfDelay + randomBetween(0, halfDelay) - } -} - -// Exponential(?) backoff generator, with decorrelated jitter. -// See https://www.awsarchitectureblog.com/2015/03/backoff.html's -// explanation of "decorrelated jitter" for more information. -// -// Note that this generator is stateful, which means that if you reuse it, it -// might not behave as you would expect. -// -// opts: options object -// opts.base: (default 5000) Base delay in ms. Used as initial delay, and -// to calculate future values. -// opts.cap: (default 60000) Delay in ms to not go above -// opts.times: (default 3) See code below. -function decorrelatedJitterBackoff (opts) { - opts = opts || {} - const base = opts.base || 1000 - const cap = opts.cap || 60000 - const times = opts.times || 3 - - let lastSleep = base - return () => { - const sleep = Math.min(cap, randomBetween(base, lastSleep * times)) - lastSleep = sleep - return sleep - } -} - -module.exports = recaller -module.exports.constantBackoff = constantBackoff -module.exports.exponentialBackoff = exponentialBackoff -module.exports.fullJitterBackoff = fullJitterBackoff -module.exports.equalJitterBackoff = equalJitterBackoff -module.exports.decorrelatedJitterBackoff = decorrelatedJitterBackoff diff --git a/package.json b/package.json index 9d1cc8b..5a30d88 100644 --- a/package.json +++ b/package.json @@ -2,13 +2,27 @@ "name": "recaller", "version": "1.0.1", "description": "Promise-based function retry utility.", - "main": "lib/index.js", + "main": "./lib/index.js", + "exports": "./lib/index.js", + "files": [ + "lib/index.js", + "lib/index.d.ts" + ], + "engines": { + "node": ">=18" + }, "scripts": { - "test": "standard && nyc ava" + "prettier": "prettier --write .", + "test:style": "prettier --check .", + "test:lint": "eslint .", + "test:types": "tsc --noEmit", + "test:vitest": "vitest --coverage", + "test": "pnpm run test:style && pnpm run test:lint && pnpm run test:types && pnpm run test:vitest", + "prepublishOnly": "tsc" }, "repository": { "type": "git", - "url": "ssh://git@github.com/SEAPUNK/recaller" + "url": "git+ssh://git@github.com/SEAPUNK/recaller.git" }, "keywords": [ "retry", @@ -18,25 +32,22 @@ "backoff", "exponential" ], - "author": "Ivan K ", + "author": "ivan k ", "license": "MIT", "bugs": { "url": "https://github.com/SEAPUNK/recaller/issues" }, "homepage": "https://github.com/SEAPUNK/recaller#readme", "devDependencies": { - "ava": "^0.18.0", - "babel-eslint": "^7.0.0", - "coveralls": "^2.11.12", - "nyc": "^10.0.0", - "rimraf": "^2.5.4", - "standard": "^9.0.0" - }, - "dependencies": { - "delay": "^1.3.1", - "minico": "^1.0.1" + "@types/node": "^20.12.4", + "@typescript-eslint/eslint-plugin": "^7.5.0", + "@typescript-eslint/parser": "^7.5.0", + "@vitest/coverage-v8": "^1.4.0", + "eslint": "^8.57.0", + "eslint-plugin-tsdoc": "^0.2.17", + "prettier": "^3.2.5", + "typescript": "^5.4.3", + "vitest": "^1.4.0" }, - "standard": { - "parser": "babel-eslint" - } + "packageManager": "pnpm@8.15.6" } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml new file mode 100644 index 0000000..21a2fc7 --- /dev/null +++ b/pnpm-lock.yaml @@ -0,0 +1,2055 @@ +lockfileVersion: '6.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +devDependencies: + '@types/node': + specifier: ^20.12.4 + version: 20.12.4 + '@typescript-eslint/eslint-plugin': + specifier: ^7.5.0 + version: 7.5.0(@typescript-eslint/parser@7.5.0)(eslint@8.57.0)(typescript@5.4.3) + '@typescript-eslint/parser': + specifier: ^7.5.0 + version: 7.5.0(eslint@8.57.0)(typescript@5.4.3) + '@vitest/coverage-v8': + specifier: ^1.4.0 + version: 1.4.0(vitest@1.4.0) + eslint: + specifier: ^8.57.0 + version: 8.57.0 + eslint-plugin-tsdoc: + specifier: ^0.2.17 + version: 0.2.17 + prettier: + specifier: ^3.2.5 + version: 3.2.5 + typescript: + specifier: ^5.4.3 + version: 5.4.3 + vitest: + specifier: ^1.4.0 + version: 1.4.0(@types/node@20.12.4) + +packages: + + /@aashutoshrathi/word-wrap@1.2.6: + resolution: {integrity: sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA==} + engines: {node: '>=0.10.0'} + dev: true + + /@ampproject/remapping@2.3.0: + resolution: {integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==} + engines: {node: '>=6.0.0'} + dependencies: + '@jridgewell/gen-mapping': 0.3.5 + '@jridgewell/trace-mapping': 0.3.25 + dev: true + + /@babel/helper-string-parser@7.24.1: + resolution: {integrity: sha512-2ofRCjnnA9y+wk8b9IAREroeUP02KHp431N2mhKniy2yKIDKpbrHv9eXwm8cBeWQYcJmzv5qKCu65P47eCF7CQ==} + engines: {node: '>=6.9.0'} + dev: true + + /@babel/helper-validator-identifier@7.22.20: + resolution: {integrity: sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==} + engines: {node: '>=6.9.0'} + dev: true + + /@babel/parser@7.24.4: + resolution: {integrity: sha512-zTvEBcghmeBma9QIGunWevvBAp4/Qu9Bdq+2k0Ot4fVMD6v3dsC9WOcRSKk7tRRyBM/53yKMJko9xOatGQAwSg==} + engines: {node: '>=6.0.0'} + hasBin: true + dependencies: + '@babel/types': 7.24.0 + dev: true + + /@babel/types@7.24.0: + resolution: {integrity: sha512-+j7a5c253RfKh8iABBhywc8NSfP5LURe7Uh4qpsh6jc+aLJguvmIUBdjSdEMQv2bENrCR5MfRdjGo7vzS/ob7w==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/helper-string-parser': 7.24.1 + '@babel/helper-validator-identifier': 7.22.20 + to-fast-properties: 2.0.0 + dev: true + + /@bcoe/v8-coverage@0.2.3: + resolution: {integrity: sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==} + dev: true + + /@esbuild/aix-ppc64@0.20.2: + resolution: {integrity: sha512-D+EBOJHXdNZcLJRBkhENNG8Wji2kgc9AZ9KiPr1JuZjsNtyHzrsfLRrY0tk2H2aoFu6RANO1y1iPPUCDYWkb5g==} + engines: {node: '>=12'} + cpu: [ppc64] + os: [aix] + requiresBuild: true + dev: true + optional: true + + /@esbuild/android-arm64@0.20.2: + resolution: {integrity: sha512-mRzjLacRtl/tWU0SvD8lUEwb61yP9cqQo6noDZP/O8VkwafSYwZ4yWy24kan8jE/IMERpYncRt2dw438LP3Xmg==} + engines: {node: '>=12'} + cpu: [arm64] + os: [android] + requiresBuild: true + dev: true + optional: true + + /@esbuild/android-arm@0.20.2: + resolution: {integrity: sha512-t98Ra6pw2VaDhqNWO2Oph2LXbz/EJcnLmKLGBJwEwXX/JAN83Fym1rU8l0JUWK6HkIbWONCSSatf4sf2NBRx/w==} + engines: {node: '>=12'} + cpu: [arm] + os: [android] + requiresBuild: true + dev: true + optional: true + + /@esbuild/android-x64@0.20.2: + resolution: {integrity: sha512-btzExgV+/lMGDDa194CcUQm53ncxzeBrWJcncOBxuC6ndBkKxnHdFJn86mCIgTELsooUmwUm9FkhSp5HYu00Rg==} + engines: {node: '>=12'} + cpu: [x64] + os: [android] + requiresBuild: true + dev: true + optional: true + + /@esbuild/darwin-arm64@0.20.2: + resolution: {integrity: sha512-4J6IRT+10J3aJH3l1yzEg9y3wkTDgDk7TSDFX+wKFiWjqWp/iCfLIYzGyasx9l0SAFPT1HwSCR+0w/h1ES/MjA==} + engines: {node: '>=12'} + cpu: [arm64] + os: [darwin] + requiresBuild: true + dev: true + optional: true + + /@esbuild/darwin-x64@0.20.2: + resolution: {integrity: sha512-tBcXp9KNphnNH0dfhv8KYkZhjc+H3XBkF5DKtswJblV7KlT9EI2+jeA8DgBjp908WEuYll6pF+UStUCfEpdysA==} + engines: {node: '>=12'} + cpu: [x64] + os: [darwin] + requiresBuild: true + dev: true + optional: true + + /@esbuild/freebsd-arm64@0.20.2: + resolution: {integrity: sha512-d3qI41G4SuLiCGCFGUrKsSeTXyWG6yem1KcGZVS+3FYlYhtNoNgYrWcvkOoaqMhwXSMrZRl69ArHsGJ9mYdbbw==} + engines: {node: '>=12'} + cpu: [arm64] + os: [freebsd] + requiresBuild: true + dev: true + optional: true + + /@esbuild/freebsd-x64@0.20.2: + resolution: {integrity: sha512-d+DipyvHRuqEeM5zDivKV1KuXn9WeRX6vqSqIDgwIfPQtwMP4jaDsQsDncjTDDsExT4lR/91OLjRo8bmC1e+Cw==} + engines: {node: '>=12'} + cpu: [x64] + os: [freebsd] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-arm64@0.20.2: + resolution: {integrity: sha512-9pb6rBjGvTFNira2FLIWqDk/uaf42sSyLE8j1rnUpuzsODBq7FvpwHYZxQ/It/8b+QOS1RYfqgGFNLRI+qlq2A==} + engines: {node: '>=12'} + cpu: [arm64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-arm@0.20.2: + resolution: {integrity: sha512-VhLPeR8HTMPccbuWWcEUD1Az68TqaTYyj6nfE4QByZIQEQVWBB8vup8PpR7y1QHL3CpcF6xd5WVBU/+SBEvGTg==} + engines: {node: '>=12'} + cpu: [arm] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-ia32@0.20.2: + resolution: {integrity: sha512-o10utieEkNPFDZFQm9CoP7Tvb33UutoJqg3qKf1PWVeeJhJw0Q347PxMvBgVVFgouYLGIhFYG0UGdBumROyiig==} + engines: {node: '>=12'} + cpu: [ia32] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-loong64@0.20.2: + resolution: {integrity: sha512-PR7sp6R/UC4CFVomVINKJ80pMFlfDfMQMYynX7t1tNTeivQ6XdX5r2XovMmha/VjR1YN/HgHWsVcTRIMkymrgQ==} + engines: {node: '>=12'} + cpu: [loong64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-mips64el@0.20.2: + resolution: {integrity: sha512-4BlTqeutE/KnOiTG5Y6Sb/Hw6hsBOZapOVF6njAESHInhlQAghVVZL1ZpIctBOoTFbQyGW+LsVYZ8lSSB3wkjA==} + engines: {node: '>=12'} + cpu: [mips64el] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-ppc64@0.20.2: + resolution: {integrity: sha512-rD3KsaDprDcfajSKdn25ooz5J5/fWBylaaXkuotBDGnMnDP1Uv5DLAN/45qfnf3JDYyJv/ytGHQaziHUdyzaAg==} + engines: {node: '>=12'} + cpu: [ppc64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-riscv64@0.20.2: + resolution: {integrity: sha512-snwmBKacKmwTMmhLlz/3aH1Q9T8v45bKYGE3j26TsaOVtjIag4wLfWSiZykXzXuE1kbCE+zJRmwp+ZbIHinnVg==} + engines: {node: '>=12'} + cpu: [riscv64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-s390x@0.20.2: + resolution: {integrity: sha512-wcWISOobRWNm3cezm5HOZcYz1sKoHLd8VL1dl309DiixxVFoFe/o8HnwuIwn6sXre88Nwj+VwZUvJf4AFxkyrQ==} + engines: {node: '>=12'} + cpu: [s390x] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-x64@0.20.2: + resolution: {integrity: sha512-1MdwI6OOTsfQfek8sLwgyjOXAu+wKhLEoaOLTjbijk6E2WONYpH9ZU2mNtR+lZ2B4uwr+usqGuVfFT9tMtGvGw==} + engines: {node: '>=12'} + cpu: [x64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/netbsd-x64@0.20.2: + resolution: {integrity: sha512-K8/DhBxcVQkzYc43yJXDSyjlFeHQJBiowJ0uVL6Tor3jGQfSGHNNJcWxNbOI8v5k82prYqzPuwkzHt3J1T1iZQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [netbsd] + requiresBuild: true + dev: true + optional: true + + /@esbuild/openbsd-x64@0.20.2: + resolution: {integrity: sha512-eMpKlV0SThJmmJgiVyN9jTPJ2VBPquf6Kt/nAoo6DgHAoN57K15ZghiHaMvqjCye/uU4X5u3YSMgVBI1h3vKrQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [openbsd] + requiresBuild: true + dev: true + optional: true + + /@esbuild/sunos-x64@0.20.2: + resolution: {integrity: sha512-2UyFtRC6cXLyejf/YEld4Hajo7UHILetzE1vsRcGL3earZEW77JxrFjH4Ez2qaTiEfMgAXxfAZCm1fvM/G/o8w==} + engines: {node: '>=12'} + cpu: [x64] + os: [sunos] + requiresBuild: true + dev: true + optional: true + + /@esbuild/win32-arm64@0.20.2: + resolution: {integrity: sha512-GRibxoawM9ZCnDxnP3usoUDO9vUkpAxIIZ6GQI+IlVmr5kP3zUq+l17xELTHMWTWzjxa2guPNyrpq1GWmPvcGQ==} + engines: {node: '>=12'} + cpu: [arm64] + os: [win32] + requiresBuild: true + dev: true + optional: true + + /@esbuild/win32-ia32@0.20.2: + resolution: {integrity: sha512-HfLOfn9YWmkSKRQqovpnITazdtquEW8/SoHW7pWpuEeguaZI4QnCRW6b+oZTztdBnZOS2hqJ6im/D5cPzBTTlQ==} + engines: {node: '>=12'} + cpu: [ia32] + os: [win32] + requiresBuild: true + dev: true + optional: true + + /@esbuild/win32-x64@0.20.2: + resolution: {integrity: sha512-N49X4lJX27+l9jbLKSqZ6bKNjzQvHaT8IIFUy+YIqmXQdjYCToGWwOItDrfby14c78aDd5NHQl29xingXfCdLQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [win32] + requiresBuild: true + dev: true + optional: true + + /@eslint-community/eslint-utils@4.4.0(eslint@8.57.0): + resolution: {integrity: sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 + dependencies: + eslint: 8.57.0 + eslint-visitor-keys: 3.4.3 + dev: true + + /@eslint-community/regexpp@4.10.0: + resolution: {integrity: sha512-Cu96Sd2By9mCNTx2iyKOmq10v22jUVQv0lQnlGNy16oE9589yE+QADPbrMGCkA51cKZSg3Pu/aTJVTGfL/qjUA==} + engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} + dev: true + + /@eslint/eslintrc@2.1.4: + resolution: {integrity: sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + dependencies: + ajv: 6.12.6 + debug: 4.3.4 + espree: 9.6.1 + globals: 13.24.0 + ignore: 5.3.1 + import-fresh: 3.3.0 + js-yaml: 4.1.0 + minimatch: 3.1.2 + strip-json-comments: 3.1.1 + transitivePeerDependencies: + - supports-color + dev: true + + /@eslint/js@8.57.0: + resolution: {integrity: sha512-Ys+3g2TaW7gADOJzPt83SJtCDhMjndcDMFVQ/Tj9iA1BfJzFKD9mAUXT3OenpuPHbI6P/myECxRJrofUsDx/5g==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + dev: true + + /@humanwhocodes/config-array@0.11.14: + resolution: {integrity: sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg==} + engines: {node: '>=10.10.0'} + dependencies: + '@humanwhocodes/object-schema': 2.0.3 + debug: 4.3.4 + minimatch: 3.1.2 + transitivePeerDependencies: + - supports-color + dev: true + + /@humanwhocodes/module-importer@1.0.1: + resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==} + engines: {node: '>=12.22'} + dev: true + + /@humanwhocodes/object-schema@2.0.3: + resolution: {integrity: sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==} + dev: true + + /@istanbuljs/schema@0.1.3: + resolution: {integrity: sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==} + engines: {node: '>=8'} + dev: true + + /@jest/schemas@29.6.3: + resolution: {integrity: sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dependencies: + '@sinclair/typebox': 0.27.8 + dev: true + + /@jridgewell/gen-mapping@0.3.5: + resolution: {integrity: sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==} + engines: {node: '>=6.0.0'} + dependencies: + '@jridgewell/set-array': 1.2.1 + '@jridgewell/sourcemap-codec': 1.4.15 + '@jridgewell/trace-mapping': 0.3.25 + dev: true + + /@jridgewell/resolve-uri@3.1.2: + resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} + engines: {node: '>=6.0.0'} + dev: true + + /@jridgewell/set-array@1.2.1: + resolution: {integrity: sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==} + engines: {node: '>=6.0.0'} + dev: true + + /@jridgewell/sourcemap-codec@1.4.15: + resolution: {integrity: sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==} + dev: true + + /@jridgewell/trace-mapping@0.3.25: + resolution: {integrity: sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==} + dependencies: + '@jridgewell/resolve-uri': 3.1.2 + '@jridgewell/sourcemap-codec': 1.4.15 + dev: true + + /@microsoft/tsdoc-config@0.16.2: + resolution: {integrity: sha512-OGiIzzoBLgWWR0UdRJX98oYO+XKGf7tiK4Zk6tQ/E4IJqGCe7dvkTvgDZV5cFJUzLGDOjeAXrnZoA6QkVySuxw==} + dependencies: + '@microsoft/tsdoc': 0.14.2 + ajv: 6.12.6 + jju: 1.4.0 + resolve: 1.19.0 + dev: true + + /@microsoft/tsdoc@0.14.2: + resolution: {integrity: sha512-9b8mPpKrfeGRuhFH5iO1iwCLeIIsV6+H1sRfxbkoGXIyQE2BTsPd9zqSqQJ+pv5sJ/hT5M1zvOFL02MnEezFug==} + dev: true + + /@nodelib/fs.scandir@2.1.5: + resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} + engines: {node: '>= 8'} + dependencies: + '@nodelib/fs.stat': 2.0.5 + run-parallel: 1.2.0 + dev: true + + /@nodelib/fs.stat@2.0.5: + resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==} + engines: {node: '>= 8'} + dev: true + + /@nodelib/fs.walk@1.2.8: + resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} + engines: {node: '>= 8'} + dependencies: + '@nodelib/fs.scandir': 2.1.5 + fastq: 1.17.1 + dev: true + + /@rollup/rollup-android-arm-eabi@4.14.1: + resolution: {integrity: sha512-fH8/o8nSUek8ceQnT7K4EQbSiV7jgkHq81m9lWZFIXjJ7lJzpWXbQFpT/Zh6OZYnpFykvzC3fbEvEAFZu03dPA==} + cpu: [arm] + os: [android] + requiresBuild: true + dev: true + optional: true + + /@rollup/rollup-android-arm64@4.14.1: + resolution: {integrity: sha512-Y/9OHLjzkunF+KGEoJr3heiD5X9OLa8sbT1lm0NYeKyaM3oMhhQFvPB0bNZYJwlq93j8Z6wSxh9+cyKQaxS7PQ==} + cpu: [arm64] + os: [android] + requiresBuild: true + dev: true + optional: true + + /@rollup/rollup-darwin-arm64@4.14.1: + resolution: {integrity: sha512-+kecg3FY84WadgcuSVm6llrABOdQAEbNdnpi5X3UwWiFVhZIZvKgGrF7kmLguvxHNQy+UuRV66cLVl3S+Rkt+Q==} + cpu: [arm64] + os: [darwin] + requiresBuild: true + dev: true + optional: true + + /@rollup/rollup-darwin-x64@4.14.1: + resolution: {integrity: sha512-2pYRzEjVqq2TB/UNv47BV/8vQiXkFGVmPFwJb+1E0IFFZbIX8/jo1olxqqMbo6xCXf8kabANhp5bzCij2tFLUA==} + cpu: [x64] + os: [darwin] + requiresBuild: true + dev: true + optional: true + + /@rollup/rollup-linux-arm-gnueabihf@4.14.1: + resolution: {integrity: sha512-mS6wQ6Do6/wmrF9aTFVpIJ3/IDXhg1EZcQFYHZLHqw6AzMBjTHWnCG35HxSqUNphh0EHqSM6wRTT8HsL1C0x5g==} + cpu: [arm] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@rollup/rollup-linux-arm64-gnu@4.14.1: + resolution: {integrity: sha512-p9rGKYkHdFMzhckOTFubfxgyIO1vw//7IIjBBRVzyZebWlzRLeNhqxuSaZ7kCEKVkm/kuC9fVRW9HkC/zNRG2w==} + cpu: [arm64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@rollup/rollup-linux-arm64-musl@4.14.1: + resolution: {integrity: sha512-nDY6Yz5xS/Y4M2i9JLQd3Rofh5OR8Bn8qe3Mv/qCVpHFlwtZSBYSPaU4mrGazWkXrdQ98GB//H0BirGR/SKFSw==} + cpu: [arm64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@rollup/rollup-linux-powerpc64le-gnu@4.14.1: + resolution: {integrity: sha512-im7HE4VBL+aDswvcmfx88Mp1soqL9OBsdDBU8NqDEYtkri0qV0THhQsvZtZeNNlLeCUQ16PZyv7cqutjDF35qw==} + cpu: [ppc64le] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@rollup/rollup-linux-riscv64-gnu@4.14.1: + resolution: {integrity: sha512-RWdiHuAxWmzPJgaHJdpvUUlDz8sdQz4P2uv367T2JocdDa98iRw2UjIJ4QxSyt077mXZT2X6pKfT2iYtVEvOFw==} + cpu: [riscv64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@rollup/rollup-linux-s390x-gnu@4.14.1: + resolution: {integrity: sha512-VMgaGQ5zRX6ZqV/fas65/sUGc9cPmsntq2FiGmayW9KMNfWVG/j0BAqImvU4KTeOOgYSf1F+k6at1UfNONuNjA==} + cpu: [s390x] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@rollup/rollup-linux-x64-gnu@4.14.1: + resolution: {integrity: sha512-9Q7DGjZN+hTdJomaQ3Iub4m6VPu1r94bmK2z3UeWP3dGUecRC54tmVu9vKHTm1bOt3ASoYtEz6JSRLFzrysKlA==} + cpu: [x64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@rollup/rollup-linux-x64-musl@4.14.1: + resolution: {integrity: sha512-JNEG/Ti55413SsreTguSx0LOVKX902OfXIKVg+TCXO6Gjans/k9O6ww9q3oLGjNDaTLxM+IHFMeXy/0RXL5R/g==} + cpu: [x64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@rollup/rollup-win32-arm64-msvc@4.14.1: + resolution: {integrity: sha512-ryS22I9y0mumlLNwDFYZRDFLwWh3aKaC72CWjFcFvxK0U6v/mOkM5Up1bTbCRAhv3kEIwW2ajROegCIQViUCeA==} + cpu: [arm64] + os: [win32] + requiresBuild: true + dev: true + optional: true + + /@rollup/rollup-win32-ia32-msvc@4.14.1: + resolution: {integrity: sha512-TdloItiGk+T0mTxKx7Hp279xy30LspMso+GzQvV2maYePMAWdmrzqSNZhUpPj3CGw12aGj57I026PgLCTu8CGg==} + cpu: [ia32] + os: [win32] + requiresBuild: true + dev: true + optional: true + + /@rollup/rollup-win32-x64-msvc@4.14.1: + resolution: {integrity: sha512-wQGI+LY/Py20zdUPq+XCem7JcPOyzIJBm3dli+56DJsQOHbnXZFEwgmnC6el1TPAfC8lBT3m+z69RmLykNUbew==} + cpu: [x64] + os: [win32] + requiresBuild: true + dev: true + optional: true + + /@sinclair/typebox@0.27.8: + resolution: {integrity: sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==} + dev: true + + /@types/estree@1.0.5: + resolution: {integrity: sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==} + dev: true + + /@types/istanbul-lib-coverage@2.0.6: + resolution: {integrity: sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==} + dev: true + + /@types/json-schema@7.0.15: + resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} + dev: true + + /@types/node@20.12.4: + resolution: {integrity: sha512-E+Fa9z3wSQpzgYQdYmme5X3OTuejnnTx88A6p6vkkJosR3KBz+HpE3kqNm98VE6cfLFcISx7zW7MsJkH6KwbTw==} + dependencies: + undici-types: 5.26.5 + dev: true + + /@types/semver@7.5.8: + resolution: {integrity: sha512-I8EUhyrgfLrcTkzV3TSsGyl1tSuPrEDzr0yd5m90UgNxQkyDXULk3b6MlQqTCpZpNtWe1K0hzclnZkTcLBe2UQ==} + dev: true + + /@typescript-eslint/eslint-plugin@7.5.0(@typescript-eslint/parser@7.5.0)(eslint@8.57.0)(typescript@5.4.3): + resolution: {integrity: sha512-HpqNTH8Du34nLxbKgVMGljZMG0rJd2O9ecvr2QLYp+7512ty1j42KnsFwspPXg1Vh8an9YImf6CokUBltisZFQ==} + engines: {node: ^18.18.0 || >=20.0.0} + peerDependencies: + '@typescript-eslint/parser': ^7.0.0 + eslint: ^8.56.0 + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + dependencies: + '@eslint-community/regexpp': 4.10.0 + '@typescript-eslint/parser': 7.5.0(eslint@8.57.0)(typescript@5.4.3) + '@typescript-eslint/scope-manager': 7.5.0 + '@typescript-eslint/type-utils': 7.5.0(eslint@8.57.0)(typescript@5.4.3) + '@typescript-eslint/utils': 7.5.0(eslint@8.57.0)(typescript@5.4.3) + '@typescript-eslint/visitor-keys': 7.5.0 + debug: 4.3.4 + eslint: 8.57.0 + graphemer: 1.4.0 + ignore: 5.3.1 + natural-compare: 1.4.0 + semver: 7.6.0 + ts-api-utils: 1.3.0(typescript@5.4.3) + typescript: 5.4.3 + transitivePeerDependencies: + - supports-color + dev: true + + /@typescript-eslint/parser@7.5.0(eslint@8.57.0)(typescript@5.4.3): + resolution: {integrity: sha512-cj+XGhNujfD2/wzR1tabNsidnYRaFfEkcULdcIyVBYcXjBvBKOes+mpMBP7hMpOyk+gBcfXsrg4NBGAStQyxjQ==} + engines: {node: ^18.18.0 || >=20.0.0} + peerDependencies: + eslint: ^8.56.0 + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + dependencies: + '@typescript-eslint/scope-manager': 7.5.0 + '@typescript-eslint/types': 7.5.0 + '@typescript-eslint/typescript-estree': 7.5.0(typescript@5.4.3) + '@typescript-eslint/visitor-keys': 7.5.0 + debug: 4.3.4 + eslint: 8.57.0 + typescript: 5.4.3 + transitivePeerDependencies: + - supports-color + dev: true + + /@typescript-eslint/scope-manager@7.5.0: + resolution: {integrity: sha512-Z1r7uJY0MDeUlql9XJ6kRVgk/sP11sr3HKXn268HZyqL7i4cEfrdFuSSY/0tUqT37l5zT0tJOsuDP16kio85iA==} + engines: {node: ^18.18.0 || >=20.0.0} + dependencies: + '@typescript-eslint/types': 7.5.0 + '@typescript-eslint/visitor-keys': 7.5.0 + dev: true + + /@typescript-eslint/type-utils@7.5.0(eslint@8.57.0)(typescript@5.4.3): + resolution: {integrity: sha512-A021Rj33+G8mx2Dqh0nMO9GyjjIBK3MqgVgZ2qlKf6CJy51wY/lkkFqq3TqqnH34XyAHUkq27IjlUkWlQRpLHw==} + engines: {node: ^18.18.0 || >=20.0.0} + peerDependencies: + eslint: ^8.56.0 + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + dependencies: + '@typescript-eslint/typescript-estree': 7.5.0(typescript@5.4.3) + '@typescript-eslint/utils': 7.5.0(eslint@8.57.0)(typescript@5.4.3) + debug: 4.3.4 + eslint: 8.57.0 + ts-api-utils: 1.3.0(typescript@5.4.3) + typescript: 5.4.3 + transitivePeerDependencies: + - supports-color + dev: true + + /@typescript-eslint/types@7.5.0: + resolution: {integrity: sha512-tv5B4IHeAdhR7uS4+bf8Ov3k793VEVHd45viRRkehIUZxm0WF82VPiLgHzA/Xl4TGPg1ZD49vfxBKFPecD5/mg==} + engines: {node: ^18.18.0 || >=20.0.0} + dev: true + + /@typescript-eslint/typescript-estree@7.5.0(typescript@5.4.3): + resolution: {integrity: sha512-YklQQfe0Rv2PZEueLTUffiQGKQneiIEKKnfIqPIOxgM9lKSZFCjT5Ad4VqRKj/U4+kQE3fa8YQpskViL7WjdPQ==} + engines: {node: ^18.18.0 || >=20.0.0} + peerDependencies: + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + dependencies: + '@typescript-eslint/types': 7.5.0 + '@typescript-eslint/visitor-keys': 7.5.0 + debug: 4.3.4 + globby: 11.1.0 + is-glob: 4.0.3 + minimatch: 9.0.3 + semver: 7.6.0 + ts-api-utils: 1.3.0(typescript@5.4.3) + typescript: 5.4.3 + transitivePeerDependencies: + - supports-color + dev: true + + /@typescript-eslint/utils@7.5.0(eslint@8.57.0)(typescript@5.4.3): + resolution: {integrity: sha512-3vZl9u0R+/FLQcpy2EHyRGNqAS/ofJ3Ji8aebilfJe+fobK8+LbIFmrHciLVDxjDoONmufDcnVSF38KwMEOjzw==} + engines: {node: ^18.18.0 || >=20.0.0} + peerDependencies: + eslint: ^8.56.0 + dependencies: + '@eslint-community/eslint-utils': 4.4.0(eslint@8.57.0) + '@types/json-schema': 7.0.15 + '@types/semver': 7.5.8 + '@typescript-eslint/scope-manager': 7.5.0 + '@typescript-eslint/types': 7.5.0 + '@typescript-eslint/typescript-estree': 7.5.0(typescript@5.4.3) + eslint: 8.57.0 + semver: 7.6.0 + transitivePeerDependencies: + - supports-color + - typescript + dev: true + + /@typescript-eslint/visitor-keys@7.5.0: + resolution: {integrity: sha512-mcuHM/QircmA6O7fy6nn2w/3ditQkj+SgtOc8DW3uQ10Yfj42amm2i+6F2K4YAOPNNTmE6iM1ynM6lrSwdendA==} + engines: {node: ^18.18.0 || >=20.0.0} + dependencies: + '@typescript-eslint/types': 7.5.0 + eslint-visitor-keys: 3.4.3 + dev: true + + /@ungap/structured-clone@1.2.0: + resolution: {integrity: sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==} + dev: true + + /@vitest/coverage-v8@1.4.0(vitest@1.4.0): + resolution: {integrity: sha512-4hDGyH1SvKpgZnIByr9LhGgCEuF9DKM34IBLCC/fVfy24Z3+PZ+Ii9hsVBsHvY1umM1aGPEjceRkzxCfcQ10wg==} + peerDependencies: + vitest: 1.4.0 + dependencies: + '@ampproject/remapping': 2.3.0 + '@bcoe/v8-coverage': 0.2.3 + debug: 4.3.4 + istanbul-lib-coverage: 3.2.2 + istanbul-lib-report: 3.0.1 + istanbul-lib-source-maps: 5.0.4 + istanbul-reports: 3.1.7 + magic-string: 0.30.9 + magicast: 0.3.3 + picocolors: 1.0.0 + std-env: 3.7.0 + strip-literal: 2.1.0 + test-exclude: 6.0.0 + v8-to-istanbul: 9.2.0 + vitest: 1.4.0(@types/node@20.12.4) + transitivePeerDependencies: + - supports-color + dev: true + + /@vitest/expect@1.4.0: + resolution: {integrity: sha512-Jths0sWCJZ8BxjKe+p+eKsoqev1/T8lYcrjavEaz8auEJ4jAVY0GwW3JKmdVU4mmNPLPHixh4GNXP7GFtAiDHA==} + dependencies: + '@vitest/spy': 1.4.0 + '@vitest/utils': 1.4.0 + chai: 4.4.1 + dev: true + + /@vitest/runner@1.4.0: + resolution: {integrity: sha512-EDYVSmesqlQ4RD2VvWo3hQgTJ7ZrFQ2VSJdfiJiArkCerDAGeyF1i6dHkmySqk573jLp6d/cfqCN+7wUB5tLgg==} + dependencies: + '@vitest/utils': 1.4.0 + p-limit: 5.0.0 + pathe: 1.1.2 + dev: true + + /@vitest/snapshot@1.4.0: + resolution: {integrity: sha512-saAFnt5pPIA5qDGxOHxJ/XxhMFKkUSBJmVt5VgDsAqPTX6JP326r5C/c9UuCMPoXNzuudTPsYDZCoJ5ilpqG2A==} + dependencies: + magic-string: 0.30.9 + pathe: 1.1.2 + pretty-format: 29.7.0 + dev: true + + /@vitest/spy@1.4.0: + resolution: {integrity: sha512-Ywau/Qs1DzM/8Uc+yA77CwSegizMlcgTJuYGAi0jujOteJOUf1ujunHThYo243KG9nAyWT3L9ifPYZ5+As/+6Q==} + dependencies: + tinyspy: 2.2.1 + dev: true + + /@vitest/utils@1.4.0: + resolution: {integrity: sha512-mx3Yd1/6e2Vt/PUC98DcqTirtfxUyAZ32uK82r8rZzbtBeBo+nqgnjx/LvqQdWsrvNtm14VmurNgcf4nqY5gJg==} + dependencies: + diff-sequences: 29.6.3 + estree-walker: 3.0.3 + loupe: 2.3.7 + pretty-format: 29.7.0 + dev: true + + /acorn-jsx@5.3.2(acorn@8.11.3): + resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} + peerDependencies: + acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 + dependencies: + acorn: 8.11.3 + dev: true + + /acorn-walk@8.3.2: + resolution: {integrity: sha512-cjkyv4OtNCIeqhHrfS81QWXoCBPExR/J62oyEqepVw8WaQeSqpW2uhuLPh1m9eWhDuOo/jUXVTlifvesOWp/4A==} + engines: {node: '>=0.4.0'} + dev: true + + /acorn@8.11.3: + resolution: {integrity: sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==} + engines: {node: '>=0.4.0'} + hasBin: true + dev: true + + /ajv@6.12.6: + resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} + dependencies: + fast-deep-equal: 3.1.3 + fast-json-stable-stringify: 2.1.0 + json-schema-traverse: 0.4.1 + uri-js: 4.4.1 + dev: true + + /ansi-regex@5.0.1: + resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} + engines: {node: '>=8'} + dev: true + + /ansi-styles@4.3.0: + resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} + engines: {node: '>=8'} + dependencies: + color-convert: 2.0.1 + dev: true + + /ansi-styles@5.2.0: + resolution: {integrity: sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==} + engines: {node: '>=10'} + dev: true + + /argparse@2.0.1: + resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} + dev: true + + /array-union@2.1.0: + resolution: {integrity: sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==} + engines: {node: '>=8'} + dev: true + + /assertion-error@1.1.0: + resolution: {integrity: sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==} + dev: true + + /balanced-match@1.0.2: + resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + dev: true + + /brace-expansion@1.1.11: + resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==} + dependencies: + balanced-match: 1.0.2 + concat-map: 0.0.1 + dev: true + + /brace-expansion@2.0.1: + resolution: {integrity: sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==} + dependencies: + balanced-match: 1.0.2 + dev: true + + /braces@3.0.2: + resolution: {integrity: sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==} + engines: {node: '>=8'} + dependencies: + fill-range: 7.0.1 + dev: true + + /cac@6.7.14: + resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==} + engines: {node: '>=8'} + dev: true + + /callsites@3.1.0: + resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} + engines: {node: '>=6'} + dev: true + + /chai@4.4.1: + resolution: {integrity: sha512-13sOfMv2+DWduEU+/xbun3LScLoqN17nBeTLUsmDfKdoiC1fr0n9PU4guu4AhRcOVFk/sW8LyZWHuhWtQZiF+g==} + engines: {node: '>=4'} + dependencies: + assertion-error: 1.1.0 + check-error: 1.0.3 + deep-eql: 4.1.3 + get-func-name: 2.0.2 + loupe: 2.3.7 + pathval: 1.1.1 + type-detect: 4.0.8 + dev: true + + /chalk@4.1.2: + resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} + engines: {node: '>=10'} + dependencies: + ansi-styles: 4.3.0 + supports-color: 7.2.0 + dev: true + + /check-error@1.0.3: + resolution: {integrity: sha512-iKEoDYaRmd1mxM90a2OEfWhjsjPpYPuQ+lMYsoxB126+t8fw7ySEO48nmDg5COTjxDI65/Y2OWpeEHk3ZOe8zg==} + dependencies: + get-func-name: 2.0.2 + dev: true + + /color-convert@2.0.1: + resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} + engines: {node: '>=7.0.0'} + dependencies: + color-name: 1.1.4 + dev: true + + /color-name@1.1.4: + resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + dev: true + + /concat-map@0.0.1: + resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} + dev: true + + /convert-source-map@2.0.0: + resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} + dev: true + + /cross-spawn@7.0.3: + resolution: {integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==} + engines: {node: '>= 8'} + dependencies: + path-key: 3.1.1 + shebang-command: 2.0.0 + which: 2.0.2 + dev: true + + /debug@4.3.4: + resolution: {integrity: sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + dependencies: + ms: 2.1.2 + dev: true + + /deep-eql@4.1.3: + resolution: {integrity: sha512-WaEtAOpRA1MQ0eohqZjpGD8zdI0Ovsm8mmFhaDN8dvDZzyoUMcYDnf5Y6iu7HTXxf8JDS23qWa4a+hKCDyOPzw==} + engines: {node: '>=6'} + dependencies: + type-detect: 4.0.8 + dev: true + + /deep-is@0.1.4: + resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} + dev: true + + /diff-sequences@29.6.3: + resolution: {integrity: sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dev: true + + /dir-glob@3.0.1: + resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==} + engines: {node: '>=8'} + dependencies: + path-type: 4.0.0 + dev: true + + /doctrine@3.0.0: + resolution: {integrity: sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==} + engines: {node: '>=6.0.0'} + dependencies: + esutils: 2.0.3 + dev: true + + /esbuild@0.20.2: + resolution: {integrity: sha512-WdOOppmUNU+IbZ0PaDiTst80zjnrOkyJNHoKupIcVyU8Lvla3Ugx94VzkQ32Ijqd7UhHJy75gNWDMUekcrSJ6g==} + engines: {node: '>=12'} + hasBin: true + requiresBuild: true + optionalDependencies: + '@esbuild/aix-ppc64': 0.20.2 + '@esbuild/android-arm': 0.20.2 + '@esbuild/android-arm64': 0.20.2 + '@esbuild/android-x64': 0.20.2 + '@esbuild/darwin-arm64': 0.20.2 + '@esbuild/darwin-x64': 0.20.2 + '@esbuild/freebsd-arm64': 0.20.2 + '@esbuild/freebsd-x64': 0.20.2 + '@esbuild/linux-arm': 0.20.2 + '@esbuild/linux-arm64': 0.20.2 + '@esbuild/linux-ia32': 0.20.2 + '@esbuild/linux-loong64': 0.20.2 + '@esbuild/linux-mips64el': 0.20.2 + '@esbuild/linux-ppc64': 0.20.2 + '@esbuild/linux-riscv64': 0.20.2 + '@esbuild/linux-s390x': 0.20.2 + '@esbuild/linux-x64': 0.20.2 + '@esbuild/netbsd-x64': 0.20.2 + '@esbuild/openbsd-x64': 0.20.2 + '@esbuild/sunos-x64': 0.20.2 + '@esbuild/win32-arm64': 0.20.2 + '@esbuild/win32-ia32': 0.20.2 + '@esbuild/win32-x64': 0.20.2 + dev: true + + /escape-string-regexp@4.0.0: + resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} + engines: {node: '>=10'} + dev: true + + /eslint-plugin-tsdoc@0.2.17: + resolution: {integrity: sha512-xRmVi7Zx44lOBuYqG8vzTXuL6IdGOeF9nHX17bjJ8+VE6fsxpdGem0/SBTmAwgYMKYB1WBkqRJVQ+n8GK041pA==} + dependencies: + '@microsoft/tsdoc': 0.14.2 + '@microsoft/tsdoc-config': 0.16.2 + dev: true + + /eslint-scope@7.2.2: + resolution: {integrity: sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + dependencies: + esrecurse: 4.3.0 + estraverse: 5.3.0 + dev: true + + /eslint-visitor-keys@3.4.3: + resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + dev: true + + /eslint@8.57.0: + resolution: {integrity: sha512-dZ6+mexnaTIbSBZWgou51U6OmzIhYM2VcNdtiTtI7qPNZm35Akpr0f6vtw3w1Kmn5PYo+tZVfh13WrhpS6oLqQ==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + hasBin: true + dependencies: + '@eslint-community/eslint-utils': 4.4.0(eslint@8.57.0) + '@eslint-community/regexpp': 4.10.0 + '@eslint/eslintrc': 2.1.4 + '@eslint/js': 8.57.0 + '@humanwhocodes/config-array': 0.11.14 + '@humanwhocodes/module-importer': 1.0.1 + '@nodelib/fs.walk': 1.2.8 + '@ungap/structured-clone': 1.2.0 + ajv: 6.12.6 + chalk: 4.1.2 + cross-spawn: 7.0.3 + debug: 4.3.4 + doctrine: 3.0.0 + escape-string-regexp: 4.0.0 + eslint-scope: 7.2.2 + eslint-visitor-keys: 3.4.3 + espree: 9.6.1 + esquery: 1.5.0 + esutils: 2.0.3 + fast-deep-equal: 3.1.3 + file-entry-cache: 6.0.1 + find-up: 5.0.0 + glob-parent: 6.0.2 + globals: 13.24.0 + graphemer: 1.4.0 + ignore: 5.3.1 + imurmurhash: 0.1.4 + is-glob: 4.0.3 + is-path-inside: 3.0.3 + js-yaml: 4.1.0 + json-stable-stringify-without-jsonify: 1.0.1 + levn: 0.4.1 + lodash.merge: 4.6.2 + minimatch: 3.1.2 + natural-compare: 1.4.0 + optionator: 0.9.3 + strip-ansi: 6.0.1 + text-table: 0.2.0 + transitivePeerDependencies: + - supports-color + dev: true + + /espree@9.6.1: + resolution: {integrity: sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + dependencies: + acorn: 8.11.3 + acorn-jsx: 5.3.2(acorn@8.11.3) + eslint-visitor-keys: 3.4.3 + dev: true + + /esquery@1.5.0: + resolution: {integrity: sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==} + engines: {node: '>=0.10'} + dependencies: + estraverse: 5.3.0 + dev: true + + /esrecurse@4.3.0: + resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==} + engines: {node: '>=4.0'} + dependencies: + estraverse: 5.3.0 + dev: true + + /estraverse@5.3.0: + resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} + engines: {node: '>=4.0'} + dev: true + + /estree-walker@3.0.3: + resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} + dependencies: + '@types/estree': 1.0.5 + dev: true + + /esutils@2.0.3: + resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} + engines: {node: '>=0.10.0'} + dev: true + + /execa@8.0.1: + resolution: {integrity: sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==} + engines: {node: '>=16.17'} + dependencies: + cross-spawn: 7.0.3 + get-stream: 8.0.1 + human-signals: 5.0.0 + is-stream: 3.0.0 + merge-stream: 2.0.0 + npm-run-path: 5.3.0 + onetime: 6.0.0 + signal-exit: 4.1.0 + strip-final-newline: 3.0.0 + dev: true + + /fast-deep-equal@3.1.3: + resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} + dev: true + + /fast-glob@3.3.2: + resolution: {integrity: sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==} + engines: {node: '>=8.6.0'} + dependencies: + '@nodelib/fs.stat': 2.0.5 + '@nodelib/fs.walk': 1.2.8 + glob-parent: 5.1.2 + merge2: 1.4.1 + micromatch: 4.0.5 + dev: true + + /fast-json-stable-stringify@2.1.0: + resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} + dev: true + + /fast-levenshtein@2.0.6: + resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} + dev: true + + /fastq@1.17.1: + resolution: {integrity: sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==} + dependencies: + reusify: 1.0.4 + dev: true + + /file-entry-cache@6.0.1: + resolution: {integrity: sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==} + engines: {node: ^10.12.0 || >=12.0.0} + dependencies: + flat-cache: 3.2.0 + dev: true + + /fill-range@7.0.1: + resolution: {integrity: sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==} + engines: {node: '>=8'} + dependencies: + to-regex-range: 5.0.1 + dev: true + + /find-up@5.0.0: + resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} + engines: {node: '>=10'} + dependencies: + locate-path: 6.0.0 + path-exists: 4.0.0 + dev: true + + /flat-cache@3.2.0: + resolution: {integrity: sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==} + engines: {node: ^10.12.0 || >=12.0.0} + dependencies: + flatted: 3.3.1 + keyv: 4.5.4 + rimraf: 3.0.2 + dev: true + + /flatted@3.3.1: + resolution: {integrity: sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==} + dev: true + + /fs.realpath@1.0.0: + resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} + dev: true + + /fsevents@2.3.3: + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + requiresBuild: true + dev: true + optional: true + + /function-bind@1.1.2: + resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} + dev: true + + /get-func-name@2.0.2: + resolution: {integrity: sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==} + dev: true + + /get-stream@8.0.1: + resolution: {integrity: sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==} + engines: {node: '>=16'} + dev: true + + /glob-parent@5.1.2: + resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} + engines: {node: '>= 6'} + dependencies: + is-glob: 4.0.3 + dev: true + + /glob-parent@6.0.2: + resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} + engines: {node: '>=10.13.0'} + dependencies: + is-glob: 4.0.3 + dev: true + + /glob@7.2.3: + resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} + dependencies: + fs.realpath: 1.0.0 + inflight: 1.0.6 + inherits: 2.0.4 + minimatch: 3.1.2 + once: 1.4.0 + path-is-absolute: 1.0.1 + dev: true + + /globals@13.24.0: + resolution: {integrity: sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==} + engines: {node: '>=8'} + dependencies: + type-fest: 0.20.2 + dev: true + + /globby@11.1.0: + resolution: {integrity: sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==} + engines: {node: '>=10'} + dependencies: + array-union: 2.1.0 + dir-glob: 3.0.1 + fast-glob: 3.3.2 + ignore: 5.3.1 + merge2: 1.4.1 + slash: 3.0.0 + dev: true + + /graphemer@1.4.0: + resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==} + dev: true + + /has-flag@4.0.0: + resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} + engines: {node: '>=8'} + dev: true + + /hasown@2.0.2: + resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} + engines: {node: '>= 0.4'} + dependencies: + function-bind: 1.1.2 + dev: true + + /html-escaper@2.0.2: + resolution: {integrity: sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==} + dev: true + + /human-signals@5.0.0: + resolution: {integrity: sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==} + engines: {node: '>=16.17.0'} + dev: true + + /ignore@5.3.1: + resolution: {integrity: sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw==} + engines: {node: '>= 4'} + dev: true + + /import-fresh@3.3.0: + resolution: {integrity: sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==} + engines: {node: '>=6'} + dependencies: + parent-module: 1.0.1 + resolve-from: 4.0.0 + dev: true + + /imurmurhash@0.1.4: + resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} + engines: {node: '>=0.8.19'} + dev: true + + /inflight@1.0.6: + resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==} + dependencies: + once: 1.4.0 + wrappy: 1.0.2 + dev: true + + /inherits@2.0.4: + resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} + dev: true + + /is-core-module@2.13.1: + resolution: {integrity: sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==} + dependencies: + hasown: 2.0.2 + dev: true + + /is-extglob@2.1.1: + resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} + engines: {node: '>=0.10.0'} + dev: true + + /is-glob@4.0.3: + resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} + engines: {node: '>=0.10.0'} + dependencies: + is-extglob: 2.1.1 + dev: true + + /is-number@7.0.0: + resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} + engines: {node: '>=0.12.0'} + dev: true + + /is-path-inside@3.0.3: + resolution: {integrity: sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==} + engines: {node: '>=8'} + dev: true + + /is-stream@3.0.0: + resolution: {integrity: sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + dev: true + + /isexe@2.0.0: + resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + dev: true + + /istanbul-lib-coverage@3.2.2: + resolution: {integrity: sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==} + engines: {node: '>=8'} + dev: true + + /istanbul-lib-report@3.0.1: + resolution: {integrity: sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==} + engines: {node: '>=10'} + dependencies: + istanbul-lib-coverage: 3.2.2 + make-dir: 4.0.0 + supports-color: 7.2.0 + dev: true + + /istanbul-lib-source-maps@5.0.4: + resolution: {integrity: sha512-wHOoEsNJTVltaJp8eVkm8w+GVkVNHT2YDYo53YdzQEL2gWm1hBX5cGFR9hQJtuGLebidVX7et3+dmDZrmclduw==} + engines: {node: '>=10'} + dependencies: + '@jridgewell/trace-mapping': 0.3.25 + debug: 4.3.4 + istanbul-lib-coverage: 3.2.2 + transitivePeerDependencies: + - supports-color + dev: true + + /istanbul-reports@3.1.7: + resolution: {integrity: sha512-BewmUXImeuRk2YY0PVbxgKAysvhRPUQE0h5QRM++nVWyubKGV0l8qQ5op8+B2DOmwSe63Jivj0BjkPQVf8fP5g==} + engines: {node: '>=8'} + dependencies: + html-escaper: 2.0.2 + istanbul-lib-report: 3.0.1 + dev: true + + /jju@1.4.0: + resolution: {integrity: sha512-8wb9Yw966OSxApiCt0K3yNJL8pnNeIv+OEq2YMidz4FKP6nonSRoOXc80iXY4JaN2FC11B9qsNmDsm+ZOfMROA==} + dev: true + + /js-tokens@9.0.0: + resolution: {integrity: sha512-WriZw1luRMlmV3LGJaR6QOJjWwgLUTf89OwT2lUOyjX2dJGBwgmIkbcz+7WFZjrZM635JOIR517++e/67CP9dQ==} + dev: true + + /js-yaml@4.1.0: + resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==} + hasBin: true + dependencies: + argparse: 2.0.1 + dev: true + + /json-buffer@3.0.1: + resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} + dev: true + + /json-schema-traverse@0.4.1: + resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} + dev: true + + /json-stable-stringify-without-jsonify@1.0.1: + resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} + dev: true + + /jsonc-parser@3.2.1: + resolution: {integrity: sha512-AilxAyFOAcK5wA1+LeaySVBrHsGQvUFCDWXKpZjzaL0PqW+xfBOttn8GNtWKFWqneyMZj41MWF9Kl6iPWLwgOA==} + dev: true + + /keyv@4.5.4: + resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} + dependencies: + json-buffer: 3.0.1 + dev: true + + /levn@0.4.1: + resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} + engines: {node: '>= 0.8.0'} + dependencies: + prelude-ls: 1.2.1 + type-check: 0.4.0 + dev: true + + /local-pkg@0.5.0: + resolution: {integrity: sha512-ok6z3qlYyCDS4ZEU27HaU6x/xZa9Whf8jD4ptH5UZTQYZVYeb9bnZ3ojVhiJNLiXK1Hfc0GNbLXcmZ5plLDDBg==} + engines: {node: '>=14'} + dependencies: + mlly: 1.6.1 + pkg-types: 1.0.3 + dev: true + + /locate-path@6.0.0: + resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} + engines: {node: '>=10'} + dependencies: + p-locate: 5.0.0 + dev: true + + /lodash.merge@4.6.2: + resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} + dev: true + + /loupe@2.3.7: + resolution: {integrity: sha512-zSMINGVYkdpYSOBmLi0D1Uo7JU9nVdQKrHxC8eYlV+9YKK9WePqAlL7lSlorG/U2Fw1w0hTBmaa/jrQ3UbPHtA==} + dependencies: + get-func-name: 2.0.2 + dev: true + + /lru-cache@6.0.0: + resolution: {integrity: sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==} + engines: {node: '>=10'} + dependencies: + yallist: 4.0.0 + dev: true + + /magic-string@0.30.9: + resolution: {integrity: sha512-S1+hd+dIrC8EZqKyT9DstTH/0Z+f76kmmvZnkfQVmOpDEF9iVgdYif3Q/pIWHmCoo59bQVGW0kVL3e2nl+9+Sw==} + engines: {node: '>=12'} + dependencies: + '@jridgewell/sourcemap-codec': 1.4.15 + dev: true + + /magicast@0.3.3: + resolution: {integrity: sha512-ZbrP1Qxnpoes8sz47AM0z08U+jW6TyRgZzcWy3Ma3vDhJttwMwAFDMMQFobwdBxByBD46JYmxRzeF7w2+wJEuw==} + dependencies: + '@babel/parser': 7.24.4 + '@babel/types': 7.24.0 + source-map-js: 1.2.0 + dev: true + + /make-dir@4.0.0: + resolution: {integrity: sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==} + engines: {node: '>=10'} + dependencies: + semver: 7.6.0 + dev: true + + /merge-stream@2.0.0: + resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==} + dev: true + + /merge2@1.4.1: + resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} + engines: {node: '>= 8'} + dev: true + + /micromatch@4.0.5: + resolution: {integrity: sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==} + engines: {node: '>=8.6'} + dependencies: + braces: 3.0.2 + picomatch: 2.3.1 + dev: true + + /mimic-fn@4.0.0: + resolution: {integrity: sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==} + engines: {node: '>=12'} + dev: true + + /minimatch@3.1.2: + resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} + dependencies: + brace-expansion: 1.1.11 + dev: true + + /minimatch@9.0.3: + resolution: {integrity: sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==} + engines: {node: '>=16 || 14 >=14.17'} + dependencies: + brace-expansion: 2.0.1 + dev: true + + /mlly@1.6.1: + resolution: {integrity: sha512-vLgaHvaeunuOXHSmEbZ9izxPx3USsk8KCQ8iC+aTlp5sKRSoZvwhHh5L9VbKSaVC6sJDqbyohIS76E2VmHIPAA==} + dependencies: + acorn: 8.11.3 + pathe: 1.1.2 + pkg-types: 1.0.3 + ufo: 1.5.3 + dev: true + + /ms@2.1.2: + resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==} + dev: true + + /nanoid@3.3.7: + resolution: {integrity: sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true + dev: true + + /natural-compare@1.4.0: + resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} + dev: true + + /npm-run-path@5.3.0: + resolution: {integrity: sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + dependencies: + path-key: 4.0.0 + dev: true + + /once@1.4.0: + resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} + dependencies: + wrappy: 1.0.2 + dev: true + + /onetime@6.0.0: + resolution: {integrity: sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==} + engines: {node: '>=12'} + dependencies: + mimic-fn: 4.0.0 + dev: true + + /optionator@0.9.3: + resolution: {integrity: sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg==} + engines: {node: '>= 0.8.0'} + dependencies: + '@aashutoshrathi/word-wrap': 1.2.6 + deep-is: 0.1.4 + fast-levenshtein: 2.0.6 + levn: 0.4.1 + prelude-ls: 1.2.1 + type-check: 0.4.0 + dev: true + + /p-limit@3.1.0: + resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} + engines: {node: '>=10'} + dependencies: + yocto-queue: 0.1.0 + dev: true + + /p-limit@5.0.0: + resolution: {integrity: sha512-/Eaoq+QyLSiXQ4lyYV23f14mZRQcXnxfHrN0vCai+ak9G0pp9iEQukIIZq5NccEvwRB8PUnZT0KsOoDCINS1qQ==} + engines: {node: '>=18'} + dependencies: + yocto-queue: 1.0.0 + dev: true + + /p-locate@5.0.0: + resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} + engines: {node: '>=10'} + dependencies: + p-limit: 3.1.0 + dev: true + + /parent-module@1.0.1: + resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} + engines: {node: '>=6'} + dependencies: + callsites: 3.1.0 + dev: true + + /path-exists@4.0.0: + resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} + engines: {node: '>=8'} + dev: true + + /path-is-absolute@1.0.1: + resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==} + engines: {node: '>=0.10.0'} + dev: true + + /path-key@3.1.1: + resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} + engines: {node: '>=8'} + dev: true + + /path-key@4.0.0: + resolution: {integrity: sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==} + engines: {node: '>=12'} + dev: true + + /path-parse@1.0.7: + resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} + dev: true + + /path-type@4.0.0: + resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} + engines: {node: '>=8'} + dev: true + + /pathe@1.1.2: + resolution: {integrity: sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==} + dev: true + + /pathval@1.1.1: + resolution: {integrity: sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==} + dev: true + + /picocolors@1.0.0: + resolution: {integrity: sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==} + dev: true + + /picomatch@2.3.1: + resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} + engines: {node: '>=8.6'} + dev: true + + /pkg-types@1.0.3: + resolution: {integrity: sha512-nN7pYi0AQqJnoLPC9eHFQ8AcyaixBUOwvqc5TDnIKCMEE6I0y8P7OKA7fPexsXGCGxQDl/cmrLAp26LhcwxZ4A==} + dependencies: + jsonc-parser: 3.2.1 + mlly: 1.6.1 + pathe: 1.1.2 + dev: true + + /postcss@8.4.38: + resolution: {integrity: sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A==} + engines: {node: ^10 || ^12 || >=14} + dependencies: + nanoid: 3.3.7 + picocolors: 1.0.0 + source-map-js: 1.2.0 + dev: true + + /prelude-ls@1.2.1: + resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} + engines: {node: '>= 0.8.0'} + dev: true + + /prettier@3.2.5: + resolution: {integrity: sha512-3/GWa9aOC0YeD7LUfvOG2NiDyhOWRvt1k+rcKhOuYnMY24iiCphgneUfJDyFXd6rZCAnuLBv6UeAULtrhT/F4A==} + engines: {node: '>=14'} + hasBin: true + dev: true + + /pretty-format@29.7.0: + resolution: {integrity: sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dependencies: + '@jest/schemas': 29.6.3 + ansi-styles: 5.2.0 + react-is: 18.2.0 + dev: true + + /punycode@2.3.1: + resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} + engines: {node: '>=6'} + dev: true + + /queue-microtask@1.2.3: + resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} + dev: true + + /react-is@18.2.0: + resolution: {integrity: sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==} + dev: true + + /resolve-from@4.0.0: + resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} + engines: {node: '>=4'} + dev: true + + /resolve@1.19.0: + resolution: {integrity: sha512-rArEXAgsBG4UgRGcynxWIWKFvh/XZCcS8UJdHhwy91zwAvCZIbcs+vAbflgBnNjYMs/i/i+/Ux6IZhML1yPvxg==} + dependencies: + is-core-module: 2.13.1 + path-parse: 1.0.7 + dev: true + + /reusify@1.0.4: + resolution: {integrity: sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==} + engines: {iojs: '>=1.0.0', node: '>=0.10.0'} + dev: true + + /rimraf@3.0.2: + resolution: {integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==} + hasBin: true + dependencies: + glob: 7.2.3 + dev: true + + /rollup@4.14.1: + resolution: {integrity: sha512-4LnHSdd3QK2pa1J6dFbfm1HN0D7vSK/ZuZTsdyUAlA6Rr1yTouUTL13HaDOGJVgby461AhrNGBS7sCGXXtT+SA==} + engines: {node: '>=18.0.0', npm: '>=8.0.0'} + hasBin: true + dependencies: + '@types/estree': 1.0.5 + optionalDependencies: + '@rollup/rollup-android-arm-eabi': 4.14.1 + '@rollup/rollup-android-arm64': 4.14.1 + '@rollup/rollup-darwin-arm64': 4.14.1 + '@rollup/rollup-darwin-x64': 4.14.1 + '@rollup/rollup-linux-arm-gnueabihf': 4.14.1 + '@rollup/rollup-linux-arm64-gnu': 4.14.1 + '@rollup/rollup-linux-arm64-musl': 4.14.1 + '@rollup/rollup-linux-powerpc64le-gnu': 4.14.1 + '@rollup/rollup-linux-riscv64-gnu': 4.14.1 + '@rollup/rollup-linux-s390x-gnu': 4.14.1 + '@rollup/rollup-linux-x64-gnu': 4.14.1 + '@rollup/rollup-linux-x64-musl': 4.14.1 + '@rollup/rollup-win32-arm64-msvc': 4.14.1 + '@rollup/rollup-win32-ia32-msvc': 4.14.1 + '@rollup/rollup-win32-x64-msvc': 4.14.1 + fsevents: 2.3.3 + dev: true + + /run-parallel@1.2.0: + resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} + dependencies: + queue-microtask: 1.2.3 + dev: true + + /semver@7.6.0: + resolution: {integrity: sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==} + engines: {node: '>=10'} + hasBin: true + dependencies: + lru-cache: 6.0.0 + dev: true + + /shebang-command@2.0.0: + resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} + engines: {node: '>=8'} + dependencies: + shebang-regex: 3.0.0 + dev: true + + /shebang-regex@3.0.0: + resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} + engines: {node: '>=8'} + dev: true + + /siginfo@2.0.0: + resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} + dev: true + + /signal-exit@4.1.0: + resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} + engines: {node: '>=14'} + dev: true + + /slash@3.0.0: + resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==} + engines: {node: '>=8'} + dev: true + + /source-map-js@1.2.0: + resolution: {integrity: sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==} + engines: {node: '>=0.10.0'} + dev: true + + /stackback@0.0.2: + resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} + dev: true + + /std-env@3.7.0: + resolution: {integrity: sha512-JPbdCEQLj1w5GilpiHAx3qJvFndqybBysA3qUOnznweH4QbNYUsW/ea8QzSrnh0vNsezMMw5bcVool8lM0gwzg==} + dev: true + + /strip-ansi@6.0.1: + resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} + engines: {node: '>=8'} + dependencies: + ansi-regex: 5.0.1 + dev: true + + /strip-final-newline@3.0.0: + resolution: {integrity: sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==} + engines: {node: '>=12'} + dev: true + + /strip-json-comments@3.1.1: + resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} + engines: {node: '>=8'} + dev: true + + /strip-literal@2.1.0: + resolution: {integrity: sha512-Op+UycaUt/8FbN/Z2TWPBLge3jWrP3xj10f3fnYxf052bKuS3EKs1ZQcVGjnEMdsNVAM+plXRdmjrZ/KgG3Skw==} + dependencies: + js-tokens: 9.0.0 + dev: true + + /supports-color@7.2.0: + resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} + engines: {node: '>=8'} + dependencies: + has-flag: 4.0.0 + dev: true + + /test-exclude@6.0.0: + resolution: {integrity: sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==} + engines: {node: '>=8'} + dependencies: + '@istanbuljs/schema': 0.1.3 + glob: 7.2.3 + minimatch: 3.1.2 + dev: true + + /text-table@0.2.0: + resolution: {integrity: sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==} + dev: true + + /tinybench@2.6.0: + resolution: {integrity: sha512-N8hW3PG/3aOoZAN5V/NSAEDz0ZixDSSt5b/a05iqtpgfLWMSVuCo7w0k2vVvEjdrIoeGqZzweX2WlyioNIHchA==} + dev: true + + /tinypool@0.8.3: + resolution: {integrity: sha512-Ud7uepAklqRH1bvwy22ynrliC7Dljz7Tm8M/0RBUW+YRa4YHhZ6e4PpgE+fu1zr/WqB1kbeuVrdfeuyIBpy4tw==} + engines: {node: '>=14.0.0'} + dev: true + + /tinyspy@2.2.1: + resolution: {integrity: sha512-KYad6Vy5VDWV4GH3fjpseMQ/XU2BhIYP7Vzd0LG44qRWm/Yt2WCOTicFdvmgo6gWaqooMQCawTtILVQJupKu7A==} + engines: {node: '>=14.0.0'} + dev: true + + /to-fast-properties@2.0.0: + resolution: {integrity: sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==} + engines: {node: '>=4'} + dev: true + + /to-regex-range@5.0.1: + resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} + engines: {node: '>=8.0'} + dependencies: + is-number: 7.0.0 + dev: true + + /ts-api-utils@1.3.0(typescript@5.4.3): + resolution: {integrity: sha512-UQMIo7pb8WRomKR1/+MFVLTroIvDVtMX3K6OUir8ynLyzB8Jeriont2bTAtmNPa1ekAgN7YPDyf6V+ygrdU+eQ==} + engines: {node: '>=16'} + peerDependencies: + typescript: '>=4.2.0' + dependencies: + typescript: 5.4.3 + dev: true + + /type-check@0.4.0: + resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} + engines: {node: '>= 0.8.0'} + dependencies: + prelude-ls: 1.2.1 + dev: true + + /type-detect@4.0.8: + resolution: {integrity: sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==} + engines: {node: '>=4'} + dev: true + + /type-fest@0.20.2: + resolution: {integrity: sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==} + engines: {node: '>=10'} + dev: true + + /typescript@5.4.3: + resolution: {integrity: sha512-KrPd3PKaCLr78MalgiwJnA25Nm8HAmdwN3mYUYZgG/wizIo9EainNVQI9/yDavtVFRN2h3k8uf3GLHuhDMgEHg==} + engines: {node: '>=14.17'} + hasBin: true + dev: true + + /ufo@1.5.3: + resolution: {integrity: sha512-Y7HYmWaFwPUmkoQCUIAYpKqkOf+SbVj/2fJJZ4RJMCfZp0rTGwRbzQD+HghfnhKOjL9E01okqz+ncJskGYfBNw==} + dev: true + + /undici-types@5.26.5: + resolution: {integrity: sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==} + dev: true + + /uri-js@4.4.1: + resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} + dependencies: + punycode: 2.3.1 + dev: true + + /v8-to-istanbul@9.2.0: + resolution: {integrity: sha512-/EH/sDgxU2eGxajKdwLCDmQ4FWq+kpi3uCmBGpw1xJtnAxEjlD8j8PEiGWpCIMIs3ciNAgH0d3TTJiUkYzyZjA==} + engines: {node: '>=10.12.0'} + dependencies: + '@jridgewell/trace-mapping': 0.3.25 + '@types/istanbul-lib-coverage': 2.0.6 + convert-source-map: 2.0.0 + dev: true + + /vite-node@1.4.0(@types/node@20.12.4): + resolution: {integrity: sha512-VZDAseqjrHgNd4Kh8icYHWzTKSCZMhia7GyHfhtzLW33fZlG9SwsB6CEhgyVOWkJfJ2pFLrp/Gj1FSfAiqH9Lw==} + engines: {node: ^18.0.0 || >=20.0.0} + hasBin: true + dependencies: + cac: 6.7.14 + debug: 4.3.4 + pathe: 1.1.2 + picocolors: 1.0.0 + vite: 5.2.8(@types/node@20.12.4) + transitivePeerDependencies: + - '@types/node' + - less + - lightningcss + - sass + - stylus + - sugarss + - supports-color + - terser + dev: true + + /vite@5.2.8(@types/node@20.12.4): + resolution: {integrity: sha512-OyZR+c1CE8yeHw5V5t59aXsUPPVTHMDjEZz8MgguLL/Q7NblxhZUlTu9xSPqlsUO/y+X7dlU05jdhvyycD55DA==} + engines: {node: ^18.0.0 || >=20.0.0} + hasBin: true + peerDependencies: + '@types/node': ^18.0.0 || >=20.0.0 + less: '*' + lightningcss: ^1.21.0 + sass: '*' + stylus: '*' + sugarss: '*' + terser: ^5.4.0 + peerDependenciesMeta: + '@types/node': + optional: true + less: + optional: true + lightningcss: + optional: true + sass: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + dependencies: + '@types/node': 20.12.4 + esbuild: 0.20.2 + postcss: 8.4.38 + rollup: 4.14.1 + optionalDependencies: + fsevents: 2.3.3 + dev: true + + /vitest@1.4.0(@types/node@20.12.4): + resolution: {integrity: sha512-gujzn0g7fmwf83/WzrDTnncZt2UiXP41mHuFYFrdwaLRVQ6JYQEiME2IfEjU3vcFL3VKa75XhI3lFgn+hfVsQw==} + engines: {node: ^18.0.0 || >=20.0.0} + hasBin: true + peerDependencies: + '@edge-runtime/vm': '*' + '@types/node': ^18.0.0 || >=20.0.0 + '@vitest/browser': 1.4.0 + '@vitest/ui': 1.4.0 + happy-dom: '*' + jsdom: '*' + peerDependenciesMeta: + '@edge-runtime/vm': + optional: true + '@types/node': + optional: true + '@vitest/browser': + optional: true + '@vitest/ui': + optional: true + happy-dom: + optional: true + jsdom: + optional: true + dependencies: + '@types/node': 20.12.4 + '@vitest/expect': 1.4.0 + '@vitest/runner': 1.4.0 + '@vitest/snapshot': 1.4.0 + '@vitest/spy': 1.4.0 + '@vitest/utils': 1.4.0 + acorn-walk: 8.3.2 + chai: 4.4.1 + debug: 4.3.4 + execa: 8.0.1 + local-pkg: 0.5.0 + magic-string: 0.30.9 + pathe: 1.1.2 + picocolors: 1.0.0 + std-env: 3.7.0 + strip-literal: 2.1.0 + tinybench: 2.6.0 + tinypool: 0.8.3 + vite: 5.2.8(@types/node@20.12.4) + vite-node: 1.4.0(@types/node@20.12.4) + why-is-node-running: 2.2.2 + transitivePeerDependencies: + - less + - lightningcss + - sass + - stylus + - sugarss + - supports-color + - terser + dev: true + + /which@2.0.2: + resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} + engines: {node: '>= 8'} + hasBin: true + dependencies: + isexe: 2.0.0 + dev: true + + /why-is-node-running@2.2.2: + resolution: {integrity: sha512-6tSwToZxTOcotxHeA+qGCq1mVzKR3CwcJGmVcY+QE8SHy6TnpFnh8PAvPNHYr7EcuVeG0QSMxtYCuO1ta/G/oA==} + engines: {node: '>=8'} + hasBin: true + dependencies: + siginfo: 2.0.0 + stackback: 0.0.2 + dev: true + + /wrappy@1.0.2: + resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} + dev: true + + /yallist@4.0.0: + resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==} + dev: true + + /yocto-queue@0.1.0: + resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} + engines: {node: '>=10'} + dev: true + + /yocto-queue@1.0.0: + resolution: {integrity: sha512-9bnSc/HEW2uRy67wc+T8UwauLuPJVn28jb+GtJY16iiKWyvmYJRXVT4UamsAEGQfPohgr2q4Tq0sQbQlxTfi1g==} + engines: {node: '>=12.20'} + dev: true diff --git a/src/index.test.ts b/src/index.test.ts new file mode 100644 index 0000000..8536c97 --- /dev/null +++ b/src/index.test.ts @@ -0,0 +1,409 @@ +import recaller, { + constantBackoff, + exponentialBackoff, + fullJitterBackoff, + equalJitterBackoff, + decorrelatedJitterBackoff, +} from "./"; +import { expect, test, assert } from "vitest"; + +test("function should not be retried if async function is ok", async () => { + expect.assertions(1); + + let called = false; + + const retval = await recaller(async () => { + if (called) return assert.fail("Called multiple times"); + called = true; + return 50; + }); + + expect(retval).toBe(50); +}); + +test("function should be retried if async function rejects (next ok)", async () => { + expect.assertions(1); + + let called = false; + + const retval = await recaller(async () => { + if (!called) { + called = true; + throw new Error("uh oh"); + } + return 50; + }); + + expect(retval).toBe(50); +}); + +test("function should be retried if async function rejects (all fail)", async () => { + expect.assertions(1); + + const result = recaller(async () => { + throw new Error("uh oh"); + }); + + await expect(result).rejects.toThrow("uh oh"); +}); + +test("bailing should work (error provided)", async () => { + expect.assertions(2); + + let called = false; + let bailed = false; + + const result = recaller(async (bail) => { + if (bailed) assert.fail("bailed, yet still caught"); + if (called) { + bailed = true; + bail(new Error("bailing!")); + expect(true).toBe(true); + return; + } + called = true; + throw new Error("uh oh"); + }); + + await expect(result).rejects.toThrow("bailing!"); +}); + +test("bailing should work (nothing provided)", async () => { + expect.assertions(1); + + const result = recaller(async (bail) => { + bail(); + throw new Error("well"); + }); + + await expect(result).rejects.toThrow("Bailed without giving a reason."); +}); + +test("recaller must require a function", async () => { + expect.assertions(1); + + // @ts-expect-error invalid args + const result = recaller(); + await expect(result).rejects.toThrow("fn is not a function"); +}); + +test("retry amounts (default, 2)", async () => { + expect.assertions(4); + + const result = recaller(async () => { + expect(true).toBe(true); + throw new Error("fail"); + }); + + await expect(result).rejects.toThrow("fail"); +}); + +test("retry amounts (set, 4)", async () => { + expect.assertions(6); + + const result = recaller( + async () => { + // for assertion counting + expect(true).toBe(true); + throw new Error("fail"); + }, + { retries: 4 }, + ); + + await expect(result).rejects.toThrow("fail"); +}); + +test("attempt should be a correct number", async () => { + expect.assertions(5); + + let currentAttempt = 1; + + const result = recaller( + async (bail, attempt) => { + expect(attempt).toBe(currentAttempt); + currentAttempt++; + throw new Error("fail"); + }, + { retries: 3 }, + ); + + await expect(result).rejects.toThrow("fail"); +}); + +test("onretry, invalid value", async () => { + expect.assertions(1); + + const result = recaller( + async () => { + throw new Error("fail"); + }, + // @ts-expect-error invalid value + { onretry: 123 }, + ); + + await expect(result).rejects.toThrow("onretry handler is not a function"); +}); + +test("onretry, called (no backoff)", async () => { + expect.assertions(3 * 2 + 1); + + let currentAttempt = 1; + + const result = recaller( + async (bail, attempt) => { + throw new Error(`fail${attempt}`); + }, + { + onretry: function (err, attempt, delayTime) { + expect(String(err)).toContain(`fail${currentAttempt}`); + expect(attempt).toBe(currentAttempt); + expect(delayTime).toBe(0); + currentAttempt++; + }, + }, + ); + + await expect(result).rejects.toThrow("fail3"); +}); + +test("onretry, called (backoff)", async () => { + expect.assertions(2); + + const result = recaller( + async () => { + throw new Error("fail"); + }, + { + onretry: function (err, attempt, delayTime) { + expect(delayTime).toBe(200); + }, + retries: 1, + backoff: constantBackoff(200), + }, + ); + + await expect(result).rejects.toThrow("fail"); +}); + +test("onretry, error throwing", async () => { + expect.assertions(2); + + let thrown = false; + + const result = recaller( + async (bail, attempt) => { + if (thrown) return bail(new Error("was thrown")); + if (attempt === 2) { + throw new TypeError("type error"); + } + throw new Error("normal error"); + }, + { + onretry: function (err) { + if (err instanceof TypeError) { + expect(thrown).toBe(false); + thrown = true; + throw err; + } + }, + }, + ); + + await expect(result).rejects.toThrow("type error"); +}); + +test("backoff (constant)", async () => { + expect.assertions(2); + + let lastDelay: number; + + const result = recaller( + async () => { + if (lastDelay) { + const duration = Date.now() - lastDelay; + expect(duration).toBeGreaterThanOrEqual(200); + } + lastDelay = Date.now(); + throw new Error("fail"); + }, + { + retries: 1, + backoff: constantBackoff(200), + }, + ); + + await expect(result).rejects.toThrow("fail"); +}); + +test("constantBackoff", () => { + expect.assertions(4); + + // default values + const cb1 = constantBackoff(); + expect(cb1(1)).toBe(5000); + expect(cb1(2)).toBe(5000); + + // custom ms + const cb2 = constantBackoff(300); + expect(cb2(1)).toBe(300); + expect(cb2(2)).toBe(300); +}); + +test("exponentialBackoff", () => { + expect.assertions(12); + + // default values + const eb1 = exponentialBackoff(); + expect(eb1(1)).toBe(1000); + expect(eb1(2)).toBe(2000); + expect(eb1(3)).toBe(4000); + + // custom base + const eb2 = exponentialBackoff({ base: 500 }); + expect(eb2(1)).toBe(500); + expect(eb2(2)).toBe(1000); + expect(eb2(3)).toBe(2000); + + // custom cap + const eb3 = exponentialBackoff({ cap: 3000 }); + expect(eb3(1)).toBe(1000); + expect(eb3(2)).toBe(2000); + expect(eb3(3)).toBe(3000); + + // custom factor + const eb4 = exponentialBackoff({ factor: 1 }); + expect(eb4(1)).toBe(1000); + expect(eb4(2)).toBe(1000); + expect(eb4(3)).toBe(1000); +}); + +test("fullJitterBackoff", () => { + expect.assertions(6 * 10000 * 2); + + // default values + const fjb1 = fullJitterBackoff(); + + for (let i = 0; i < 10000; i++) { + const val = fjb1(1); + expect(val).toBeGreaterThanOrEqual(0); + expect(val).toBeLessThanOrEqual(1000); + } + + for (let i = 0; i < 10000; i++) { + const val = fjb1(2); + expect(val).toBeGreaterThanOrEqual(0); + expect(val).toBeLessThanOrEqual(2000); + } + + for (let i = 0; i < 10000; i++) { + const val = fjb1(3); + expect(val).toBeGreaterThanOrEqual(0); + expect(val).toBeLessThanOrEqual(4000); + } + + // custom values + const fjb2 = fullJitterBackoff({ cap: 2000, base: 500 }); + + for (let i = 0; i < 10000; i++) { + const val = fjb2(1); + expect(val).toBeGreaterThanOrEqual(0); + expect(val).toBeLessThanOrEqual(500); + } + + for (let i = 0; i < 10000; i++) { + const val = fjb2(2); + expect(val).toBeGreaterThanOrEqual(0); + expect(val).toBeLessThanOrEqual(1000); + } + + for (let i = 0; i < 10000; i++) { + const val = fjb2(1); + expect(val).toBeGreaterThanOrEqual(0); + expect(val).toBeLessThanOrEqual(2000); + } +}); + +test("equalJitterBackoff", () => { + expect.assertions(6 * 10000 * 2); + + // default values + const ejb1 = equalJitterBackoff(); + + for (let i = 0; i < 10000; i++) { + const val = ejb1(1); + expect(val).toBeGreaterThanOrEqual(500); + expect(val).toBeLessThanOrEqual(1000); + } + + for (let i = 0; i < 10000; i++) { + const val = ejb1(2); + expect(val).toBeGreaterThanOrEqual(1000); + expect(val).toBeLessThanOrEqual(2000); + } + + for (let i = 0; i < 10000; i++) { + const val = ejb1(3); + expect(val).toBeGreaterThanOrEqual(2000); + expect(val).toBeLessThanOrEqual(4000); + } + + // custom values + const ejb2 = equalJitterBackoff({ cap: 2000, base: 500 }); + + for (let i = 0; i < 10000; i++) { + const val = ejb2(1); + expect(val).toBeGreaterThanOrEqual(250); + expect(val).toBeLessThanOrEqual(500); + } + + for (let i = 0; i < 10000; i++) { + const val = ejb2(2); + expect(val).toBeGreaterThanOrEqual(500); + expect(val).toBeLessThanOrEqual(1000); + } + + for (let i = 0; i < 10000; i++) { + const val = ejb2(3); + expect(val).toBeGreaterThanOrEqual(1000); + expect(val).toBeLessThanOrEqual(2000); + } +}); + +test("decorrelatedJitterBackoff", () => { + expect.assertions(4 * 10000 * 2); + + // Default options, first value + for (let i = 0; i < 10000; i++) { + const gen = decorrelatedJitterBackoff(); + const val = gen(1); + expect(val).toBeGreaterThanOrEqual(1000); + expect(val).toBeLessThanOrEqual(3000); + } + + // Default options, second value + for (let i = 0; i < 10000; i++) { + const gen = decorrelatedJitterBackoff(); + gen(1); // generate first + const val = gen(1); + expect(val).toBeGreaterThanOrEqual(1000); + expect(val).toBeLessThanOrEqual(9000); + } + + // Capped (otherwise default), third value + for (let i = 0; i < 10000; i++) { + const gen = decorrelatedJitterBackoff({ cap: 24000 }); + gen(1); // generate first + gen(1); // generate second + const val = gen(1); + expect(val).toBeGreaterThanOrEqual(1000); + expect(val).toBeLessThanOrEqual(24000); + } + + // times 4, first value + for (let i = 0; i < 10000; i++) { + const gen = decorrelatedJitterBackoff({ times: 4 }); + const val = gen(1); + expect(val).toBeGreaterThanOrEqual(1000); + expect(val).toBeLessThanOrEqual(4000); + } +}); diff --git a/src/index.ts b/src/index.ts new file mode 100644 index 0000000..0fbe84d --- /dev/null +++ b/src/index.ts @@ -0,0 +1,371 @@ +"use strict"; + +function delay(ms: number) { + return new Promise((resolve) => setTimeout(resolve, ms)); +} + +function randomBetween(minInclusive: number, maxInclusive: number) { + return Math.floor( + Math.random() * (maxInclusive - minInclusive + 1) + minInclusive, + ); +} + +/** + * Function that returns the next backoff delay. + * + * @example Basic backoff function + * ```ts + * const backoff = (attempt: number) => { + * return 1000 * attempt; + * } + * ``` + * + * @param attempt - Current attempt number. Starts at 1. + */ +export type BackoffFn = (attempt: number) => number; + +/** + * When called in the function, it will stop retrying, and abort recaller the error given. + * Note that unless you return after bailing, it will continue to run. + * + * @example Bailing from error + * ```ts + * try { + * await recaller((bail) => { + * try { + * return await trySomethingThatCouldFail(); + * } catch (err) { + * if (err instanceof HttpNotFoundError) { + * return bail(new Error('Not found')); + * } + * } + * }); + * } catch (err) { + * err; // Error: Not found + * } + * ``` + * + * @param err - Error to abort with. Defaults to a generic bail error. + */ +export type BailFn = (err?: unknown) => void; + +/** + * Function that gets called with each try. + * + * @see {@link BailFn} + * + * @param bail - @see {@link BailFn} + * @param attempt - Current attempt number. Starts at 1. + */ +export type RecallerFn = (bail: BailFn, attempt: number) => unknown; + +/** + * Function that gets called after attempt fails, before we wait for backoff for next attempt. + * + * You can use this to inspect the error and decide if you want to bail before next attempt. + * + * @param err - Error thrown by the last attempt + * @param attempt - Which attempt that was (starts at 1) + * @param delayTime - How long we will wait in ms before attempting again. If no delay, this is 0. + */ +export type OnRetryFn = ( + err: unknown, + attempt: number, + delayTime: number, +) => void; + +export type RecallerOptions = { + /** + * How many times to retry before giving up. + * With `retries: 1`, the function will be called twice total before giving up. + * + * @defaultValue `2` + */ + retries?: number; + /** + * Function that returns the next backoff delay. + * If `undefined`, retries are immediate. + * + * @see {@link BackoffFn} + * + * Recaller comes with a few standard backoff functions: + * - constantBackoff + * - exponentialBackoff + * - fullJitterBackoff (recommended) + * - equalJitterBackoff + * - decorrelatedJitterBackoff + * + * @defaultValue `undefined` + */ + backoff?: BackoffFn; + /** + * Function that gets called after attempt fails, before we wait for backoff for next attempt. + * + * @see {@link OnRetryFn} + * + * @defaultValue `undefined` + */ + onretry?: OnRetryFn; +}; +/** + * Given a function to retry, retry a function, optionally with backoff. + * + * @example + * ```ts + * import recaller, {constantBackoff} from 'recaller'; + * + * const result = await recaller(async () => { + * await trySomethingThatCouldFail(); + * return 'cool'; + * }, { + * // try 3 times total + * retries: 2, + * // wait 1 second before attempts + * backoff: constantBackoff(1000), + * }) + * + * result; // 'cool' + * ``` + * + * @param fn - {@link RecallerFn} + * @param opts - {@link RecallerOptions} + * @returns Result of the function. + */ +export default async function recaller(fn: RecallerFn, opts?: RecallerOptions) { + return new Promise((resolve, reject) => { + if (typeof fn !== "function") throw new Error("fn is not a function"); + const _opts = opts ?? {}; + if (_opts.onretry && typeof _opts.onretry !== "function") + throw new Error("onretry handler is not a function"); + + const retries = _opts.retries ?? 2; + + let bailed = false; + let attempt = 0; + + function bail(err: unknown) { + bailed = true; + return reject(err ?? new Error("Bailed without giving a reason.")); + } + + const runner = async () => { + const backoff = _opts.backoff; + while (true) { + try { + attempt++; + return await fn(bail, attempt); + } catch (err) { + if (bailed) return; + let delayTime = 0; + if (backoff) { + delayTime = backoff(attempt); + } + if (attempt > retries) { + throw err; + } else { + if (_opts.onretry) _opts.onretry(err, attempt, delayTime); + if (delayTime) await delay(delayTime); + continue; + } + } + } + }; + + runner().then(resolve, reject); + }); +} + +// Backoff generator functions +// These are functions that you can conveniently use when setting the backoff +// options for the retrier. + +/** + * Constant backoff delay. + * + * @example + * ```ts + * const backoff = constantBackoff(5000); + * backoff(1); // 5000 + * backoff(2); // 5000 + * ``` + * + * @see {@link BackoffFn} + * + * @param ms - Delay in ms. Defaults to 5000 + * @returns BackoffFn + */ +export function constantBackoff(ms?: number): BackoffFn { + const _ms = ms ?? 5000; + return (_attempt: number) => _ms; +} + +// Below functions use algorithms and formulas taken from: +// https://www.awsarchitectureblog.com/2015/03/backoff.html +// https://github.com/awslabs/aws-arch-backoff-simulator +// +// If you can't decide which to use, I recommend `fullJitterBackoff`. + +export type ExponentialBackoffOpts = { + /** + * Base delay in ms. Used as initial delay (before applying jitter), and to calculate future values. + * + * @defaultValue `1000` + */ + base?: number; + /** + * Maximum delay in ms. + * + * @defaultValue `60000` + */ + cap?: number; + /** + * `uncappedDelay = base * (factor ^ (attempt - 1))` + * + * @defaultValue `2` + */ + factor?: number; +}; + +/** + * Exponential backoff delay. + * + * @deprecated You should probably use {@link fullJitterBackoff} instead. + * + * @example + * ```ts + * const backoff = exponentialBackoff({ base: 1000, cap: 5000, factor: 2 }); + * backoff(1); // 1000 + * backoff(2); // 2000 + * backoff(3); // 4000 + * backoff(4); // 8000 + * ``` + * + * @see {@link ExponentialBackoffOpts} + * @see {@link BackoffFn} + * + * @param opts - Backoff options. See ExponentialBackoffOpts + * @returns BackoffFn + */ +export function exponentialBackoff(opts?: ExponentialBackoffOpts): BackoffFn { + opts = opts ?? {}; + const base = opts.base ?? 1000; + const cap = opts.cap ?? 60000; + const factor = opts.factor ?? 2; + + return (attempt: number) => + Math.min(cap, base * Math.pow(factor, attempt - 1)); +} + +/** + * Exponential backoff delay, with full jitter. + * More-or-less the same as exponentialBackoff, except the resulting delay + * is between 0 and the generated delay. + * + * @example + * ```ts + * const backoff = fullJitterBackoff({ base: 1000, cap: 5000, factor: 2 }); + * backoff(1); // between 0 and 1000 + * backoff(2); // between 0 and 2000 + * backoff(3); // between 0 and 4000 + * ``` + * + * @see {@link ExponentialBackoffOpts} + * @see {@link BackoffFn} + * + * @param opts - Backoff options. See ExponentialBackoffOpts + * @returns BackoffFn + */ +export function fullJitterBackoff(opts?: ExponentialBackoffOpts): BackoffFn { + const gen = exponentialBackoff(opts); + return (attempt: number) => randomBetween(0, gen(attempt)); +} + +/** + * Exponential backoff generator, with equal jitter. + * See https://www.awsarchitectureblog.com/2015/03/backoff.html + * for explanation of "equal jitter" for more information. + * + * @deprecated You should probably use {@link fullJitterBackoff} instead. + * + * @example + * ```ts + * const backoff = fullJitterBackoff({ base: 1000, cap: 5000, factor: 2 }); + * backoff(1); // between 500 and 1000 + * backoff(2); // between 1000 and 2000 + * backoff(3); // between 2000 and 4000 + * ``` + * + * @see {@link ExponentialBackoffOpts} + * @see {@link BackoffFn} + * + * @param opts - Backoff options. See ExponentialBackoffOpts + * @returns BackoffFn + */ +export function equalJitterBackoff(opts?: ExponentialBackoffOpts): BackoffFn { + const gen = exponentialBackoff(opts); + return (attempt: number) => { + const halfDelay = gen(attempt) / 2; + return halfDelay + randomBetween(0, halfDelay); + }; +} + +export type DecorrelatedJitterBackoffOpts = { + /** + * Base delay in ms. Delay will not be lower than this. + * + * @defaultValue `1000` + */ + base?: number; + /** + * Maximum delay in ms. + * + * @defaultValue `60000` + */ + cap?: number; + /** + * Multiplier for the next delay based on last delay time. + * + * @defaultValue `3` + */ + times?: number; +}; + +/** + * Decorrelated jitter backoff. + * See https://www.awsarchitectureblog.com/2015/03/backoff.html + * for explanation of "decorrelated jitter" for more information. + * + * NOTE: This function ignores attempt count, and keeps internal state instead! + * If you reuse it, it will not behave as you would expect. + * + * @deprecated You should probably use {@link fullJitterBackoff} instead. + * + * @example + * ```ts + * const backoff = decorrelatedJitterBackoff({ base: 1000, cap: 60000, times: 2 }); + * backoff(1); // between 1000 and (theoretically) 3000 + * backoff(1); // between 1000 and (theoretically) 6000 + * backoff(1); // between 1000 and (theoretically) 12000 + * ``` + * + * @see {@link DecorrelatedJitterBackoffOpts} + * @see {@link BackoffFn} + * + * @param opts - Backoff options. See DecorrelatedJitterBackoffOpts + * @returns BackoffFn + */ +export function decorrelatedJitterBackoff( + opts?: DecorrelatedJitterBackoffOpts, +): BackoffFn { + opts = opts ?? {}; + const base = opts.base ?? 1000; + const cap = opts.cap ?? 60000; + const times = opts.times ?? 3; + + let lastSleep = base; + return () => { + const sleep = Math.min(cap, randomBetween(base, lastSleep * times)); + lastSleep = sleep; + return sleep; + }; +} diff --git a/test.js b/test.js deleted file mode 100644 index c0d8c48..0000000 --- a/test.js +++ /dev/null @@ -1,451 +0,0 @@ -import test from 'ava' -import recaller, { - constantBackoff, - exponentialBackoff, - fullJitterBackoff, - equalJitterBackoff, - decorrelatedJitterBackoff -} from './' - -test('function should not be retried if async function is ok', async t => { - t.plan(1) - - let called = false - - const retval = await recaller(async () => { - if (called) return t.fail('Called multiple times') - called = true - return 50 - }) - - t.is(retval, 50) -}) - -test('function should be retried if async function rejects (next ok)', async t => { - t.plan(1) - - let called = false - - const retval = await recaller(async () => { - if (!called) { - called = true - throw new Error('uh oh') - } - return 50 - }) - - t.is(retval, 50) -}) - -test('function should be retried if async function rejects (all fail)', async t => { - t.plan(1) - - try { - await recaller(async () => { - throw new Error('uh oh') - }) - t.fail('did not throw') - } catch (err) { - t.is(err.message, 'uh oh') - } -}) - -test('bailing should work (error provided)', async t => { - t.plan(2) - - let called = false - let bailed = false - - try { - await recaller(async (bail) => { - if (bailed) t.fail('bailed, but still called') - if (called) { - bailed = true - bail(new Error('bailed!')) - t.pass() - return - } - called = true - throw new Error('uh oh') - }) - t.fail('did not throw') - } catch (err) { - t.is(err.message, 'bailed!') - } -}) - -test('bailing should work (nothing provided)', async t => { - t.plan(1) - - try { - await recaller(async (bail) => { - bail() - throw new Error('well') - }) - t.fail('did not throw') - } catch (err) { - t.is(err.message, 'Bailed without giving a reason.') - } -}) - -test('recaller must require a function', async t => { - t.plan(1) - - try { - await recaller() - t.fail('did not throw') - } catch (err) { - t.is(err.message, 'fn is not a function') - } -}) - -test('retry amounts (default, 10)', async t => { - t.plan(12) - - try { - await recaller(async () => { - t.pass() - throw new Error('fail') - }) - t.fail('did not throw') - } catch (err) { - t.is(err.message, 'fail') - } -}) - -test('retry amounts (set, 4)', async t => { - t.plan(6) - - try { - await recaller(async () => { - t.pass() - throw new Error('fail') - }, {retries: 4}) - t.fail('did not throw') - } catch (err) { - t.is(err.message, 'fail') - } -}) - -test('attempt should be a correct number', async t => { - t.plan(5) - - let currentAttempt = 1 - - try { - await recaller(async (bail, attempt) => { - t.is(attempt, currentAttempt) - currentAttempt++ - throw new Error('fail') - }, {retries: 3}) - t.fail('did not throw') - } catch (err) { - t.is(err.message, 'fail') - } -}) - -test('onretry, invalid value', async t => { - t.plan(1) - - try { - await recaller(async () => { - throw new Error('fail') - }, {onretry: 123}) - t.fail('did not throw') - } catch (err) { - t.is(err.message, 'onretry handler is not a function') - } -}) - -test('onretry, called (no backoff)', async t => { - t.plan(31) - - let currentAttempt = 1 - - try { - await recaller(async (bail, attempt) => { - throw new Error('fail' + attempt) - }, {onretry: function (err, attempt, delayTime) { - t.is(err.message, 'fail' + currentAttempt) - t.is(attempt, currentAttempt) - t.is(delayTime, 0) - currentAttempt++ - }}) - t.fail('did not throw') - } catch (err) { - t.is(err.message, 'fail11') - } -}) - -test('onretry, called (backoff)', async t => { - t.plan(2) - - try { - await recaller(async () => { - throw new Error('fail') - }, { - onretry: function (err, attempt, delayTime) { - err // touch so linter doesn't complain - t.is(delayTime, 200) - }, - retries: 1, - backoff: constantBackoff(200) - }) - } catch (err) { - t.is(err.message, 'fail') - } -}) - -test('onretry, error throwing', async t => { - t.plan(2) - - let thrown = false - - try { - await recaller(async (bail, attempt) => { - if (thrown) return bail(new Error('was thrown')) - if (attempt === 3) { - throw new TypeError('type error') - } - throw new Error('normal error') - }, { - onretry: function (err, attempt, delayTime) { - if (err instanceof TypeError) { - t.is(thrown, false) - thrown = true - throw err - } - } - }) - t.fail('did not throw') - } catch (err) { - t.is(err.message, 'type error') - } -}) - -test('backoff (constant)', async t => { - t.plan(2) - - let lastDelay - - try { - await recaller(async () => { - if (lastDelay) { - const duration = Date.now() - lastDelay - t.is((duration > 200), true) - } - lastDelay = Date.now() - throw new Error('fail') - }, { - retries: 1, - backoff: constantBackoff(200) - }) - } catch (err) { - t.is(err.message, 'fail') - } -}) - -test('constantBackoff', t => { - t.plan(4) - - // default values - const cb1 = constantBackoff() - t.is(cb1(1), 5000) - t.is(cb1(2), 5000) - - // custom ms - const cb2 = constantBackoff(300) - t.is(cb2(1), 300) - t.is(cb2(2), 300) -}) - -test('exponentialBackoff', t => { - t.plan(12) - - // default values - const eb1 = exponentialBackoff() - t.is(eb1(1), 1000) - t.is(eb1(2), 2000) - t.is(eb1(3), 4000) - - // custom base - const eb2 = exponentialBackoff({base: 500}) - t.is(eb2(1), 500) - t.is(eb2(2), 1000) - t.is(eb2(3), 2000) - - // custom cap - const eb3 = exponentialBackoff({cap: 3000}) - t.is(eb3(1), 1000) - t.is(eb3(2), 2000) - t.is(eb3(3), 3000) - - // custom factor - const eb4 = exponentialBackoff({factor: 1}) - t.is(eb4(1), 1000) - t.is(eb4(2), 1000) - t.is(eb4(3), 1000) -}) - -test('fullJitterBackoff', t => { - t.plan(6 * 10000) - - // default values - const fjb1 = fullJitterBackoff() - - for (let i = 0; i < 10000; i++) { - const val = fjb1(1) - t.is(( - val <= 1000 && - val >= 0 - ), true) - } - - for (let i = 0; i < 10000; i++) { - const val = fjb1(2) - t.is(( - val <= 2000 && - val >= 0 - ), true) - } - - for (let i = 0; i < 10000; i++) { - const val = fjb1(3) - t.is(( - val <= 4000 && - val >= 0 - ), true) - } - - // custom values - const fjb2 = fullJitterBackoff({cap: 2000, base: 500}) - - for (let i = 0; i < 10000; i++) { - const val = fjb2(1) - t.is(( - val <= 500 && - val >= 0 - ), true) - } - - for (let i = 0; i < 10000; i++) { - const val = fjb2(2) - t.is(( - val <= 1000 && - val >= 0 - ), true) - } - - for (let i = 0; i < 10000; i++) { - const val = fjb2(1) - t.is(( - val <= 2000 && - val >= 0 - ), true) - } -}) - -test('equalJitterBackoff', t => { - t.plan(6 * 10000) - - // default values - const ejb1 = equalJitterBackoff() - - for (let i = 0; i < 10000; i++) { - const val = ejb1(1) - t.is(( - val <= 1000 && - val >= 500 - ), true) - } - - for (let i = 0; i < 10000; i++) { - const val = ejb1(2) - t.is(( - val <= 2000 && - val >= 1000 - ), true) - } - - for (let i = 0; i < 10000; i++) { - const val = ejb1(3) - t.is(( - val <= 4000 && - val >= 2000 - ), true) - } - - // custom values - const ejb2 = equalJitterBackoff({cap: 2000, base: 500}) - - for (let i = 0; i < 10000; i++) { - const val = ejb2(1) - t.is(( - val <= 500 && - val >= 250 - ), true) - } - - for (let i = 0; i < 10000; i++) { - const val = ejb2(2) - t.is(( - val <= 1000 && - val >= 500 - ), true) - } - - for (let i = 0; i < 10000; i++) { - const val = ejb2(3) - t.is(( - val <= 2000 && - val >= 1000 - ), true) - } -}) - -test('decorrelatedJitterBackoff', t => { - t.plan(4 * 10000) - - // Default options, first value - for (let i = 0; i < 10000; i++) { - const gen = decorrelatedJitterBackoff() - const val = gen() - t.is(( - val >= 1000 && - val <= 3000 - ), true) - } - - // Default options, second value - for (let i = 0; i < 10000; i++) { - const gen = decorrelatedJitterBackoff() - gen() // generate first - const val = gen() - t.is(( - val >= 1000 && - val <= 9000 - ), true) - } - - // Capped (otherwise default), third value - for (let i = 0; i < 10000; i++) { - const gen = decorrelatedJitterBackoff({cap: 24000}) - gen() // generate first - gen() // generate second - const val = gen() - t.is(( - val >= 1000 && - val <= 24000 - ), true) - } - - // times 4, first value - for (let i = 0; i < 10000; i++) { - const gen = decorrelatedJitterBackoff({times: 4}) - const val = gen() - t.is(( - val >= 1000 && - val <= 4000 - ), true) - } -}) diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..19f9335 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,18 @@ +{ + "compilerOptions": { + "lib": ["esnext"], + "target": "esnext", + "module": "nodenext", + "moduleDetection": "force", + "allowJs": false, + "outDir": "./lib", + "esModuleInterop": true, + "skipLibCheck": true, + "declaration": true, + + "strict": true, + "noFallthroughCasesInSwitch": true, + "forceConsistentCasingInFileNames": true + }, + "include": ["src/**/*"] +} diff --git a/vitest.config.ts b/vitest.config.ts new file mode 100644 index 0000000..d9ec64a --- /dev/null +++ b/vitest.config.ts @@ -0,0 +1,10 @@ +import { configDefaults, defineConfig } from "vitest/config"; + +export default defineConfig({ + test: { + exclude: [...configDefaults.exclude, "lib/*"], + coverage: { + exclude: [...configDefaults.coverage.exclude!, "lib/*"], + }, + }, +});