From a07bdb463c65fbdee0afead8eaaeda683b38514c Mon Sep 17 00:00:00 2001 From: Sergio Moreno Date: Mon, 9 Dec 2024 03:12:47 +0100 Subject: [PATCH] refactor: migrate to ts, undici, and improve contributing --- .eslintrc.json | 13 - .github/workflows/build.yml | 24 +- .github/workflows/publish.yml | 29 +- .gitignore | 2 + .prettierrc | 4 + CONTRIBUTING.md | 97 + README.md | 204 +- docs/Sender.html | 51 +- docs/SenderOptions.html | 12 +- docs/global.html | 694 ++ docs/index.html | 228 +- docs/index.js.html | 1341 ++- docs/module-@questdb_nodejs-client.html | 186 +- docs/options.js.html | 86 +- docs/scripts/prettify/Apache-License-2.0.txt | 0 docs/sender.js.html | 86 +- docs/src_options.js.html | 94 +- docs/src_sender.js.html | 94 +- eslint.config.mjs | 9 + examples.manifest.yaml | 2 +- examples/auth.js | 30 +- examples/auth_tls.js | 30 +- examples/basic.js | 20 +- examples/from_conf.js | 12 +- examples/workers.js | 113 +- index.js | 11 - notes.md | 2 - package-lock.json | 9112 ------------------ package.json | 47 +- pnpm-lock.yaml | 3965 ++++++++ src/index.ts | 8 + src/logging.js | 30 - src/logging.ts | 28 + src/options.js | 416 - src/options.ts | 483 + src/sender.js | 963 -- src/sender.ts | 1105 +++ src/validation.js | 123 - src/validation.ts | 122 + test/_utils_/mockhttp.ts | 93 + test/_utils_/mockproxy.ts | 60 + test/_utils_/proxy.ts | 53 + test/_utils_/proxyfunctions.ts | 70 + test/logging.test.js | 73 - test/logging.test.ts | 84 + test/mockhttp.js | 86 - test/mockproxy.js | 49 - test/options.test.js | 638 -- test/options.test.ts | 738 ++ test/proxy.js | 47 - test/proxyfunctions.js | 73 - test/sender.test.js | 1700 ---- test/sender.test.ts | 2152 +++++ test/testapp.js | 64 - test/testapp.ts | 78 + tsconfig.json | 16 +- types/index.d.ts | 3 - types/index.d.ts.map | 1 - types/src/logging.d.ts | 10 - types/src/logging.d.ts.map | 1 - types/src/options.d.ts | 167 - types/src/options.d.ts.map | 1 - types/src/sender.d.ts | 252 - types/src/sender.d.ts.map | 1 - types/src/validation.d.ts | 17 - types/src/validation.d.ts.map | 1 - 66 files changed, 11905 insertions(+), 14499 deletions(-) delete mode 100644 .eslintrc.json create mode 100644 .prettierrc create mode 100644 CONTRIBUTING.md create mode 100644 docs/global.html mode change 100644 => 100755 docs/scripts/prettify/Apache-License-2.0.txt create mode 100644 eslint.config.mjs delete mode 100644 index.js delete mode 100644 notes.md delete mode 100644 package-lock.json create mode 100644 pnpm-lock.yaml create mode 100644 src/index.ts delete mode 100644 src/logging.js create mode 100644 src/logging.ts delete mode 100644 src/options.js create mode 100644 src/options.ts delete mode 100644 src/sender.js create mode 100644 src/sender.ts delete mode 100644 src/validation.js create mode 100644 src/validation.ts create mode 100644 test/_utils_/mockhttp.ts create mode 100644 test/_utils_/mockproxy.ts create mode 100644 test/_utils_/proxy.ts create mode 100644 test/_utils_/proxyfunctions.ts delete mode 100644 test/logging.test.js create mode 100644 test/logging.test.ts delete mode 100644 test/mockhttp.js delete mode 100644 test/mockproxy.js delete mode 100644 test/options.test.js create mode 100644 test/options.test.ts delete mode 100644 test/proxy.js delete mode 100644 test/proxyfunctions.js delete mode 100644 test/sender.test.js create mode 100644 test/sender.test.ts delete mode 100644 test/testapp.js create mode 100644 test/testapp.ts delete mode 100644 types/index.d.ts delete mode 100644 types/index.d.ts.map delete mode 100644 types/src/logging.d.ts delete mode 100644 types/src/logging.d.ts.map delete mode 100644 types/src/options.d.ts delete mode 100644 types/src/options.d.ts.map delete mode 100644 types/src/sender.d.ts delete mode 100644 types/src/sender.d.ts.map delete mode 100644 types/src/validation.d.ts delete mode 100644 types/src/validation.d.ts.map diff --git a/.eslintrc.json b/.eslintrc.json deleted file mode 100644 index 3a28b3e..0000000 --- a/.eslintrc.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "env": { - "commonjs": true, - "es2021": true, - "node": true - }, - "extends": "eslint:recommended", - "parserOptions": { - "ecmaVersion": "latest" - }, - "rules": { - } -} diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index f5d09df..3dead93 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -2,8 +2,9 @@ name: build on: push: - schedule: - - cron: '15 2,10,18 * * *' + branches: + - main + pull_request: jobs: test: @@ -11,7 +12,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - node-version: [16, latest] + node-version: [20, 22, latest] steps: - name: Checkout repository uses: actions/checkout@v4 @@ -23,11 +24,16 @@ jobs: with: node-version: ${{ matrix.node-version }} - - name: Install dependencies - run: npm ci + - uses: pnpm/action-setup@v4 + with: + version: 9 + run_install: true + + - name: Linting + run: pnpm eslint - - name: Run tests - run: npm test + - name: Type-checking + run: pnpm typecheck - - name: Run linter - run: npm run eslint + - name: Tests + run: pnpm test diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index bd38699..545bed8 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -1,13 +1,11 @@ -name: build +name: publish on: - push: - branches: - - main + workflow_dispatch: jobs: - build: - name: Publish @questdb/nodejs-questdb-client to npm + test: + name: Publish runs-on: ubuntu-latest steps: - name: Checkout repository @@ -18,16 +16,21 @@ jobs: - name: Setup node uses: actions/setup-node@v4 with: - node-version: latest + node-version: 22 - - name: Install dependencies - run: npm ci + - uses: pnpm/action-setup@v4 + with: + version: 9 + run_install: true + + - name: Linting + run: pnpm eslint - - name: Run tests - run: npm test + - name: Type-checking + run: pnpm typecheck - - name: Run linter - run: npm run eslint + - name: Tests + run: pnpm test - name: Publish uses: JS-DevTools/npm-publish@v3 diff --git a/.gitignore b/.gitignore index 9c47831..24018fa 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,5 @@ coverage .idea *.iml .DS_Store +certs +dist \ No newline at end of file diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 0000000..222861c --- /dev/null +++ b/.prettierrc @@ -0,0 +1,4 @@ +{ + "tabWidth": 2, + "useTabs": false +} diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..563e1b1 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,97 @@ +# Contributing to nodejs-questdb-client + +Thank you for your interest in contributing to nodejs-questdb-client! This document provides guidelines and instructions for contributing to the project. + +## Development Setup + +1. Fork and clone the repository: +```bash +git clone https://github.com/YOUR_USERNAME/nodejs-questdb-client.git +cd nodejs-questdb-client +``` + +2. Install dependencies: +```bash +pnpm install +``` + + +## Running Tests + +The project uses Vitest for testing. Tests are located in the `test` directory. + +1. Run tests in watch mode during development: +```bash +pnpm run test +``` + +### Test Requirements + +- Some tests use mock servers and certificates located in the `test/certs` directory + +> You can generate the certificates by running the `generateCerts.sh` script in the `scripts` directory. The script requires two arguments: the output directory and the password for the certificates. +`./scripts/generateCerts.sh . questdbPwd123` + + +## Code Style and Quality + +1. The project uses TypeScript. Make sure your code is properly typed. + +2. Format your code using Prettier + +3. Lint your code: +```bash +pnpm run lint +``` + +4. Fix linting issues: +```bash +pnpm run lint --fix +``` + +## Making Changes + +1. Create a new branch for your changes: +```bash +git checkout -b feature/your-feature-name +``` + +2. Make your changes and commit them with clear, descriptive commit messages: +```bash +git add . +git commit -m "feat: add new feature" +``` + +We follow the [Conventional Commits](https://www.conventionalcommits.org/) specification for commit messages. + +3. Push your changes to your fork: +```bash +git push origin feature/your-feature-name +``` + +4. Create a Pull Request from your fork to the main repository. + +## Pull Request Guidelines + +1. Make sure all tests pass +2. Update documentation if needed +3. Add tests for new features +4. Keep PRs focused - one feature or bug fix per PR +5. Link any related issues in the PR description + +## Documentation + +- Update the README.md if you're adding new features or changing existing ones +- Add JSDoc comments for new public APIs +- Include examples in the documentation when appropriate + +## Need Help? + +If you have questions or need help, you can: +- Open an issue with your question +- Join our community discussions (if available) + +## License + +By contributing to nodejs-questdb-client, you agree that your contributions will be licensed under the project's license. + diff --git a/README.md b/README.md index 93484c5..354f487 100644 --- a/README.md +++ b/README.md @@ -2,13 +2,26 @@ ## Requirements -The client requires Node.js v16 or newer version. +The client requires Node.js v20 or newer version. ## Installation + ```shell +# With npm npm i -s @questdb/nodejs-client +# With yarn +yarn add @questdb/nodejs-client +# With pnpm +pnpm add @questdb/nodejs-client ``` +## Compatibility table + +| QuestDB version | Node.js client version | HTTP Agent | +| --------------- | ---------------------- | ------------ | +| ^4.0.0 | >=v20.X.X | Undici Agent | +| ^3.0.0 | { - // create a sender using HTTPS protocol with bearer token authentication - const sender: Sender = Sender.fromConfig("https::addr=127.0.0.1:9000;token=Xyvd3er6GF87ysaHk;") + // create a sender using HTTPS protocol with bearer token authentication + const sender: Sender = Sender.fromConfig( + "https::addr=127.0.0.1:9000;token=Xyvd3er6GF87ysaHk;", + ); - // send the data over the authenticated and secure connection - await sender - .table("trades") - .symbol("symbol", "ETH-USD") - .symbol("side", "sell") - .floatColumn("price", 2615.54) - .floatColumn("amount", 0.00044) - .at(Date.now(), "ms") - await sender.flush() + // send the data over the authenticated and secure connection + await sender + .table("trades") + .symbol("symbol", "ETH-USD") + .symbol("side", "sell") + .floatColumn("price", 2615.54) + .floatColumn("amount", 0.00044) + .at(Date.now(), "ms"); + await sender.flush(); - // close the connection after all rows ingested - await sender.close() + // close the connection after all rows ingested + await sender.close(); } run().catch(console.error); @@ -104,79 +121,88 @@ run().catch(console.error); ### Worker threads example ```javascript -const { Sender } = require("@questdb/nodejs-client") -const { Worker, isMainThread, parentPort, workerData } = require("worker_threads") +import { Sender } from "@questdb/nodejs-client"; +const { + Worker, + isMainThread, + parentPort, + workerData, +} = require("worker_threads"); // fake venue // generates random prices and amounts for a ticker for max 5 seconds, then the feed closes function* venue(ticker) { - let end = false - setTimeout(() => { end = true; }, rndInt(5000)) - while (!end) { - yield {ticker, price: Math.random(), amount: Math.random()} - } + let end = false; + setTimeout(() => { + end = true; + }, rndInt(5000)); + while (!end) { + yield { ticker, price: Math.random(), amount: Math.random() }; + } } // market data feed simulator // uses the fake venue to deliver price and amount updates to the feed handler (onTick() callback) async function subscribe(ticker, onTick) { - const feed = venue(workerData.ticker) - let tick; - while (tick = feed.next().value) { - await onTick(tick) - await sleep(rndInt(30)) - } + const feed = venue(workerData.ticker); + let tick; + while ((tick = feed.next().value)) { + await onTick(tick); + await sleep(rndInt(30)); + } } async function run() { - if (isMainThread) { - const tickers = ["ETH-USD", "BTC-USD", "SOL-USD", "DOGE-USD"] - // main thread to start a worker thread for each ticker - for (let ticker of tickers) { - const worker = new Worker(__filename, { workerData: { ticker: ticker } }) - .on('error', (err) => { throw err; }) - .on('exit', () => { console.log(`${ticker} thread exiting...`); }) - .on('message', (msg) => { - console.log(`Ingested ${msg.count} prices for ticker ${msg.ticker}`) - }); - } - } else { - // it is important that each worker has a dedicated sender object - // threads cannot share the sender because they would write into the same buffer - const sender = Sender.fromConfig("http::addr=127.0.0.1:9000"); - - // subscribe for the market data of the ticker assigned to the worker - // ingest each price update into the database using the sender - let count = 0; - await subscribe(workerData.ticker, async (tick) => { - await sender - .table("trades") - .symbol("symbol", tick.ticker) - .symbol("side", "sell") - .floatColumn("price", tick.price) - .floatColumn("amount", tick.amount) - .at(Date.now(), "ms") - await sender.flush(); - count++; + if (isMainThread) { + const tickers = ["ETH-USD", "BTC-USD", "SOL-USD", "DOGE-USD"]; + // main thread to start a worker thread for each ticker + for (let ticker of tickers) { + const worker = new Worker(__filename, { workerData: { ticker: ticker } }) + .on("error", (err) => { + throw err; + }) + .on("exit", () => { + console.log(`${ticker} thread exiting...`); + }) + .on("message", (msg) => { + console.log(`Ingested ${msg.count} prices for ticker ${msg.ticker}`); }); - - // let the main thread know how many prices were ingested - parentPort.postMessage({ticker: workerData.ticker, count}) - - // close the connection to the database - await sender.close() } + } else { + // it is important that each worker has a dedicated sender object + // threads cannot share the sender because they would write into the same buffer + const sender = Sender.fromConfig("http::addr=127.0.0.1:9000"); + + // subscribe for the market data of the ticker assigned to the worker + // ingest each price update into the database using the sender + let count = 0; + await subscribe(workerData.ticker, async (tick) => { + await sender + .table("trades") + .symbol("symbol", tick.ticker) + .symbol("side", "sell") + .floatColumn("price", tick.price) + .floatColumn("amount", tick.amount) + .at(Date.now(), "ms"); + await sender.flush(); + count++; + }); + + // let the main thread know how many prices were ingested + parentPort.postMessage({ ticker: workerData.ticker, count }); + + // close the connection to the database + await sender.close(); + } } function sleep(ms) { - return new Promise(resolve => setTimeout(resolve, ms)) + return new Promise((resolve) => setTimeout(resolve, ms)); } function rndInt(limit) { - return Math.floor((Math.random() * limit) + 1) + return Math.floor(Math.random() * limit + 1); } -run() - .then(console.log) - .catch(console.error) +run().then(console.log).catch(console.error); ``` diff --git a/docs/Sender.html b/docs/Sender.html index 656a3d1..0d8d510 100644 --- a/docs/Sender.html +++ b/docs/Sender.html @@ -68,10 +68,9 @@

SenderkeepAlive set to false.
-For example: Sender.fromConfig(`http::addr=host:port`, { agent: new http.Agent({ keepAlive: false })})
+For example: Sender.fromConfig(`http::addr=host:port`, { agent: new undici.Agent({ connect: { keepAlive: false } })})
If no custom agent is configured, the Sender will use its own agent which overrides some default values -of http.Agent/https.Agent. The Sender's own agent uses persistent connections with 1 minute idle -timeout, and limits the number of open connections to the server, which is set to 256 for each host. +of undici.Agent. The Sender's own agent uses persistent connections with 1 minute idle timeout, pipelines requests default to 1.

@@ -189,7 +188,7 @@
Parameters:
Source:
@@ -404,7 +403,7 @@
Parameters:
Source:
@@ -493,7 +492,7 @@

(async) atNowSource:
@@ -653,7 +652,7 @@
Parameters:
Source:
@@ -764,7 +763,7 @@

(async) closeSource:
@@ -904,7 +903,7 @@
Parameters:
Source:
@@ -1086,7 +1085,7 @@
Parameters:
Source:
@@ -1197,7 +1196,7 @@

(async) flushSource:
@@ -1379,7 +1378,7 @@
Parameters:
Source:
@@ -1490,7 +1489,7 @@

resetSource:
@@ -1651,7 +1650,7 @@
Parameters:
Source:
@@ -1811,7 +1810,7 @@
Parameters:
Source:
@@ -1993,7 +1992,7 @@
Parameters:
Source:
@@ -2152,7 +2151,7 @@
Parameters:
Source:
@@ -2404,7 +2403,7 @@
Parameters:
Source:
@@ -2548,8 +2547,8 @@
Parameters:
Optional extra configuration.
- 'log' is a logging function used by the Sender.
Prototype: (level: 'error'|'warn'|'info'|'debug', message: string) => void.
-- 'agent' is a custom http/https agent used by the Sender when http/https transport is used.
-A http.Agent or https.Agent object is expected. +- 'agent' is a custom Undici agent used by the Sender when http/https transport is used.
+A undici.Agent object is expected. @@ -2590,7 +2589,7 @@
Parameters:
Source:
@@ -2711,8 +2710,8 @@
Parameters:
Optional extra configuration.
- 'log' is a logging function used by the Sender.
Prototype: (level: 'error'|'warn'|'info'|'debug', message: string) => void.
-- 'agent' is a custom http/https agent used by the Sender when http/https transport is used.
-A http.Agent or https.Agent object is expected. +- 'agent' is a custom Undici agent used by the Sender when http/https transport is used.
+A undici.Agent object is expected. @@ -2753,7 +2752,7 @@
Parameters:
Source:
@@ -2821,13 +2820,13 @@
Returns:

- Documentation generated by JSDoc 4.0.2 on Tue Aug 13 2024 14:27:30 GMT+0300 (Eastern European Summer Time) + Documentation generated by JSDoc 4.0.4 on Mon Dec 09 2024 02:16:59 GMT+0100 (hora estándar de Europa central)
diff --git a/docs/SenderOptions.html b/docs/SenderOptions.html index d13d2fd..6bfe165 100644 --- a/docs/SenderOptions.html +++ b/docs/SenderOptions.html @@ -269,7 +269,7 @@
Parameters:
Source:
@@ -453,7 +453,7 @@
Parameters:
Source:
@@ -573,7 +573,7 @@
Parameters:
Optional extra configuration.
- 'log' is a logging function used by the Sender.
-Prototype: (level: 'error'|'warn'|'info'|'debug', message: string) => void.
+ }in /**br> - 'agent' is a custom http/https agent used by the Sender when http/https transport is used.
A http.Agent or https.Agent object is expected. @@ -616,7 +616,7 @@
Parameters:
Source:
@@ -684,13 +684,13 @@
Returns:

- Documentation generated by JSDoc 4.0.2 on Tue Aug 13 2024 14:27:30 GMT+0300 (Eastern European Summer Time) + Documentation generated by JSDoc 4.0.4 on Mon Dec 09 2024 02:16:59 GMT+0100 (hora estándar de Europa central)
diff --git a/docs/global.html b/docs/global.html new file mode 100644 index 0000000..09db35e --- /dev/null +++ b/docs/global.html @@ -0,0 +1,694 @@ + + + + + JSDoc: Global + + + + + + + + + + +
+ +

Global

+ + + + + + +
+ +
+ +

+ + +
+ +
+
+ + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + +
+ + + + + + + + + + + + + + +

Members

+ + + +

(constant) DEFAULT_HTTP_OPTIONS :Agent.Options

+ + + + + + +
Type:
+
    +
  • + +Agent.Options + + +
  • +
+ + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + +

Methods

+ + + + + + + +

log(level, message)

+ + + + + + +
+ Simple logger to write log messages to the console.
+Supported logging levels are `error`, `warn`, `info` and `debug`.
+Throws an error if logging level is invalid. +
+ + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
level + + +'error' +| + +'warn' +| + +'info' +| + +'debug' + + + + The log level of the message.
message + + +string + + + + The log message.
+ + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +

validateColumnName(name, maxNameLength)

+ + + + + + +
+ Validates a column name.
+Throws an error if column name is invalid. +
+ + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
name + + +string + + + + The column name to validate.
maxNameLength + + +number + + + + The maximum length of column names.
+ + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +

validateTableName(name, maxNameLength)

+ + + + + + +
+ Validates a table name.
+Throws an error if table name is invalid. +
+ + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
name + + +string + + + + The table name to validate.
maxNameLength + + +number + + + + The maximum length of table names.
+ + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + +
+ + + +
+ +
+ Documentation generated by JSDoc 4.0.4 on Mon Dec 09 2024 02:16:59 GMT+0100 (hora estándar de Europa central) +
+ + + + + \ No newline at end of file diff --git a/docs/index.html b/docs/index.html index 1f43695..0321e83 100644 --- a/docs/index.html +++ b/docs/index.html @@ -45,22 +45,49 @@

QuestDB Node.js Client

Requirements

-

The client requires Node.js v16 or newer version.

+

The client requires Node.js v20 or newer version.

Installation

-
npm i -s @questdb/nodejs-client
+
# With npm
+npm i -s @questdb/nodejs-client
+# With yarn
+yarn add @questdb/nodejs-client
+# With pnpm
+pnpm add @questdb/nodejs-client
 
+

Compatibility table

+ + + + + + + + + + + + + + + + + + + + +
QuestDB versionNode.js client versionHTTP Agent
^5.0.0>=v20.X.XUndici Agent
^4.0.0<v20.X.XHttp.Agent

Configuration options

Detailed description of the client's configuration options can be found in -the SenderOptions documentation.

+the SenderOptions documentation.

Examples

The examples below demonstrate how to use the client.
-For more details, please, check the Sender's documentation.

+For more details, please, check the Sender's documentation.

Basic API usage

-
const { Sender } = require("@questdb/nodejs-client")
+
import { Sender } from "@questdb/nodejs-client";
 
 async function run() {
   // create a sender using HTTP protocol
-  const sender = Sender.fromConfig("http::addr=127.0.0.1:9000")
+  const sender = Sender.fromConfig("http::addr=127.0.0.1:9000");
 
   // add rows to the buffer of the sender
   await sender
@@ -69,141 +96,154 @@ 

Basic API usage

.symbol("side", "sell") .floatColumn("price", 2615.54) .floatColumn("amount", 0.00044) - .at(Date.now(), "ms") + .at(Date.now(), "ms"); // flush the buffer of the sender, sending the data to QuestDB // the buffer is cleared after the data is sent, and the sender is ready to accept new data - await sender.flush() + await sender.flush(); // close the connection after all rows ingested // unflushed data will be lost - await sender.close() + await sender.close(); } -run().then(console.log).catch(console.error) +run().then(console.log).catch(console.error);

Authentication and secure connection

-
const { Sender } = require("@questdb/nodejs-client")
+
import { Sender } from "@questdb/nodejs-client";
 
 async function run() {
-    // create a sender using HTTPS protocol with username and password authentication
-    const sender = Sender.fromConfig("https::addr=127.0.0.1:9000;username=admin;password=quest;")
+  // create a sender using HTTPS protocol with username and password authentication
+  const sender = Sender.fromConfig(
+    "https::addr=127.0.0.1:9000;username=admin;password=quest;",
+  );
 
-    // send the data over the authenticated and secure connection
-    await sender
-        .table("trades")
-        .symbol("symbol", "ETH-USD")
-        .symbol("side", "sell")
-        .floatColumn("price", 2615.54)
-        .floatColumn("amount", 0.00044)
-        .at(Date.now(), "ms")
-    await sender.flush()
+  // send the data over the authenticated and secure connection
+  await sender
+    .table("trades")
+    .symbol("symbol", "ETH-USD")
+    .symbol("side", "sell")
+    .floatColumn("price", 2615.54)
+    .floatColumn("amount", 0.00044)
+    .at(Date.now(), "ms");
+  await sender.flush();
 
-    // close the connection after all rows ingested
-    await sender.close()
+  // close the connection after all rows ingested
+  await sender.close();
 }
 
-run().catch(console.error)
+run().catch(console.error);
 

TypeScript example

-
import { Sender } from "@questdb/nodejs-client"
+
import { Sender } from "@questdb/nodejs-client";
 
 async function run(): Promise<void> {
-    // create a sender using HTTPS protocol with bearer token authentication
-    const sender: Sender = Sender.fromConfig("https::addr=127.0.0.1:9000;token=Xyvd3er6GF87ysaHk;")
+  // create a sender using HTTPS protocol with bearer token authentication
+  const sender: Sender = Sender.fromConfig(
+    "https::addr=127.0.0.1:9000;token=Xyvd3er6GF87ysaHk;",
+  );
 
-    // send the data over the authenticated and secure connection
-    await sender
-        .table("trades")
-        .symbol("symbol", "ETH-USD")
-        .symbol("side", "sell")
-        .floatColumn("price", 2615.54)
-        .floatColumn("amount", 0.00044)
-        .at(Date.now(), "ms")
-    await sender.flush()
+  // send the data over the authenticated and secure connection
+  await sender
+    .table("trades")
+    .symbol("symbol", "ETH-USD")
+    .symbol("side", "sell")
+    .floatColumn("price", 2615.54)
+    .floatColumn("amount", 0.00044)
+    .at(Date.now(), "ms");
+  await sender.flush();
 
-    // close the connection after all rows ingested
-    await sender.close()
+  // close the connection after all rows ingested
+  await sender.close();
 }
 
 run().catch(console.error);
 

Worker threads example

-
const { Sender } = require("@questdb/nodejs-client")
-const { Worker, isMainThread, parentPort, workerData } = require("worker_threads")
+
import { Sender } from "@questdb/nodejs-client";
+const {
+  Worker,
+  isMainThread,
+  parentPort,
+  workerData,
+} = require("worker_threads");
 
 // fake venue
 // generates random prices and amounts for a ticker for max 5 seconds, then the feed closes
 function* venue(ticker) {
-    let end = false
-    setTimeout(() => { end = true; }, rndInt(5000))
-    while (!end) {
-        yield {ticker, price: Math.random(), amount: Math.random()}
-    }
+  let end = false;
+  setTimeout(() => {
+    end = true;
+  }, rndInt(5000));
+  while (!end) {
+    yield { ticker, price: Math.random(), amount: Math.random() };
+  }
 }
 
 // market data feed simulator
 // uses the fake venue to deliver price and amount updates to the feed handler (onTick() callback)
 async function subscribe(ticker, onTick) {
-    const feed = venue(workerData.ticker)
-    let tick;
-    while (tick = feed.next().value) {
-        await onTick(tick)
-        await sleep(rndInt(30))
-    }
+  const feed = venue(workerData.ticker);
+  let tick;
+  while ((tick = feed.next().value)) {
+    await onTick(tick);
+    await sleep(rndInt(30));
+  }
 }
 
 async function run() {
-    if (isMainThread) {
-        const tickers = ["ETH-USD", "BTC-USD", "SOL-USD", "DOGE-USD"]
-        // main thread to start a worker thread for each ticker
-        for (let ticker of tickers) {
-            const worker = new Worker(__filename, { workerData: { ticker: ticker } })
-                .on('error', (err) => { throw err; })
-                .on('exit', () => { console.log(`${ticker} thread exiting...`); })
-                .on('message', (msg) => {
-                    console.log(`Ingested ${msg.count} prices for ticker ${msg.ticker}`)
-                });
-        }
-    } else {
-        // it is important that each worker has a dedicated sender object
-        // threads cannot share the sender because they would write into the same buffer
-        const sender = Sender.fromConfig("http::addr=127.0.0.1:9000");
-
-        // subscribe for the market data of the ticker assigned to the worker
-        // ingest each price update into the database using the sender
-        let count = 0;
-        await subscribe(workerData.ticker, async (tick) => {
-            await sender
-                .table("trades")
-                .symbol("symbol", tick.ticker)
-                .symbol("side", "sell")
-                .floatColumn("price", tick.price)
-                .floatColumn("amount", tick.amount)
-                .at(Date.now(), "ms")
-            await sender.flush();
-            count++;
+  if (isMainThread) {
+    const tickers = ["ETH-USD", "BTC-USD", "SOL-USD", "DOGE-USD"];
+    // main thread to start a worker thread for each ticker
+    for (let ticker of tickers) {
+      const worker = new Worker(__filename, { workerData: { ticker: ticker } })
+        .on("error", (err) => {
+          throw err;
+        })
+        .on("exit", () => {
+          console.log(`${ticker} thread exiting...`);
+        })
+        .on("message", (msg) => {
+          console.log(`Ingested ${msg.count} prices for ticker ${msg.ticker}`);
         });
-
-        // let the main thread know how many prices were ingested
-        parentPort.postMessage({ticker: workerData.ticker, count})
-
-        // close the connection to the database
-        await sender.close()
     }
+  } else {
+    // it is important that each worker has a dedicated sender object
+    // threads cannot share the sender because they would write into the same buffer
+    const sender = Sender.fromConfig("http::addr=127.0.0.1:9000");
+
+    // subscribe for the market data of the ticker assigned to the worker
+    // ingest each price update into the database using the sender
+    let count = 0;
+    await subscribe(workerData.ticker, async (tick) => {
+      await sender
+        .table("trades")
+        .symbol("symbol", tick.ticker)
+        .symbol("side", "sell")
+        .floatColumn("price", tick.price)
+        .floatColumn("amount", tick.amount)
+        .at(Date.now(), "ms");
+      await sender.flush();
+      count++;
+    });
+
+    // let the main thread know how many prices were ingested
+    parentPort.postMessage({ ticker: workerData.ticker, count });
+
+    // close the connection to the database
+    await sender.close();
+  }
 }
 
 function sleep(ms) {
-    return new Promise(resolve => setTimeout(resolve, ms))
+  return new Promise((resolve) => setTimeout(resolve, ms));
 }
 
 function rndInt(limit) {
-    return Math.floor((Math.random() * limit) + 1)
+  return Math.floor(Math.random() * limit + 1);
 }
 
-run()
-    .then(console.log)
-    .catch(console.error)
+run().then(console.log).catch(console.error);
 
@@ -215,13 +255,13 @@

Worker threads example


- Documentation generated by JSDoc 4.0.2 on Tue Aug 13 2024 14:27:30 GMT+0300 (Eastern European Summer Time) + Documentation generated by JSDoc 4.0.4 on Mon Dec 09 2024 02:16:59 GMT+0100 (hora estándar de Europa central)
diff --git a/docs/index.js.html b/docs/index.js.html index e7c140d..843f5c2 100644 --- a/docs/index.js.html +++ b/docs/index.js.html @@ -26,17 +26,1342 @@

Source: index.js

-
'use strict';
+            
var node_fs = require('node:fs');
+var node_buffer = require('node:buffer');
+var net = require('node:net');
+var tls = require('node:tls');
+var crypto = require('node:crypto');
+var undici = require('undici');
+
+function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
+
+var net__default = /*#__PURE__*/_interopDefault(net);
+var tls__default = /*#__PURE__*/_interopDefault(tls);
+var crypto__default = /*#__PURE__*/_interopDefault(crypto);
+
+const LOG_LEVELS = {
+    error: {
+        log: console.error,
+        criticality: 3
+    },
+    warn: {
+        log: console.warn,
+        criticality: 2
+    },
+    info: {
+        log: console.info,
+        criticality: 1
+    },
+    debug: {
+        log: console.debug,
+        criticality: 0
+    }
+};
+const DEFAULT_CRITICALITY = LOG_LEVELS.info.criticality;
+/**
+ * Simple logger to write log messages to the console. <br>
+ * Supported logging levels are `error`, `warn`, `info` and `debug`. <br>
+ * Throws an error if logging level is invalid.
+ *
+ * @param {'error'|'warn'|'info'|'debug'} level - The log level of the message.
+ * @param {string} message - The log message.
+ */ function log(level, message) {
+    const logLevel = LOG_LEVELS[level];
+    if (!logLevel) {
+        throw new Error(`Invalid log level: '${level}'`);
+    }
+    if (logLevel.criticality >= DEFAULT_CRITICALITY) {
+        logLevel.log(message);
+    }
+}
 
 /**
- * A Node.js client for QuestDB.
+ * Validates a table name. <br>
+ * Throws an error if table name is invalid.
  *
- * @module @questdb/nodejs-client
- */
+ * @param {string} name - The table name to validate.
+ * @param {number} maxNameLength - The maximum length of table names.
+ */ function validateTableName(name, maxNameLength) {
+    const len = name.length;
+    if (len > maxNameLength) {
+        throw new Error(`Table name is too long, max length is ${maxNameLength}`);
+    }
+    if (len === 0) {
+        throw new Error("Empty string is not allowed as table name");
+    }
+    for(let i = 0; i < len; i++){
+        const ch = name[i];
+        switch(ch){
+            case ".":
+                if (i === 0 || i === len - 1 || name[i - 1] === ".") // single dot is allowed in the middle only
+                // starting with a dot hides directory in Linux
+                // ending with a dot can be trimmed by some Windows versions / file systems
+                // double or triple dot looks suspicious
+                // single dot allowed as compatibility,
+                // when someone uploads 'file_name.csv' the file name used as the table name
+                throw new Error("Table name cannot start or end with a dot, and only a single dot allowed");
+                break;
+            case "?":
+            case ",":
+            case "'":
+            case '"':
+            case "\\":
+            case "/":
+            case ":":
+            case ")":
+            case "(":
+            case "+":
+            case "*":
+            case "%":
+            case "~":
+            case "\u0000":
+            case "\u0001":
+            case "\u0002":
+            case "\u0003":
+            case "\u0004":
+            case "\u0005":
+            case "\u0006":
+            case "\u0007":
+            case "\u0008":
+            case "\u0009":
+            case "\u000B":
+            case "\u000c":
+            case "\r":
+            case "\n":
+            case "\u000e":
+            case "\u000f":
+            case "\u007f":
+            case "\ufeff":
+                throw new Error(`Invalid character in table name: ${ch}`);
+        }
+    }
+}
+/**
+ * Validates a column name. <br>
+ * Throws an error if column name is invalid.
+ *
+ * @param {string} name - The column name to validate.
+ * @param {number} maxNameLength - The maximum length of column names.
+ */ function validateColumnName(name, maxNameLength) {
+    const len = name.length;
+    if (len > maxNameLength) {
+        throw new Error(`Column name is too long, max length is ${maxNameLength}`);
+    }
+    if (len === 0) {
+        throw new Error("Empty string is not allowed as column name");
+    }
+    for (const ch of name){
+        switch(ch){
+            case "?":
+            case ".":
+            case ",":
+            case "'":
+            case '"':
+            case "\\":
+            case "/":
+            case ":":
+            case ")":
+            case "(":
+            case "+":
+            case "-":
+            case "*":
+            case "%":
+            case "~":
+            case "\u0000":
+            case "\u0001":
+            case "\u0002":
+            case "\u0003":
+            case "\u0004":
+            case "\u0005":
+            case "\u0006":
+            case "\u0007":
+            case "\u0008":
+            case "\u0009":
+            case "\u000B":
+            case "\u000c":
+            case "\r":
+            case "\n":
+            case "\u000e":
+            case "\u000f":
+            case "\u007f":
+            case "\ufeff":
+                throw new Error(`Invalid character in column name: ${ch}`);
+        }
+    }
+}
+
+const HTTP_PORT = 9000;
+const TCP_PORT = 9009;
+const HTTP = "http";
+const HTTPS = "https";
+const TCP = "tcp";
+const TCPS = "tcps";
+const ON = "on";
+const OFF = "off";
+const UNSAFE_OFF = "unsafe_off";
+/** @classdesc
+ * <a href="Sender.html">Sender</a> configuration options. <br>
+ * <br>
+ * Properties of the object are initialized through a configuration string. <br>
+ * The configuration string has the following format: <i>&ltprotocol&gt::&ltkey&gt=&ltvalue&gt;&ltkey&gt=&ltvalue&gt;...;</i> <br>
+ * The keys are case-sensitive, the trailing semicolon is optional. <br>
+ * The values are validated, and an error is thrown if the format is invalid. <br>
+ * <br>
+ * Connection and protocol options
+ * <ul>
+ * <li> <b>protocol</b>: <i>enum, accepted values: http, https, tcp, tcps</i> - The protocol used to communicate with the server. <br>
+ * When <i>https</i> or <i>tcps</i> used, the connection is secured with TLS encryption.
+ * </li>
+ * <li> addr: <i>string</i> - Hostname and port, separated by colon. This key is mandatory, but the port part is optional. <br>
+ * If no port is specified, a default will be used. <br>
+ * When the protocol is HTTP/HTTPS, the port defaults to 9000. When the protocol is TCP/TCPS, the port defaults to 9009. <br>
+ * <br>
+ * Examples: <i>http::addr=localhost:9000</i>, <i>https::addr=localhost:9000</i>, <i>http::addr=localhost</i>, <i>tcp::addr=localhost:9009</i>
+ * </li>
+ * </ul>
+ * <br>
+ * Authentication options
+ * <ul>
+ * <li> username: <i>string</i> - Used for authentication. <br>
+ * For HTTP, Basic Authentication requires the <i>password</i> option. <br>
+ * For TCP with JWK token authentication, <i>token</i> option is required.
+ * </li>
+ * <li> password: <i>string</i> - Password for HTTP Basic authentication, should be accompanied by the <i>username</i> option.
+ * </li>
+ * <li> token: <i>string</i> - For HTTP with Bearer authentication, this is the bearer token. <br>
+ * For TCP with JWK token authentication, this is the private key part of the JWK token,
+ * and must be accompanied by the <i>username</i> option.
+ * </li>
+ * </ul>
+ * <br>
+ * TLS options
+ * <ul>
+ * <li> tls_verify: <i>enum, accepted values: on, unsafe_off</i> - When the HTTPS or TCPS protocols are selected, TLS encryption is used. <br>
+ * By default, the Sender will verify the server's certificate, but this check can be disabled by setting this option to <i>off</i>. This is useful
+ * non-production environments where self-signed certificates might be used, but should be avoided in production if possible.
+ * </li>
+ * <li> tls_ca: <i>string</i> - Path to a file containing the root CA's certificate in PEM format. <br>
+ * Can be useful when self-signed certificates are used, otherwise should not be set.
+ * </li>
+ * </ul>
+ * <br>
+ * Auto flush options
+ * <ul>
+ * <li> auto_flush: <i>enum, accepted values: on, off</i> - The Sender automatically flushes the buffer by default. This can be switched off
+ * by setting this option to <i>off</i>. <br>
+ * When disabled, the flush() method of the Sender has to be called explicitly to make sure data is sent to the server. <br>
+ * Manual buffer flushing can be useful, especially when we want to use transactions. When the HTTP protocol is used, each flush results in a single HTTP
+ * request, which becomes a single transaction on the server side. The transaction either succeeds, and all rows sent in the request are
+ * inserted; or it fails, and none of the rows make it into the database.
+ * </li>
+ * <li> auto_flush_rows: <i>integer</i> - The number of rows that will trigger a flush. When set to 0, row-based flushing is disabled. <br>
+ * The Sender will default this parameter to 75000 rows when HTTP protocol is used, and to 600 in case of TCP protocol.
+ * </li>
+ * <li> auto_flush_interval: <i>integer</i> - The number of milliseconds that will trigger a flush, default value is 1000.
+ * When set to 0, interval-based flushing is disabled. <br>
+ * Note that the setting is checked only when a new row is added to the buffer. There is no timer registered to flush the buffer automatically.
+ * </li>
+ * </ul>
+ * <br>
+ * Buffer sizing options
+ * <ul>
+ * <li> init_buf_size: <i>integer</i> - Initial buffer size, defaults to 64 KiB in the Sender.
+ * </li>
+ * <li> max_buf_size: <i>integer</i> - Maximum buffer size, defaults to 100 MiB in the Sender. <br>
+ * If the buffer would need to be extended beyond the maximum size, an error is thrown.
+ * </li>
+ * </ul>
+ * <br>
+ * HTTP request specific options
+ * <ul>
+ * <li> request_timeout: <i>integer</i> - The time in milliseconds to wait for a response from the server, set to 10 seconds by default. <br>
+ * This is in addition to the calculation derived from the <i>request_min_throughput</i> parameter.
+ * </li>
+ * <li> request_min_throughput: <i>integer</i> - Minimum expected throughput in bytes per second for HTTP requests, set to 100 KiB/s seconds by default. <br>
+ * If the throughput is lower than this value, the connection will time out. This is used to calculate an additional
+ * timeout on top of <i>request_timeout</i>. This is useful for large requests. You can set this value to 0 to disable this logic.
+ * </li>
+ * <li> retry_timeout: <i>integer</i> - The time in milliseconds to continue retrying after a failed HTTP request, set to 10 seconds by default. <br>
+ * The interval between retries is an exponential backoff starting at 10ms and doubling after each failed attempt up to a maximum of 1 second.
+ * </li>
+ * </ul>
+ * <br>
+ * Other options
+ * <ul>
+ * <li> max_name_len: <i>integer</i> - The maximum length of a table or column name, the Sender defaults this parameter to 127. <br>
+ * Recommended to use the same setting as the server, which also uses 127 by default.
+ * </li>
+ * <li> copy_buffer: <i>enum, accepted values: on, off</i> - By default, the Sender creates a new buffer for every flush() call,
+ * and the data to be sent to the server is copied into this new buffer.
+ * Setting the flag to <i>off</i> results in reusing the same buffer instance for each flush() call. <br>
+ * Use this flag only if calls to the client are serialised.
+ * </li>
+ * </ul>
+ */ class SenderOptions {
+    /**
+   * Creates a Sender options object by parsing the provided configuration string.
+   *
+   * @param {string} configurationString - Configuration string. <br>
+   * @param {object} extraOptions - Optional extra configuration. <br>
+   * - 'log' is a logging function used by the <a href="Sender.html">Sender</a>. <br>
+   * Prototype: <i>(level: 'error'|'warn'|'info'|'debug', message: string) => void</i>. <br>
+   * - 'agent' is a custom http/https agent used by the <a href="Sender.html">Sender</a> when http/https transport is used. <br>
+   * A <i>http.Agent</i> or <i>https.Agent</i> object is expected.
+   */ constructor(configurationString, extraOptions = undefined){
+        parseConfigurationString(this, configurationString);
+        if (extraOptions) {
+            if (extraOptions.log && typeof extraOptions.log !== "function") {
+                throw new Error("Invalid logging function");
+            }
+            this.log = extraOptions.log;
+            if (extraOptions.agent && !(extraOptions.agent instanceof undici.Agent)) {
+                throw new Error("Invalid http/https agent");
+            }
+            this.agent = extraOptions.agent;
+        }
+    }
+    /**
+   * Creates a Sender options object by parsing the provided configuration string.
+   *
+   * @param {string} configurationString - Configuration string. <br>
+   * @param {object} extraOptions - Optional extra configuration. <br>
+   * - 'log' is a logging function used by the <a href="Sender.html">Sender</a>. <br>
+   * Prototype: <i>(level: 'error'|'warn'|'info'|'debug', message: string) => void</i>. <br>
+   * - 'agent' is a custom http/https agent used by the <a href="Sender.html">Sender</a> when http/https transport is used. <br>
+   * A <i>http.Agent</i> or <i>https.Agent</i> object is expected.
+   *
+   * @return {SenderOptions} A Sender configuration object initialized from the provided configuration string.
+   */ static fromConfig(configurationString, extraOptions = undefined) {
+        return new SenderOptions(configurationString, extraOptions);
+    }
+    /**
+   * Creates a Sender options object by parsing the configuration string set in the <b>QDB_CLIENT_CONF</b> environment variable.
+   *
+   * @param {object} extraOptions - Optional extra configuration. <br>
+   * - 'log' is a logging function used by the <a href="Sender.html">Sender</a>. <br>
+  }in  /**br>
+   * - 'agent' is a custom http/https agent used by the <a href="Sender.html">Sender</a> when http/https transport is used. <br>
+   * A <i>http.Agent</i> or <i>https.Agent</i> object is expected.
+   *
+   * @return {SenderOptions} A Sender configuration object initialized from the <b>QDB_CLIENT_CONF</b> environment variable.
+   */ static fromEnv(extraOptions = undefined) {
+        return SenderOptions.fromConfig(process.env.QDB_CLIENT_CONF, extraOptions);
+    }
+}
+function parseConfigurationString(options, configString) {
+    if (!configString) {
+        throw new Error("Configuration string is missing or empty");
+    }
+    const position = parseProtocol(options, configString);
+    parseSettings(options, configString, position);
+    parseAddress(options);
+    parseBufferSizes(options);
+    parseAutoFlushOptions(options);
+    parseTlsOptions(options);
+    parseRequestTimeoutOptions(options);
+    parseMaxNameLength(options);
+    parseCopyBuffer(options);
+}
+function parseSettings(options, configString, position) {
+    let index = configString.indexOf(";", position);
+    while(index > -1){
+        if (index + 1 < configString.length && configString.charAt(index + 1) === ";") {
+            index = configString.indexOf(";", index + 2);
+            continue;
+        }
+        parseSetting(options, configString, position, index);
+        position = index + 1;
+        index = configString.indexOf(";", position);
+    }
+    if (position < configString.length) {
+        parseSetting(options, configString, position, configString.length);
+    }
+}
+function parseSetting(options, configString, position, index) {
+    const setting = configString.slice(position, index).replaceAll(";;", ";");
+    const equalsIndex = setting.indexOf("=");
+    if (equalsIndex < 0) {
+        throw new Error(`Missing '=' sign in '${setting}'`);
+    }
+    const key = setting.slice(0, equalsIndex);
+    const value = setting.slice(equalsIndex + 1);
+    validateConfigKey(key);
+    validateConfigValue(key, value);
+    options[key] = value;
+}
+const ValidConfigKeys = [
+    "addr",
+    "username",
+    "password",
+    "token",
+    "token_x",
+    "token_y",
+    "auto_flush",
+    "auto_flush_rows",
+    "auto_flush_interval",
+    "copy_buffer",
+    "request_min_throughput",
+    "request_timeout",
+    "retry_timeout",
+    "init_buf_size",
+    "max_buf_size",
+    "max_name_len",
+    "tls_verify",
+    "tls_ca",
+    "tls_roots",
+    "tls_roots_password"
+];
+function validateConfigKey(key) {
+    if (!ValidConfigKeys.includes(key)) {
+        throw new Error(`Unknown configuration key: '${key}'`);
+    }
+}
+function validateConfigValue(key, value) {
+    if (!value) {
+        throw new Error(`Invalid configuration, value is not set for '${key}'`);
+    }
+    for(let i = 0; i < value.length; i++){
+        const unicode = value.codePointAt(i);
+        if (unicode < 0x20 || unicode > 0x7e && unicode < 0xa0) {
+            throw new Error(`Invalid configuration, control characters are not allowed: '${value}'`);
+        }
+    }
+}
+function parseProtocol(options, configString) {
+    const index = configString.indexOf("::");
+    if (index < 0) {
+        throw new Error("Missing protocol, configuration string format: 'protocol::key1=value1;key2=value2;key3=value3;'");
+    }
+    options.protocol = configString.slice(0, index);
+    switch(options.protocol){
+        case HTTP:
+        case HTTPS:
+        case TCP:
+        case TCPS:
+            break;
+        default:
+            throw new Error(`Invalid protocol: '${options.protocol}', accepted protocols: 'http', 'https', 'tcp', 'tcps'`);
+    }
+    return index + 2;
+}
+function parseAddress(options) {
+    if (!options.addr) {
+        throw new Error("Invalid configuration, 'addr' is required");
+    }
+    const index = options.addr.indexOf(":");
+    if (index < 0) {
+        options.host = options.addr;
+        switch(options.protocol){
+            case HTTP:
+            case HTTPS:
+                options.port = HTTP_PORT;
+                return;
+            case TCP:
+            case TCPS:
+                options.port = TCP_PORT;
+                return;
+            default:
+                throw new Error(`Invalid protocol: '${options.protocol}', accepted protocols: 'http', 'https', 'tcp', 'tcps'`);
+        }
+    }
+    options.host = options.addr.slice(0, index);
+    if (!options.host) {
+        throw new Error(`Host name is required`);
+    }
+    const portStr = options.addr.slice(index + 1);
+    if (!portStr) {
+        throw new Error(`Port is required`);
+    }
+    options.port = Number(portStr);
+    if (isNaN(options.port)) {
+        throw new Error(`Invalid port: '${portStr}'`);
+    }
+    if (!Number.isInteger(options.port) || options.port < 1) {
+        throw new Error(`Invalid port: ${options.port}`);
+    }
+}
+function parseBufferSizes(options) {
+    parseInteger(options, "init_buf_size", "initial buffer size", 1);
+    parseInteger(options, "max_buf_size", "max buffer size", 1);
+}
+function parseAutoFlushOptions(options) {
+    parseBoolean(options, "auto_flush", "auto flush");
+    parseInteger(options, "auto_flush_rows", "auto flush rows", 0);
+    parseInteger(options, "auto_flush_interval", "auto flush interval", 0);
+}
+function parseTlsOptions(options) {
+    parseBoolean(options, "tls_verify", "TLS verify", UNSAFE_OFF);
+    if (options.tls_roots || options.tls_roots_password) {
+        throw new Error("'tls_roots' and 'tls_roots_password' options are not supported, please, " + "use the 'tls_ca' option or the NODE_EXTRA_CA_CERTS environment variable instead");
+    }
+}
+function parseRequestTimeoutOptions(options) {
+    parseInteger(options, "request_min_throughput", "request min throughput", 1);
+    parseInteger(options, "request_timeout", "request timeout", 1);
+    parseInteger(options, "retry_timeout", "retry timeout", 0);
+}
+function parseMaxNameLength(options) {
+    parseInteger(options, "max_name_len", "max name length", 1);
+}
+function parseCopyBuffer(options) {
+    parseBoolean(options, "copy_buffer", "copy buffer");
+}
+function parseBoolean(options, property, description, offValue = OFF) {
+    if (options[property]) {
+        const property_str = options[property];
+        switch(property_str){
+            case ON:
+                options[property] = true;
+                break;
+            case offValue:
+                options[property] = false;
+                break;
+            default:
+                throw new Error(`Invalid ${description} option: '${property_str}'`);
+        }
+    }
+}
+function parseInteger(options, property, description, lowerBound) {
+    if (options[property]) {
+        const property_str = options[property];
+        options[property] = Number(property_str);
+        if (isNaN(options[property])) {
+            throw new Error(`Invalid ${description} option, not a number: '${property_str}'`);
+        }
+        if (!Number.isInteger(options[property]) || options[property] < lowerBound) {
+            throw new Error(`Invalid ${description} option: ${options[property]}`);
+        }
+    }
+}
+
+// @ts-check
+const HTTP_NO_CONTENT = 204; // success
+const DEFAULT_HTTP_AUTO_FLUSH_ROWS = 75000;
+const DEFAULT_TCP_AUTO_FLUSH_ROWS = 600;
+const DEFAULT_AUTO_FLUSH_INTERVAL = 1000; // 1 sec
+const DEFAULT_MAX_NAME_LENGTH = 127;
+const DEFAULT_REQUEST_MIN_THROUGHPUT = 102400; // 100 KB/sec
+const DEFAULT_REQUEST_TIMEOUT = 10000; // 10 sec
+const DEFAULT_RETRY_TIMEOUT = 10000; // 10 sec
+const DEFAULT_BUFFER_SIZE = 65536; //  64 KB
+const DEFAULT_MAX_BUFFER_SIZE = 104857600; // 100 MB
+/** @type {Agent.Options} */ const DEFAULT_HTTP_OPTIONS = {
+    connect: {
+        keepAlive: true
+    },
+    pipelining: 1,
+    keepAliveTimeout: 60000
+};
+// an arbitrary public key, not used in authentication
+// only used to construct a valid JWK token which is accepted by the crypto API
+const PUBLIC_KEY = {
+    x: "aultdA0PjhD_cWViqKKyL5chm6H1n-BiZBo_48T-uqc",
+    y: "__ptaol41JWSpTTL525yVEfzmY8A6Vi_QrW1FjKcHMg"
+};
+/*
+We are retrying on the following response codes (copied from the Rust client):
+500:  Internal Server Error
+503:  Service Unavailable
+504:  Gateway Timeout
 
-const { Sender } = require('./src/sender');
+// Unofficial extensions
+507:  Insufficient Storage
+509:  Bandwidth Limit Exceeded
+523:  Origin is Unreachable
+524:  A Timeout Occurred
+529:  Site is overloaded
+599:  Network Connect Timeout Error
+*/ const RETRIABLE_STATUS_CODES = [
+    500,
+    503,
+    504,
+    507,
+    509,
+    523,
+    524,
+    529,
+    599
+];
+/** @classdesc
+ * The QuestDB client's API provides methods to connect to the database, ingest data, and close the connection.
+ * The supported protocols are HTTP and TCP. HTTP is preferred as it provides feedback in the HTTP response. <br>
+ * Based on benchmarks HTTP also provides higher throughput, if configured to ingest data in bigger batches.
+ * <p>
+ * The client supports authentication. <br>
+ * Authentication details can be passed to the Sender in its configuration options. <br>
+ * The client supports Basic username/password and Bearer token authentication methods when used with HTTP protocol,
+ * and JWK token authentication when ingesting data via TCP. <br>
+ * Please, note that authentication is enabled by default in QuestDB Enterprise only. <br>
+ * Details on how to configure authentication in the open source version of
+ * QuestDB: {@link https://questdb.io/docs/reference/api/ilp/authenticate}
+ * </p>
+ * <p>
+ * The client also supports TLS encryption for both, HTTP and TCP transports to provide a secure connection. <br>
+ * Please, note that the open source version of QuestDB does not support TLS, and requires an external reverse-proxy,
+ * such as Nginx to enable encryption.
+ * </p>
+ * <p>
+ * The client uses a buffer to store data. It automatically flushes the buffer by sending its content to the server.
+ * Auto flushing can be disabled via configuration options to gain control over transactions. Initial and maximum
+ * buffer sizes can also be set.
+ * </p>
+ * <p>
+ * It is recommended that the Sender is created by using one of the static factory methods,
+ * <i>Sender.fromConfig(configString, extraOptions)</i> or <i>Sender.fromEnv(extraOptions)</i>).
+ * If the Sender is created via its constructor, at least the SenderOptions configuration object should be
+ * initialized from a configuration string to make sure that the parameters are validated. <br>
+ * Detailed description of the Sender's configuration options can be found in
+ * the <a href="SenderOptions.html">SenderOptions</a> documentation.
+ * </p>
+ * <p>
+ * Extra options can be provided to the Sender in the <i>extraOptions</i> configuration object. <br>
+ * A custom logging function and a custom HTTP(S) agent can be passed to the Sender in this object. <br>
+ * The logger implementation provides the option to direct log messages to the same place where the host application's
+ * log is saved. The default logger writes to the console. <br>
+ * The custom HTTP(S) agent option becomes handy if there is a need to modify the default options set for the
+ * HTTP(S) connections. A popular setting would be disabling persistent connections, in this case an agent can be
+ * passed to the Sender with <i>keepAlive</i> set to <i>false</i>. <br>
+ * For example: <i>Sender.fromConfig(`http::addr=host:port`, { agent: new undici.Agent({ connect: { keepAlive: false } })})</i> <br>
+ * If no custom agent is configured, the Sender will use its own agent which overrides some default values
+ * of <i>undici.Agent</i>. The Sender's own agent uses persistent connections with 1 minute idle timeout, pipelines requests default to 1.
+ * </p>
+ */ class Sender {
+    /**
+   * Creates an instance of Sender.
+   *
+   * @param {SenderOptions} options - Sender configuration object. <br>
+   * See SenderOptions documentation for detailed description of configuration options. <br>
+   */ constructor(options){
+        if (!options || !options.protocol) {
+            throw new Error("The 'protocol' option is mandatory");
+        }
+        replaceDeprecatedOptions(options);
+        this.log = typeof options.log === "function" ? options.log : log;
+        switch(options.protocol){
+            case HTTP:
+                this.http = true;
+                this.secure = false;
+                this.agent = options.agent instanceof undici.Agent ? options.agent : this.getDefaultHttpAgent();
+                break;
+            case HTTPS:
+                this.http = true;
+                this.secure = true;
+                this.agent = options.agent instanceof undici.Agent ? options.agent : this.getDefaultHttpAgent();
+                break;
+            case TCP:
+                this.http = false;
+                this.secure = false;
+                break;
+            case TCPS:
+                this.http = false;
+                this.secure = true;
+                break;
+            default:
+                throw new Error(`Invalid protocol: '${options.protocol}'`);
+        }
+        if (this.http) {
+            this.username = options.username;
+            this.password = options.password;
+            this.token = options.token;
+            if (!options.port) {
+                options.port = 9000;
+            }
+        } else {
+            if (!options.auth && !options.jwk) {
+                constructAuth(options);
+            }
+            this.jwk = constructJwk(options);
+            if (!options.port) {
+                options.port = 9009;
+            }
+        }
+        this.host = options.host;
+        this.port = options.port;
+        this.tlsVerify = isBoolean(options.tls_verify) ? options.tls_verify : true;
+        this.tlsCA = options.tls_ca ? node_fs.readFileSync(options.tls_ca) : undefined;
+        this.autoFlush = isBoolean(options.auto_flush) ? options.auto_flush : true;
+        this.autoFlushRows = isInteger(options.auto_flush_rows, 0) ? options.auto_flush_rows : this.http ? DEFAULT_HTTP_AUTO_FLUSH_ROWS : DEFAULT_TCP_AUTO_FLUSH_ROWS;
+        this.autoFlushInterval = isInteger(options.auto_flush_interval, 0) ? options.auto_flush_interval : DEFAULT_AUTO_FLUSH_INTERVAL;
+        this.maxNameLength = isInteger(options.max_name_len, 1) ? options.max_name_len : DEFAULT_MAX_NAME_LENGTH;
+        this.requestMinThroughput = isInteger(options.request_min_throughput, 0) ? options.request_min_throughput : DEFAULT_REQUEST_MIN_THROUGHPUT;
+        this.requestTimeout = isInteger(options.request_timeout, 1) ? options.request_timeout : DEFAULT_REQUEST_TIMEOUT;
+        this.retryTimeout = isInteger(options.retry_timeout, 0) ? options.retry_timeout : DEFAULT_RETRY_TIMEOUT;
+        const noCopy = isBoolean(options.copy_buffer) && !options.copy_buffer;
+        this.toBuffer = noCopy ? this.toBufferView : this.toBufferNew;
+        this.doResolve = noCopy ? (resolve)=>{
+            compact(this);
+            resolve(true);
+        } : (resolve)=>{
+            resolve(true);
+        };
+        this.maxBufferSize = isInteger(options.max_buf_size, 1) ? options.max_buf_size : DEFAULT_MAX_BUFFER_SIZE;
+        this.resize(isInteger(options.init_buf_size, 1) ? options.init_buf_size : DEFAULT_BUFFER_SIZE);
+        this.reset();
+    }
+    /**
+   * Creates a Sender options object by parsing the provided configuration string.
+   *
+   * @param {string} configurationString - Configuration string. <br>
+   * @param {object} extraOptions - Optional extra configuration. <br>
+   * - 'log' is a logging function used by the <a href="Sender.html">Sender</a>. <br>
+   * Prototype: <i>(level: 'error'|'warn'|'info'|'debug', message: string) => void</i>. <br>
+   * - 'agent' is a custom Undici agent used by the <a href="Sender.html">Sender</a> when http/https transport is used. <br>
+   * A <i>undici.Agent</i>  object is expected.
+   *
+   * @return {Sender} A Sender object initialized from the provided configuration string.
+   */ static fromConfig(configurationString, extraOptions = undefined) {
+        return new Sender(SenderOptions.fromConfig(configurationString, extraOptions));
+    }
+    /**
+   * Creates a Sender options object by parsing the configuration string set in the <b>QDB_CLIENT_CONF</b> environment variable.
+   *
+   * @param {object} extraOptions - Optional extra configuration. <br>
+   * - 'log' is a logging function used by the <a href="Sender.html">Sender</a>. <br>
+   * Prototype: <i>(level: 'error'|'warn'|'info'|'debug', message: string) => void</i>. <br>
+   * - 'agent' is a custom Undici agent used by the <a href="Sender.html">Sender</a> when http/https transport is used. <br>
+   * A <i>undici.Agent</i>  object is expected.
+   *
+   * @return {Sender} A Sender object initialized from the <b>QDB_CLIENT_CONF</b> environment variable.
+   */ static fromEnv(extraOptions = undefined) {
+        return new Sender(SenderOptions.fromConfig(process.env.QDB_CLIENT_CONF, extraOptions));
+    }
+    /**
+   * Extends the size of the sender's buffer. <br>
+   * Can be used to increase the size of buffer if overflown.
+   * The buffer's content is copied into the new buffer.
+   *
+   * @param {number} bufferSize - New size of the buffer used by the sender, provided in bytes.
+   */ resize(bufferSize) {
+        if (bufferSize > this.maxBufferSize) {
+            throw new Error(`Max buffer size is ${this.maxBufferSize} bytes, requested buffer size: ${bufferSize}`);
+        }
+        this.bufferSize = bufferSize;
+        // Allocating an extra byte because Buffer.write() does not fail if the length of the data to be written is
+        // longer than the size of the buffer. It simply just writes whatever it can, and returns.
+        // If we can write into the extra byte, that indicates buffer overflow.
+        // See the check in our write() function.
+        const newBuffer = node_buffer.Buffer.alloc(this.bufferSize + 1, 0, "utf8");
+        if (this.buffer) {
+            this.buffer.copy(newBuffer);
+        }
+        this.buffer = newBuffer;
+    }
+    /**
+   * Resets the buffer, data added to the buffer will be lost. <br>
+   * In other words it clears the buffer and sets the writing position to the beginning of the buffer.
+   *
+   * @return {Sender} Returns with a reference to this sender.
+   */ reset() {
+        this.position = 0;
+        this.lastFlushTime = Date.now();
+        this.pendingRowCount = 0;
+        startNewRow(this);
+        return this;
+    }
+    /**
+   * Creates a TCP connection to the database.
+   *
+   * @param {net.NetConnectOpts | tls.ConnectionOptions} connectOptions - Connection options, host and port are required.
+   *
+   * @return {Promise<boolean>} Resolves to true if the client is connected.
+   */ connect(connectOptions = undefined) {
+        if (this.http) {
+            throw new Error("'connect()' should be called only if the sender connects via TCP");
+        }
+        if (!connectOptions) {
+            connectOptions = {
+                host: this.host,
+                port: this.port,
+                ca: this.tlsCA
+            };
+        }
+        if (!connectOptions.host) {
+            throw new Error("Hostname is not set");
+        }
+        if (!connectOptions.port) {
+            throw new Error("Port is not set");
+        }
+        return new Promise((resolve, reject)=>{
+            if (this.socket) {
+                throw new Error("Sender connected already");
+            }
+            let authenticated = false;
+            let data;
+            this.socket = !this.secure ? net__default.default.connect(connectOptions) : tls__default.default.connect(connectOptions, ()=>{
+                if (authenticated) {
+                    resolve(true);
+                }
+            });
+            this.socket.setKeepAlive(true);
+            this.socket.on("data", async (raw)=>{
+                data = !data ? raw : node_buffer.Buffer.concat([
+                    data,
+                    raw
+                ]);
+                if (!authenticated) {
+                    authenticated = await authenticate(this, data);
+                    if (authenticated) {
+                        resolve(true);
+                    }
+                } else {
+                    this.log("warn", `Received unexpected data: ${data}`);
+                }
+            }).on("ready", async ()=>{
+                this.log("info", `Successfully connected to ${connectOptions.host}:${connectOptions.port}`);
+                if (this.jwk) {
+                    this.log("info", `Authenticating with ${connectOptions.host}:${connectOptions.port}`);
+                    await this.socket.write(`${this.jwk.kid}\n`, (err)=>{
+                        if (err) {
+                            reject(err);
+                        }
+                    });
+                } else {
+                    authenticated = true;
+                    if (!this.secure || !this.tlsVerify) {
+                        resolve(true);
+                    }
+                }
+            }).on("error", (err)=>{
+                this.log("error", err);
+                if (err.code !== "SELF_SIGNED_CERT_IN_CHAIN" || this.tlsVerify) {
+                    reject(err);
+                }
+            });
+        });
+    }
+    /**
+   * @ignore
+   * @return {Agent} Returns the default http agent.
+   */ getDefaultHttpAgent() {
+        if (!Sender.DEFAULT_HTTP_AGENT) {
+            Sender.DEFAULT_HTTP_AGENT = new undici.Agent(DEFAULT_HTTP_OPTIONS);
+        }
+        return Sender.DEFAULT_HTTP_AGENT;
+    }
+    /**
+   * Closes the TCP connection to the database. <br>
+   * Data sitting in the Sender's buffer will be lost unless flush() is called before close().
+   */ async close() {
+        if (this.socket) {
+            const address = this.socket.remoteAddress;
+            const port = this.socket.remotePort;
+            this.socket.destroy();
+            this.socket = null;
+            this.log("info", `Connection to ${address}:${port} is closed`);
+        }
+    }
+    /**
+   * Sends the buffer's content to the database and compacts the buffer.
+   * If the last row is not finished it stays in the sender's buffer.
+   *
+   * @return {Promise<boolean>} Resolves to true when there was data in the buffer to send.
+   */ async flush() {
+        const data = this.toBuffer(this.endOfLastRow);
+        if (!data) {
+            return false;
+        }
+        if (this.http) {
+            // const request = this.secure ? https.request : http.request;
+            const options = createRequestOptions(this, data);
+            return sendHttp(this, options, data, this.retryTimeout);
+        } else {
+            if (!this.socket) {
+                throw new Error("Sender is not connected");
+            }
+            return sendTcp(this, data);
+        }
+    }
+    /**
+   * @ignore
+   * @return {Buffer} Returns a cropped buffer ready to send to the server or null if there is nothing to send.
+   * The returned buffer is backed by the sender's buffer.
+   */ toBufferView(pos = this.position) {
+        return pos > 0 ? this.buffer.subarray(0, pos) : null;
+    }
+    /**
+   * @ignore
+   * @return {Buffer|null} Returns a cropped buffer ready to send to the server or null if there is nothing to send.
+   * The returned buffer is a copy of the sender's buffer.
+   */ toBufferNew(pos = this.position) {
+        if (pos > 0) {
+            const data = node_buffer.Buffer.allocUnsafe(pos);
+            this.buffer.copy(data, 0, 0, pos);
+            compact(this);
+            return data;
+        }
+        return null;
+    }
+    /**
+   * Write the table name into the buffer of the sender.
+   *
+   * @param {string} table - Table name.
+   * @return {Sender} Returns with a reference to this sender.
+   */ table(table) {
+        if (typeof table !== "string") {
+            throw new Error(`Table name must be a string, received ${typeof table}`);
+        }
+        if (this.hasTable) {
+            throw new Error("Table name has already been set");
+        }
+        validateTableName(table, this.maxNameLength);
+        checkCapacity(this, [
+            table
+        ]);
+        writeEscaped(this, table);
+        this.hasTable = true;
+        return this;
+    }
+    /**
+   * Write a symbol name and value into the buffer of the sender.
+   *
+   * @param {string} name - Symbol name.
+   * @param {any} value - Symbol value, toString() will be called to extract the actual symbol value from the parameter.
+   * @return {Sender} Returns with a reference to this sender.
+   */ symbol(name, value) {
+        if (typeof name !== "string") {
+            throw new Error(`Symbol name must be a string, received ${typeof name}`);
+        }
+        if (!this.hasTable || this.hasColumns) {
+            throw new Error("Symbol can be added only after table name is set and before any column added");
+        }
+        const valueStr = value.toString();
+        checkCapacity(this, [
+            name,
+            valueStr
+        ], 2 + name.length + valueStr.length);
+        write(this, ",");
+        validateColumnName(name, this.maxNameLength);
+        writeEscaped(this, name);
+        write(this, "=");
+        writeEscaped(this, valueStr);
+        this.hasSymbols = true;
+        return this;
+    }
+    /**
+   * Write a string column with its value into the buffer of the sender.
+   *
+   * @param {string} name - Column name.
+   * @param {string} value - Column value, accepts only string values.
+   * @return {Sender} Returns with a reference to this sender.
+   */ stringColumn(name, value) {
+        writeColumn(this, name, value, ()=>{
+            checkCapacity(this, [
+                value
+            ], 2 + value.length);
+            write(this, '"');
+            writeEscaped(this, value, true);
+            write(this, '"');
+        }, "string");
+        return this;
+    }
+    /**
+   * Write a boolean column with its value into the buffer of the sender.
+   *
+   * @param {string} name - Column name.
+   * @param {boolean} value - Column value, accepts only boolean values.
+   * @return {Sender} Returns with a reference to this sender.
+   */ booleanColumn(name, value) {
+        writeColumn(this, name, value, ()=>{
+            checkCapacity(this, [], 1);
+            write(this, value ? "t" : "f");
+        }, "boolean");
+        return this;
+    }
+    /**
+   * Write a float column with its value into the buffer of the sender.
+   *
+   * @param {string} name - Column name.
+   * @param {number} value - Column value, accepts only number values.
+   * @return {Sender} Returns with a reference to this sender.
+   */ floatColumn(name, value) {
+        writeColumn(this, name, value, ()=>{
+            const valueStr = value.toString();
+            checkCapacity(this, [
+                valueStr
+            ], valueStr.length);
+            write(this, valueStr);
+        }, "number");
+        return this;
+    }
+    /**
+   * Write an integer column with its value into the buffer of the sender.
+   *
+   * @param {string} name - Column name.
+   * @param {number} value - Column value, accepts only number values.
+   * @return {Sender} Returns with a reference to this sender.
+   */ intColumn(name, value) {
+        if (!Number.isInteger(value)) {
+            throw new Error(`Value must be an integer, received ${value}`);
+        }
+        writeColumn(this, name, value, ()=>{
+            const valueStr = value.toString();
+            checkCapacity(this, [
+                valueStr
+            ], 1 + valueStr.length);
+            write(this, valueStr);
+            write(this, "i");
+        });
+        return this;
+    }
+    /**
+   * Write a timestamp column with its value into the buffer of the sender.
+   *
+   * @param {string} name - Column name.
+   * @param {number | bigint} value - Epoch timestamp, accepts numbers or BigInts.
+   * @param {string} [unit=us] - Timestamp unit. Supported values: 'ns' - nanoseconds, 'us' - microseconds, 'ms' - milliseconds. Defaults to 'us'.
+   * @return {Sender} Returns with a reference to this sender.
+   */ timestampColumn(name, value, unit = "us") {
+        if (typeof value !== "bigint" && !Number.isInteger(value)) {
+            throw new Error(`Value must be an integer or BigInt, received ${value}`);
+        }
+        writeColumn(this, name, value, ()=>{
+            const valueMicros = timestampToMicros(BigInt(value), unit);
+            const valueStr = valueMicros.toString();
+            checkCapacity(this, [
+                valueStr
+            ], 1 + valueStr.length);
+            write(this, valueStr);
+            write(this, "t");
+        });
+        return this;
+    }
+    /**
+   * Closing the row after writing the designated timestamp into the buffer of the sender.
+   *
+   * @param {number | bigint} timestamp - Designated epoch timestamp, accepts numbers or BigInts.
+   * @param {string} [unit=us] - Timestamp unit. Supported values: 'ns' - nanoseconds, 'us' - microseconds, 'ms' - milliseconds. Defaults to 'us'.
+   */ async at(timestamp, unit = "us") {
+        if (!this.hasSymbols && !this.hasColumns) {
+            throw new Error("The row must have a symbol or column set before it is closed");
+        }
+        if (typeof timestamp !== "bigint" && !Number.isInteger(timestamp)) {
+            throw new Error(`Designated timestamp must be an integer or BigInt, received ${timestamp}`);
+        }
+        const timestampNanos = timestampToNanos(BigInt(timestamp), unit);
+        const timestampStr = timestampNanos.toString();
+        checkCapacity(this, [], 2 + timestampStr.length);
+        write(this, " ");
+        write(this, timestampStr);
+        write(this, "\n");
+        this.pendingRowCount++;
+        startNewRow(this);
+        await autoFlush(this);
+    }
+    /**
+   * Closing the row without writing designated timestamp into the buffer of the sender. <br>
+   * Designated timestamp will be populated by the server on this record.
+   */ async atNow() {
+        if (!this.hasSymbols && !this.hasColumns) {
+            throw new Error("The row must have a symbol or column set before it is closed");
+        }
+        checkCapacity(this, [], 1);
+        write(this, "\n");
+        this.pendingRowCount++;
+        startNewRow(this);
+        await autoFlush(this);
+    }
+}
+function isBoolean(value) {
+    return typeof value === "boolean";
+}
+function isInteger(value, lowerBound) {
+    return typeof value === "number" && Number.isInteger(value) && value >= lowerBound;
+}
+async function authenticate(sender, challenge) {
+    // Check for trailing \n which ends the challenge
+    if (challenge.subarray(-1).readInt8() === 10) {
+        const keyObject = crypto__default.default.createPrivateKey({
+            key: sender.jwk,
+            format: "jwk"
+        });
+        const signature = crypto__default.default.sign("RSA-SHA256", challenge.subarray(0, challenge.length - 1), keyObject);
+        return new Promise((resolve, reject)=>{
+            sender.socket.write(`${node_buffer.Buffer.from(signature).toString("base64")}\n`, (err)=>{
+                if (err) {
+                    reject(err);
+                } else {
+                    resolve(true);
+                }
+            });
+        });
+    }
+    return false;
+}
+function startNewRow(sender) {
+    sender.endOfLastRow = sender.position;
+    sender.hasTable = false;
+    sender.hasSymbols = false;
+    sender.hasColumns = false;
+}
+function createRequestOptions(sender, data) {
+    const timeoutMillis = data.length / sender.requestMinThroughput * 1000 + sender.requestTimeout;
+    const options = {
+        hostname: sender.host,
+        port: sender.port,
+        agent: sender.agent,
+        protocol: sender.secure ? "https" : "http",
+        path: "/write?precision=n",
+        method: "POST",
+        timeout: timeoutMillis
+    };
+    return options;
+}
+async function sendHttp(sender, options, data, retryTimeout) {
+    const retryBegin = Date.now();
+    const headers = {};
+    if (sender.secure) {
+        sender.agent = new undici.Agent({
+            ...DEFAULT_HTTP_OPTIONS,
+            connect: {
+                ...DEFAULT_HTTP_OPTIONS.connect,
+                requestCert: sender.tlsVerify,
+                rejectUnauthorized: sender.tlsVerify,
+                ca: sender.tlsCA
+            }
+        });
+    }
+    const dispatcher = new undici.RetryAgent(sender.agent, {
+        maxRetries: Infinity,
+        minTimeout: 10,
+        maxTimeout: 1000,
+        timeoutFactor: 2,
+        retryAfter: true,
+        methods: [
+            "GET",
+            "POST",
+            "PUT",
+            "DELETE",
+            "PATCH",
+            "OPTIONS",
+            "HEAD"
+        ],
+        statusCodes: RETRIABLE_STATUS_CODES,
+        errorCodes: [
+            "ECONNRESET",
+            "EAI_AGAIN",
+            "ECONNREFUSED",
+            "ETIMEDOUT",
+            "EPIPE",
+            "UND_ERR_CONNECT_TIMEOUT",
+            "UND_ERR_HEADERS_TIMEOUT",
+            "UND_ERR_BODY_TIMEOUT"
+        ],
+        retry (err, context, callback) {
+            const elapsed = Date.now() - retryBegin;
+            if (elapsed > retryTimeout) {
+                // Stop retrying if the total retry timeout is exceeded
+                return callback(err);
+            }
+            return callback(null);
+        }
+    });
+    if (sender.token) {
+        headers["Authorization"] = "Bearer " + sender.token;
+    } else if (sender.username && sender.password) {
+        headers["Authorization"] = "Basic " + node_buffer.Buffer.from(sender.username + ":" + sender.password).toString("base64");
+    }
+    try {
+        const { statusCode, body } = await dispatcher.request({
+            origin: `${options.protocol}://${options.hostname}:${options.port}`,
+            path: options.path,
+            method: options.method,
+            headers,
+            body: data,
+            headersTimeout: sender.requestTimeout
+        });
+        const responseBody = await body.arrayBuffer();
+        if (statusCode === HTTP_NO_CONTENT) {
+            if (responseBody.byteLength > 0) {
+                sender.log("warn", `Unexpected message from server: ${responseBody.toString()}`);
+            }
+            return true;
+        } else {
+            const error = new Error(`HTTP request failed, statusCode=${statusCode}, error=${responseBody.toString()}`);
+            throw error;
+        }
+    } catch (err) {
+        if (err.code === "UND_ERR_HEADERS_TIMEOUT") {
+            sender.log("error", `HTTP request timeout, no response from server in time`);
+            throw new Error(`HTTP request timeout, no response from server in time`);
+        }
+        sender.log("error", `HTTP request failed, statusCode=500, error=`);
+        throw new Error(`HTTP request failed, statusCode=500, error=${err.message}`);
+    }
+}
+async function autoFlush(sender) {
+    if (sender.autoFlush && sender.pendingRowCount > 0 && (sender.autoFlushRows > 0 && sender.pendingRowCount >= sender.autoFlushRows || sender.autoFlushInterval > 0 && Date.now() - sender.lastFlushTime >= sender.autoFlushInterval)) {
+        await sender.flush();
+    }
+}
+function sendTcp(sender, data) {
+    return new Promise((resolve, reject)=>{
+        sender.socket.write(data, (err)=>{
+            if (err) {
+                reject(err);
+            } else {
+                sender.doResolve(resolve);
+            }
+        });
+    });
+}
+function checkCapacity(sender, data, base = 0) {
+    let length = base;
+    for (const str of data){
+        length += node_buffer.Buffer.byteLength(str, "utf8");
+    }
+    if (sender.position + length > sender.bufferSize) {
+        let newSize = sender.bufferSize;
+        do {
+            newSize += sender.bufferSize;
+        }while (sender.position + length > newSize)
+        sender.resize(newSize);
+    }
+}
+function compact(sender) {
+    if (sender.endOfLastRow > 0) {
+        sender.buffer.copy(sender.buffer, 0, sender.endOfLastRow, sender.position);
+        sender.position = sender.position - sender.endOfLastRow;
+        sender.endOfLastRow = 0;
+        sender.lastFlushTime = Date.now();
+        sender.pendingRowCount = 0;
+    }
+}
+function writeColumn(sender, name, value, writeValue, valueType) {
+    if (typeof name !== "string") {
+        throw new Error(`Column name must be a string, received ${typeof name}`);
+    }
+    if (valueType != null && typeof value !== valueType) {
+        throw new Error(`Column value must be of type ${valueType}, received ${typeof value}`);
+    }
+    if (!sender.hasTable) {
+        throw new Error("Column can be set only after table name is set");
+    }
+    checkCapacity(sender, [
+        name
+    ], 2 + name.length);
+    write(sender, sender.hasColumns ? "," : " ");
+    validateColumnName(name, sender.maxNameLength);
+    writeEscaped(sender, name);
+    write(sender, "=");
+    writeValue();
+    sender.hasColumns = true;
+}
+function write(sender, data) {
+    sender.position += sender.buffer.write(data, sender.position);
+    if (sender.position > sender.bufferSize) {
+        throw new Error(`Buffer overflow [position=${sender.position}, bufferSize=${sender.bufferSize}]`);
+    }
+}
+function writeEscaped(sender, data, quoted = false) {
+    for (const ch of data){
+        if (ch > "\\") {
+            write(sender, ch);
+            continue;
+        }
+        switch(ch){
+            case " ":
+            case ",":
+            case "=":
+                if (!quoted) {
+                    write(sender, "\\");
+                }
+                write(sender, ch);
+                break;
+            case "\n":
+            case "\r":
+                write(sender, "\\");
+                write(sender, ch);
+                break;
+            case '"':
+                if (quoted) {
+                    write(sender, "\\");
+                }
+                write(sender, ch);
+                break;
+            case "\\":
+                write(sender, "\\\\");
+                break;
+            default:
+                write(sender, ch);
+                break;
+        }
+    }
+}
+function timestampToMicros(timestamp, unit) {
+    switch(unit){
+        case "ns":
+            return timestamp / 1000n;
+        case "us":
+            return timestamp;
+        case "ms":
+            return timestamp * 1000n;
+        default:
+            throw new Error("Unknown timestamp unit: " + unit);
+    }
+}
+function timestampToNanos(timestamp, unit) {
+    switch(unit){
+        case "ns":
+            return timestamp;
+        case "us":
+            return timestamp * 1000n;
+        case "ms":
+            return timestamp * 1000_000n;
+        default:
+            throw new Error("Unknown timestamp unit: " + unit);
+    }
+}
+function replaceDeprecatedOptions(options) {
+    // deal with deprecated options
+    if (options.copyBuffer) {
+        options.copy_buffer = options.copyBuffer;
+        options.copyBuffer = undefined;
+    }
+    if (options.bufferSize) {
+        options.init_buf_size = options.bufferSize;
+        options.bufferSize = undefined;
+    }
+}
+function constructAuth(options) {
+    if (!options.username && !options.token && !options.password) {
+        // no intention to authenticate
+        return;
+    }
+    if (!options.username || !options.token) {
+        throw new Error("TCP transport requires a username and a private key for authentication, " + "please, specify the 'username' and 'token' config options");
+    }
+    options.auth = {
+        keyId: options.username,
+        token: options.token
+    };
+}
+function constructJwk(options) {
+    if (options.auth) {
+        if (!options.auth.keyId) {
+            throw new Error("Missing username, please, specify the 'keyId' property of the 'auth' config option. " + "For example: new Sender({protocol: 'tcp', host: 'host', auth: {keyId: 'username', token: 'private key'}})");
+        }
+        if (typeof options.auth.keyId !== "string") {
+            throw new Error("Please, specify the 'keyId' property of the 'auth' config option as a string. " + "For example: new Sender({protocol: 'tcp', host: 'host', auth: {keyId: 'username', token: 'private key'}})");
+        }
+        if (!options.auth.token) {
+            throw new Error("Missing private key, please, specify the 'token' property of the 'auth' config option. " + "For example: new Sender({protocol: 'tcp', host: 'host', auth: {keyId: 'username', token: 'private key'}})");
+        }
+        if (typeof options.auth.token !== "string") {
+            throw new Error("Please, specify the 'token' property of the 'auth' config option as a string. " + "For example: new Sender({protocol: 'tcp', host: 'host', auth: {keyId: 'username', token: 'private key'}})");
+        }
+        return {
+            kid: options.auth.keyId,
+            d: options.auth.token,
+            ...PUBLIC_KEY,
+            kty: "EC",
+            crv: "P-256"
+        };
+    } else {
+        return options.jwk;
+    }
+}
 
-module.exports = { Sender };
+exports.Sender = Sender;
 
@@ -47,13 +1372,13 @@

Source: index.js


- Documentation generated by JSDoc 4.0.2 on Wed Apr 17 2024 03:38:42 GMT+0100 (British Summer Time) + Documentation generated by JSDoc 4.0.4 on Mon Dec 09 2024 02:16:59 GMT+0100 (hora estándar de Europa central)
diff --git a/docs/module-@questdb_nodejs-client.html b/docs/module-@questdb_nodejs-client.html index 49bf299..9c04369 100644 --- a/docs/module-@questdb_nodejs-client.html +++ b/docs/module-@questdb_nodejs-client.html @@ -1,129 +1,77 @@ - + - - + + JSDoc: Module: @questdb/nodejs-client - - + + - - - - - - -
- -

Module: @questdb/nodejs-client

- - - - - - -
- -
- -
- -
-
- - + + + + + +
+

Module: @questdb/nodejs-client

+ +
+
+ +
+
A Node.js client for QuestDB.
- - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - -
Source:
-
- - - - - - - -
- - - - +
+
Source:
+
+ +
+
+
+
+
- - - - - - - - - - - - - - - - - - - -
- -
- - - - -
- - - -
- -
- Documentation generated by JSDoc 4.0.2 on Wed Apr 17 2024 03:38:43 GMT+0100 (British Summer Time) -
- - - - - \ No newline at end of file + + +
+ +
+ Documentation generated by + JSDoc 4.0.2 on Wed Apr 17 + 2024 03:38:43 GMT+0100 (British Summer Time) +
+ + + + + diff --git a/docs/options.js.html b/docs/options.js.html index a2025ff..92b46d6 100644 --- a/docs/options.js.html +++ b/docs/options.js.html @@ -1,32 +1,29 @@ - + - - + + JSDoc: Source: options.js - - + + - - - - - - -
- -

Source: options.js

- - - - - - -
+ + + + + +
+

Source: options.js

+ +
-
'use strict';
+          
'use strict';
 
 const http = require('http');
 const https = require('https');
@@ -444,24 +441,29 @@ 

Source: options.js

exports.TCPS = TCPS;
-
- - - - -
- - - -
- -
- Documentation generated by JSDoc 4.0.2 on Tue Aug 13 2024 14:27:30 GMT+0300 (Eastern European Summer Time) -
- - - - +
+
+ + + +
+ +
+ Documentation generated by + JSDoc 4.0.2 on Tue Aug 13 + 2024 14:27:30 GMT+0300 (Eastern European Summer Time) +
+ + + + diff --git a/docs/scripts/prettify/Apache-License-2.0.txt b/docs/scripts/prettify/Apache-License-2.0.txt old mode 100644 new mode 100755 diff --git a/docs/sender.js.html b/docs/sender.js.html index e049a45..3ca8b4f 100644 --- a/docs/sender.js.html +++ b/docs/sender.js.html @@ -1,32 +1,29 @@ - + - - + + JSDoc: Source: sender.js - - + + - - - - - - -
- -

Source: sender.js

- - - - - - -
+ + + + + +
+

Source: sender.js

+ +
-
'use strict';
+          
'use strict';
 
 /* eslint-disable no-unused-vars */
 
@@ -991,24 +988,29 @@ 

Source: sender.js

exports.DEFAULT_MAX_BUFFER_SIZE = DEFAULT_MAX_BUFFER_SIZE;
-
- - - - -
- - - -
- -
- Documentation generated by JSDoc 4.0.2 on Tue Aug 13 2024 14:27:30 GMT+0300 (Eastern European Summer Time) -
- - - - +
+
+ + + +
+ +
+ Documentation generated by + JSDoc 4.0.2 on Tue Aug 13 + 2024 14:27:30 GMT+0300 (Eastern European Summer Time) +
+ + + + diff --git a/docs/src_options.js.html b/docs/src_options.js.html index 313c6e6..0038d94 100644 --- a/docs/src_options.js.html +++ b/docs/src_options.js.html @@ -1,32 +1,29 @@ - + - - + + JSDoc: Source: src/options.js - - + + - - - - - - -
- -

Source: src/options.js

- - - - - - -
+ + + + + +
+

Source: src/options.js

+ +
-
'use strict';
+          
'use strict';
 
 const HTTP_PORT = 9000;
 const TCP_PORT = 9009;
@@ -420,24 +417,37 @@ 

Source: src/options.js

exports.TCPS = TCPS;
-
- - - - -
- - - -
- -
- Documentation generated by JSDoc 4.0.2 on Wed Apr 17 2024 03:38:42 GMT+0100 (British Summer Time) -
- - - - +
+
+ + + +
+ +
+ Documentation generated by + JSDoc 4.0.2 on Wed Apr 17 + 2024 03:38:42 GMT+0100 (British Summer Time) +
+ + + + diff --git a/docs/src_sender.js.html b/docs/src_sender.js.html index 2b50d7f..e822b39 100644 --- a/docs/src_sender.js.html +++ b/docs/src_sender.js.html @@ -1,32 +1,29 @@ - + - - + + JSDoc: Source: src/sender.js - - + + - - - - - - -
- -

Source: src/sender.js

- - - - - - -
+ + + + + +
+

Source: src/sender.js

+ +
-
'use strict';
+          
'use strict';
 
 /* eslint-disable no-unused-vars */
 
@@ -901,24 +898,37 @@ 

Source: src/sender.js

exports.DEFAULT_MAX_BUFFER_SIZE = DEFAULT_MAX_BUFFER_SIZE;
-
- - - - -
- - - -
- -
- Documentation generated by JSDoc 4.0.2 on Wed Apr 17 2024 03:38:42 GMT+0100 (British Summer Time) -
- - - - +
+
+ + + +
+ +
+ Documentation generated by + JSDoc 4.0.2 on Wed Apr 17 + 2024 03:38:42 GMT+0100 (British Summer Time) +
+ + + + diff --git a/eslint.config.mjs b/eslint.config.mjs new file mode 100644 index 0000000..31953fd --- /dev/null +++ b/eslint.config.mjs @@ -0,0 +1,9 @@ +// @ts-check + +import eslint from '@eslint/js'; +import tseslint from 'typescript-eslint'; + +export default tseslint.config( + eslint.configs.recommended, + tseslint.configs.recommended, +); \ No newline at end of file diff --git a/examples.manifest.yaml b/examples.manifest.yaml index 9274652..0c5454d 100644 --- a/examples.manifest.yaml +++ b/examples.manifest.yaml @@ -34,4 +34,4 @@ path: examples/from_conf.js header: |- NodeJS client library [repo](https://github.com/questdb/nodejs-questdb-client). - conf: http::addr=localhost:9000 \ No newline at end of file + conf: http::addr=localhost:9000 diff --git a/examples/auth.js b/examples/auth.js index 59faebd..543a313 100644 --- a/examples/auth.js +++ b/examples/auth.js @@ -1,17 +1,23 @@ -const { Sender } = require('@questdb/nodejs-client'); +const { Sender } = require("@questdb/nodejs-client"); async function run() { // authentication details - const CLIENT_ID = 'admin' - const PRIVATE_KEY = 'ZRxmCOQBpZoj2fZ-lEtqzVDkCre_ouF3ePpaQNDwoQk' + const CLIENT_ID = "admin"; + const PRIVATE_KEY = "ZRxmCOQBpZoj2fZ-lEtqzVDkCre_ouF3ePpaQNDwoQk"; const AUTH = { keyId: CLIENT_ID, - token: PRIVATE_KEY - } + token: PRIVATE_KEY, + }; // pass the authentication details to the sender - const sender = new Sender({ protocol: 'tcp', host: '127.0.0.1', port: 9009, bufferSize: 4096, auth: AUTH }) - await sender.connect() + const sender = new Sender({ + protocol: "tcp", + host: "127.0.0.1", + port: 9009, + bufferSize: 4096, + auth: AUTH, + }); + await sender.connect(); // send the data over the authenticated connection await sender @@ -20,7 +26,7 @@ async function run() { .symbol("side", "sell") .floatColumn("price", 2615.54) .floatColumn("amount", 0.00044) - .at(Date.now(), 'ms') + .at(Date.now(), "ms"); // add rows to the buffer of the sender await sender @@ -29,13 +35,13 @@ async function run() { .symbol("side", "sell") .floatColumn("price", 39269.98) .floatColumn("amount", 0.001) - .at(Date.now(), 'ms') + .at(Date.now(), "ms"); // flush the buffer of the sender, sending the data to QuestDB - await sender.flush() + await sender.flush(); // close the connection after all rows ingested - await sender.close() + await sender.close(); } -run().catch(console.error) +run().catch(console.error); diff --git a/examples/auth_tls.js b/examples/auth_tls.js index 3bb5b28..e5e1146 100644 --- a/examples/auth_tls.js +++ b/examples/auth_tls.js @@ -1,17 +1,23 @@ -const { Sender } = require('@questdb/nodejs-client'); +const { Sender } = require("@questdb/nodejs-client"); async function run() { // authentication details - const CLIENT_ID = 'admin' - const PRIVATE_KEY = 'ZRxmCOQBpZoj2fZ-lEtqzVDkCre_ouF3ePpaQNDwoQk' + const CLIENT_ID = "admin"; + const PRIVATE_KEY = "ZRxmCOQBpZoj2fZ-lEtqzVDkCre_ouF3ePpaQNDwoQk"; const AUTH = { keyId: CLIENT_ID, - token: PRIVATE_KEY - } + token: PRIVATE_KEY, + }; // pass the authentication details to the sender - const sender = new Sender({ protocol: 'tcps', host: '127.0.0.1', port: 9009, bufferSize: 4096, auth: AUTH }) - await sender.connect() + const sender = new Sender({ + protocol: "tcps", + host: "127.0.0.1", + port: 9009, + bufferSize: 4096, + auth: AUTH, + }); + await sender.connect(); // send the data over the authenticated connection await sender @@ -20,7 +26,7 @@ async function run() { .symbol("side", "sell") .floatColumn("price", 2615.54) .floatColumn("amount", 0.00044) - .at(Date.now(), 'ms') + .at(Date.now(), "ms"); // add rows to the buffer of the sender await sender @@ -29,13 +35,13 @@ async function run() { .symbol("side", "sell") .floatColumn("price", 39269.98) .floatColumn("amount", 0.001) - .at(Date.now(), 'ms') + .at(Date.now(), "ms"); // flush the buffer of the sender, sending the data to QuestDB - await sender.flush() + await sender.flush(); // close the connection after all rows ingested - await sender.close() + await sender.close(); } -run().catch(console.error) +run().catch(console.error); diff --git a/examples/basic.js b/examples/basic.js index fa65423..83b43d5 100644 --- a/examples/basic.js +++ b/examples/basic.js @@ -1,10 +1,14 @@ -const { Sender } = require('@questdb/nodejs-client') +const { Sender } = require("@questdb/nodejs-client"); async function run() { // create a sender with a 4KB buffer - const sender = new Sender({ protocol: 'tcp', host: '127.0.0.1', port: 9009, bufferSize: 4096 }) - await sender.connect() - + const sender = new Sender({ + protocol: "tcp", + host: "127.0.0.1", + port: 9009, + bufferSize: 4096, + }); + await sender.connect(); // add rows to the buffer of the sender await sender @@ -13,15 +17,15 @@ async function run() { .symbol("side", "sell") .floatColumn("price", 2615.54) .floatColumn("amount", 0.00044) - .at(Date.now(), 'ms') + .at(Date.now(), "ms"); // flush the buffer of the sender, sending the data to QuestDB // the buffer is cleared after the data is sent, and the sender is ready to accept new data - await sender.flush() + await sender.flush(); // close the connection after all rows ingested // unflushed data will be lost - await sender.close() + await sender.close(); } -run().then(console.log).catch(console.error) +run().then(console.log).catch(console.error); diff --git a/examples/from_conf.js b/examples/from_conf.js index 8a82563..e6ef5e7 100644 --- a/examples/from_conf.js +++ b/examples/from_conf.js @@ -1,8 +1,8 @@ -const { Sender } = require("@questdb/nodejs-client") +const { Sender } = require("@questdb/nodejs-client"); async function run() { // create a sender using HTTP protocol - const sender = Sender.fromConfig("http::addr=127.0.0.1:9000") + const sender = Sender.fromConfig("http::addr=127.0.0.1:9000"); // add rows to the buffer of the sender await sender @@ -11,15 +11,15 @@ async function run() { .symbol("side", "sell") .floatColumn("price", 2615.54) .floatColumn("amount", 0.00044) - .at(Date.now(), 'ms') + .at(Date.now(), "ms"); // flush the buffer of the sender, sending the data to QuestDB // the buffer is cleared after the data is sent, and the sender is ready to accept new data - await sender.flush() + await sender.flush(); // close the connection after all rows ingested // unflushed data will be lost - await sender.close() + await sender.close(); } -run().then(console.log).catch(console.error) +run().then(console.log).catch(console.error); diff --git a/examples/workers.js b/examples/workers.js index 9f90480..fb7b7d2 100644 --- a/examples/workers.js +++ b/examples/workers.js @@ -1,75 +1,84 @@ -const { Sender } = require('@questdb/nodejs-client') -const { Worker, isMainThread, parentPort, workerData } = require('worker_threads') +const { Sender } = require("@questdb/nodejs-client"); +const { + Worker, + isMainThread, + parentPort, + workerData, +} = require("worker_threads"); // fake venue // generates random prices and amounts for a ticker for max 5 seconds, then the feed closes function* venue(ticker) { - let end = false - setTimeout(() => { end = true; }, rndInt(5000)) - while (!end) { - yield {ticker, price: Math.random(), amount: Math.random()} - } + let end = false; + setTimeout(() => { + end = true; + }, rndInt(5000)); + while (!end) { + yield { ticker, price: Math.random(), amount: Math.random() }; + } } // market data feed simulator // uses the fake venue to deliver price and amount updates to the feed handler (onTick() callback) async function subscribe(ticker, onTick) { - const feed = venue(workerData.ticker) - let tick; - while (tick = feed.next().value) { - await onTick(tick); - await sleep(rndInt(30)) - } + const feed = venue(workerData.ticker); + let tick; + while ((tick = feed.next().value)) { + await onTick(tick); + await sleep(rndInt(30)); + } } async function run() { - if (isMainThread) { - const tickers = ['ETH-USD', 'BTC-USD', 'SOL-USD', 'DOGE-USD'] - // main thread to start a worker thread for each ticker - for (let ticker of tickers) { - const worker = new Worker(__filename, { workerData: { ticker: ticker } }) - .on('error', (err) => { throw err; }) - .on('exit', () => { console.log(`${ticker} thread exiting...`); }) - .on('message', (msg) => { - console.log(`Ingested ${msg.count} prices for ticker ${msg.ticker}`) - }); - } - } else { - // it is important that each worker has a dedicated sender object - // threads cannot share the sender because they would write into the same buffer - const sender = Sender.fromConfig('http::addr=127.0.0.1:9000') - - // subscribe for the market data of the ticker assigned to the worker - // ingest each price update into the database using the sender - let count = 0; - await subscribe(workerData.ticker, async (tick) => { - await sender - .table("trades") - .symbol("symbol", tick.ticker) - .symbol("side", "sell") - .floatColumn("price", tick.price) - .floatColumn("amount", tick.amount) - .at(Date.now(), 'ms') - await sender.flush() - count++; + if (isMainThread) { + const tickers = ["ETH-USD", "BTC-USD", "SOL-USD", "DOGE-USD"]; + // main thread to start a worker thread for each ticker + for (let ticker of tickers) { + const worker = new Worker(__filename, { workerData: { ticker: ticker } }) + .on("error", (err) => { + throw err; + }) + .on("exit", () => { + console.log(`${ticker} thread exiting...`); + }) + .on("message", (msg) => { + console.log(`Ingested ${msg.count} prices for ticker ${msg.ticker}`); }); + } + } else { + // it is important that each worker has a dedicated sender object + // threads cannot share the sender because they would write into the same buffer + const sender = Sender.fromConfig("http::addr=127.0.0.1:9000"); - // let the main thread know how many prices were ingested - parentPort.postMessage({ticker: workerData.ticker, count}) + // subscribe for the market data of the ticker assigned to the worker + // ingest each price update into the database using the sender + let count = 0; + await subscribe(workerData.ticker, async (tick) => { + await sender + .table("trades") + .symbol("symbol", tick.ticker) + .symbol("side", "sell") + .floatColumn("price", tick.price) + .floatColumn("amount", tick.amount) + .at(Date.now(), "ms"); + await sender.flush(); + count++; + }); - // close the connection to the database - await sender.close(); - } + // let the main thread know how many prices were ingested + parentPort.postMessage({ ticker: workerData.ticker, count }); + + // close the connection to the database + await sender.close(); + } } function sleep(ms) { - return new Promise(resolve => setTimeout(resolve, ms)); + return new Promise((resolve) => setTimeout(resolve, ms)); } function rndInt(limit) { - return Math.floor((Math.random() * limit) + 1) + return Math.floor(Math.random() * limit + 1); } -run() - .then(console.log) - .catch(console.error) +run().then(console.log).catch(console.error); diff --git a/index.js b/index.js deleted file mode 100644 index ad78262..0000000 --- a/index.js +++ /dev/null @@ -1,11 +0,0 @@ -'use strict'; - -/** - * A Node.js client for QuestDB. - * - * @module @questdb/nodejs-client - */ - -const { Sender } = require('./src/sender'); - -module.exports = { Sender }; diff --git a/notes.md b/notes.md deleted file mode 100644 index d2f840e..0000000 --- a/notes.md +++ /dev/null @@ -1,2 +0,0 @@ -### Certs used in tests generated by running: -> ./scripts/generateCerts.sh . questdbPwd123 diff --git a/package-lock.json b/package-lock.json deleted file mode 100644 index 0763b4c..0000000 --- a/package-lock.json +++ /dev/null @@ -1,9112 +0,0 @@ -{ - "name": "@questdb/nodejs-client", - "version": "3.0.0", - "lockfileVersion": 2, - "requires": true, - "packages": { - "": { - "name": "@questdb/nodejs-client", - "version": "3.0.0", - "license": "Apache-2.0", - "devDependencies": { - "eslint": "^8.50.0", - "jest": "^29.7.0", - "jest-jasmine2": "^29.7.0", - "testcontainers": "^10.2.1" - } - }, - "node_modules/@aashutoshrathi/word-wrap": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/@aashutoshrathi/word-wrap/-/word-wrap-1.2.6.tgz", - "integrity": "sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/@ampproject/remapping": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.1.tgz", - "integrity": "sha512-lFMjJTrFL3j7L9yBxwYfCq2k6qqwHyzuUl/XBnif78PWTJYyL/dfowQHWE3sp6U6ZzqWiiIZnpTMO96zhkjwtg==", - "dev": true, - "dependencies": { - "@jridgewell/gen-mapping": "^0.3.0", - "@jridgewell/trace-mapping": "^0.3.9" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@babel/code-frame": { - "version": "7.23.4", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.23.4.tgz", - "integrity": "sha512-r1IONyb6Ia+jYR2vvIDhdWdlTGhqbBoFqLTQidzZ4kepUFH15ejXvFHxCVbtl7BOXIudsIubf4E81xeA3h3IXA==", - "dev": true, - "dependencies": { - "@babel/highlight": "^7.23.4", - "chalk": "^2.4.2" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/code-frame/node_modules/ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "dependencies": { - "color-convert": "^1.9.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/code-frame/node_modules/chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, - "dependencies": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/code-frame/node_modules/color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dev": true, - "dependencies": { - "color-name": "1.1.3" - } - }, - "node_modules/@babel/code-frame/node_modules/color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", - "dev": true - }, - "node_modules/@babel/code-frame/node_modules/escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", - "dev": true, - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/@babel/code-frame/node_modules/has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/code-frame/node_modules/supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "dependencies": { - "has-flag": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/compat-data": { - "version": "7.22.20", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.22.20.tgz", - "integrity": "sha512-BQYjKbpXjoXwFW5jGqiizJQQT/aC7pFm9Ok1OWssonuguICi264lbgMzRp2ZMmRSlfkX6DsWDDcsrctK8Rwfiw==", - "dev": true, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/core": { - "version": "7.23.0", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.23.0.tgz", - "integrity": "sha512-97z/ju/Jy1rZmDxybphrBuI+jtJjFVoz7Mr9yUQVVVi+DNZE333uFQeMOqcCIy1x3WYBIbWftUSLmbNXNT7qFQ==", - "dev": true, - "dependencies": { - "@ampproject/remapping": "^2.2.0", - "@babel/code-frame": "^7.22.13", - "@babel/generator": "^7.23.0", - "@babel/helper-compilation-targets": "^7.22.15", - "@babel/helper-module-transforms": "^7.23.0", - "@babel/helpers": "^7.23.0", - "@babel/parser": "^7.23.0", - "@babel/template": "^7.22.15", - "@babel/traverse": "^7.23.0", - "@babel/types": "^7.23.0", - "convert-source-map": "^2.0.0", - "debug": "^4.1.0", - "gensync": "^1.0.0-beta.2", - "json5": "^2.2.3", - "semver": "^6.3.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/babel" - } - }, - "node_modules/@babel/generator": { - "version": "7.23.4", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.23.4.tgz", - "integrity": "sha512-esuS49Cga3HcThFNebGhlgsrVLkvhqvYDTzgjfFFlHJcIfLe5jFmRRfCQ1KuBfc4Jrtn3ndLgKWAKjBE+IraYQ==", - "dev": true, - "dependencies": { - "@babel/types": "^7.23.4", - "@jridgewell/gen-mapping": "^0.3.2", - "@jridgewell/trace-mapping": "^0.3.17", - "jsesc": "^2.5.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-compilation-targets": { - "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.22.15.tgz", - "integrity": "sha512-y6EEzULok0Qvz8yyLkCvVX+02ic+By2UdOhylwUOvOn9dvYc9mKICJuuU1n1XBI02YWsNsnrY1kc6DVbjcXbtw==", - "dev": true, - "dependencies": { - "@babel/compat-data": "^7.22.9", - "@babel/helper-validator-option": "^7.22.15", - "browserslist": "^4.21.9", - "lru-cache": "^5.1.1", - "semver": "^6.3.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-environment-visitor": { - "version": "7.22.20", - "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz", - "integrity": "sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==", - "dev": true, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-function-name": { - "version": "7.23.0", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz", - "integrity": "sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==", - "dev": true, - "dependencies": { - "@babel/template": "^7.22.15", - "@babel/types": "^7.23.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-hoist-variables": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz", - "integrity": "sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==", - "dev": true, - "dependencies": { - "@babel/types": "^7.22.5" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-module-imports": { - "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.22.15.tgz", - "integrity": "sha512-0pYVBnDKZO2fnSPCrgM/6WMc7eS20Fbok+0r88fp+YtWVLZrp4CkafFGIp+W0VKw4a22sgebPT99y+FDNMdP4w==", - "dev": true, - "dependencies": { - "@babel/types": "^7.22.15" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-module-transforms": { - "version": "7.23.0", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.23.0.tgz", - "integrity": "sha512-WhDWw1tdrlT0gMgUJSlX0IQvoO1eN279zrAUbVB+KpV2c3Tylz8+GnKOLllCS6Z/iZQEyVYxhZVUdPTqs2YYPw==", - "dev": true, - "dependencies": { - "@babel/helper-environment-visitor": "^7.22.20", - "@babel/helper-module-imports": "^7.22.15", - "@babel/helper-simple-access": "^7.22.5", - "@babel/helper-split-export-declaration": "^7.22.6", - "@babel/helper-validator-identifier": "^7.22.20" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/helper-plugin-utils": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.22.5.tgz", - "integrity": "sha512-uLls06UVKgFG9QD4OeFYLEGteMIAa5kpTPcFL28yuCIIzsf6ZyKZMllKVOCZFhiZ5ptnwX4mtKdWCBE/uT4amg==", - "dev": true, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-simple-access": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.22.5.tgz", - "integrity": "sha512-n0H99E/K+Bika3++WNL17POvo4rKWZ7lZEp1Q+fStVbUi8nxPQEBOlTmCOxW/0JsS56SKKQ+ojAe2pHKJHN35w==", - "dev": true, - "dependencies": { - "@babel/types": "^7.22.5" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-split-export-declaration": { - "version": "7.22.6", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz", - "integrity": "sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==", - "dev": true, - "dependencies": { - "@babel/types": "^7.22.5" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-string-parser": { - "version": "7.23.4", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.23.4.tgz", - "integrity": "sha512-803gmbQdqwdf4olxrX4AJyFBV/RTr3rSmOj0rKwesmzlfhYNDEs+/iOcznzpNWlJlIlTJC2QfPFcHB6DlzdVLQ==", - "dev": true, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-validator-identifier": { - "version": "7.22.20", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz", - "integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==", - "dev": true, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-validator-option": { - "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.22.15.tgz", - "integrity": "sha512-bMn7RmyFjY/mdECUbgn9eoSY4vqvacUnS9i9vGAGttgFWesO6B4CYWA7XlpbWgBt71iv/hfbPlynohStqnu5hA==", - "dev": true, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helpers": { - "version": "7.23.1", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.23.1.tgz", - "integrity": "sha512-chNpneuK18yW5Oxsr+t553UZzzAs3aZnFm4bxhebsNTeshrC95yA7l5yl7GBAG+JG1rF0F7zzD2EixK9mWSDoA==", - "dev": true, - "dependencies": { - "@babel/template": "^7.22.15", - "@babel/traverse": "^7.23.0", - "@babel/types": "^7.23.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/highlight": { - "version": "7.23.4", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.23.4.tgz", - "integrity": "sha512-acGdbYSfp2WheJoJm/EBBBLh/ID8KDc64ISZ9DYtBmC8/Q204PZJLHyzeB5qMzJ5trcOkybd78M4x2KWsUq++A==", - "dev": true, - "dependencies": { - "@babel/helper-validator-identifier": "^7.22.20", - "chalk": "^2.4.2", - "js-tokens": "^4.0.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/highlight/node_modules/ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "dependencies": { - "color-convert": "^1.9.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/highlight/node_modules/chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, - "dependencies": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/highlight/node_modules/color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dev": true, - "dependencies": { - "color-name": "1.1.3" - } - }, - "node_modules/@babel/highlight/node_modules/color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", - "dev": true - }, - "node_modules/@babel/highlight/node_modules/escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", - "dev": true, - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/@babel/highlight/node_modules/has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/highlight/node_modules/supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "dependencies": { - "has-flag": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/parser": { - "version": "7.23.4", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.4.tgz", - "integrity": "sha512-vf3Xna6UEprW+7t6EtOmFpHNAuxw3xqPZghy+brsnusscJRW5BMUzzHZc5ICjULee81WeUV2jjakG09MDglJXQ==", - "dev": true, - "bin": { - "parser": "bin/babel-parser.js" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@babel/plugin-syntax-async-generators": { - "version": "7.8.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", - "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-bigint": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz", - "integrity": "sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-class-properties": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", - "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.12.13" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-import-meta": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", - "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-json-strings": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", - "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-jsx": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.22.5.tgz", - "integrity": "sha512-gvyP4hZrgrs/wWMaocvxZ44Hw0b3W8Pe+cMxc8V1ULQ07oh8VNbIRaoD1LRZVTvD+0nieDKjfgKg89sD7rrKrg==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-logical-assignment-operators": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", - "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-nullish-coalescing-operator": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", - "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-numeric-separator": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", - "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-object-rest-spread": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", - "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-optional-catch-binding": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", - "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-optional-chaining": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", - "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-top-level-await": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", - "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.14.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-typescript": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.22.5.tgz", - "integrity": "sha512-1mS2o03i7t1c6VzH6fdQ3OA8tcEIxwG18zIPRp+UY1Ihv6W+XZzBCVxExF9upussPXJ0xE9XRHwMoNs1ep/nRQ==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/template": { - "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.15.tgz", - "integrity": "sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w==", - "dev": true, - "dependencies": { - "@babel/code-frame": "^7.22.13", - "@babel/parser": "^7.22.15", - "@babel/types": "^7.22.15" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/traverse": { - "version": "7.23.4", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.23.4.tgz", - "integrity": "sha512-IYM8wSUwunWTB6tFC2dkKZhxbIjHoWemdK+3f8/wq8aKhbUscxD5MX72ubd90fxvFknaLPeGw5ycU84V1obHJg==", - "dev": true, - "dependencies": { - "@babel/code-frame": "^7.23.4", - "@babel/generator": "^7.23.4", - "@babel/helper-environment-visitor": "^7.22.20", - "@babel/helper-function-name": "^7.23.0", - "@babel/helper-hoist-variables": "^7.22.5", - "@babel/helper-split-export-declaration": "^7.22.6", - "@babel/parser": "^7.23.4", - "@babel/types": "^7.23.4", - "debug": "^4.1.0", - "globals": "^11.1.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/traverse/node_modules/globals": { - "version": "11.12.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", - "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/types": { - "version": "7.23.4", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.4.tgz", - "integrity": "sha512-7uIFwVYpoplT5jp/kVv6EF93VaJ8H+Yn5IczYiaAi98ajzjfoZfslet/e0sLh+wVBjb2qqIut1b0S26VSafsSQ==", - "dev": true, - "dependencies": { - "@babel/helper-string-parser": "^7.23.4", - "@babel/helper-validator-identifier": "^7.22.20", - "to-fast-properties": "^2.0.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@balena/dockerignore": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@balena/dockerignore/-/dockerignore-1.0.2.tgz", - "integrity": "sha512-wMue2Sy4GAVTk6Ic4tJVcnfdau+gx2EnG7S+uAEe+TWJFqE4YoWN4/H8MSLj4eYJKxGg26lZwboEniNiNwZQ6Q==", - "dev": true - }, - "node_modules/@bcoe/v8-coverage": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", - "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", - "dev": true - }, - "node_modules/@eslint-community/eslint-utils": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", - "integrity": "sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==", - "dev": true, - "dependencies": { - "eslint-visitor-keys": "^3.3.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "peerDependencies": { - "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" - } - }, - "node_modules/@eslint-community/regexpp": { - "version": "4.9.1", - "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.9.1.tgz", - "integrity": "sha512-Y27x+MBLjXa+0JWDhykM3+JE+il3kHKAEqabfEWq3SDhZjLYb6/BHL/JKFnH3fe207JaXkyDo685Oc2Glt6ifA==", - "dev": true, - "engines": { - "node": "^12.0.0 || ^14.0.0 || >=16.0.0" - } - }, - "node_modules/@eslint/eslintrc": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.2.tgz", - "integrity": "sha512-+wvgpDsrB1YqAMdEUCcnTlpfVBH7Vqn6A/NT3D8WVXFIaKMlErPIZT3oCIAVCOtarRpMtelZLqJeU3t7WY6X6g==", - "dev": true, - "dependencies": { - "ajv": "^6.12.4", - "debug": "^4.3.2", - "espree": "^9.6.0", - "globals": "^13.19.0", - "ignore": "^5.2.0", - "import-fresh": "^3.2.1", - "js-yaml": "^4.1.0", - "minimatch": "^3.1.2", - "strip-json-comments": "^3.1.1" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/@eslint/js": { - "version": "8.50.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.50.0.tgz", - "integrity": "sha512-NCC3zz2+nvYd+Ckfh87rA47zfu2QsQpvc6k1yzTk+b9KzRj0wkGa8LSoGOXN6Zv4lRf/EIoZ80biDh9HOI+RNQ==", - "dev": true, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - } - }, - "node_modules/@humanwhocodes/config-array": { - "version": "0.11.11", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.11.tgz", - "integrity": "sha512-N2brEuAadi0CcdeMXUkhbZB84eskAc8MEX1By6qEchoVywSgXPIjou4rYsl0V3Hj0ZnuGycGCjdNgockbzeWNA==", - "dev": true, - "dependencies": { - "@humanwhocodes/object-schema": "^1.2.1", - "debug": "^4.1.1", - "minimatch": "^3.0.5" - }, - "engines": { - "node": ">=10.10.0" - } - }, - "node_modules/@humanwhocodes/module-importer": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", - "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", - "dev": true, - "engines": { - "node": ">=12.22" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/nzakas" - } - }, - "node_modules/@humanwhocodes/object-schema": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz", - "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", - "dev": true - }, - "node_modules/@istanbuljs/load-nyc-config": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", - "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", - "dev": true, - "dependencies": { - "camelcase": "^5.3.1", - "find-up": "^4.1.0", - "get-package-type": "^0.1.0", - "js-yaml": "^3.13.1", - "resolve-from": "^5.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@istanbuljs/load-nyc-config/node_modules/argparse": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", - "dev": true, - "dependencies": { - "sprintf-js": "~1.0.2" - } - }, - "node_modules/@istanbuljs/load-nyc-config/node_modules/js-yaml": { - "version": "3.14.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", - "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", - "dev": true, - "dependencies": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "node_modules/@istanbuljs/load-nyc-config/node_modules/resolve-from": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", - "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/@istanbuljs/schema": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", - "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/@jest/console": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/console/-/console-29.7.0.tgz", - "integrity": "sha512-5Ni4CU7XHQi32IJ398EEP4RrB8eV09sXP2ROqD4bksHrnTree52PsxvX8tpL8LvTZ3pFzXyPbNQReSN41CAhOg==", - "dev": true, - "dependencies": { - "@jest/types": "^29.6.3", - "@types/node": "*", - "chalk": "^4.0.0", - "jest-message-util": "^29.7.0", - "jest-util": "^29.7.0", - "slash": "^3.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/core": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/core/-/core-29.7.0.tgz", - "integrity": "sha512-n7aeXWKMnGtDA48y8TLWJPJmLmmZ642Ceo78cYWEpiD7FzDgmNDV/GCVRorPABdXLJZ/9wzzgZAlHjXjxDHGsg==", - "dev": true, - "dependencies": { - "@jest/console": "^29.7.0", - "@jest/reporters": "^29.7.0", - "@jest/test-result": "^29.7.0", - "@jest/transform": "^29.7.0", - "@jest/types": "^29.6.3", - "@types/node": "*", - "ansi-escapes": "^4.2.1", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "exit": "^0.1.2", - "graceful-fs": "^4.2.9", - "jest-changed-files": "^29.7.0", - "jest-config": "^29.7.0", - "jest-haste-map": "^29.7.0", - "jest-message-util": "^29.7.0", - "jest-regex-util": "^29.6.3", - "jest-resolve": "^29.7.0", - "jest-resolve-dependencies": "^29.7.0", - "jest-runner": "^29.7.0", - "jest-runtime": "^29.7.0", - "jest-snapshot": "^29.7.0", - "jest-util": "^29.7.0", - "jest-validate": "^29.7.0", - "jest-watcher": "^29.7.0", - "micromatch": "^4.0.4", - "pretty-format": "^29.7.0", - "slash": "^3.0.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - }, - "peerDependencies": { - "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" - }, - "peerDependenciesMeta": { - "node-notifier": { - "optional": true - } - } - }, - "node_modules/@jest/environment": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-29.7.0.tgz", - "integrity": "sha512-aQIfHDq33ExsN4jP1NWGXhxgQ/wixs60gDiKO+XVMd8Mn0NWPWgc34ZQDTb2jKaUWQ7MuwoitXAsN2XVXNMpAw==", - "dev": true, - "dependencies": { - "@jest/fake-timers": "^29.7.0", - "@jest/types": "^29.6.3", - "@types/node": "*", - "jest-mock": "^29.7.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/expect": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-29.7.0.tgz", - "integrity": "sha512-8uMeAMycttpva3P1lBHB8VciS9V0XAr3GymPpipdyQXbBcuhkLQOSe8E/p92RyAdToS6ZD1tFkX+CkhoECE0dQ==", - "dev": true, - "dependencies": { - "expect": "^29.7.0", - "jest-snapshot": "^29.7.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/expect-utils": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-29.7.0.tgz", - "integrity": "sha512-GlsNBWiFQFCVi9QVSx7f5AgMeLxe9YCCs5PuP2O2LdjDAA8Jh9eX7lA1Jq/xdXw3Wb3hyvlFNfZIfcRetSzYcA==", - "dev": true, - "dependencies": { - "jest-get-type": "^29.6.3" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/fake-timers": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-29.7.0.tgz", - "integrity": "sha512-q4DH1Ha4TTFPdxLsqDXK1d3+ioSL7yL5oCMJZgDYm6i+6CygW5E5xVr/D1HdsGxjt1ZWSfUAs9OxSB/BNelWrQ==", - "dev": true, - "dependencies": { - "@jest/types": "^29.6.3", - "@sinonjs/fake-timers": "^10.0.2", - "@types/node": "*", - "jest-message-util": "^29.7.0", - "jest-mock": "^29.7.0", - "jest-util": "^29.7.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/globals": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-29.7.0.tgz", - "integrity": "sha512-mpiz3dutLbkW2MNFubUGUEVLkTGiqW6yLVTA+JbP6fI6J5iL9Y0Nlg8k95pcF8ctKwCS7WVxteBs29hhfAotzQ==", - "dev": true, - "dependencies": { - "@jest/environment": "^29.7.0", - "@jest/expect": "^29.7.0", - "@jest/types": "^29.6.3", - "jest-mock": "^29.7.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/reporters": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-29.7.0.tgz", - "integrity": "sha512-DApq0KJbJOEzAFYjHADNNxAE3KbhxQB1y5Kplb5Waqw6zVbuWatSnMjE5gs8FUgEPmNsnZA3NCWl9NG0ia04Pg==", - "dev": true, - "dependencies": { - "@bcoe/v8-coverage": "^0.2.3", - "@jest/console": "^29.7.0", - "@jest/test-result": "^29.7.0", - "@jest/transform": "^29.7.0", - "@jest/types": "^29.6.3", - "@jridgewell/trace-mapping": "^0.3.18", - "@types/node": "*", - "chalk": "^4.0.0", - "collect-v8-coverage": "^1.0.0", - "exit": "^0.1.2", - "glob": "^7.1.3", - "graceful-fs": "^4.2.9", - "istanbul-lib-coverage": "^3.0.0", - "istanbul-lib-instrument": "^6.0.0", - "istanbul-lib-report": "^3.0.0", - "istanbul-lib-source-maps": "^4.0.0", - "istanbul-reports": "^3.1.3", - "jest-message-util": "^29.7.0", - "jest-util": "^29.7.0", - "jest-worker": "^29.7.0", - "slash": "^3.0.0", - "string-length": "^4.0.1", - "strip-ansi": "^6.0.0", - "v8-to-istanbul": "^9.0.1" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - }, - "peerDependencies": { - "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" - }, - "peerDependenciesMeta": { - "node-notifier": { - "optional": true - } - } - }, - "node_modules/@jest/schemas": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", - "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", - "dev": true, - "dependencies": { - "@sinclair/typebox": "^0.27.8" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/source-map": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-29.6.3.tgz", - "integrity": "sha512-MHjT95QuipcPrpLM+8JMSzFx6eHp5Bm+4XeFDJlwsvVBjmKNiIAvasGK2fxz2WbGRlnvqehFbh07MMa7n3YJnw==", - "dev": true, - "dependencies": { - "@jridgewell/trace-mapping": "^0.3.18", - "callsites": "^3.0.0", - "graceful-fs": "^4.2.9" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/test-result": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-29.7.0.tgz", - "integrity": "sha512-Fdx+tv6x1zlkJPcWXmMDAG2HBnaR9XPSd5aDWQVsfrZmLVT3lU1cwyxLgRmXR9yrq4NBoEm9BMsfgFzTQAbJYA==", - "dev": true, - "dependencies": { - "@jest/console": "^29.7.0", - "@jest/types": "^29.6.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "collect-v8-coverage": "^1.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/test-sequencer": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-29.7.0.tgz", - "integrity": "sha512-GQwJ5WZVrKnOJuiYiAF52UNUJXgTZx1NHjFSEB0qEMmSZKAkdMoIzw/Cj6x6NF4AvV23AUqDpFzQkN/eYCYTxw==", - "dev": true, - "dependencies": { - "@jest/test-result": "^29.7.0", - "graceful-fs": "^4.2.9", - "jest-haste-map": "^29.7.0", - "slash": "^3.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/transform": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-29.7.0.tgz", - "integrity": "sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw==", - "dev": true, - "dependencies": { - "@babel/core": "^7.11.6", - "@jest/types": "^29.6.3", - "@jridgewell/trace-mapping": "^0.3.18", - "babel-plugin-istanbul": "^6.1.1", - "chalk": "^4.0.0", - "convert-source-map": "^2.0.0", - "fast-json-stable-stringify": "^2.1.0", - "graceful-fs": "^4.2.9", - "jest-haste-map": "^29.7.0", - "jest-regex-util": "^29.6.3", - "jest-util": "^29.7.0", - "micromatch": "^4.0.4", - "pirates": "^4.0.4", - "slash": "^3.0.0", - "write-file-atomic": "^4.0.2" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/types": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", - "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", - "dev": true, - "dependencies": { - "@jest/schemas": "^29.6.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz", - "integrity": "sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==", - "dev": true, - "dependencies": { - "@jridgewell/set-array": "^1.0.1", - "@jridgewell/sourcemap-codec": "^1.4.10", - "@jridgewell/trace-mapping": "^0.3.9" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz", - "integrity": "sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA==", - "dev": true, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/set-array": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz", - "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==", - "dev": true, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.4.15", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", - "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==", - "dev": true - }, - "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.19", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.19.tgz", - "integrity": "sha512-kf37QtfW+Hwx/buWGMPcR60iF9ziHa6r/CZJIHbmcm4+0qrXiVdxegAH0F6yddEVQ7zdkjcGCgCzUu+BcbhQxw==", - "dev": true, - "dependencies": { - "@jridgewell/resolve-uri": "^3.1.0", - "@jridgewell/sourcemap-codec": "^1.4.14" - } - }, - "node_modules/@nodelib/fs.scandir": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", - "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", - "dev": true, - "dependencies": { - "@nodelib/fs.stat": "2.0.5", - "run-parallel": "^1.1.9" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@nodelib/fs.stat": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", - "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", - "dev": true, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@nodelib/fs.walk": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", - "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", - "dev": true, - "dependencies": { - "@nodelib/fs.scandir": "2.1.5", - "fastq": "^1.6.0" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@sinclair/typebox": { - "version": "0.27.8", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", - "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==", - "dev": true - }, - "node_modules/@sinonjs/commons": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.0.tgz", - "integrity": "sha512-jXBtWAF4vmdNmZgD5FoKsVLv3rPgDnLgPbU84LIJ3otV44vJlDRokVng5v8NFJdCf/da9legHcKaRuZs4L7faA==", - "dev": true, - "dependencies": { - "type-detect": "4.0.8" - } - }, - "node_modules/@sinonjs/fake-timers": { - "version": "10.3.0", - "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-10.3.0.tgz", - "integrity": "sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA==", - "dev": true, - "dependencies": { - "@sinonjs/commons": "^3.0.0" - } - }, - "node_modules/@types/babel__core": { - "version": "7.20.2", - "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.2.tgz", - "integrity": "sha512-pNpr1T1xLUc2l3xJKuPtsEky3ybxN3m4fJkknfIpTCTfIZCDW57oAg+EfCgIIp2rvCe0Wn++/FfodDS4YXxBwA==", - "dev": true, - "dependencies": { - "@babel/parser": "^7.20.7", - "@babel/types": "^7.20.7", - "@types/babel__generator": "*", - "@types/babel__template": "*", - "@types/babel__traverse": "*" - } - }, - "node_modules/@types/babel__generator": { - "version": "7.6.5", - "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.5.tgz", - "integrity": "sha512-h9yIuWbJKdOPLJTbmSpPzkF67e659PbQDba7ifWm5BJ8xTv+sDmS7rFmywkWOvXedGTivCdeGSIIX8WLcRTz8w==", - "dev": true, - "dependencies": { - "@babel/types": "^7.0.0" - } - }, - "node_modules/@types/babel__template": { - "version": "7.4.2", - "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.2.tgz", - "integrity": "sha512-/AVzPICMhMOMYoSx9MoKpGDKdBRsIXMNByh1PXSZoa+v6ZoLa8xxtsT/uLQ/NJm0XVAWl/BvId4MlDeXJaeIZQ==", - "dev": true, - "dependencies": { - "@babel/parser": "^7.1.0", - "@babel/types": "^7.0.0" - } - }, - "node_modules/@types/babel__traverse": { - "version": "7.20.2", - "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.20.2.tgz", - "integrity": "sha512-ojlGK1Hsfce93J0+kn3H5R73elidKUaZonirN33GSmgTUMpzI/MIFfSpF3haANe3G1bEBS9/9/QEqwTzwqFsKw==", - "dev": true, - "dependencies": { - "@babel/types": "^7.20.7" - } - }, - "node_modules/@types/graceful-fs": { - "version": "4.1.7", - "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.7.tgz", - "integrity": "sha512-MhzcwU8aUygZroVwL2jeYk6JisJrPl/oov/gsgGCue9mkgl9wjGbzReYQClxiUgFDnib9FuHqTndccKeZKxTRw==", - "dev": true, - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/istanbul-lib-coverage": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.4.tgz", - "integrity": "sha512-z/QT1XN4K4KYuslS23k62yDIDLwLFkzxOuMplDtObz0+y7VqJCaO2o+SPwHCvLFZh7xazvvoor2tA/hPz9ee7g==", - "dev": true - }, - "node_modules/@types/istanbul-lib-report": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", - "integrity": "sha512-gPQuzaPR5h/djlAv2apEG1HVOyj1IUs7GpfMZixU0/0KXT3pm64ylHuMUI1/Akh+sq/iikxg6Z2j+fcMDXaaTQ==", - "dev": true, - "dependencies": { - "@types/istanbul-lib-coverage": "*" - } - }, - "node_modules/@types/istanbul-reports": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.2.tgz", - "integrity": "sha512-kv43F9eb3Lhj+lr/Hn6OcLCs/sSM8bt+fIaP11rCYngfV6NVjzWXJ17owQtDQTL9tQ8WSLUrGsSJ6rJz0F1w1A==", - "dev": true, - "dependencies": { - "@types/istanbul-lib-report": "*" - } - }, - "node_modules/@types/node": { - "version": "18.6.1", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.6.1.tgz", - "integrity": "sha512-z+2vB6yDt1fNwKOeGbckpmirO+VBDuQqecXkgeIqDlaOtmKn6hPR/viQ8cxCfqLU4fTlvM3+YjM367TukWdxpg==", - "dev": true - }, - "node_modules/@types/ssh2-streams": { - "version": "0.1.9", - "resolved": "https://registry.npmjs.org/@types/ssh2-streams/-/ssh2-streams-0.1.9.tgz", - "integrity": "sha512-I2J9jKqfmvXLR5GomDiCoHrEJ58hAOmFrekfFqmCFd+A6gaEStvWnPykoWUwld1PNg4G5ag1LwdA+Lz1doRJqg==", - "dev": true, - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/stack-utils": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.1.tgz", - "integrity": "sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw==", - "dev": true - }, - "node_modules/@types/yargs": { - "version": "17.0.26", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.26.tgz", - "integrity": "sha512-Y3vDy2X6zw/ZCumcwLpdhM5L7jmyGpmBCTYMHDLqT2IKVMYRRLdv6ZakA+wxhra6Z/3bwhNbNl9bDGXaFU+6rw==", - "dev": true, - "dependencies": { - "@types/yargs-parser": "*" - } - }, - "node_modules/@types/yargs-parser": { - "version": "21.0.1", - "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.1.tgz", - "integrity": "sha512-axdPBuLuEJt0c4yI5OZssC19K2Mq1uKdrfZBzuxLvaztgqUtFYZUNw7lETExPYJR9jdEoIg4mb7RQKRQzOkeGQ==", - "dev": true - }, - "node_modules/acorn": { - "version": "8.10.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.10.0.tgz", - "integrity": "sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw==", - "dev": true, - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/acorn-jsx": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", - "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", - "dev": true, - "peerDependencies": { - "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" - } - }, - "node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "dev": true, - "dependencies": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/ansi-escapes": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", - "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", - "dev": true, - "dependencies": { - "type-fest": "^0.21.3" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/ansi-escapes/node_modules/type-fest": { - "version": "0.21.3", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", - "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/anymatch": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", - "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", - "dev": true, - "dependencies": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/archiver": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/archiver/-/archiver-5.3.1.tgz", - "integrity": "sha512-8KyabkmbYrH+9ibcTScQ1xCJC/CGcugdVIwB+53f5sZziXgwUh3iXlAlANMxcZyDEfTHMe6+Z5FofV8nopXP7w==", - "dev": true, - "dependencies": { - "archiver-utils": "^2.1.0", - "async": "^3.2.3", - "buffer-crc32": "^0.2.1", - "readable-stream": "^3.6.0", - "readdir-glob": "^1.0.0", - "tar-stream": "^2.2.0", - "zip-stream": "^4.1.0" - }, - "engines": { - "node": ">= 10" - } - }, - "node_modules/archiver-utils": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/archiver-utils/-/archiver-utils-2.1.0.tgz", - "integrity": "sha512-bEL/yUb/fNNiNTuUz979Z0Yg5L+LzLxGJz8x79lYmR54fmTIb6ob/hNQgkQnIUDWIFjZVQwl9Xs356I6BAMHfw==", - "dev": true, - "dependencies": { - "glob": "^7.1.4", - "graceful-fs": "^4.2.0", - "lazystream": "^1.0.0", - "lodash.defaults": "^4.2.0", - "lodash.difference": "^4.5.0", - "lodash.flatten": "^4.4.0", - "lodash.isplainobject": "^4.0.6", - "lodash.union": "^4.6.0", - "normalize-path": "^3.0.0", - "readable-stream": "^2.0.0" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/archiver-utils/node_modules/readable-stream": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", - "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", - "dev": true, - "dependencies": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "node_modules/archiver-utils/node_modules/string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dev": true, - "dependencies": { - "safe-buffer": "~5.1.0" - } - }, - "node_modules/argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true - }, - "node_modules/asn1": { - "version": "0.2.6", - "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.6.tgz", - "integrity": "sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==", - "dev": true, - "dependencies": { - "safer-buffer": "~2.1.0" - } - }, - "node_modules/async": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/async/-/async-3.2.4.tgz", - "integrity": "sha512-iAB+JbDEGXhyIUavoDl9WP/Jj106Kz9DEn1DPgYw5ruDn0e3Wgi3sKFm55sASdGBNOQB8F59d9qQ7deqrHA8wQ==", - "dev": true - }, - "node_modules/async-lock": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/async-lock/-/async-lock-1.4.0.tgz", - "integrity": "sha512-coglx5yIWuetakm3/1dsX9hxCNox22h7+V80RQOu2XUUMidtArxKoZoOtHUPuR84SycKTXzgGzAUR5hJxujyJQ==", - "dev": true - }, - "node_modules/b4a": { - "version": "1.6.4", - "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.6.4.tgz", - "integrity": "sha512-fpWrvyVHEKyeEvbKZTVOeZF3VSKKWtJxFIxX/jaVPf+cLbGUSitjb49pHLqPV2BUNNZ0LcoeEGfE/YCpyDYHIw==", - "dev": true - }, - "node_modules/babel-jest": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.7.0.tgz", - "integrity": "sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg==", - "dev": true, - "dependencies": { - "@jest/transform": "^29.7.0", - "@types/babel__core": "^7.1.14", - "babel-plugin-istanbul": "^6.1.1", - "babel-preset-jest": "^29.6.3", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.9", - "slash": "^3.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - }, - "peerDependencies": { - "@babel/core": "^7.8.0" - } - }, - "node_modules/babel-plugin-istanbul": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz", - "integrity": "sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.0.0", - "@istanbuljs/load-nyc-config": "^1.0.0", - "@istanbuljs/schema": "^0.1.2", - "istanbul-lib-instrument": "^5.0.4", - "test-exclude": "^6.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/babel-plugin-istanbul/node_modules/istanbul-lib-instrument": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz", - "integrity": "sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==", - "dev": true, - "dependencies": { - "@babel/core": "^7.12.3", - "@babel/parser": "^7.14.7", - "@istanbuljs/schema": "^0.1.2", - "istanbul-lib-coverage": "^3.2.0", - "semver": "^6.3.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/babel-plugin-jest-hoist": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.6.3.tgz", - "integrity": "sha512-ESAc/RJvGTFEzRwOTT4+lNDk/GNHMkKbNzsvT0qKRfDyyYTskxB5rnU2njIDYVxXCBHHEI1c0YwHob3WaYujOg==", - "dev": true, - "dependencies": { - "@babel/template": "^7.3.3", - "@babel/types": "^7.3.3", - "@types/babel__core": "^7.1.14", - "@types/babel__traverse": "^7.0.6" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/babel-preset-current-node-syntax": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.0.1.tgz", - "integrity": "sha512-M7LQ0bxarkxQoN+vz5aJPsLBn77n8QgTFmo8WK0/44auK2xlCXrYcUxHFxgU7qW5Yzw/CjmLRK2uJzaCd7LvqQ==", - "dev": true, - "dependencies": { - "@babel/plugin-syntax-async-generators": "^7.8.4", - "@babel/plugin-syntax-bigint": "^7.8.3", - "@babel/plugin-syntax-class-properties": "^7.8.3", - "@babel/plugin-syntax-import-meta": "^7.8.3", - "@babel/plugin-syntax-json-strings": "^7.8.3", - "@babel/plugin-syntax-logical-assignment-operators": "^7.8.3", - "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", - "@babel/plugin-syntax-numeric-separator": "^7.8.3", - "@babel/plugin-syntax-object-rest-spread": "^7.8.3", - "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", - "@babel/plugin-syntax-optional-chaining": "^7.8.3", - "@babel/plugin-syntax-top-level-await": "^7.8.3" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/babel-preset-jest": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-29.6.3.tgz", - "integrity": "sha512-0B3bhxR6snWXJZtR/RliHTDPRgn1sNHOR0yVtq/IiQFyuOVjFS+wuio/R4gSNkyYmKmJB4wGZv2NZanmKmTnNA==", - "dev": true, - "dependencies": { - "babel-plugin-jest-hoist": "^29.6.3", - "babel-preset-current-node-syntax": "^1.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true - }, - "node_modules/base64-js": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", - "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, - "node_modules/bcrypt-pbkdf": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", - "integrity": "sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w==", - "dev": true, - "dependencies": { - "tweetnacl": "^0.14.3" - } - }, - "node_modules/bl": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", - "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", - "dev": true, - "dependencies": { - "buffer": "^5.5.0", - "inherits": "^2.0.4", - "readable-stream": "^3.4.0" - } - }, - "node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/braces": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", - "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", - "dev": true, - "license": "MIT", - "dependencies": { - "fill-range": "^7.1.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/browserslist": { - "version": "4.22.1", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.22.1.tgz", - "integrity": "sha512-FEVc202+2iuClEhZhrWy6ZiAcRLvNMyYcxZ8raemul1DYVOVdFsbqckWLdsixQZCpJlwe77Z3UTalE7jsjnKfQ==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "dependencies": { - "caniuse-lite": "^1.0.30001541", - "electron-to-chromium": "^1.4.535", - "node-releases": "^2.0.13", - "update-browserslist-db": "^1.0.13" - }, - "bin": { - "browserslist": "cli.js" - }, - "engines": { - "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" - } - }, - "node_modules/bser": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz", - "integrity": "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==", - "dev": true, - "dependencies": { - "node-int64": "^0.4.0" - } - }, - "node_modules/buffer": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", - "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "dependencies": { - "base64-js": "^1.3.1", - "ieee754": "^1.1.13" - } - }, - "node_modules/buffer-crc32": { - "version": "0.2.13", - "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", - "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==", - "dev": true, - "engines": { - "node": "*" - } - }, - "node_modules/buffer-from": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", - "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", - "dev": true - }, - "node_modules/buildcheck": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/buildcheck/-/buildcheck-0.0.3.tgz", - "integrity": "sha512-pziaA+p/wdVImfcbsZLNF32EiWyujlQLwolMqUQE8xpKNOH7KmZQaY8sXN7DGOEzPAElo9QTaeNRfGnf3iOJbA==", - "dev": true, - "optional": true, - "engines": { - "node": ">=10.0.0" - } - }, - "node_modules/byline": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/byline/-/byline-5.0.0.tgz", - "integrity": "sha512-s6webAy+R4SR8XVuJWt2V2rGvhnrhxN+9S15GNuTK3wKPOXFF6RNc+8ug2XhH+2s4f+uudG4kUVYmYOQWL2g0Q==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/callsites": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/camelcase": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/caniuse-lite": { - "version": "1.0.30001546", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001546.tgz", - "integrity": "sha512-zvtSJwuQFpewSyRrI3AsftF6rM0X80mZkChIt1spBGEvRglCrjTniXvinc8JKRoqTwXAgvqTImaN9igfSMtUBw==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/caniuse-lite" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ] - }, - "node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/char-regex": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz", - "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==", - "dev": true, - "engines": { - "node": ">=10" - } - }, - "node_modules/chownr": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", - "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", - "dev": true - }, - "node_modules/ci-info": { - "version": "3.9.0", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", - "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/sibiraj-s" - } - ], - "engines": { - "node": ">=8" - } - }, - "node_modules/cjs-module-lexer": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.2.3.tgz", - "integrity": "sha512-0TNiGstbQmCFwt4akjjBg5pLRTSyj/PkWQ1ZoO2zntmg9yLqSRxwEa4iCfQLGjqhiqBfOJa7W/E8wfGrTDmlZQ==", - "dev": true - }, - "node_modules/cliui": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", - "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", - "dev": true, - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.1", - "wrap-ansi": "^7.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/co": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", - "integrity": "sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==", - "dev": true, - "engines": { - "iojs": ">= 1.0.0", - "node": ">= 0.12.0" - } - }, - "node_modules/collect-v8-coverage": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.2.tgz", - "integrity": "sha512-lHl4d5/ONEbLlJvaJNtsF/Lz+WvB07u2ycqTYbdrq7UypDXailES4valYb2eWiJFxZlVmpGekfqoxQhzyFdT4Q==", - "dev": true - }, - "node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/compress-commons": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/compress-commons/-/compress-commons-4.1.1.tgz", - "integrity": "sha512-QLdDLCKNV2dtoTorqgxngQCMA+gWXkM/Nwu7FpeBhk/RdkzimqC3jueb/FDmaZeXh+uby1jkBqE3xArsLBE5wQ==", - "dev": true, - "dependencies": { - "buffer-crc32": "^0.2.13", - "crc32-stream": "^4.0.2", - "normalize-path": "^3.0.0", - "readable-stream": "^3.6.0" - }, - "engines": { - "node": ">= 10" - } - }, - "node_modules/concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "dev": true - }, - "node_modules/convert-source-map": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", - "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", - "dev": true - }, - "node_modules/core-util-is": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", - "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", - "dev": true - }, - "node_modules/cpu-features": { - "version": "0.0.4", - "resolved": "https://registry.npmjs.org/cpu-features/-/cpu-features-0.0.4.tgz", - "integrity": "sha512-fKiZ/zp1mUwQbnzb9IghXtHtDoTMtNeb8oYGx6kX2SYfhnG0HNdBEBIzB9b5KlXu5DQPhfy3mInbBxFcgwAr3A==", - "dev": true, - "hasInstallScript": true, - "optional": true, - "dependencies": { - "buildcheck": "0.0.3", - "nan": "^2.15.0" - }, - "engines": { - "node": ">=10.0.0" - } - }, - "node_modules/crc-32": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/crc-32/-/crc-32-1.2.2.tgz", - "integrity": "sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==", - "dev": true, - "bin": { - "crc32": "bin/crc32.njs" - }, - "engines": { - "node": ">=0.8" - } - }, - "node_modules/crc32-stream": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/crc32-stream/-/crc32-stream-4.0.2.tgz", - "integrity": "sha512-DxFZ/Hk473b/muq1VJ///PMNLj0ZMnzye9thBpmjpJKCc5eMgB95aK8zCGrGfQ90cWo561Te6HK9D+j4KPdM6w==", - "dev": true, - "dependencies": { - "crc-32": "^1.2.0", - "readable-stream": "^3.4.0" - }, - "engines": { - "node": ">= 10" - } - }, - "node_modules/create-jest": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/create-jest/-/create-jest-29.7.0.tgz", - "integrity": "sha512-Adz2bdH0Vq3F53KEMJOoftQFutWCukm6J24wbPWRO4k1kMY7gS7ds/uoJkNuV8wDCtWWnuwGcJwpWcih+zEW1Q==", - "dev": true, - "dependencies": { - "@jest/types": "^29.6.3", - "chalk": "^4.0.0", - "exit": "^0.1.2", - "graceful-fs": "^4.2.9", - "jest-config": "^29.7.0", - "jest-util": "^29.7.0", - "prompts": "^2.0.1" - }, - "bin": { - "create-jest": "bin/create-jest.js" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", - "dev": true, - "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "dev": true, - "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/dedent": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.5.1.tgz", - "integrity": "sha512-+LxW+KLWxu3HW3M2w2ympwtqPrqYRzU8fqi6Fhd18fBALe15blJPI/I4+UHveMVG6lJqB4JNd4UG0S5cnVHwIg==", - "dev": true, - "peerDependencies": { - "babel-plugin-macros": "^3.1.0" - }, - "peerDependenciesMeta": { - "babel-plugin-macros": { - "optional": true - } - } - }, - "node_modules/deep-is": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", - "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", - "dev": true - }, - "node_modules/deepmerge": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", - "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/detect-newline": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", - "integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/diff-sequences": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz", - "integrity": "sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==", - "dev": true, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/docker-compose": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/docker-compose/-/docker-compose-0.24.2.tgz", - "integrity": "sha512-2/WLvA7UZ6A2LDLQrYW0idKipmNBWhtfvrn2yzjC5PnHDzuFVj1zAZN6MJxVMKP0zZH8uzAK6OwVZYHGuyCmTw==", - "dev": true, - "dependencies": { - "yaml": "^2.2.2" - }, - "engines": { - "node": ">= 6.0.0" - } - }, - "node_modules/docker-modem": { - "version": "3.0.8", - "resolved": "https://registry.npmjs.org/docker-modem/-/docker-modem-3.0.8.tgz", - "integrity": "sha512-f0ReSURdM3pcKPNS30mxOHSbaFLcknGmQjwSfmbcdOw1XWKXVhukM3NJHhr7NpY9BIyyWQb0EBo3KQvvuU5egQ==", - "dev": true, - "dependencies": { - "debug": "^4.1.1", - "readable-stream": "^3.5.0", - "split-ca": "^1.0.1", - "ssh2": "^1.11.0" - }, - "engines": { - "node": ">= 8.0" - } - }, - "node_modules/dockerode": { - "version": "3.3.5", - "resolved": "https://registry.npmjs.org/dockerode/-/dockerode-3.3.5.tgz", - "integrity": "sha512-/0YNa3ZDNeLr/tSckmD69+Gq+qVNhvKfAHNeZJBnp7EOP6RGKV8ORrJHkUn20So5wU+xxT7+1n5u8PjHbfjbSA==", - "dev": true, - "dependencies": { - "@balena/dockerignore": "^1.0.2", - "docker-modem": "^3.0.0", - "tar-fs": "~2.0.1" - }, - "engines": { - "node": ">= 8.0" - } - }, - "node_modules/dockerode/node_modules/tar-fs": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.0.1.tgz", - "integrity": "sha512-6tzWDMeroL87uF/+lin46k+Q+46rAJ0SyPGz7OW7wTgblI273hsBqk2C1j0/xNadNLKDTUL9BukSjB7cwgmlPA==", - "dev": true, - "dependencies": { - "chownr": "^1.1.1", - "mkdirp-classic": "^0.5.2", - "pump": "^3.0.0", - "tar-stream": "^2.0.0" - } - }, - "node_modules/doctrine": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", - "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", - "dev": true, - "dependencies": { - "esutils": "^2.0.2" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/electron-to-chromium": { - "version": "1.4.543", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.543.tgz", - "integrity": "sha512-t2ZP4AcGE0iKCCQCBx/K2426crYdxD3YU6l0uK2EO3FZH0pbC4pFz/sZm2ruZsND6hQBTcDWWlo/MLpiOdif5g==", - "dev": true - }, - "node_modules/emittery": { - "version": "0.13.1", - "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.13.1.tgz", - "integrity": "sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==", - "dev": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sindresorhus/emittery?sponsor=1" - } - }, - "node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true - }, - "node_modules/end-of-stream": { - "version": "1.4.4", - "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", - "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", - "dev": true, - "dependencies": { - "once": "^1.4.0" - } - }, - "node_modules/error-ex": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", - "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", - "dev": true, - "dependencies": { - "is-arrayish": "^0.2.1" - } - }, - "node_modules/escalade": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", - "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/escape-string-regexp": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", - "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/eslint": { - "version": "8.50.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.50.0.tgz", - "integrity": "sha512-FOnOGSuFuFLv/Sa+FDVRZl4GGVAAFFi8LecRsI5a1tMO5HIE8nCm4ivAlzt4dT3ol/PaaGC0rJEEXQmHJBGoOg==", - "dev": true, - "dependencies": { - "@eslint-community/eslint-utils": "^4.2.0", - "@eslint-community/regexpp": "^4.6.1", - "@eslint/eslintrc": "^2.1.2", - "@eslint/js": "8.50.0", - "@humanwhocodes/config-array": "^0.11.11", - "@humanwhocodes/module-importer": "^1.0.1", - "@nodelib/fs.walk": "^1.2.8", - "ajv": "^6.12.4", - "chalk": "^4.0.0", - "cross-spawn": "^7.0.2", - "debug": "^4.3.2", - "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.4.2", - "esutils": "^2.0.2", - "fast-deep-equal": "^3.1.3", - "file-entry-cache": "^6.0.1", - "find-up": "^5.0.0", - "glob-parent": "^6.0.2", - "globals": "^13.19.0", - "graphemer": "^1.4.0", - "ignore": "^5.2.0", - "imurmurhash": "^0.1.4", - "is-glob": "^4.0.0", - "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" - }, - "bin": { - "eslint": "bin/eslint.js" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/eslint-scope": { - "version": "7.2.2", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", - "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", - "dev": true, - "dependencies": { - "esrecurse": "^4.3.0", - "estraverse": "^5.2.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/eslint-visitor-keys": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", - "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", - "dev": true, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/eslint/node_modules/escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/eslint/node_modules/find-up": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", - "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", - "dev": true, - "dependencies": { - "locate-path": "^6.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/eslint/node_modules/locate-path": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", - "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", - "dev": true, - "dependencies": { - "p-locate": "^5.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/eslint/node_modules/p-locate": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", - "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", - "dev": true, - "dependencies": { - "p-limit": "^3.0.2" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/espree": { - "version": "9.6.1", - "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", - "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", - "dev": true, - "dependencies": { - "acorn": "^8.9.0", - "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^3.4.1" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/esprima": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", - "dev": true, - "bin": { - "esparse": "bin/esparse.js", - "esvalidate": "bin/esvalidate.js" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/esquery": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.5.0.tgz", - "integrity": "sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==", - "dev": true, - "dependencies": { - "estraverse": "^5.1.0" - }, - "engines": { - "node": ">=0.10" - } - }, - "node_modules/esrecurse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", - "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", - "dev": true, - "dependencies": { - "estraverse": "^5.2.0" - }, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/esutils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", - "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/execa": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", - "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", - "dev": true, - "dependencies": { - "cross-spawn": "^7.0.3", - "get-stream": "^6.0.0", - "human-signals": "^2.1.0", - "is-stream": "^2.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^4.0.1", - "onetime": "^5.1.2", - "signal-exit": "^3.0.3", - "strip-final-newline": "^2.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sindresorhus/execa?sponsor=1" - } - }, - "node_modules/exit": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", - "integrity": "sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==", - "dev": true, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/expect": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/expect/-/expect-29.7.0.tgz", - "integrity": "sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw==", - "dev": true, - "dependencies": { - "@jest/expect-utils": "^29.7.0", - "jest-get-type": "^29.6.3", - "jest-matcher-utils": "^29.7.0", - "jest-message-util": "^29.7.0", - "jest-util": "^29.7.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/fast-deep-equal": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true - }, - "node_modules/fast-fifo": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/fast-fifo/-/fast-fifo-1.3.2.tgz", - "integrity": "sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==", - "dev": true - }, - "node_modules/fast-json-stable-stringify": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", - "dev": true - }, - "node_modules/fast-levenshtein": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", - "dev": true - }, - "node_modules/fastq": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz", - "integrity": "sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==", - "dev": true, - "dependencies": { - "reusify": "^1.0.4" - } - }, - "node_modules/fb-watchman": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz", - "integrity": "sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==", - "dev": true, - "dependencies": { - "bser": "2.1.1" - } - }, - "node_modules/file-entry-cache": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", - "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", - "dev": true, - "dependencies": { - "flat-cache": "^3.0.4" - }, - "engines": { - "node": "^10.12.0 || >=12.0.0" - } - }, - "node_modules/fill-range": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", - "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", - "dev": true, - "license": "MIT", - "dependencies": { - "to-regex-range": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "dev": true, - "dependencies": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/flat-cache": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", - "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==", - "dev": true, - "dependencies": { - "flatted": "^3.1.0", - "rimraf": "^3.0.2" - }, - "engines": { - "node": "^10.12.0 || >=12.0.0" - } - }, - "node_modules/flatted": { - "version": "3.2.6", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.6.tgz", - "integrity": "sha512-0sQoMh9s0BYsm+12Huy/rkKxVu4R1+r96YX5cG44rHV0pQ6iC3Q+mkoMFaGWObMFYQxCVT+ssG1ksneA2MI9KQ==", - "dev": true - }, - "node_modules/fs-constants": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", - "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", - "dev": true - }, - "node_modules/fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", - "dev": true - }, - "node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "dev": true, - "hasInstallScript": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, - "node_modules/gensync": { - "version": "1.0.0-beta.2", - "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", - "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", - "dev": true, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/get-caller-file": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", - "dev": true, - "engines": { - "node": "6.* || 8.* || >= 10.*" - } - }, - "node_modules/get-package-type": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", - "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", - "dev": true, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/get-port": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/get-port/-/get-port-5.1.1.tgz", - "integrity": "sha512-g/Q1aTSDOxFpchXC4i8ZWvxA1lnPqx/JHqcpIw0/LX9T8x/GBbi6YnlN5nhaKIFkT8oFsscUKgDJYxfwfS6QsQ==", - "dev": true, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/get-stream": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", - "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "dev": true, - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/glob-parent": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", - "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", - "dev": true, - "dependencies": { - "is-glob": "^4.0.3" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/globals": { - "version": "13.23.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.23.0.tgz", - "integrity": "sha512-XAmF0RjlrjY23MA51q3HltdlGxUpXPvg0GioKiD9X6HD28iMjo2dKC8Vqwm7lne4GNr78+RHTfliktR6ZH09wA==", - "dev": true, - "dependencies": { - "type-fest": "^0.20.2" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/graceful-fs": { - "version": "4.2.10", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", - "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==", - "dev": true - }, - "node_modules/graphemer": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", - "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", - "dev": true - }, - "node_modules/has": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/has/-/has-1.0.4.tgz", - "integrity": "sha512-qdSAmqLF6209RFj4VVItywPMbm3vWylknmB3nvNiUIs72xAimcM8nVYxYr7ncvZq5qzk9MKIZR8ijqD/1QuYjQ==", - "dev": true, - "engines": { - "node": ">= 0.4.0" - } - }, - "node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/html-escaper": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", - "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", - "dev": true - }, - "node_modules/human-signals": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", - "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", - "dev": true, - "engines": { - "node": ">=10.17.0" - } - }, - "node_modules/ieee754": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", - "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, - "node_modules/ignore": { - "version": "5.2.4", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz", - "integrity": "sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==", - "dev": true, - "engines": { - "node": ">= 4" - } - }, - "node_modules/import-fresh": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", - "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", - "dev": true, - "dependencies": { - "parent-module": "^1.0.0", - "resolve-from": "^4.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/import-local": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.1.0.tgz", - "integrity": "sha512-ASB07uLtnDs1o6EHjKpX34BKYDSqnFerfTOJL2HvMqF70LnxpjkzDB8J44oT9pu4AMPkQwf8jl6szgvNd2tRIg==", - "dev": true, - "dependencies": { - "pkg-dir": "^4.2.0", - "resolve-cwd": "^3.0.0" - }, - "bin": { - "import-local-fixture": "fixtures/cli.js" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/imurmurhash": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", - "dev": true, - "engines": { - "node": ">=0.8.19" - } - }, - "node_modules/inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", - "dev": true, - "dependencies": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true - }, - "node_modules/is-arrayish": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", - "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", - "dev": true - }, - "node_modules/is-core-module": { - "version": "2.13.0", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.0.tgz", - "integrity": "sha512-Z7dk6Qo8pOCp3l4tsX2C5ZVas4V+UxwQodwZhLopL91TX8UyyHEXafPcyoeeWuLrwzHcr3igO78wNLwHJHsMCQ==", - "dev": true, - "dependencies": { - "has": "^1.0.3" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/is-generator-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz", - "integrity": "sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/is-glob": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", - "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "dev": true, - "dependencies": { - "is-extglob": "^2.1.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.12.0" - } - }, - "node_modules/is-path-inside": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", - "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/is-stream": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", - "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", - "dev": true, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", - "dev": true - }, - "node_modules/isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true - }, - "node_modules/istanbul-lib-coverage": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.0.tgz", - "integrity": "sha512-eOeJ5BHCmHYvQK7xt9GkdHuzuCGS1Y6g9Gvnx3Ym33fz/HpLRYxiS0wHNr+m/MBC8B647Xt608vCDEvhl9c6Mw==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/istanbul-lib-instrument": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.1.tgz", - "integrity": "sha512-EAMEJBsYuyyztxMxW3g7ugGPkrZsV57v0Hmv3mm1uQsmB+QnZuepg731CRaIgeUVSdmsTngOkSnauNF8p7FIhA==", - "dev": true, - "dependencies": { - "@babel/core": "^7.12.3", - "@babel/parser": "^7.14.7", - "@istanbuljs/schema": "^0.1.2", - "istanbul-lib-coverage": "^3.2.0", - "semver": "^7.5.4" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/istanbul-lib-instrument/node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/istanbul-lib-instrument/node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", - "dev": true, - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/istanbul-lib-instrument/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - }, - "node_modules/istanbul-lib-report": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", - "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", - "dev": true, - "dependencies": { - "istanbul-lib-coverage": "^3.0.0", - "make-dir": "^4.0.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/istanbul-lib-source-maps": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz", - "integrity": "sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==", - "dev": true, - "dependencies": { - "debug": "^4.1.1", - "istanbul-lib-coverage": "^3.0.0", - "source-map": "^0.6.1" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/istanbul-reports": { - "version": "3.1.6", - "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.6.tgz", - "integrity": "sha512-TLgnMkKg3iTDsQ9PbPTdpfAK2DzjF9mqUG7RMgcQl8oFjad8ob4laGxv5XV5U9MAfx8D6tSJiUyuAwzLicaxlg==", - "dev": true, - "dependencies": { - "html-escaper": "^2.0.0", - "istanbul-lib-report": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest/-/jest-29.7.0.tgz", - "integrity": "sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw==", - "dev": true, - "dependencies": { - "@jest/core": "^29.7.0", - "@jest/types": "^29.6.3", - "import-local": "^3.0.2", - "jest-cli": "^29.7.0" - }, - "bin": { - "jest": "bin/jest.js" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - }, - "peerDependencies": { - "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" - }, - "peerDependenciesMeta": { - "node-notifier": { - "optional": true - } - } - }, - "node_modules/jest-changed-files": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-29.7.0.tgz", - "integrity": "sha512-fEArFiwf1BpQ+4bXSprcDc3/x4HSzL4al2tozwVpDFpsxALjLYdyiIK4e5Vz66GQJIbXJ82+35PtysofptNX2w==", - "dev": true, - "dependencies": { - "execa": "^5.0.0", - "jest-util": "^29.7.0", - "p-limit": "^3.1.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-circus": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-29.7.0.tgz", - "integrity": "sha512-3E1nCMgipcTkCocFwM90XXQab9bS+GMsjdpmPrlelaxwD93Ad8iVEjX/vvHPdLPnFf+L40u+5+iutRdA1N9myw==", - "dev": true, - "dependencies": { - "@jest/environment": "^29.7.0", - "@jest/expect": "^29.7.0", - "@jest/test-result": "^29.7.0", - "@jest/types": "^29.6.3", - "@types/node": "*", - "chalk": "^4.0.0", - "co": "^4.6.0", - "dedent": "^1.0.0", - "is-generator-fn": "^2.0.0", - "jest-each": "^29.7.0", - "jest-matcher-utils": "^29.7.0", - "jest-message-util": "^29.7.0", - "jest-runtime": "^29.7.0", - "jest-snapshot": "^29.7.0", - "jest-util": "^29.7.0", - "p-limit": "^3.1.0", - "pretty-format": "^29.7.0", - "pure-rand": "^6.0.0", - "slash": "^3.0.0", - "stack-utils": "^2.0.3" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-cli": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-29.7.0.tgz", - "integrity": "sha512-OVVobw2IubN/GSYsxETi+gOe7Ka59EFMR/twOU3Jb2GnKKeMGJB5SGUUrEz3SFVmJASUdZUzy83sLNNQ2gZslg==", - "dev": true, - "dependencies": { - "@jest/core": "^29.7.0", - "@jest/test-result": "^29.7.0", - "@jest/types": "^29.6.3", - "chalk": "^4.0.0", - "create-jest": "^29.7.0", - "exit": "^0.1.2", - "import-local": "^3.0.2", - "jest-config": "^29.7.0", - "jest-util": "^29.7.0", - "jest-validate": "^29.7.0", - "yargs": "^17.3.1" - }, - "bin": { - "jest": "bin/jest.js" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - }, - "peerDependencies": { - "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" - }, - "peerDependenciesMeta": { - "node-notifier": { - "optional": true - } - } - }, - "node_modules/jest-config": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-29.7.0.tgz", - "integrity": "sha512-uXbpfeQ7R6TZBqI3/TxCU4q4ttk3u0PJeC+E0zbfSoSjq6bJ7buBPxzQPL0ifrkY4DNu4JUdk0ImlBUYi840eQ==", - "dev": true, - "dependencies": { - "@babel/core": "^7.11.6", - "@jest/test-sequencer": "^29.7.0", - "@jest/types": "^29.6.3", - "babel-jest": "^29.7.0", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "deepmerge": "^4.2.2", - "glob": "^7.1.3", - "graceful-fs": "^4.2.9", - "jest-circus": "^29.7.0", - "jest-environment-node": "^29.7.0", - "jest-get-type": "^29.6.3", - "jest-regex-util": "^29.6.3", - "jest-resolve": "^29.7.0", - "jest-runner": "^29.7.0", - "jest-util": "^29.7.0", - "jest-validate": "^29.7.0", - "micromatch": "^4.0.4", - "parse-json": "^5.2.0", - "pretty-format": "^29.7.0", - "slash": "^3.0.0", - "strip-json-comments": "^3.1.1" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - }, - "peerDependencies": { - "@types/node": "*", - "ts-node": ">=9.0.0" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - }, - "ts-node": { - "optional": true - } - } - }, - "node_modules/jest-diff": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.7.0.tgz", - "integrity": "sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw==", - "dev": true, - "dependencies": { - "chalk": "^4.0.0", - "diff-sequences": "^29.6.3", - "jest-get-type": "^29.6.3", - "pretty-format": "^29.7.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-docblock": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-29.7.0.tgz", - "integrity": "sha512-q617Auw3A612guyaFgsbFeYpNP5t2aoUNLwBUbc/0kD1R4t9ixDbyFTHd1nok4epoVFpr7PmeWHrhvuV3XaJ4g==", - "dev": true, - "dependencies": { - "detect-newline": "^3.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-each": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-29.7.0.tgz", - "integrity": "sha512-gns+Er14+ZrEoC5fhOfYCY1LOHHr0TI+rQUHZS8Ttw2l7gl+80eHc/gFf2Ktkw0+SIACDTeWvpFcv3B04VembQ==", - "dev": true, - "dependencies": { - "@jest/types": "^29.6.3", - "chalk": "^4.0.0", - "jest-get-type": "^29.6.3", - "jest-util": "^29.7.0", - "pretty-format": "^29.7.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-environment-node": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-29.7.0.tgz", - "integrity": "sha512-DOSwCRqXirTOyheM+4d5YZOrWcdu0LNZ87ewUoywbcb2XR4wKgqiG8vNeYwhjFMbEkfju7wx2GYH0P2gevGvFw==", - "dev": true, - "dependencies": { - "@jest/environment": "^29.7.0", - "@jest/fake-timers": "^29.7.0", - "@jest/types": "^29.6.3", - "@types/node": "*", - "jest-mock": "^29.7.0", - "jest-util": "^29.7.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-get-type": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.6.3.tgz", - "integrity": "sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw==", - "dev": true, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-haste-map": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-29.7.0.tgz", - "integrity": "sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA==", - "dev": true, - "dependencies": { - "@jest/types": "^29.6.3", - "@types/graceful-fs": "^4.1.3", - "@types/node": "*", - "anymatch": "^3.0.3", - "fb-watchman": "^2.0.0", - "graceful-fs": "^4.2.9", - "jest-regex-util": "^29.6.3", - "jest-util": "^29.7.0", - "jest-worker": "^29.7.0", - "micromatch": "^4.0.4", - "walker": "^1.0.8" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - }, - "optionalDependencies": { - "fsevents": "^2.3.2" - } - }, - "node_modules/jest-jasmine2": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-jasmine2/-/jest-jasmine2-29.7.0.tgz", - "integrity": "sha512-N3nRpBVTM5erHtMi6ODBUEqG/LpVgSJC8qk14duw88d9Eigx2vL+n4LF1d8eV8pegnnzKyNHdTGxa/NsIKj0Zw==", - "dev": true, - "dependencies": { - "@jest/environment": "^29.7.0", - "@jest/expect": "^29.7.0", - "@jest/source-map": "^29.6.3", - "@jest/test-result": "^29.7.0", - "@jest/types": "^29.6.3", - "@types/node": "*", - "chalk": "^4.0.0", - "co": "^4.6.0", - "is-generator-fn": "^2.0.0", - "jest-each": "^29.7.0", - "jest-matcher-utils": "^29.7.0", - "jest-message-util": "^29.7.0", - "jest-runtime": "^29.7.0", - "jest-snapshot": "^29.7.0", - "jest-util": "^29.7.0", - "p-limit": "^3.1.0", - "pretty-format": "^29.7.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-leak-detector": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-29.7.0.tgz", - "integrity": "sha512-kYA8IJcSYtST2BY9I+SMC32nDpBT3J2NvWJx8+JCuCdl/CR1I4EKUJROiP8XtCcxqgTTBGJNdbB1A8XRKbTetw==", - "dev": true, - "dependencies": { - "jest-get-type": "^29.6.3", - "pretty-format": "^29.7.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-matcher-utils": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-29.7.0.tgz", - "integrity": "sha512-sBkD+Xi9DtcChsI3L3u0+N0opgPYnCRPtGcQYrgXmR+hmt/fYfWAL0xRXYU8eWOdfuLgBe0YCW3AFtnRLagq/g==", - "dev": true, - "dependencies": { - "chalk": "^4.0.0", - "jest-diff": "^29.7.0", - "jest-get-type": "^29.6.3", - "pretty-format": "^29.7.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-message-util": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.7.0.tgz", - "integrity": "sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w==", - "dev": true, - "dependencies": { - "@babel/code-frame": "^7.12.13", - "@jest/types": "^29.6.3", - "@types/stack-utils": "^2.0.0", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.9", - "micromatch": "^4.0.4", - "pretty-format": "^29.7.0", - "slash": "^3.0.0", - "stack-utils": "^2.0.3" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-mock": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-29.7.0.tgz", - "integrity": "sha512-ITOMZn+UkYS4ZFh83xYAOzWStloNzJFO2s8DWrE4lhtGD+AorgnbkiKERe4wQVBydIGPx059g6riW5Btp6Llnw==", - "dev": true, - "dependencies": { - "@jest/types": "^29.6.3", - "@types/node": "*", - "jest-util": "^29.7.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-pnp-resolver": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz", - "integrity": "sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==", - "dev": true, - "engines": { - "node": ">=6" - }, - "peerDependencies": { - "jest-resolve": "*" - }, - "peerDependenciesMeta": { - "jest-resolve": { - "optional": true - } - } - }, - "node_modules/jest-regex-util": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-29.6.3.tgz", - "integrity": "sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg==", - "dev": true, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-resolve": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-29.7.0.tgz", - "integrity": "sha512-IOVhZSrg+UvVAshDSDtHyFCCBUl/Q3AAJv8iZ6ZjnZ74xzvwuzLXid9IIIPgTnY62SJjfuupMKZsZQRsCvxEgA==", - "dev": true, - "dependencies": { - "chalk": "^4.0.0", - "graceful-fs": "^4.2.9", - "jest-haste-map": "^29.7.0", - "jest-pnp-resolver": "^1.2.2", - "jest-util": "^29.7.0", - "jest-validate": "^29.7.0", - "resolve": "^1.20.0", - "resolve.exports": "^2.0.0", - "slash": "^3.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-resolve-dependencies": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-29.7.0.tgz", - "integrity": "sha512-un0zD/6qxJ+S0et7WxeI3H5XSe9lTBBR7bOHCHXkKR6luG5mwDDlIzVQ0V5cZCuoTgEdcdwzTghYkTWfubi+nA==", - "dev": true, - "dependencies": { - "jest-regex-util": "^29.6.3", - "jest-snapshot": "^29.7.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-runner": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-29.7.0.tgz", - "integrity": "sha512-fsc4N6cPCAahybGBfTRcq5wFR6fpLznMg47sY5aDpsoejOcVYFb07AHuSnR0liMcPTgBsA3ZJL6kFOjPdoNipQ==", - "dev": true, - "dependencies": { - "@jest/console": "^29.7.0", - "@jest/environment": "^29.7.0", - "@jest/test-result": "^29.7.0", - "@jest/transform": "^29.7.0", - "@jest/types": "^29.6.3", - "@types/node": "*", - "chalk": "^4.0.0", - "emittery": "^0.13.1", - "graceful-fs": "^4.2.9", - "jest-docblock": "^29.7.0", - "jest-environment-node": "^29.7.0", - "jest-haste-map": "^29.7.0", - "jest-leak-detector": "^29.7.0", - "jest-message-util": "^29.7.0", - "jest-resolve": "^29.7.0", - "jest-runtime": "^29.7.0", - "jest-util": "^29.7.0", - "jest-watcher": "^29.7.0", - "jest-worker": "^29.7.0", - "p-limit": "^3.1.0", - "source-map-support": "0.5.13" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-runtime": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-29.7.0.tgz", - "integrity": "sha512-gUnLjgwdGqW7B4LvOIkbKs9WGbn+QLqRQQ9juC6HndeDiezIwhDP+mhMwHWCEcfQ5RUXa6OPnFF8BJh5xegwwQ==", - "dev": true, - "dependencies": { - "@jest/environment": "^29.7.0", - "@jest/fake-timers": "^29.7.0", - "@jest/globals": "^29.7.0", - "@jest/source-map": "^29.6.3", - "@jest/test-result": "^29.7.0", - "@jest/transform": "^29.7.0", - "@jest/types": "^29.6.3", - "@types/node": "*", - "chalk": "^4.0.0", - "cjs-module-lexer": "^1.0.0", - "collect-v8-coverage": "^1.0.0", - "glob": "^7.1.3", - "graceful-fs": "^4.2.9", - "jest-haste-map": "^29.7.0", - "jest-message-util": "^29.7.0", - "jest-mock": "^29.7.0", - "jest-regex-util": "^29.6.3", - "jest-resolve": "^29.7.0", - "jest-snapshot": "^29.7.0", - "jest-util": "^29.7.0", - "slash": "^3.0.0", - "strip-bom": "^4.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-snapshot": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-29.7.0.tgz", - "integrity": "sha512-Rm0BMWtxBcioHr1/OX5YCP8Uov4riHvKPknOGs804Zg9JGZgmIBkbtlxJC/7Z4msKYVbIJtfU+tKb8xlYNfdkw==", - "dev": true, - "dependencies": { - "@babel/core": "^7.11.6", - "@babel/generator": "^7.7.2", - "@babel/plugin-syntax-jsx": "^7.7.2", - "@babel/plugin-syntax-typescript": "^7.7.2", - "@babel/types": "^7.3.3", - "@jest/expect-utils": "^29.7.0", - "@jest/transform": "^29.7.0", - "@jest/types": "^29.6.3", - "babel-preset-current-node-syntax": "^1.0.0", - "chalk": "^4.0.0", - "expect": "^29.7.0", - "graceful-fs": "^4.2.9", - "jest-diff": "^29.7.0", - "jest-get-type": "^29.6.3", - "jest-matcher-utils": "^29.7.0", - "jest-message-util": "^29.7.0", - "jest-util": "^29.7.0", - "natural-compare": "^1.4.0", - "pretty-format": "^29.7.0", - "semver": "^7.5.3" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-snapshot/node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/jest-snapshot/node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", - "dev": true, - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/jest-snapshot/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - }, - "node_modules/jest-util": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.7.0.tgz", - "integrity": "sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==", - "dev": true, - "dependencies": { - "@jest/types": "^29.6.3", - "@types/node": "*", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "graceful-fs": "^4.2.9", - "picomatch": "^2.2.3" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-validate": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-29.7.0.tgz", - "integrity": "sha512-ZB7wHqaRGVw/9hST/OuFUReG7M8vKeq0/J2egIGLdvjHCmYqGARhzXmtgi+gVeZ5uXFF219aOc3Ls2yLg27tkw==", - "dev": true, - "dependencies": { - "@jest/types": "^29.6.3", - "camelcase": "^6.2.0", - "chalk": "^4.0.0", - "jest-get-type": "^29.6.3", - "leven": "^3.1.0", - "pretty-format": "^29.7.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-validate/node_modules/camelcase": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", - "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/jest-watcher": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-29.7.0.tgz", - "integrity": "sha512-49Fg7WXkU3Vl2h6LbLtMQ/HyB6rXSIX7SqvBLQmssRBGN9I0PNvPmAmCWSOY6SOvrjhI/F7/bGAv9RtnsPA03g==", - "dev": true, - "dependencies": { - "@jest/test-result": "^29.7.0", - "@jest/types": "^29.6.3", - "@types/node": "*", - "ansi-escapes": "^4.2.1", - "chalk": "^4.0.0", - "emittery": "^0.13.1", - "jest-util": "^29.7.0", - "string-length": "^4.0.1" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-worker": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.7.0.tgz", - "integrity": "sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==", - "dev": true, - "dependencies": { - "@types/node": "*", - "jest-util": "^29.7.0", - "merge-stream": "^2.0.0", - "supports-color": "^8.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-worker/node_modules/supports-color": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", - "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/supports-color?sponsor=1" - } - }, - "node_modules/js-tokens": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "dev": true - }, - "node_modules/js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", - "dev": true, - "dependencies": { - "argparse": "^2.0.1" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "node_modules/jsesc": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", - "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", - "dev": true, - "bin": { - "jsesc": "bin/jsesc" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/json-parse-even-better-errors": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", - "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", - "dev": true - }, - "node_modules/json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true - }, - "node_modules/json-stable-stringify-without-jsonify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", - "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", - "dev": true - }, - "node_modules/json5": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", - "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", - "dev": true, - "bin": { - "json5": "lib/cli.js" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/kleur": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", - "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/lazystream": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/lazystream/-/lazystream-1.0.1.tgz", - "integrity": "sha512-b94GiNHQNy6JNTrt5w6zNyffMrNkXZb3KTkCZJb2V1xaEGCk093vkZ2jk3tpaeP33/OiXC+WvK9AxUebnf5nbw==", - "dev": true, - "dependencies": { - "readable-stream": "^2.0.5" - }, - "engines": { - "node": ">= 0.6.3" - } - }, - "node_modules/lazystream/node_modules/readable-stream": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", - "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", - "dev": true, - "dependencies": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "node_modules/lazystream/node_modules/string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dev": true, - "dependencies": { - "safe-buffer": "~5.1.0" - } - }, - "node_modules/leven": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", - "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/levn": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", - "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", - "dev": true, - "dependencies": { - "prelude-ls": "^1.2.1", - "type-check": "~0.4.0" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/lines-and-columns": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", - "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", - "dev": true - }, - "node_modules/locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "dev": true, - "dependencies": { - "p-locate": "^4.1.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/lodash.defaults": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz", - "integrity": "sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ==", - "dev": true - }, - "node_modules/lodash.difference": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/lodash.difference/-/lodash.difference-4.5.0.tgz", - "integrity": "sha512-dS2j+W26TQ7taQBGN8Lbbq04ssV3emRw4NY58WErlTO29pIqS0HmoT5aJ9+TUQ1N3G+JOZSji4eugsWwGp9yPA==", - "dev": true - }, - "node_modules/lodash.flatten": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/lodash.flatten/-/lodash.flatten-4.4.0.tgz", - "integrity": "sha512-C5N2Z3DgnnKr0LOpv/hKCgKdb7ZZwafIrsesve6lmzvZIRZRGaZ/l6Q8+2W7NaT+ZwO3fFlSCzCzrDCFdJfZ4g==", - "dev": true - }, - "node_modules/lodash.isplainobject": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", - "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==", - "dev": true - }, - "node_modules/lodash.merge": { - "version": "4.6.2", - "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", - "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", - "dev": true - }, - "node_modules/lodash.union": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/lodash.union/-/lodash.union-4.6.0.tgz", - "integrity": "sha512-c4pB2CdGrGdjMKYLA+XiRDO7Y0PRQbm/Gzg8qMj+QH+pFVAoTp5sBpO0odL3FjoPCGjK96p6qsP+yQoiLoOBcw==", - "dev": true - }, - "node_modules/lru-cache": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", - "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", - "dev": true, - "dependencies": { - "yallist": "^3.0.2" - } - }, - "node_modules/make-dir": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", - "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", - "dev": true, - "dependencies": { - "semver": "^7.5.3" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/make-dir/node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/make-dir/node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", - "dev": true, - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/make-dir/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - }, - "node_modules/makeerror": { - "version": "1.0.12", - "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz", - "integrity": "sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==", - "dev": true, - "dependencies": { - "tmpl": "1.0.5" - } - }, - "node_modules/merge-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", - "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", - "dev": true - }, - "node_modules/micromatch": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", - "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", - "dev": true, - "dependencies": { - "braces": "^3.0.2", - "picomatch": "^2.3.1" - }, - "engines": { - "node": ">=8.6" - } - }, - "node_modules/mimic-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", - "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/mkdirp": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", - "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", - "dev": true, - "bin": { - "mkdirp": "bin/cmd.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/mkdirp-classic": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", - "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==", - "dev": true - }, - "node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - }, - "node_modules/nan": { - "version": "2.16.0", - "resolved": "https://registry.npmjs.org/nan/-/nan-2.16.0.tgz", - "integrity": "sha512-UdAqHyFngu7TfQKsCBgAA6pWDkT8MAO7d0jyOecVhN5354xbLqdn8mV9Tat9gepAupm0bt2DbeaSC8vS52MuFA==", - "dev": true, - "optional": true - }, - "node_modules/natural-compare": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", - "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", - "dev": true - }, - "node_modules/node-fetch": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", - "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", - "dev": true, - "dependencies": { - "whatwg-url": "^5.0.0" - }, - "engines": { - "node": "4.x || >=6.0.0" - }, - "peerDependencies": { - "encoding": "^0.1.0" - }, - "peerDependenciesMeta": { - "encoding": { - "optional": true - } - } - }, - "node_modules/node-int64": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", - "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==", - "dev": true - }, - "node_modules/node-releases": { - "version": "2.0.13", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.13.tgz", - "integrity": "sha512-uYr7J37ae/ORWdZeQ1xxMJe3NtdmqMC/JZK+geofDrkLUApKRHPd18/TxtBOJ4A0/+uUIliorNrfYV6s1b02eQ==", - "dev": true - }, - "node_modules/normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/npm-run-path": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", - "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", - "dev": true, - "dependencies": { - "path-key": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "dev": true, - "dependencies": { - "wrappy": "1" - } - }, - "node_modules/onetime": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", - "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", - "dev": true, - "dependencies": { - "mimic-fn": "^2.1.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/optionator": { - "version": "0.9.3", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.3.tgz", - "integrity": "sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg==", - "dev": true, - "dependencies": { - "@aashutoshrathi/word-wrap": "^1.2.3", - "deep-is": "^0.1.3", - "fast-levenshtein": "^2.0.6", - "levn": "^0.4.1", - "prelude-ls": "^1.2.1", - "type-check": "^0.4.0" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/p-limit": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", - "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", - "dev": true, - "dependencies": { - "yocto-queue": "^0.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "dev": true, - "dependencies": { - "p-limit": "^2.2.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/p-locate/node_modules/p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dev": true, - "dependencies": { - "p-try": "^2.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-try": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/parent-module": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", - "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", - "dev": true, - "dependencies": { - "callsites": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/parse-json": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", - "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", - "dev": true, - "dependencies": { - "@babel/code-frame": "^7.0.0", - "error-ex": "^1.3.1", - "json-parse-even-better-errors": "^2.3.0", - "lines-and-columns": "^1.1.6" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/path-parse": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", - "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", - "dev": true - }, - "node_modules/picocolors": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", - "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", - "dev": true - }, - "node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "dev": true, - "engines": { - "node": ">=8.6" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/pirates": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.6.tgz", - "integrity": "sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==", - "dev": true, - "engines": { - "node": ">= 6" - } - }, - "node_modules/pkg-dir": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", - "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", - "dev": true, - "dependencies": { - "find-up": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/prelude-ls": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", - "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", - "dev": true, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/pretty-format": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", - "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", - "dev": true, - "dependencies": { - "@jest/schemas": "^29.6.3", - "ansi-styles": "^5.0.0", - "react-is": "^18.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/pretty-format/node_modules/ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/process-nextick-args": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", - "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", - "dev": true - }, - "node_modules/prompts": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", - "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==", - "dev": true, - "dependencies": { - "kleur": "^3.0.3", - "sisteransi": "^1.0.5" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/proper-lockfile": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/proper-lockfile/-/proper-lockfile-4.1.2.tgz", - "integrity": "sha512-TjNPblN4BwAWMXU8s9AEz4JmQxnD1NNL7bNOY/AKUzyamc379FWASUhc/K1pL2noVb+XmZKLL68cjzLsiOAMaA==", - "dev": true, - "dependencies": { - "graceful-fs": "^4.2.4", - "retry": "^0.12.0", - "signal-exit": "^3.0.2" - } - }, - "node_modules/properties-reader": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/properties-reader/-/properties-reader-2.2.0.tgz", - "integrity": "sha512-CgVcr8MwGoBKK24r9TwHfZkLLaNFHQ6y4wgT9w/XzdpacOOi5ciH4hcuLechSDAwXsfrGQtI2JTutY2djOx2Ow==", - "dev": true, - "dependencies": { - "mkdirp": "^1.0.4" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/pump": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", - "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", - "dev": true, - "dependencies": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" - } - }, - "node_modules/punycode": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz", - "integrity": "sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/pure-rand": { - "version": "6.0.4", - "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-6.0.4.tgz", - "integrity": "sha512-LA0Y9kxMYv47GIPJy6MI84fqTd2HmYZI83W/kM/SkKfDlajnZYfmXFTxkbY+xSBPkLJxltMa9hIkmdc29eguMA==", - "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://github.com/sponsors/dubzzz" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/fast-check" - } - ] - }, - "node_modules/queue-microtask": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", - "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, - "node_modules/queue-tick": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/queue-tick/-/queue-tick-1.0.1.tgz", - "integrity": "sha512-kJt5qhMxoszgU/62PLP1CJytzd2NKetjSRnyuj31fDd3Rlcz3fzlFdFLD1SItunPwyqEOkca6GbV612BWfaBag==", - "dev": true - }, - "node_modules/react-is": { - "version": "18.2.0", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", - "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", - "dev": true - }, - "node_modules/readable-stream": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", - "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", - "dev": true, - "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/readdir-glob": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/readdir-glob/-/readdir-glob-1.1.2.tgz", - "integrity": "sha512-6RLVvwJtVwEDfPdn6X6Ille4/lxGl0ATOY4FN/B9nxQcgOazvvI0nodiD19ScKq0PvA/29VpaOQML36o5IzZWA==", - "dev": true, - "dependencies": { - "minimatch": "^5.1.0" - } - }, - "node_modules/readdir-glob/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "dev": true, - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "node_modules/readdir-glob/node_modules/minimatch": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.0.tgz", - "integrity": "sha512-9TPBGGak4nHfGZsPBohm9AWg6NoT7QTCehS3BIJABslyZbzxfV78QM2Y6+i741OPZIafFAaiiEMh5OyIrJPgtg==", - "dev": true, - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/require-directory": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/resolve": { - "version": "1.22.6", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.6.tgz", - "integrity": "sha512-njhxM7mV12JfufShqGy3Rz8j11RPdLy4xi15UurGJeoHLfJpVXKdh3ueuOqbYUcDZnffr6X739JBo5LzyahEsw==", - "dev": true, - "dependencies": { - "is-core-module": "^2.13.0", - "path-parse": "^1.0.7", - "supports-preserve-symlinks-flag": "^1.0.0" - }, - "bin": { - "resolve": "bin/resolve" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/resolve-cwd": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", - "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", - "dev": true, - "dependencies": { - "resolve-from": "^5.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/resolve-cwd/node_modules/resolve-from": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", - "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/resolve-from": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", - "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/resolve.exports": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/resolve.exports/-/resolve.exports-2.0.2.tgz", - "integrity": "sha512-X2UW6Nw3n/aMgDVy+0rSqgHlv39WZAlZrXCdnbyEiKm17DSqHX4MmQMaST3FbeWR5FTuRcUwYAziZajji0Y7mg==", - "dev": true, - "engines": { - "node": ">=10" - } - }, - "node_modules/retry": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", - "integrity": "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==", - "dev": true, - "engines": { - "node": ">= 4" - } - }, - "node_modules/reusify": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", - "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", - "dev": true, - "engines": { - "iojs": ">=1.0.0", - "node": ">=0.10.0" - } - }, - "node_modules/rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "dev": true, - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/run-parallel": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", - "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "dependencies": { - "queue-microtask": "^1.2.2" - } - }, - "node_modules/safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true - }, - "node_modules/safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", - "dev": true - }, - "node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, - "dependencies": { - "shebang-regex": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/signal-exit": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", - "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", - "dev": true - }, - "node_modules/sisteransi": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", - "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", - "dev": true - }, - "node_modules/slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/source-map-support": { - "version": "0.5.13", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.13.tgz", - "integrity": "sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==", - "dev": true, - "dependencies": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" - } - }, - "node_modules/split-ca": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/split-ca/-/split-ca-1.0.1.tgz", - "integrity": "sha512-Q5thBSxp5t8WPTTJQS59LrGqOZqOsrhDGDVm8azCqIBjSBd7nd9o2PM+mDulQQkh8h//4U6hFZnc/mul8t5pWQ==", - "dev": true - }, - "node_modules/sprintf-js": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", - "dev": true - }, - "node_modules/ssh-remote-port-forward": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/ssh-remote-port-forward/-/ssh-remote-port-forward-1.0.4.tgz", - "integrity": "sha512-x0LV1eVDwjf1gmG7TTnfqIzf+3VPRz7vrNIjX6oYLbeCrf/PeVY6hkT68Mg+q02qXxQhrLjB0jfgvhevoCRmLQ==", - "dev": true, - "dependencies": { - "@types/ssh2": "^0.5.48", - "ssh2": "^1.4.0" - } - }, - "node_modules/ssh-remote-port-forward/node_modules/@types/ssh2": { - "version": "0.5.52", - "resolved": "https://registry.npmjs.org/@types/ssh2/-/ssh2-0.5.52.tgz", - "integrity": "sha512-lbLLlXxdCZOSJMCInKH2+9V/77ET2J6NPQHpFI0kda61Dd1KglJs+fPQBchizmzYSOJBgdTajhPqBO1xxLywvg==", - "dev": true, - "dependencies": { - "@types/node": "*", - "@types/ssh2-streams": "*" - } - }, - "node_modules/ssh2": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/ssh2/-/ssh2-1.11.0.tgz", - "integrity": "sha512-nfg0wZWGSsfUe/IBJkXVll3PEZ//YH2guww+mP88gTpuSU4FtZN7zu9JoeTGOyCNx2dTDtT9fOpWwlzyj4uOOw==", - "dev": true, - "hasInstallScript": true, - "dependencies": { - "asn1": "^0.2.4", - "bcrypt-pbkdf": "^1.0.2" - }, - "engines": { - "node": ">=10.16.0" - }, - "optionalDependencies": { - "cpu-features": "~0.0.4", - "nan": "^2.16.0" - } - }, - "node_modules/stack-utils": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz", - "integrity": "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==", - "dev": true, - "dependencies": { - "escape-string-regexp": "^2.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/streamx": { - "version": "2.15.1", - "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.15.1.tgz", - "integrity": "sha512-fQMzy2O/Q47rgwErk/eGeLu/roaFWV0jVsogDmrszM9uIw8L5OA+t+V93MgYlufNptfjmYR1tOMWhei/Eh7TQA==", - "dev": true, - "dependencies": { - "fast-fifo": "^1.1.0", - "queue-tick": "^1.0.1" - } - }, - "node_modules/string_decoder": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", - "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", - "dev": true, - "dependencies": { - "safe-buffer": "~5.2.0" - } - }, - "node_modules/string_decoder/node_modules/safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, - "node_modules/string-length": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz", - "integrity": "sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==", - "dev": true, - "dependencies": { - "char-regex": "^1.0.2", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-bom": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", - "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-final-newline": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", - "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/strip-json-comments": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", - "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", - "dev": true, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/supports-preserve-symlinks-flag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", - "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", - "dev": true, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/tar-fs": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.0.4.tgz", - "integrity": "sha512-5AFQU8b9qLfZCX9zp2duONhPmZv0hGYiBPJsyUdqMjzq/mqVpy/rEUSeHk1+YitmxugaptgBh5oDGU3VsAJq4w==", - "dev": true, - "dependencies": { - "mkdirp-classic": "^0.5.2", - "pump": "^3.0.0", - "tar-stream": "^3.1.5" - } - }, - "node_modules/tar-fs/node_modules/tar-stream": { - "version": "3.1.6", - "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-3.1.6.tgz", - "integrity": "sha512-B/UyjYwPpMBv+PaFSWAmtYjwdrlEaZQEhMIBFNC5oEG8lpiW8XjcSdmEaClj28ArfKScKHs2nshz3k2le6crsg==", - "dev": true, - "dependencies": { - "b4a": "^1.6.4", - "fast-fifo": "^1.2.0", - "streamx": "^2.15.0" - } - }, - "node_modules/tar-stream": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", - "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", - "dev": true, - "dependencies": { - "bl": "^4.0.3", - "end-of-stream": "^1.4.1", - "fs-constants": "^1.0.0", - "inherits": "^2.0.3", - "readable-stream": "^3.1.1" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/test-exclude": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", - "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", - "dev": true, - "dependencies": { - "@istanbuljs/schema": "^0.1.2", - "glob": "^7.1.4", - "minimatch": "^3.0.4" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/testcontainers": { - "version": "10.2.1", - "resolved": "https://registry.npmjs.org/testcontainers/-/testcontainers-10.2.1.tgz", - "integrity": "sha512-R9LUMUEkKGSL2M4cP466Jah+Vi+ZLFlvrT4BENjEKJKNzubATOmDk26RHe8DHeFT+hnMD6fvVii+McXr0UTO7g==", - "dev": true, - "dependencies": { - "@balena/dockerignore": "^1.0.2", - "archiver": "^5.3.1", - "async-lock": "^1.4.0", - "byline": "^5.0.0", - "debug": "^4.3.4", - "docker-compose": "^0.24.2", - "dockerode": "^3.3.5", - "get-port": "^5.1.1", - "node-fetch": "^2.6.12", - "proper-lockfile": "^4.1.2", - "properties-reader": "^2.2.0", - "ssh-remote-port-forward": "^1.0.4", - "tar-fs": "^3.0.4", - "tmp": "^0.2.1" - } - }, - "node_modules/text-table": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", - "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", - "dev": true - }, - "node_modules/tmp": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.1.tgz", - "integrity": "sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ==", - "dev": true, - "dependencies": { - "rimraf": "^3.0.0" - }, - "engines": { - "node": ">=8.17.0" - } - }, - "node_modules/tmpl": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", - "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==", - "dev": true - }, - "node_modules/to-fast-properties": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", - "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-number": "^7.0.0" - }, - "engines": { - "node": ">=8.0" - } - }, - "node_modules/tr46": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", - "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", - "dev": true - }, - "node_modules/tweetnacl": { - "version": "0.14.5", - "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", - "integrity": "sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA==", - "dev": true - }, - "node_modules/type-check": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", - "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", - "dev": true, - "dependencies": { - "prelude-ls": "^1.2.1" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/type-detect": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", - "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/type-fest": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", - "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/update-browserslist-db": { - "version": "1.0.13", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.13.tgz", - "integrity": "sha512-xebP81SNcPuNpPP3uzeW1NYXxI3rxyJzF3pD6sH4jE7o/IX+WtSpwnVU+qIsDPyk0d3hmFQ7mjqc6AtV604hbg==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "dependencies": { - "escalade": "^3.1.1", - "picocolors": "^1.0.0" - }, - "bin": { - "update-browserslist-db": "cli.js" - }, - "peerDependencies": { - "browserslist": ">= 4.21.0" - } - }, - "node_modules/uri-js": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", - "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", - "dev": true, - "dependencies": { - "punycode": "^2.1.0" - } - }, - "node_modules/util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", - "dev": true - }, - "node_modules/v8-to-istanbul": { - "version": "9.1.3", - "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.1.3.tgz", - "integrity": "sha512-9lDD+EVI2fjFsMWXc6dy5JJzBsVTcQ2fVkfBvncZ6xJWG9wtBhOldG+mHkSL0+V1K/xgZz0JDO5UT5hFwHUghg==", - "dev": true, - "dependencies": { - "@jridgewell/trace-mapping": "^0.3.12", - "@types/istanbul-lib-coverage": "^2.0.1", - "convert-source-map": "^2.0.0" - }, - "engines": { - "node": ">=10.12.0" - } - }, - "node_modules/walker": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz", - "integrity": "sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==", - "dev": true, - "dependencies": { - "makeerror": "1.0.12" - } - }, - "node_modules/webidl-conversions": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", - "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", - "dev": true - }, - "node_modules/whatwg-url": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", - "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", - "dev": true, - "dependencies": { - "tr46": "~0.0.3", - "webidl-conversions": "^3.0.0" - } - }, - "node_modules/which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/node-which" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/wrap-ansi": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", - "dev": true - }, - "node_modules/write-file-atomic": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-4.0.2.tgz", - "integrity": "sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==", - "dev": true, - "dependencies": { - "imurmurhash": "^0.1.4", - "signal-exit": "^3.0.7" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" - } - }, - "node_modules/y18n": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", - "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", - "dev": true, - "engines": { - "node": ">=10" - } - }, - "node_modules/yallist": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", - "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", - "dev": true - }, - "node_modules/yaml": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.3.2.tgz", - "integrity": "sha512-N/lyzTPaJasoDmfV7YTrYCI0G/3ivm/9wdG0aHuheKowWQwGTsK0Eoiw6utmzAnI6pkJa0DUVygvp3spqqEKXg==", - "dev": true, - "engines": { - "node": ">= 14" - } - }, - "node_modules/yargs": { - "version": "17.7.2", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", - "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", - "dev": true, - "dependencies": { - "cliui": "^8.0.1", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.3", - "y18n": "^5.0.5", - "yargs-parser": "^21.1.1" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/yargs-parser": { - "version": "21.1.1", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", - "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", - "dev": true, - "engines": { - "node": ">=12" - } - }, - "node_modules/yocto-queue": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", - "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/zip-stream": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/zip-stream/-/zip-stream-4.1.0.tgz", - "integrity": "sha512-zshzwQW7gG7hjpBlgeQP9RuyPGNxvJdzR8SUM3QhxCnLjWN2E7j3dOvpeDcQoETfHx0urRS7EtmVToql7YpU4A==", - "dev": true, - "dependencies": { - "archiver-utils": "^2.1.0", - "compress-commons": "^4.1.0", - "readable-stream": "^3.6.0" - }, - "engines": { - "node": ">= 10" - } - } - }, - "dependencies": { - "@aashutoshrathi/word-wrap": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/@aashutoshrathi/word-wrap/-/word-wrap-1.2.6.tgz", - "integrity": "sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA==", - "dev": true - }, - "@ampproject/remapping": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.1.tgz", - "integrity": "sha512-lFMjJTrFL3j7L9yBxwYfCq2k6qqwHyzuUl/XBnif78PWTJYyL/dfowQHWE3sp6U6ZzqWiiIZnpTMO96zhkjwtg==", - "dev": true, - "requires": { - "@jridgewell/gen-mapping": "^0.3.0", - "@jridgewell/trace-mapping": "^0.3.9" - } - }, - "@babel/code-frame": { - "version": "7.23.4", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.23.4.tgz", - "integrity": "sha512-r1IONyb6Ia+jYR2vvIDhdWdlTGhqbBoFqLTQidzZ4kepUFH15ejXvFHxCVbtl7BOXIudsIubf4E81xeA3h3IXA==", - "dev": true, - "requires": { - "@babel/highlight": "^7.23.4", - "chalk": "^2.4.2" - }, - "dependencies": { - "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "requires": { - "color-convert": "^1.9.0" - } - }, - "chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - } - }, - "color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dev": true, - "requires": { - "color-name": "1.1.3" - } - }, - "color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", - "dev": true - }, - "escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", - "dev": true - }, - "has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", - "dev": true - }, - "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "requires": { - "has-flag": "^3.0.0" - } - } - } - }, - "@babel/compat-data": { - "version": "7.22.20", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.22.20.tgz", - "integrity": "sha512-BQYjKbpXjoXwFW5jGqiizJQQT/aC7pFm9Ok1OWssonuguICi264lbgMzRp2ZMmRSlfkX6DsWDDcsrctK8Rwfiw==", - "dev": true - }, - "@babel/core": { - "version": "7.23.0", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.23.0.tgz", - "integrity": "sha512-97z/ju/Jy1rZmDxybphrBuI+jtJjFVoz7Mr9yUQVVVi+DNZE333uFQeMOqcCIy1x3WYBIbWftUSLmbNXNT7qFQ==", - "dev": true, - "requires": { - "@ampproject/remapping": "^2.2.0", - "@babel/code-frame": "^7.22.13", - "@babel/generator": "^7.23.0", - "@babel/helper-compilation-targets": "^7.22.15", - "@babel/helper-module-transforms": "^7.23.0", - "@babel/helpers": "^7.23.0", - "@babel/parser": "^7.23.0", - "@babel/template": "^7.22.15", - "@babel/traverse": "^7.23.0", - "@babel/types": "^7.23.0", - "convert-source-map": "^2.0.0", - "debug": "^4.1.0", - "gensync": "^1.0.0-beta.2", - "json5": "^2.2.3", - "semver": "^6.3.1" - } - }, - "@babel/generator": { - "version": "7.23.4", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.23.4.tgz", - "integrity": "sha512-esuS49Cga3HcThFNebGhlgsrVLkvhqvYDTzgjfFFlHJcIfLe5jFmRRfCQ1KuBfc4Jrtn3ndLgKWAKjBE+IraYQ==", - "dev": true, - "requires": { - "@babel/types": "^7.23.4", - "@jridgewell/gen-mapping": "^0.3.2", - "@jridgewell/trace-mapping": "^0.3.17", - "jsesc": "^2.5.1" - } - }, - "@babel/helper-compilation-targets": { - "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.22.15.tgz", - "integrity": "sha512-y6EEzULok0Qvz8yyLkCvVX+02ic+By2UdOhylwUOvOn9dvYc9mKICJuuU1n1XBI02YWsNsnrY1kc6DVbjcXbtw==", - "dev": true, - "requires": { - "@babel/compat-data": "^7.22.9", - "@babel/helper-validator-option": "^7.22.15", - "browserslist": "^4.21.9", - "lru-cache": "^5.1.1", - "semver": "^6.3.1" - } - }, - "@babel/helper-environment-visitor": { - "version": "7.22.20", - "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz", - "integrity": "sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==", - "dev": true - }, - "@babel/helper-function-name": { - "version": "7.23.0", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz", - "integrity": "sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==", - "dev": true, - "requires": { - "@babel/template": "^7.22.15", - "@babel/types": "^7.23.0" - } - }, - "@babel/helper-hoist-variables": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz", - "integrity": "sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==", - "dev": true, - "requires": { - "@babel/types": "^7.22.5" - } - }, - "@babel/helper-module-imports": { - "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.22.15.tgz", - "integrity": "sha512-0pYVBnDKZO2fnSPCrgM/6WMc7eS20Fbok+0r88fp+YtWVLZrp4CkafFGIp+W0VKw4a22sgebPT99y+FDNMdP4w==", - "dev": true, - "requires": { - "@babel/types": "^7.22.15" - } - }, - "@babel/helper-module-transforms": { - "version": "7.23.0", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.23.0.tgz", - "integrity": "sha512-WhDWw1tdrlT0gMgUJSlX0IQvoO1eN279zrAUbVB+KpV2c3Tylz8+GnKOLllCS6Z/iZQEyVYxhZVUdPTqs2YYPw==", - "dev": true, - "requires": { - "@babel/helper-environment-visitor": "^7.22.20", - "@babel/helper-module-imports": "^7.22.15", - "@babel/helper-simple-access": "^7.22.5", - "@babel/helper-split-export-declaration": "^7.22.6", - "@babel/helper-validator-identifier": "^7.22.20" - } - }, - "@babel/helper-plugin-utils": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.22.5.tgz", - "integrity": "sha512-uLls06UVKgFG9QD4OeFYLEGteMIAa5kpTPcFL28yuCIIzsf6ZyKZMllKVOCZFhiZ5ptnwX4mtKdWCBE/uT4amg==", - "dev": true - }, - "@babel/helper-simple-access": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.22.5.tgz", - "integrity": "sha512-n0H99E/K+Bika3++WNL17POvo4rKWZ7lZEp1Q+fStVbUi8nxPQEBOlTmCOxW/0JsS56SKKQ+ojAe2pHKJHN35w==", - "dev": true, - "requires": { - "@babel/types": "^7.22.5" - } - }, - "@babel/helper-split-export-declaration": { - "version": "7.22.6", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz", - "integrity": "sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==", - "dev": true, - "requires": { - "@babel/types": "^7.22.5" - } - }, - "@babel/helper-string-parser": { - "version": "7.23.4", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.23.4.tgz", - "integrity": "sha512-803gmbQdqwdf4olxrX4AJyFBV/RTr3rSmOj0rKwesmzlfhYNDEs+/iOcznzpNWlJlIlTJC2QfPFcHB6DlzdVLQ==", - "dev": true - }, - "@babel/helper-validator-identifier": { - "version": "7.22.20", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz", - "integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==", - "dev": true - }, - "@babel/helper-validator-option": { - "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.22.15.tgz", - "integrity": "sha512-bMn7RmyFjY/mdECUbgn9eoSY4vqvacUnS9i9vGAGttgFWesO6B4CYWA7XlpbWgBt71iv/hfbPlynohStqnu5hA==", - "dev": true - }, - "@babel/helpers": { - "version": "7.23.1", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.23.1.tgz", - "integrity": "sha512-chNpneuK18yW5Oxsr+t553UZzzAs3aZnFm4bxhebsNTeshrC95yA7l5yl7GBAG+JG1rF0F7zzD2EixK9mWSDoA==", - "dev": true, - "requires": { - "@babel/template": "^7.22.15", - "@babel/traverse": "^7.23.0", - "@babel/types": "^7.23.0" - } - }, - "@babel/highlight": { - "version": "7.23.4", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.23.4.tgz", - "integrity": "sha512-acGdbYSfp2WheJoJm/EBBBLh/ID8KDc64ISZ9DYtBmC8/Q204PZJLHyzeB5qMzJ5trcOkybd78M4x2KWsUq++A==", - "dev": true, - "requires": { - "@babel/helper-validator-identifier": "^7.22.20", - "chalk": "^2.4.2", - "js-tokens": "^4.0.0" - }, - "dependencies": { - "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "requires": { - "color-convert": "^1.9.0" - } - }, - "chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - } - }, - "color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dev": true, - "requires": { - "color-name": "1.1.3" - } - }, - "color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", - "dev": true - }, - "escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", - "dev": true - }, - "has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", - "dev": true - }, - "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "requires": { - "has-flag": "^3.0.0" - } - } - } - }, - "@babel/parser": { - "version": "7.23.4", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.4.tgz", - "integrity": "sha512-vf3Xna6UEprW+7t6EtOmFpHNAuxw3xqPZghy+brsnusscJRW5BMUzzHZc5ICjULee81WeUV2jjakG09MDglJXQ==", - "dev": true - }, - "@babel/plugin-syntax-async-generators": { - "version": "7.8.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", - "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@babel/plugin-syntax-bigint": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz", - "integrity": "sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@babel/plugin-syntax-class-properties": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", - "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.12.13" - } - }, - "@babel/plugin-syntax-import-meta": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", - "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.10.4" - } - }, - "@babel/plugin-syntax-json-strings": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", - "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@babel/plugin-syntax-jsx": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.22.5.tgz", - "integrity": "sha512-gvyP4hZrgrs/wWMaocvxZ44Hw0b3W8Pe+cMxc8V1ULQ07oh8VNbIRaoD1LRZVTvD+0nieDKjfgKg89sD7rrKrg==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.22.5" - } - }, - "@babel/plugin-syntax-logical-assignment-operators": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", - "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.10.4" - } - }, - "@babel/plugin-syntax-nullish-coalescing-operator": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", - "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@babel/plugin-syntax-numeric-separator": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", - "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.10.4" - } - }, - "@babel/plugin-syntax-object-rest-spread": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", - "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@babel/plugin-syntax-optional-catch-binding": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", - "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@babel/plugin-syntax-optional-chaining": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", - "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@babel/plugin-syntax-top-level-await": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", - "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.14.5" - } - }, - "@babel/plugin-syntax-typescript": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.22.5.tgz", - "integrity": "sha512-1mS2o03i7t1c6VzH6fdQ3OA8tcEIxwG18zIPRp+UY1Ihv6W+XZzBCVxExF9upussPXJ0xE9XRHwMoNs1ep/nRQ==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.22.5" - } - }, - "@babel/template": { - "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.15.tgz", - "integrity": "sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.22.13", - "@babel/parser": "^7.22.15", - "@babel/types": "^7.22.15" - } - }, - "@babel/traverse": { - "version": "7.23.4", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.23.4.tgz", - "integrity": "sha512-IYM8wSUwunWTB6tFC2dkKZhxbIjHoWemdK+3f8/wq8aKhbUscxD5MX72ubd90fxvFknaLPeGw5ycU84V1obHJg==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.23.4", - "@babel/generator": "^7.23.4", - "@babel/helper-environment-visitor": "^7.22.20", - "@babel/helper-function-name": "^7.23.0", - "@babel/helper-hoist-variables": "^7.22.5", - "@babel/helper-split-export-declaration": "^7.22.6", - "@babel/parser": "^7.23.4", - "@babel/types": "^7.23.4", - "debug": "^4.1.0", - "globals": "^11.1.0" - }, - "dependencies": { - "globals": { - "version": "11.12.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", - "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", - "dev": true - } - } - }, - "@babel/types": { - "version": "7.23.4", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.4.tgz", - "integrity": "sha512-7uIFwVYpoplT5jp/kVv6EF93VaJ8H+Yn5IczYiaAi98ajzjfoZfslet/e0sLh+wVBjb2qqIut1b0S26VSafsSQ==", - "dev": true, - "requires": { - "@babel/helper-string-parser": "^7.23.4", - "@babel/helper-validator-identifier": "^7.22.20", - "to-fast-properties": "^2.0.0" - } - }, - "@balena/dockerignore": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@balena/dockerignore/-/dockerignore-1.0.2.tgz", - "integrity": "sha512-wMue2Sy4GAVTk6Ic4tJVcnfdau+gx2EnG7S+uAEe+TWJFqE4YoWN4/H8MSLj4eYJKxGg26lZwboEniNiNwZQ6Q==", - "dev": true - }, - "@bcoe/v8-coverage": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", - "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", - "dev": true - }, - "@eslint-community/eslint-utils": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", - "integrity": "sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==", - "dev": true, - "requires": { - "eslint-visitor-keys": "^3.3.0" - } - }, - "@eslint-community/regexpp": { - "version": "4.9.1", - "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.9.1.tgz", - "integrity": "sha512-Y27x+MBLjXa+0JWDhykM3+JE+il3kHKAEqabfEWq3SDhZjLYb6/BHL/JKFnH3fe207JaXkyDo685Oc2Glt6ifA==", - "dev": true - }, - "@eslint/eslintrc": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.2.tgz", - "integrity": "sha512-+wvgpDsrB1YqAMdEUCcnTlpfVBH7Vqn6A/NT3D8WVXFIaKMlErPIZT3oCIAVCOtarRpMtelZLqJeU3t7WY6X6g==", - "dev": true, - "requires": { - "ajv": "^6.12.4", - "debug": "^4.3.2", - "espree": "^9.6.0", - "globals": "^13.19.0", - "ignore": "^5.2.0", - "import-fresh": "^3.2.1", - "js-yaml": "^4.1.0", - "minimatch": "^3.1.2", - "strip-json-comments": "^3.1.1" - } - }, - "@eslint/js": { - "version": "8.50.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.50.0.tgz", - "integrity": "sha512-NCC3zz2+nvYd+Ckfh87rA47zfu2QsQpvc6k1yzTk+b9KzRj0wkGa8LSoGOXN6Zv4lRf/EIoZ80biDh9HOI+RNQ==", - "dev": true - }, - "@humanwhocodes/config-array": { - "version": "0.11.11", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.11.tgz", - "integrity": "sha512-N2brEuAadi0CcdeMXUkhbZB84eskAc8MEX1By6qEchoVywSgXPIjou4rYsl0V3Hj0ZnuGycGCjdNgockbzeWNA==", - "dev": true, - "requires": { - "@humanwhocodes/object-schema": "^1.2.1", - "debug": "^4.1.1", - "minimatch": "^3.0.5" - } - }, - "@humanwhocodes/module-importer": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", - "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", - "dev": true - }, - "@humanwhocodes/object-schema": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz", - "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", - "dev": true - }, - "@istanbuljs/load-nyc-config": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", - "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", - "dev": true, - "requires": { - "camelcase": "^5.3.1", - "find-up": "^4.1.0", - "get-package-type": "^0.1.0", - "js-yaml": "^3.13.1", - "resolve-from": "^5.0.0" - }, - "dependencies": { - "argparse": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", - "dev": true, - "requires": { - "sprintf-js": "~1.0.2" - } - }, - "js-yaml": { - "version": "3.14.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", - "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", - "dev": true, - "requires": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" - } - }, - "resolve-from": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", - "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", - "dev": true - } - } - }, - "@istanbuljs/schema": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", - "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", - "dev": true - }, - "@jest/console": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/console/-/console-29.7.0.tgz", - "integrity": "sha512-5Ni4CU7XHQi32IJ398EEP4RrB8eV09sXP2ROqD4bksHrnTree52PsxvX8tpL8LvTZ3pFzXyPbNQReSN41CAhOg==", - "dev": true, - "requires": { - "@jest/types": "^29.6.3", - "@types/node": "*", - "chalk": "^4.0.0", - "jest-message-util": "^29.7.0", - "jest-util": "^29.7.0", - "slash": "^3.0.0" - } - }, - "@jest/core": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/core/-/core-29.7.0.tgz", - "integrity": "sha512-n7aeXWKMnGtDA48y8TLWJPJmLmmZ642Ceo78cYWEpiD7FzDgmNDV/GCVRorPABdXLJZ/9wzzgZAlHjXjxDHGsg==", - "dev": true, - "requires": { - "@jest/console": "^29.7.0", - "@jest/reporters": "^29.7.0", - "@jest/test-result": "^29.7.0", - "@jest/transform": "^29.7.0", - "@jest/types": "^29.6.3", - "@types/node": "*", - "ansi-escapes": "^4.2.1", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "exit": "^0.1.2", - "graceful-fs": "^4.2.9", - "jest-changed-files": "^29.7.0", - "jest-config": "^29.7.0", - "jest-haste-map": "^29.7.0", - "jest-message-util": "^29.7.0", - "jest-regex-util": "^29.6.3", - "jest-resolve": "^29.7.0", - "jest-resolve-dependencies": "^29.7.0", - "jest-runner": "^29.7.0", - "jest-runtime": "^29.7.0", - "jest-snapshot": "^29.7.0", - "jest-util": "^29.7.0", - "jest-validate": "^29.7.0", - "jest-watcher": "^29.7.0", - "micromatch": "^4.0.4", - "pretty-format": "^29.7.0", - "slash": "^3.0.0", - "strip-ansi": "^6.0.0" - } - }, - "@jest/environment": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-29.7.0.tgz", - "integrity": "sha512-aQIfHDq33ExsN4jP1NWGXhxgQ/wixs60gDiKO+XVMd8Mn0NWPWgc34ZQDTb2jKaUWQ7MuwoitXAsN2XVXNMpAw==", - "dev": true, - "requires": { - "@jest/fake-timers": "^29.7.0", - "@jest/types": "^29.6.3", - "@types/node": "*", - "jest-mock": "^29.7.0" - } - }, - "@jest/expect": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-29.7.0.tgz", - "integrity": "sha512-8uMeAMycttpva3P1lBHB8VciS9V0XAr3GymPpipdyQXbBcuhkLQOSe8E/p92RyAdToS6ZD1tFkX+CkhoECE0dQ==", - "dev": true, - "requires": { - "expect": "^29.7.0", - "jest-snapshot": "^29.7.0" - } - }, - "@jest/expect-utils": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-29.7.0.tgz", - "integrity": "sha512-GlsNBWiFQFCVi9QVSx7f5AgMeLxe9YCCs5PuP2O2LdjDAA8Jh9eX7lA1Jq/xdXw3Wb3hyvlFNfZIfcRetSzYcA==", - "dev": true, - "requires": { - "jest-get-type": "^29.6.3" - } - }, - "@jest/fake-timers": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-29.7.0.tgz", - "integrity": "sha512-q4DH1Ha4TTFPdxLsqDXK1d3+ioSL7yL5oCMJZgDYm6i+6CygW5E5xVr/D1HdsGxjt1ZWSfUAs9OxSB/BNelWrQ==", - "dev": true, - "requires": { - "@jest/types": "^29.6.3", - "@sinonjs/fake-timers": "^10.0.2", - "@types/node": "*", - "jest-message-util": "^29.7.0", - "jest-mock": "^29.7.0", - "jest-util": "^29.7.0" - } - }, - "@jest/globals": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-29.7.0.tgz", - "integrity": "sha512-mpiz3dutLbkW2MNFubUGUEVLkTGiqW6yLVTA+JbP6fI6J5iL9Y0Nlg8k95pcF8ctKwCS7WVxteBs29hhfAotzQ==", - "dev": true, - "requires": { - "@jest/environment": "^29.7.0", - "@jest/expect": "^29.7.0", - "@jest/types": "^29.6.3", - "jest-mock": "^29.7.0" - } - }, - "@jest/reporters": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-29.7.0.tgz", - "integrity": "sha512-DApq0KJbJOEzAFYjHADNNxAE3KbhxQB1y5Kplb5Waqw6zVbuWatSnMjE5gs8FUgEPmNsnZA3NCWl9NG0ia04Pg==", - "dev": true, - "requires": { - "@bcoe/v8-coverage": "^0.2.3", - "@jest/console": "^29.7.0", - "@jest/test-result": "^29.7.0", - "@jest/transform": "^29.7.0", - "@jest/types": "^29.6.3", - "@jridgewell/trace-mapping": "^0.3.18", - "@types/node": "*", - "chalk": "^4.0.0", - "collect-v8-coverage": "^1.0.0", - "exit": "^0.1.2", - "glob": "^7.1.3", - "graceful-fs": "^4.2.9", - "istanbul-lib-coverage": "^3.0.0", - "istanbul-lib-instrument": "^6.0.0", - "istanbul-lib-report": "^3.0.0", - "istanbul-lib-source-maps": "^4.0.0", - "istanbul-reports": "^3.1.3", - "jest-message-util": "^29.7.0", - "jest-util": "^29.7.0", - "jest-worker": "^29.7.0", - "slash": "^3.0.0", - "string-length": "^4.0.1", - "strip-ansi": "^6.0.0", - "v8-to-istanbul": "^9.0.1" - } - }, - "@jest/schemas": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", - "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", - "dev": true, - "requires": { - "@sinclair/typebox": "^0.27.8" - } - }, - "@jest/source-map": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-29.6.3.tgz", - "integrity": "sha512-MHjT95QuipcPrpLM+8JMSzFx6eHp5Bm+4XeFDJlwsvVBjmKNiIAvasGK2fxz2WbGRlnvqehFbh07MMa7n3YJnw==", - "dev": true, - "requires": { - "@jridgewell/trace-mapping": "^0.3.18", - "callsites": "^3.0.0", - "graceful-fs": "^4.2.9" - } - }, - "@jest/test-result": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-29.7.0.tgz", - "integrity": "sha512-Fdx+tv6x1zlkJPcWXmMDAG2HBnaR9XPSd5aDWQVsfrZmLVT3lU1cwyxLgRmXR9yrq4NBoEm9BMsfgFzTQAbJYA==", - "dev": true, - "requires": { - "@jest/console": "^29.7.0", - "@jest/types": "^29.6.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "collect-v8-coverage": "^1.0.0" - } - }, - "@jest/test-sequencer": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-29.7.0.tgz", - "integrity": "sha512-GQwJ5WZVrKnOJuiYiAF52UNUJXgTZx1NHjFSEB0qEMmSZKAkdMoIzw/Cj6x6NF4AvV23AUqDpFzQkN/eYCYTxw==", - "dev": true, - "requires": { - "@jest/test-result": "^29.7.0", - "graceful-fs": "^4.2.9", - "jest-haste-map": "^29.7.0", - "slash": "^3.0.0" - } - }, - "@jest/transform": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-29.7.0.tgz", - "integrity": "sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw==", - "dev": true, - "requires": { - "@babel/core": "^7.11.6", - "@jest/types": "^29.6.3", - "@jridgewell/trace-mapping": "^0.3.18", - "babel-plugin-istanbul": "^6.1.1", - "chalk": "^4.0.0", - "convert-source-map": "^2.0.0", - "fast-json-stable-stringify": "^2.1.0", - "graceful-fs": "^4.2.9", - "jest-haste-map": "^29.7.0", - "jest-regex-util": "^29.6.3", - "jest-util": "^29.7.0", - "micromatch": "^4.0.4", - "pirates": "^4.0.4", - "slash": "^3.0.0", - "write-file-atomic": "^4.0.2" - } - }, - "@jest/types": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", - "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", - "dev": true, - "requires": { - "@jest/schemas": "^29.6.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" - } - }, - "@jridgewell/gen-mapping": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz", - "integrity": "sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==", - "dev": true, - "requires": { - "@jridgewell/set-array": "^1.0.1", - "@jridgewell/sourcemap-codec": "^1.4.10", - "@jridgewell/trace-mapping": "^0.3.9" - } - }, - "@jridgewell/resolve-uri": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz", - "integrity": "sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA==", - "dev": true - }, - "@jridgewell/set-array": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz", - "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==", - "dev": true - }, - "@jridgewell/sourcemap-codec": { - "version": "1.4.15", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", - "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==", - "dev": true - }, - "@jridgewell/trace-mapping": { - "version": "0.3.19", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.19.tgz", - "integrity": "sha512-kf37QtfW+Hwx/buWGMPcR60iF9ziHa6r/CZJIHbmcm4+0qrXiVdxegAH0F6yddEVQ7zdkjcGCgCzUu+BcbhQxw==", - "dev": true, - "requires": { - "@jridgewell/resolve-uri": "^3.1.0", - "@jridgewell/sourcemap-codec": "^1.4.14" - } - }, - "@nodelib/fs.scandir": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", - "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", - "dev": true, - "requires": { - "@nodelib/fs.stat": "2.0.5", - "run-parallel": "^1.1.9" - } - }, - "@nodelib/fs.stat": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", - "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", - "dev": true - }, - "@nodelib/fs.walk": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", - "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", - "dev": true, - "requires": { - "@nodelib/fs.scandir": "2.1.5", - "fastq": "^1.6.0" - } - }, - "@sinclair/typebox": { - "version": "0.27.8", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", - "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==", - "dev": true - }, - "@sinonjs/commons": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.0.tgz", - "integrity": "sha512-jXBtWAF4vmdNmZgD5FoKsVLv3rPgDnLgPbU84LIJ3otV44vJlDRokVng5v8NFJdCf/da9legHcKaRuZs4L7faA==", - "dev": true, - "requires": { - "type-detect": "4.0.8" - } - }, - "@sinonjs/fake-timers": { - "version": "10.3.0", - "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-10.3.0.tgz", - "integrity": "sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA==", - "dev": true, - "requires": { - "@sinonjs/commons": "^3.0.0" - } - }, - "@types/babel__core": { - "version": "7.20.2", - "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.2.tgz", - "integrity": "sha512-pNpr1T1xLUc2l3xJKuPtsEky3ybxN3m4fJkknfIpTCTfIZCDW57oAg+EfCgIIp2rvCe0Wn++/FfodDS4YXxBwA==", - "dev": true, - "requires": { - "@babel/parser": "^7.20.7", - "@babel/types": "^7.20.7", - "@types/babel__generator": "*", - "@types/babel__template": "*", - "@types/babel__traverse": "*" - } - }, - "@types/babel__generator": { - "version": "7.6.5", - "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.5.tgz", - "integrity": "sha512-h9yIuWbJKdOPLJTbmSpPzkF67e659PbQDba7ifWm5BJ8xTv+sDmS7rFmywkWOvXedGTivCdeGSIIX8WLcRTz8w==", - "dev": true, - "requires": { - "@babel/types": "^7.0.0" - } - }, - "@types/babel__template": { - "version": "7.4.2", - "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.2.tgz", - "integrity": "sha512-/AVzPICMhMOMYoSx9MoKpGDKdBRsIXMNByh1PXSZoa+v6ZoLa8xxtsT/uLQ/NJm0XVAWl/BvId4MlDeXJaeIZQ==", - "dev": true, - "requires": { - "@babel/parser": "^7.1.0", - "@babel/types": "^7.0.0" - } - }, - "@types/babel__traverse": { - "version": "7.20.2", - "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.20.2.tgz", - "integrity": "sha512-ojlGK1Hsfce93J0+kn3H5R73elidKUaZonirN33GSmgTUMpzI/MIFfSpF3haANe3G1bEBS9/9/QEqwTzwqFsKw==", - "dev": true, - "requires": { - "@babel/types": "^7.20.7" - } - }, - "@types/graceful-fs": { - "version": "4.1.7", - "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.7.tgz", - "integrity": "sha512-MhzcwU8aUygZroVwL2jeYk6JisJrPl/oov/gsgGCue9mkgl9wjGbzReYQClxiUgFDnib9FuHqTndccKeZKxTRw==", - "dev": true, - "requires": { - "@types/node": "*" - } - }, - "@types/istanbul-lib-coverage": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.4.tgz", - "integrity": "sha512-z/QT1XN4K4KYuslS23k62yDIDLwLFkzxOuMplDtObz0+y7VqJCaO2o+SPwHCvLFZh7xazvvoor2tA/hPz9ee7g==", - "dev": true - }, - "@types/istanbul-lib-report": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", - "integrity": "sha512-gPQuzaPR5h/djlAv2apEG1HVOyj1IUs7GpfMZixU0/0KXT3pm64ylHuMUI1/Akh+sq/iikxg6Z2j+fcMDXaaTQ==", - "dev": true, - "requires": { - "@types/istanbul-lib-coverage": "*" - } - }, - "@types/istanbul-reports": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.2.tgz", - "integrity": "sha512-kv43F9eb3Lhj+lr/Hn6OcLCs/sSM8bt+fIaP11rCYngfV6NVjzWXJ17owQtDQTL9tQ8WSLUrGsSJ6rJz0F1w1A==", - "dev": true, - "requires": { - "@types/istanbul-lib-report": "*" - } - }, - "@types/node": { - "version": "18.6.1", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.6.1.tgz", - "integrity": "sha512-z+2vB6yDt1fNwKOeGbckpmirO+VBDuQqecXkgeIqDlaOtmKn6hPR/viQ8cxCfqLU4fTlvM3+YjM367TukWdxpg==", - "dev": true - }, - "@types/ssh2-streams": { - "version": "0.1.9", - "resolved": "https://registry.npmjs.org/@types/ssh2-streams/-/ssh2-streams-0.1.9.tgz", - "integrity": "sha512-I2J9jKqfmvXLR5GomDiCoHrEJ58hAOmFrekfFqmCFd+A6gaEStvWnPykoWUwld1PNg4G5ag1LwdA+Lz1doRJqg==", - "dev": true, - "requires": { - "@types/node": "*" - } - }, - "@types/stack-utils": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.1.tgz", - "integrity": "sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw==", - "dev": true - }, - "@types/yargs": { - "version": "17.0.26", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.26.tgz", - "integrity": "sha512-Y3vDy2X6zw/ZCumcwLpdhM5L7jmyGpmBCTYMHDLqT2IKVMYRRLdv6ZakA+wxhra6Z/3bwhNbNl9bDGXaFU+6rw==", - "dev": true, - "requires": { - "@types/yargs-parser": "*" - } - }, - "@types/yargs-parser": { - "version": "21.0.1", - "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.1.tgz", - "integrity": "sha512-axdPBuLuEJt0c4yI5OZssC19K2Mq1uKdrfZBzuxLvaztgqUtFYZUNw7lETExPYJR9jdEoIg4mb7RQKRQzOkeGQ==", - "dev": true - }, - "acorn": { - "version": "8.10.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.10.0.tgz", - "integrity": "sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw==", - "dev": true - }, - "acorn-jsx": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", - "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", - "dev": true, - "requires": {} - }, - "ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "dev": true, - "requires": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - } - }, - "ansi-escapes": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", - "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", - "dev": true, - "requires": { - "type-fest": "^0.21.3" - }, - "dependencies": { - "type-fest": { - "version": "0.21.3", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", - "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", - "dev": true - } - } - }, - "ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true - }, - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "anymatch": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", - "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", - "dev": true, - "requires": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" - } - }, - "archiver": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/archiver/-/archiver-5.3.1.tgz", - "integrity": "sha512-8KyabkmbYrH+9ibcTScQ1xCJC/CGcugdVIwB+53f5sZziXgwUh3iXlAlANMxcZyDEfTHMe6+Z5FofV8nopXP7w==", - "dev": true, - "requires": { - "archiver-utils": "^2.1.0", - "async": "^3.2.3", - "buffer-crc32": "^0.2.1", - "readable-stream": "^3.6.0", - "readdir-glob": "^1.0.0", - "tar-stream": "^2.2.0", - "zip-stream": "^4.1.0" - } - }, - "archiver-utils": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/archiver-utils/-/archiver-utils-2.1.0.tgz", - "integrity": "sha512-bEL/yUb/fNNiNTuUz979Z0Yg5L+LzLxGJz8x79lYmR54fmTIb6ob/hNQgkQnIUDWIFjZVQwl9Xs356I6BAMHfw==", - "dev": true, - "requires": { - "glob": "^7.1.4", - "graceful-fs": "^4.2.0", - "lazystream": "^1.0.0", - "lodash.defaults": "^4.2.0", - "lodash.difference": "^4.5.0", - "lodash.flatten": "^4.4.0", - "lodash.isplainobject": "^4.0.6", - "lodash.union": "^4.6.0", - "normalize-path": "^3.0.0", - "readable-stream": "^2.0.0" - }, - "dependencies": { - "readable-stream": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", - "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", - "dev": true, - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dev": true, - "requires": { - "safe-buffer": "~5.1.0" - } - } - } - }, - "argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true - }, - "asn1": { - "version": "0.2.6", - "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.6.tgz", - "integrity": "sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==", - "dev": true, - "requires": { - "safer-buffer": "~2.1.0" - } - }, - "async": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/async/-/async-3.2.4.tgz", - "integrity": "sha512-iAB+JbDEGXhyIUavoDl9WP/Jj106Kz9DEn1DPgYw5ruDn0e3Wgi3sKFm55sASdGBNOQB8F59d9qQ7deqrHA8wQ==", - "dev": true - }, - "async-lock": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/async-lock/-/async-lock-1.4.0.tgz", - "integrity": "sha512-coglx5yIWuetakm3/1dsX9hxCNox22h7+V80RQOu2XUUMidtArxKoZoOtHUPuR84SycKTXzgGzAUR5hJxujyJQ==", - "dev": true - }, - "b4a": { - "version": "1.6.4", - "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.6.4.tgz", - "integrity": "sha512-fpWrvyVHEKyeEvbKZTVOeZF3VSKKWtJxFIxX/jaVPf+cLbGUSitjb49pHLqPV2BUNNZ0LcoeEGfE/YCpyDYHIw==", - "dev": true - }, - "babel-jest": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.7.0.tgz", - "integrity": "sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg==", - "dev": true, - "requires": { - "@jest/transform": "^29.7.0", - "@types/babel__core": "^7.1.14", - "babel-plugin-istanbul": "^6.1.1", - "babel-preset-jest": "^29.6.3", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.9", - "slash": "^3.0.0" - } - }, - "babel-plugin-istanbul": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz", - "integrity": "sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.0.0", - "@istanbuljs/load-nyc-config": "^1.0.0", - "@istanbuljs/schema": "^0.1.2", - "istanbul-lib-instrument": "^5.0.4", - "test-exclude": "^6.0.0" - }, - "dependencies": { - "istanbul-lib-instrument": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz", - "integrity": "sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==", - "dev": true, - "requires": { - "@babel/core": "^7.12.3", - "@babel/parser": "^7.14.7", - "@istanbuljs/schema": "^0.1.2", - "istanbul-lib-coverage": "^3.2.0", - "semver": "^6.3.0" - } - } - } - }, - "babel-plugin-jest-hoist": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.6.3.tgz", - "integrity": "sha512-ESAc/RJvGTFEzRwOTT4+lNDk/GNHMkKbNzsvT0qKRfDyyYTskxB5rnU2njIDYVxXCBHHEI1c0YwHob3WaYujOg==", - "dev": true, - "requires": { - "@babel/template": "^7.3.3", - "@babel/types": "^7.3.3", - "@types/babel__core": "^7.1.14", - "@types/babel__traverse": "^7.0.6" - } - }, - "babel-preset-current-node-syntax": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.0.1.tgz", - "integrity": "sha512-M7LQ0bxarkxQoN+vz5aJPsLBn77n8QgTFmo8WK0/44auK2xlCXrYcUxHFxgU7qW5Yzw/CjmLRK2uJzaCd7LvqQ==", - "dev": true, - "requires": { - "@babel/plugin-syntax-async-generators": "^7.8.4", - "@babel/plugin-syntax-bigint": "^7.8.3", - "@babel/plugin-syntax-class-properties": "^7.8.3", - "@babel/plugin-syntax-import-meta": "^7.8.3", - "@babel/plugin-syntax-json-strings": "^7.8.3", - "@babel/plugin-syntax-logical-assignment-operators": "^7.8.3", - "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", - "@babel/plugin-syntax-numeric-separator": "^7.8.3", - "@babel/plugin-syntax-object-rest-spread": "^7.8.3", - "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", - "@babel/plugin-syntax-optional-chaining": "^7.8.3", - "@babel/plugin-syntax-top-level-await": "^7.8.3" - } - }, - "babel-preset-jest": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-29.6.3.tgz", - "integrity": "sha512-0B3bhxR6snWXJZtR/RliHTDPRgn1sNHOR0yVtq/IiQFyuOVjFS+wuio/R4gSNkyYmKmJB4wGZv2NZanmKmTnNA==", - "dev": true, - "requires": { - "babel-plugin-jest-hoist": "^29.6.3", - "babel-preset-current-node-syntax": "^1.0.0" - } - }, - "balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true - }, - "base64-js": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", - "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", - "dev": true - }, - "bcrypt-pbkdf": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", - "integrity": "sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w==", - "dev": true, - "requires": { - "tweetnacl": "^0.14.3" - } - }, - "bl": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", - "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", - "dev": true, - "requires": { - "buffer": "^5.5.0", - "inherits": "^2.0.4", - "readable-stream": "^3.4.0" - } - }, - "brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "requires": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "braces": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", - "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", - "dev": true, - "requires": { - "fill-range": "^7.1.1" - } - }, - "browserslist": { - "version": "4.22.1", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.22.1.tgz", - "integrity": "sha512-FEVc202+2iuClEhZhrWy6ZiAcRLvNMyYcxZ8raemul1DYVOVdFsbqckWLdsixQZCpJlwe77Z3UTalE7jsjnKfQ==", - "dev": true, - "requires": { - "caniuse-lite": "^1.0.30001541", - "electron-to-chromium": "^1.4.535", - "node-releases": "^2.0.13", - "update-browserslist-db": "^1.0.13" - } - }, - "bser": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz", - "integrity": "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==", - "dev": true, - "requires": { - "node-int64": "^0.4.0" - } - }, - "buffer": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", - "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", - "dev": true, - "requires": { - "base64-js": "^1.3.1", - "ieee754": "^1.1.13" - } - }, - "buffer-crc32": { - "version": "0.2.13", - "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", - "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==", - "dev": true - }, - "buffer-from": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", - "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", - "dev": true - }, - "buildcheck": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/buildcheck/-/buildcheck-0.0.3.tgz", - "integrity": "sha512-pziaA+p/wdVImfcbsZLNF32EiWyujlQLwolMqUQE8xpKNOH7KmZQaY8sXN7DGOEzPAElo9QTaeNRfGnf3iOJbA==", - "dev": true, - "optional": true - }, - "byline": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/byline/-/byline-5.0.0.tgz", - "integrity": "sha512-s6webAy+R4SR8XVuJWt2V2rGvhnrhxN+9S15GNuTK3wKPOXFF6RNc+8ug2XhH+2s4f+uudG4kUVYmYOQWL2g0Q==", - "dev": true - }, - "callsites": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", - "dev": true - }, - "camelcase": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", - "dev": true - }, - "caniuse-lite": { - "version": "1.0.30001546", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001546.tgz", - "integrity": "sha512-zvtSJwuQFpewSyRrI3AsftF6rM0X80mZkChIt1spBGEvRglCrjTniXvinc8JKRoqTwXAgvqTImaN9igfSMtUBw==", - "dev": true - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "char-regex": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz", - "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==", - "dev": true - }, - "chownr": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", - "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", - "dev": true - }, - "ci-info": { - "version": "3.9.0", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", - "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==", - "dev": true - }, - "cjs-module-lexer": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.2.3.tgz", - "integrity": "sha512-0TNiGstbQmCFwt4akjjBg5pLRTSyj/PkWQ1ZoO2zntmg9yLqSRxwEa4iCfQLGjqhiqBfOJa7W/E8wfGrTDmlZQ==", - "dev": true - }, - "cliui": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", - "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", - "dev": true, - "requires": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.1", - "wrap-ansi": "^7.0.0" - } - }, - "co": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", - "integrity": "sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==", - "dev": true - }, - "collect-v8-coverage": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.2.tgz", - "integrity": "sha512-lHl4d5/ONEbLlJvaJNtsF/Lz+WvB07u2ycqTYbdrq7UypDXailES4valYb2eWiJFxZlVmpGekfqoxQhzyFdT4Q==", - "dev": true - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "compress-commons": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/compress-commons/-/compress-commons-4.1.1.tgz", - "integrity": "sha512-QLdDLCKNV2dtoTorqgxngQCMA+gWXkM/Nwu7FpeBhk/RdkzimqC3jueb/FDmaZeXh+uby1jkBqE3xArsLBE5wQ==", - "dev": true, - "requires": { - "buffer-crc32": "^0.2.13", - "crc32-stream": "^4.0.2", - "normalize-path": "^3.0.0", - "readable-stream": "^3.6.0" - } - }, - "concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "dev": true - }, - "convert-source-map": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", - "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", - "dev": true - }, - "core-util-is": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", - "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", - "dev": true - }, - "cpu-features": { - "version": "0.0.4", - "resolved": "https://registry.npmjs.org/cpu-features/-/cpu-features-0.0.4.tgz", - "integrity": "sha512-fKiZ/zp1mUwQbnzb9IghXtHtDoTMtNeb8oYGx6kX2SYfhnG0HNdBEBIzB9b5KlXu5DQPhfy3mInbBxFcgwAr3A==", - "dev": true, - "optional": true, - "requires": { - "buildcheck": "0.0.3", - "nan": "^2.15.0" - } - }, - "crc-32": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/crc-32/-/crc-32-1.2.2.tgz", - "integrity": "sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==", - "dev": true - }, - "crc32-stream": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/crc32-stream/-/crc32-stream-4.0.2.tgz", - "integrity": "sha512-DxFZ/Hk473b/muq1VJ///PMNLj0ZMnzye9thBpmjpJKCc5eMgB95aK8zCGrGfQ90cWo561Te6HK9D+j4KPdM6w==", - "dev": true, - "requires": { - "crc-32": "^1.2.0", - "readable-stream": "^3.4.0" - } - }, - "create-jest": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/create-jest/-/create-jest-29.7.0.tgz", - "integrity": "sha512-Adz2bdH0Vq3F53KEMJOoftQFutWCukm6J24wbPWRO4k1kMY7gS7ds/uoJkNuV8wDCtWWnuwGcJwpWcih+zEW1Q==", - "dev": true, - "requires": { - "@jest/types": "^29.6.3", - "chalk": "^4.0.0", - "exit": "^0.1.2", - "graceful-fs": "^4.2.9", - "jest-config": "^29.7.0", - "jest-util": "^29.7.0", - "prompts": "^2.0.1" - } - }, - "cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", - "dev": true, - "requires": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - } - }, - "debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "dev": true, - "requires": { - "ms": "2.1.2" - } - }, - "dedent": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.5.1.tgz", - "integrity": "sha512-+LxW+KLWxu3HW3M2w2ympwtqPrqYRzU8fqi6Fhd18fBALe15blJPI/I4+UHveMVG6lJqB4JNd4UG0S5cnVHwIg==", - "dev": true, - "requires": {} - }, - "deep-is": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", - "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", - "dev": true - }, - "deepmerge": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", - "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", - "dev": true - }, - "detect-newline": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", - "integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==", - "dev": true - }, - "diff-sequences": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz", - "integrity": "sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==", - "dev": true - }, - "docker-compose": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/docker-compose/-/docker-compose-0.24.2.tgz", - "integrity": "sha512-2/WLvA7UZ6A2LDLQrYW0idKipmNBWhtfvrn2yzjC5PnHDzuFVj1zAZN6MJxVMKP0zZH8uzAK6OwVZYHGuyCmTw==", - "dev": true, - "requires": { - "yaml": "^2.2.2" - } - }, - "docker-modem": { - "version": "3.0.8", - "resolved": "https://registry.npmjs.org/docker-modem/-/docker-modem-3.0.8.tgz", - "integrity": "sha512-f0ReSURdM3pcKPNS30mxOHSbaFLcknGmQjwSfmbcdOw1XWKXVhukM3NJHhr7NpY9BIyyWQb0EBo3KQvvuU5egQ==", - "dev": true, - "requires": { - "debug": "^4.1.1", - "readable-stream": "^3.5.0", - "split-ca": "^1.0.1", - "ssh2": "^1.11.0" - } - }, - "dockerode": { - "version": "3.3.5", - "resolved": "https://registry.npmjs.org/dockerode/-/dockerode-3.3.5.tgz", - "integrity": "sha512-/0YNa3ZDNeLr/tSckmD69+Gq+qVNhvKfAHNeZJBnp7EOP6RGKV8ORrJHkUn20So5wU+xxT7+1n5u8PjHbfjbSA==", - "dev": true, - "requires": { - "@balena/dockerignore": "^1.0.2", - "docker-modem": "^3.0.0", - "tar-fs": "~2.0.1" - }, - "dependencies": { - "tar-fs": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.0.1.tgz", - "integrity": "sha512-6tzWDMeroL87uF/+lin46k+Q+46rAJ0SyPGz7OW7wTgblI273hsBqk2C1j0/xNadNLKDTUL9BukSjB7cwgmlPA==", - "dev": true, - "requires": { - "chownr": "^1.1.1", - "mkdirp-classic": "^0.5.2", - "pump": "^3.0.0", - "tar-stream": "^2.0.0" - } - } - } - }, - "doctrine": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", - "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", - "dev": true, - "requires": { - "esutils": "^2.0.2" - } - }, - "electron-to-chromium": { - "version": "1.4.543", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.543.tgz", - "integrity": "sha512-t2ZP4AcGE0iKCCQCBx/K2426crYdxD3YU6l0uK2EO3FZH0pbC4pFz/sZm2ruZsND6hQBTcDWWlo/MLpiOdif5g==", - "dev": true - }, - "emittery": { - "version": "0.13.1", - "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.13.1.tgz", - "integrity": "sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==", - "dev": true - }, - "emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true - }, - "end-of-stream": { - "version": "1.4.4", - "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", - "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", - "dev": true, - "requires": { - "once": "^1.4.0" - } - }, - "error-ex": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", - "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", - "dev": true, - "requires": { - "is-arrayish": "^0.2.1" - } - }, - "escalade": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", - "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", - "dev": true - }, - "escape-string-regexp": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", - "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", - "dev": true - }, - "eslint": { - "version": "8.50.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.50.0.tgz", - "integrity": "sha512-FOnOGSuFuFLv/Sa+FDVRZl4GGVAAFFi8LecRsI5a1tMO5HIE8nCm4ivAlzt4dT3ol/PaaGC0rJEEXQmHJBGoOg==", - "dev": true, - "requires": { - "@eslint-community/eslint-utils": "^4.2.0", - "@eslint-community/regexpp": "^4.6.1", - "@eslint/eslintrc": "^2.1.2", - "@eslint/js": "8.50.0", - "@humanwhocodes/config-array": "^0.11.11", - "@humanwhocodes/module-importer": "^1.0.1", - "@nodelib/fs.walk": "^1.2.8", - "ajv": "^6.12.4", - "chalk": "^4.0.0", - "cross-spawn": "^7.0.2", - "debug": "^4.3.2", - "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.4.2", - "esutils": "^2.0.2", - "fast-deep-equal": "^3.1.3", - "file-entry-cache": "^6.0.1", - "find-up": "^5.0.0", - "glob-parent": "^6.0.2", - "globals": "^13.19.0", - "graphemer": "^1.4.0", - "ignore": "^5.2.0", - "imurmurhash": "^0.1.4", - "is-glob": "^4.0.0", - "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" - }, - "dependencies": { - "escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "dev": true - }, - "find-up": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", - "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", - "dev": true, - "requires": { - "locate-path": "^6.0.0", - "path-exists": "^4.0.0" - } - }, - "locate-path": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", - "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", - "dev": true, - "requires": { - "p-locate": "^5.0.0" - } - }, - "p-locate": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", - "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", - "dev": true, - "requires": { - "p-limit": "^3.0.2" - } - } - } - }, - "eslint-scope": { - "version": "7.2.2", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", - "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", - "dev": true, - "requires": { - "esrecurse": "^4.3.0", - "estraverse": "^5.2.0" - } - }, - "eslint-visitor-keys": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", - "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", - "dev": true - }, - "espree": { - "version": "9.6.1", - "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", - "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", - "dev": true, - "requires": { - "acorn": "^8.9.0", - "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^3.4.1" - } - }, - "esprima": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", - "dev": true - }, - "esquery": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.5.0.tgz", - "integrity": "sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==", - "dev": true, - "requires": { - "estraverse": "^5.1.0" - } - }, - "esrecurse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", - "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", - "dev": true, - "requires": { - "estraverse": "^5.2.0" - } - }, - "estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true - }, - "esutils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", - "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", - "dev": true - }, - "execa": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", - "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", - "dev": true, - "requires": { - "cross-spawn": "^7.0.3", - "get-stream": "^6.0.0", - "human-signals": "^2.1.0", - "is-stream": "^2.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^4.0.1", - "onetime": "^5.1.2", - "signal-exit": "^3.0.3", - "strip-final-newline": "^2.0.0" - } - }, - "exit": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", - "integrity": "sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==", - "dev": true - }, - "expect": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/expect/-/expect-29.7.0.tgz", - "integrity": "sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw==", - "dev": true, - "requires": { - "@jest/expect-utils": "^29.7.0", - "jest-get-type": "^29.6.3", - "jest-matcher-utils": "^29.7.0", - "jest-message-util": "^29.7.0", - "jest-util": "^29.7.0" - } - }, - "fast-deep-equal": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true - }, - "fast-fifo": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/fast-fifo/-/fast-fifo-1.3.2.tgz", - "integrity": "sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==", - "dev": true - }, - "fast-json-stable-stringify": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", - "dev": true - }, - "fast-levenshtein": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", - "dev": true - }, - "fastq": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz", - "integrity": "sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==", - "dev": true, - "requires": { - "reusify": "^1.0.4" - } - }, - "fb-watchman": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz", - "integrity": "sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==", - "dev": true, - "requires": { - "bser": "2.1.1" - } - }, - "file-entry-cache": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", - "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", - "dev": true, - "requires": { - "flat-cache": "^3.0.4" - } - }, - "fill-range": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", - "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", - "dev": true, - "requires": { - "to-regex-range": "^5.0.1" - } - }, - "find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "dev": true, - "requires": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - } - }, - "flat-cache": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", - "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==", - "dev": true, - "requires": { - "flatted": "^3.1.0", - "rimraf": "^3.0.2" - } - }, - "flatted": { - "version": "3.2.6", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.6.tgz", - "integrity": "sha512-0sQoMh9s0BYsm+12Huy/rkKxVu4R1+r96YX5cG44rHV0pQ6iC3Q+mkoMFaGWObMFYQxCVT+ssG1ksneA2MI9KQ==", - "dev": true - }, - "fs-constants": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", - "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", - "dev": true - }, - "fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", - "dev": true - }, - "fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "dev": true, - "optional": true - }, - "gensync": { - "version": "1.0.0-beta.2", - "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", - "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", - "dev": true - }, - "get-caller-file": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", - "dev": true - }, - "get-package-type": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", - "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", - "dev": true - }, - "get-port": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/get-port/-/get-port-5.1.1.tgz", - "integrity": "sha512-g/Q1aTSDOxFpchXC4i8ZWvxA1lnPqx/JHqcpIw0/LX9T8x/GBbi6YnlN5nhaKIFkT8oFsscUKgDJYxfwfS6QsQ==", - "dev": true - }, - "get-stream": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", - "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", - "dev": true - }, - "glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "dev": true, - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - }, - "glob-parent": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", - "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", - "dev": true, - "requires": { - "is-glob": "^4.0.3" - } - }, - "globals": { - "version": "13.23.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.23.0.tgz", - "integrity": "sha512-XAmF0RjlrjY23MA51q3HltdlGxUpXPvg0GioKiD9X6HD28iMjo2dKC8Vqwm7lne4GNr78+RHTfliktR6ZH09wA==", - "dev": true, - "requires": { - "type-fest": "^0.20.2" - } - }, - "graceful-fs": { - "version": "4.2.10", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", - "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==", - "dev": true - }, - "graphemer": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", - "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", - "dev": true - }, - "has": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/has/-/has-1.0.4.tgz", - "integrity": "sha512-qdSAmqLF6209RFj4VVItywPMbm3vWylknmB3nvNiUIs72xAimcM8nVYxYr7ncvZq5qzk9MKIZR8ijqD/1QuYjQ==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "html-escaper": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", - "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", - "dev": true - }, - "human-signals": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", - "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", - "dev": true - }, - "ieee754": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", - "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", - "dev": true - }, - "ignore": { - "version": "5.2.4", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz", - "integrity": "sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==", - "dev": true - }, - "import-fresh": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", - "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", - "dev": true, - "requires": { - "parent-module": "^1.0.0", - "resolve-from": "^4.0.0" - } - }, - "import-local": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.1.0.tgz", - "integrity": "sha512-ASB07uLtnDs1o6EHjKpX34BKYDSqnFerfTOJL2HvMqF70LnxpjkzDB8J44oT9pu4AMPkQwf8jl6szgvNd2tRIg==", - "dev": true, - "requires": { - "pkg-dir": "^4.2.0", - "resolve-cwd": "^3.0.0" - } - }, - "imurmurhash": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", - "dev": true - }, - "inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", - "dev": true, - "requires": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true - }, - "is-arrayish": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", - "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", - "dev": true - }, - "is-core-module": { - "version": "2.13.0", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.0.tgz", - "integrity": "sha512-Z7dk6Qo8pOCp3l4tsX2C5ZVas4V+UxwQodwZhLopL91TX8UyyHEXafPcyoeeWuLrwzHcr3igO78wNLwHJHsMCQ==", - "dev": true, - "requires": { - "has": "^1.0.3" - } - }, - "is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", - "dev": true - }, - "is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true - }, - "is-generator-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz", - "integrity": "sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==", - "dev": true - }, - "is-glob": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", - "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "dev": true, - "requires": { - "is-extglob": "^2.1.1" - } - }, - "is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true - }, - "is-path-inside": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", - "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", - "dev": true - }, - "is-stream": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", - "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", - "dev": true - }, - "isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", - "dev": true - }, - "isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true - }, - "istanbul-lib-coverage": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.0.tgz", - "integrity": "sha512-eOeJ5BHCmHYvQK7xt9GkdHuzuCGS1Y6g9Gvnx3Ym33fz/HpLRYxiS0wHNr+m/MBC8B647Xt608vCDEvhl9c6Mw==", - "dev": true - }, - "istanbul-lib-instrument": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.1.tgz", - "integrity": "sha512-EAMEJBsYuyyztxMxW3g7ugGPkrZsV57v0Hmv3mm1uQsmB+QnZuepg731CRaIgeUVSdmsTngOkSnauNF8p7FIhA==", - "dev": true, - "requires": { - "@babel/core": "^7.12.3", - "@babel/parser": "^7.14.7", - "@istanbuljs/schema": "^0.1.2", - "istanbul-lib-coverage": "^3.2.0", - "semver": "^7.5.4" - }, - "dependencies": { - "lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, - "requires": { - "yallist": "^4.0.0" - } - }, - "semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", - "dev": true, - "requires": { - "lru-cache": "^6.0.0" - } - }, - "yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - } - } - }, - "istanbul-lib-report": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", - "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", - "dev": true, - "requires": { - "istanbul-lib-coverage": "^3.0.0", - "make-dir": "^4.0.0", - "supports-color": "^7.1.0" - } - }, - "istanbul-lib-source-maps": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz", - "integrity": "sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==", - "dev": true, - "requires": { - "debug": "^4.1.1", - "istanbul-lib-coverage": "^3.0.0", - "source-map": "^0.6.1" - } - }, - "istanbul-reports": { - "version": "3.1.6", - "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.6.tgz", - "integrity": "sha512-TLgnMkKg3iTDsQ9PbPTdpfAK2DzjF9mqUG7RMgcQl8oFjad8ob4laGxv5XV5U9MAfx8D6tSJiUyuAwzLicaxlg==", - "dev": true, - "requires": { - "html-escaper": "^2.0.0", - "istanbul-lib-report": "^3.0.0" - } - }, - "jest": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest/-/jest-29.7.0.tgz", - "integrity": "sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw==", - "dev": true, - "requires": { - "@jest/core": "^29.7.0", - "@jest/types": "^29.6.3", - "import-local": "^3.0.2", - "jest-cli": "^29.7.0" - } - }, - "jest-changed-files": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-29.7.0.tgz", - "integrity": "sha512-fEArFiwf1BpQ+4bXSprcDc3/x4HSzL4al2tozwVpDFpsxALjLYdyiIK4e5Vz66GQJIbXJ82+35PtysofptNX2w==", - "dev": true, - "requires": { - "execa": "^5.0.0", - "jest-util": "^29.7.0", - "p-limit": "^3.1.0" - } - }, - "jest-circus": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-29.7.0.tgz", - "integrity": "sha512-3E1nCMgipcTkCocFwM90XXQab9bS+GMsjdpmPrlelaxwD93Ad8iVEjX/vvHPdLPnFf+L40u+5+iutRdA1N9myw==", - "dev": true, - "requires": { - "@jest/environment": "^29.7.0", - "@jest/expect": "^29.7.0", - "@jest/test-result": "^29.7.0", - "@jest/types": "^29.6.3", - "@types/node": "*", - "chalk": "^4.0.0", - "co": "^4.6.0", - "dedent": "^1.0.0", - "is-generator-fn": "^2.0.0", - "jest-each": "^29.7.0", - "jest-matcher-utils": "^29.7.0", - "jest-message-util": "^29.7.0", - "jest-runtime": "^29.7.0", - "jest-snapshot": "^29.7.0", - "jest-util": "^29.7.0", - "p-limit": "^3.1.0", - "pretty-format": "^29.7.0", - "pure-rand": "^6.0.0", - "slash": "^3.0.0", - "stack-utils": "^2.0.3" - } - }, - "jest-cli": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-29.7.0.tgz", - "integrity": "sha512-OVVobw2IubN/GSYsxETi+gOe7Ka59EFMR/twOU3Jb2GnKKeMGJB5SGUUrEz3SFVmJASUdZUzy83sLNNQ2gZslg==", - "dev": true, - "requires": { - "@jest/core": "^29.7.0", - "@jest/test-result": "^29.7.0", - "@jest/types": "^29.6.3", - "chalk": "^4.0.0", - "create-jest": "^29.7.0", - "exit": "^0.1.2", - "import-local": "^3.0.2", - "jest-config": "^29.7.0", - "jest-util": "^29.7.0", - "jest-validate": "^29.7.0", - "yargs": "^17.3.1" - } - }, - "jest-config": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-29.7.0.tgz", - "integrity": "sha512-uXbpfeQ7R6TZBqI3/TxCU4q4ttk3u0PJeC+E0zbfSoSjq6bJ7buBPxzQPL0ifrkY4DNu4JUdk0ImlBUYi840eQ==", - "dev": true, - "requires": { - "@babel/core": "^7.11.6", - "@jest/test-sequencer": "^29.7.0", - "@jest/types": "^29.6.3", - "babel-jest": "^29.7.0", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "deepmerge": "^4.2.2", - "glob": "^7.1.3", - "graceful-fs": "^4.2.9", - "jest-circus": "^29.7.0", - "jest-environment-node": "^29.7.0", - "jest-get-type": "^29.6.3", - "jest-regex-util": "^29.6.3", - "jest-resolve": "^29.7.0", - "jest-runner": "^29.7.0", - "jest-util": "^29.7.0", - "jest-validate": "^29.7.0", - "micromatch": "^4.0.4", - "parse-json": "^5.2.0", - "pretty-format": "^29.7.0", - "slash": "^3.0.0", - "strip-json-comments": "^3.1.1" - } - }, - "jest-diff": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.7.0.tgz", - "integrity": "sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw==", - "dev": true, - "requires": { - "chalk": "^4.0.0", - "diff-sequences": "^29.6.3", - "jest-get-type": "^29.6.3", - "pretty-format": "^29.7.0" - } - }, - "jest-docblock": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-29.7.0.tgz", - "integrity": "sha512-q617Auw3A612guyaFgsbFeYpNP5t2aoUNLwBUbc/0kD1R4t9ixDbyFTHd1nok4epoVFpr7PmeWHrhvuV3XaJ4g==", - "dev": true, - "requires": { - "detect-newline": "^3.0.0" - } - }, - "jest-each": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-29.7.0.tgz", - "integrity": "sha512-gns+Er14+ZrEoC5fhOfYCY1LOHHr0TI+rQUHZS8Ttw2l7gl+80eHc/gFf2Ktkw0+SIACDTeWvpFcv3B04VembQ==", - "dev": true, - "requires": { - "@jest/types": "^29.6.3", - "chalk": "^4.0.0", - "jest-get-type": "^29.6.3", - "jest-util": "^29.7.0", - "pretty-format": "^29.7.0" - } - }, - "jest-environment-node": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-29.7.0.tgz", - "integrity": "sha512-DOSwCRqXirTOyheM+4d5YZOrWcdu0LNZ87ewUoywbcb2XR4wKgqiG8vNeYwhjFMbEkfju7wx2GYH0P2gevGvFw==", - "dev": true, - "requires": { - "@jest/environment": "^29.7.0", - "@jest/fake-timers": "^29.7.0", - "@jest/types": "^29.6.3", - "@types/node": "*", - "jest-mock": "^29.7.0", - "jest-util": "^29.7.0" - } - }, - "jest-get-type": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.6.3.tgz", - "integrity": "sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw==", - "dev": true - }, - "jest-haste-map": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-29.7.0.tgz", - "integrity": "sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA==", - "dev": true, - "requires": { - "@jest/types": "^29.6.3", - "@types/graceful-fs": "^4.1.3", - "@types/node": "*", - "anymatch": "^3.0.3", - "fb-watchman": "^2.0.0", - "fsevents": "^2.3.2", - "graceful-fs": "^4.2.9", - "jest-regex-util": "^29.6.3", - "jest-util": "^29.7.0", - "jest-worker": "^29.7.0", - "micromatch": "^4.0.4", - "walker": "^1.0.8" - } - }, - "jest-jasmine2": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-jasmine2/-/jest-jasmine2-29.7.0.tgz", - "integrity": "sha512-N3nRpBVTM5erHtMi6ODBUEqG/LpVgSJC8qk14duw88d9Eigx2vL+n4LF1d8eV8pegnnzKyNHdTGxa/NsIKj0Zw==", - "dev": true, - "requires": { - "@jest/environment": "^29.7.0", - "@jest/expect": "^29.7.0", - "@jest/source-map": "^29.6.3", - "@jest/test-result": "^29.7.0", - "@jest/types": "^29.6.3", - "@types/node": "*", - "chalk": "^4.0.0", - "co": "^4.6.0", - "is-generator-fn": "^2.0.0", - "jest-each": "^29.7.0", - "jest-matcher-utils": "^29.7.0", - "jest-message-util": "^29.7.0", - "jest-runtime": "^29.7.0", - "jest-snapshot": "^29.7.0", - "jest-util": "^29.7.0", - "p-limit": "^3.1.0", - "pretty-format": "^29.7.0" - } - }, - "jest-leak-detector": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-29.7.0.tgz", - "integrity": "sha512-kYA8IJcSYtST2BY9I+SMC32nDpBT3J2NvWJx8+JCuCdl/CR1I4EKUJROiP8XtCcxqgTTBGJNdbB1A8XRKbTetw==", - "dev": true, - "requires": { - "jest-get-type": "^29.6.3", - "pretty-format": "^29.7.0" - } - }, - "jest-matcher-utils": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-29.7.0.tgz", - "integrity": "sha512-sBkD+Xi9DtcChsI3L3u0+N0opgPYnCRPtGcQYrgXmR+hmt/fYfWAL0xRXYU8eWOdfuLgBe0YCW3AFtnRLagq/g==", - "dev": true, - "requires": { - "chalk": "^4.0.0", - "jest-diff": "^29.7.0", - "jest-get-type": "^29.6.3", - "pretty-format": "^29.7.0" - } - }, - "jest-message-util": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.7.0.tgz", - "integrity": "sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.12.13", - "@jest/types": "^29.6.3", - "@types/stack-utils": "^2.0.0", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.9", - "micromatch": "^4.0.4", - "pretty-format": "^29.7.0", - "slash": "^3.0.0", - "stack-utils": "^2.0.3" - } - }, - "jest-mock": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-29.7.0.tgz", - "integrity": "sha512-ITOMZn+UkYS4ZFh83xYAOzWStloNzJFO2s8DWrE4lhtGD+AorgnbkiKERe4wQVBydIGPx059g6riW5Btp6Llnw==", - "dev": true, - "requires": { - "@jest/types": "^29.6.3", - "@types/node": "*", - "jest-util": "^29.7.0" - } - }, - "jest-pnp-resolver": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz", - "integrity": "sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==", - "dev": true, - "requires": {} - }, - "jest-regex-util": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-29.6.3.tgz", - "integrity": "sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg==", - "dev": true - }, - "jest-resolve": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-29.7.0.tgz", - "integrity": "sha512-IOVhZSrg+UvVAshDSDtHyFCCBUl/Q3AAJv8iZ6ZjnZ74xzvwuzLXid9IIIPgTnY62SJjfuupMKZsZQRsCvxEgA==", - "dev": true, - "requires": { - "chalk": "^4.0.0", - "graceful-fs": "^4.2.9", - "jest-haste-map": "^29.7.0", - "jest-pnp-resolver": "^1.2.2", - "jest-util": "^29.7.0", - "jest-validate": "^29.7.0", - "resolve": "^1.20.0", - "resolve.exports": "^2.0.0", - "slash": "^3.0.0" - } - }, - "jest-resolve-dependencies": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-29.7.0.tgz", - "integrity": "sha512-un0zD/6qxJ+S0et7WxeI3H5XSe9lTBBR7bOHCHXkKR6luG5mwDDlIzVQ0V5cZCuoTgEdcdwzTghYkTWfubi+nA==", - "dev": true, - "requires": { - "jest-regex-util": "^29.6.3", - "jest-snapshot": "^29.7.0" - } - }, - "jest-runner": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-29.7.0.tgz", - "integrity": "sha512-fsc4N6cPCAahybGBfTRcq5wFR6fpLznMg47sY5aDpsoejOcVYFb07AHuSnR0liMcPTgBsA3ZJL6kFOjPdoNipQ==", - "dev": true, - "requires": { - "@jest/console": "^29.7.0", - "@jest/environment": "^29.7.0", - "@jest/test-result": "^29.7.0", - "@jest/transform": "^29.7.0", - "@jest/types": "^29.6.3", - "@types/node": "*", - "chalk": "^4.0.0", - "emittery": "^0.13.1", - "graceful-fs": "^4.2.9", - "jest-docblock": "^29.7.0", - "jest-environment-node": "^29.7.0", - "jest-haste-map": "^29.7.0", - "jest-leak-detector": "^29.7.0", - "jest-message-util": "^29.7.0", - "jest-resolve": "^29.7.0", - "jest-runtime": "^29.7.0", - "jest-util": "^29.7.0", - "jest-watcher": "^29.7.0", - "jest-worker": "^29.7.0", - "p-limit": "^3.1.0", - "source-map-support": "0.5.13" - } - }, - "jest-runtime": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-29.7.0.tgz", - "integrity": "sha512-gUnLjgwdGqW7B4LvOIkbKs9WGbn+QLqRQQ9juC6HndeDiezIwhDP+mhMwHWCEcfQ5RUXa6OPnFF8BJh5xegwwQ==", - "dev": true, - "requires": { - "@jest/environment": "^29.7.0", - "@jest/fake-timers": "^29.7.0", - "@jest/globals": "^29.7.0", - "@jest/source-map": "^29.6.3", - "@jest/test-result": "^29.7.0", - "@jest/transform": "^29.7.0", - "@jest/types": "^29.6.3", - "@types/node": "*", - "chalk": "^4.0.0", - "cjs-module-lexer": "^1.0.0", - "collect-v8-coverage": "^1.0.0", - "glob": "^7.1.3", - "graceful-fs": "^4.2.9", - "jest-haste-map": "^29.7.0", - "jest-message-util": "^29.7.0", - "jest-mock": "^29.7.0", - "jest-regex-util": "^29.6.3", - "jest-resolve": "^29.7.0", - "jest-snapshot": "^29.7.0", - "jest-util": "^29.7.0", - "slash": "^3.0.0", - "strip-bom": "^4.0.0" - } - }, - "jest-snapshot": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-29.7.0.tgz", - "integrity": "sha512-Rm0BMWtxBcioHr1/OX5YCP8Uov4riHvKPknOGs804Zg9JGZgmIBkbtlxJC/7Z4msKYVbIJtfU+tKb8xlYNfdkw==", - "dev": true, - "requires": { - "@babel/core": "^7.11.6", - "@babel/generator": "^7.7.2", - "@babel/plugin-syntax-jsx": "^7.7.2", - "@babel/plugin-syntax-typescript": "^7.7.2", - "@babel/types": "^7.3.3", - "@jest/expect-utils": "^29.7.0", - "@jest/transform": "^29.7.0", - "@jest/types": "^29.6.3", - "babel-preset-current-node-syntax": "^1.0.0", - "chalk": "^4.0.0", - "expect": "^29.7.0", - "graceful-fs": "^4.2.9", - "jest-diff": "^29.7.0", - "jest-get-type": "^29.6.3", - "jest-matcher-utils": "^29.7.0", - "jest-message-util": "^29.7.0", - "jest-util": "^29.7.0", - "natural-compare": "^1.4.0", - "pretty-format": "^29.7.0", - "semver": "^7.5.3" - }, - "dependencies": { - "lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, - "requires": { - "yallist": "^4.0.0" - } - }, - "semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", - "dev": true, - "requires": { - "lru-cache": "^6.0.0" - } - }, - "yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - } - } - }, - "jest-util": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.7.0.tgz", - "integrity": "sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==", - "dev": true, - "requires": { - "@jest/types": "^29.6.3", - "@types/node": "*", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "graceful-fs": "^4.2.9", - "picomatch": "^2.2.3" - } - }, - "jest-validate": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-29.7.0.tgz", - "integrity": "sha512-ZB7wHqaRGVw/9hST/OuFUReG7M8vKeq0/J2egIGLdvjHCmYqGARhzXmtgi+gVeZ5uXFF219aOc3Ls2yLg27tkw==", - "dev": true, - "requires": { - "@jest/types": "^29.6.3", - "camelcase": "^6.2.0", - "chalk": "^4.0.0", - "jest-get-type": "^29.6.3", - "leven": "^3.1.0", - "pretty-format": "^29.7.0" - }, - "dependencies": { - "camelcase": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", - "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", - "dev": true - } - } - }, - "jest-watcher": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-29.7.0.tgz", - "integrity": "sha512-49Fg7WXkU3Vl2h6LbLtMQ/HyB6rXSIX7SqvBLQmssRBGN9I0PNvPmAmCWSOY6SOvrjhI/F7/bGAv9RtnsPA03g==", - "dev": true, - "requires": { - "@jest/test-result": "^29.7.0", - "@jest/types": "^29.6.3", - "@types/node": "*", - "ansi-escapes": "^4.2.1", - "chalk": "^4.0.0", - "emittery": "^0.13.1", - "jest-util": "^29.7.0", - "string-length": "^4.0.1" - } - }, - "jest-worker": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.7.0.tgz", - "integrity": "sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==", - "dev": true, - "requires": { - "@types/node": "*", - "jest-util": "^29.7.0", - "merge-stream": "^2.0.0", - "supports-color": "^8.0.0" - }, - "dependencies": { - "supports-color": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", - "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "js-tokens": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "dev": true - }, - "js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", - "dev": true, - "requires": { - "argparse": "^2.0.1" - } - }, - "jsesc": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", - "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", - "dev": true - }, - "json-parse-even-better-errors": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", - "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", - "dev": true - }, - "json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true - }, - "json-stable-stringify-without-jsonify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", - "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", - "dev": true - }, - "json5": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", - "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", - "dev": true - }, - "kleur": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", - "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", - "dev": true - }, - "lazystream": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/lazystream/-/lazystream-1.0.1.tgz", - "integrity": "sha512-b94GiNHQNy6JNTrt5w6zNyffMrNkXZb3KTkCZJb2V1xaEGCk093vkZ2jk3tpaeP33/OiXC+WvK9AxUebnf5nbw==", - "dev": true, - "requires": { - "readable-stream": "^2.0.5" - }, - "dependencies": { - "readable-stream": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", - "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", - "dev": true, - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dev": true, - "requires": { - "safe-buffer": "~5.1.0" - } - } - } - }, - "leven": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", - "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", - "dev": true - }, - "levn": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", - "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", - "dev": true, - "requires": { - "prelude-ls": "^1.2.1", - "type-check": "~0.4.0" - } - }, - "lines-and-columns": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", - "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", - "dev": true - }, - "locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "dev": true, - "requires": { - "p-locate": "^4.1.0" - } - }, - "lodash.defaults": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz", - "integrity": "sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ==", - "dev": true - }, - "lodash.difference": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/lodash.difference/-/lodash.difference-4.5.0.tgz", - "integrity": "sha512-dS2j+W26TQ7taQBGN8Lbbq04ssV3emRw4NY58WErlTO29pIqS0HmoT5aJ9+TUQ1N3G+JOZSji4eugsWwGp9yPA==", - "dev": true - }, - "lodash.flatten": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/lodash.flatten/-/lodash.flatten-4.4.0.tgz", - "integrity": "sha512-C5N2Z3DgnnKr0LOpv/hKCgKdb7ZZwafIrsesve6lmzvZIRZRGaZ/l6Q8+2W7NaT+ZwO3fFlSCzCzrDCFdJfZ4g==", - "dev": true - }, - "lodash.isplainobject": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", - "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==", - "dev": true - }, - "lodash.merge": { - "version": "4.6.2", - "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", - "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", - "dev": true - }, - "lodash.union": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/lodash.union/-/lodash.union-4.6.0.tgz", - "integrity": "sha512-c4pB2CdGrGdjMKYLA+XiRDO7Y0PRQbm/Gzg8qMj+QH+pFVAoTp5sBpO0odL3FjoPCGjK96p6qsP+yQoiLoOBcw==", - "dev": true - }, - "lru-cache": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", - "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", - "dev": true, - "requires": { - "yallist": "^3.0.2" - } - }, - "make-dir": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", - "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", - "dev": true, - "requires": { - "semver": "^7.5.3" - }, - "dependencies": { - "lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, - "requires": { - "yallist": "^4.0.0" - } - }, - "semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", - "dev": true, - "requires": { - "lru-cache": "^6.0.0" - } - }, - "yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - } - } - }, - "makeerror": { - "version": "1.0.12", - "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz", - "integrity": "sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==", - "dev": true, - "requires": { - "tmpl": "1.0.5" - } - }, - "merge-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", - "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", - "dev": true - }, - "micromatch": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", - "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", - "dev": true, - "requires": { - "braces": "^3.0.2", - "picomatch": "^2.3.1" - } - }, - "mimic-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", - "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", - "dev": true - }, - "minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "requires": { - "brace-expansion": "^1.1.7" - } - }, - "mkdirp": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", - "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", - "dev": true - }, - "mkdirp-classic": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", - "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==", - "dev": true - }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - }, - "nan": { - "version": "2.16.0", - "resolved": "https://registry.npmjs.org/nan/-/nan-2.16.0.tgz", - "integrity": "sha512-UdAqHyFngu7TfQKsCBgAA6pWDkT8MAO7d0jyOecVhN5354xbLqdn8mV9Tat9gepAupm0bt2DbeaSC8vS52MuFA==", - "dev": true, - "optional": true - }, - "natural-compare": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", - "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", - "dev": true - }, - "node-fetch": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", - "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", - "dev": true, - "requires": { - "whatwg-url": "^5.0.0" - } - }, - "node-int64": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", - "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==", - "dev": true - }, - "node-releases": { - "version": "2.0.13", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.13.tgz", - "integrity": "sha512-uYr7J37ae/ORWdZeQ1xxMJe3NtdmqMC/JZK+geofDrkLUApKRHPd18/TxtBOJ4A0/+uUIliorNrfYV6s1b02eQ==", - "dev": true - }, - "normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "dev": true - }, - "npm-run-path": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", - "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", - "dev": true, - "requires": { - "path-key": "^3.0.0" - } - }, - "once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "dev": true, - "requires": { - "wrappy": "1" - } - }, - "onetime": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", - "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", - "dev": true, - "requires": { - "mimic-fn": "^2.1.0" - } - }, - "optionator": { - "version": "0.9.3", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.3.tgz", - "integrity": "sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg==", - "dev": true, - "requires": { - "@aashutoshrathi/word-wrap": "^1.2.3", - "deep-is": "^0.1.3", - "fast-levenshtein": "^2.0.6", - "levn": "^0.4.1", - "prelude-ls": "^1.2.1", - "type-check": "^0.4.0" - } - }, - "p-limit": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", - "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", - "dev": true, - "requires": { - "yocto-queue": "^0.1.0" - } - }, - "p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "dev": true, - "requires": { - "p-limit": "^2.2.0" - }, - "dependencies": { - "p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dev": true, - "requires": { - "p-try": "^2.0.0" - } - } - } - }, - "p-try": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", - "dev": true - }, - "parent-module": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", - "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", - "dev": true, - "requires": { - "callsites": "^3.0.0" - } - }, - "parse-json": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", - "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.0.0", - "error-ex": "^1.3.1", - "json-parse-even-better-errors": "^2.3.0", - "lines-and-columns": "^1.1.6" - } - }, - "path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true - }, - "path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", - "dev": true - }, - "path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true - }, - "path-parse": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", - "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", - "dev": true - }, - "picocolors": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", - "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", - "dev": true - }, - "picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "dev": true - }, - "pirates": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.6.tgz", - "integrity": "sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==", - "dev": true - }, - "pkg-dir": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", - "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", - "dev": true, - "requires": { - "find-up": "^4.0.0" - } - }, - "prelude-ls": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", - "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", - "dev": true - }, - "pretty-format": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", - "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", - "dev": true, - "requires": { - "@jest/schemas": "^29.6.3", - "ansi-styles": "^5.0.0", - "react-is": "^18.0.0" - }, - "dependencies": { - "ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true - } - } - }, - "process-nextick-args": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", - "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", - "dev": true - }, - "prompts": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", - "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==", - "dev": true, - "requires": { - "kleur": "^3.0.3", - "sisteransi": "^1.0.5" - } - }, - "proper-lockfile": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/proper-lockfile/-/proper-lockfile-4.1.2.tgz", - "integrity": "sha512-TjNPblN4BwAWMXU8s9AEz4JmQxnD1NNL7bNOY/AKUzyamc379FWASUhc/K1pL2noVb+XmZKLL68cjzLsiOAMaA==", - "dev": true, - "requires": { - "graceful-fs": "^4.2.4", - "retry": "^0.12.0", - "signal-exit": "^3.0.2" - } - }, - "properties-reader": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/properties-reader/-/properties-reader-2.2.0.tgz", - "integrity": "sha512-CgVcr8MwGoBKK24r9TwHfZkLLaNFHQ6y4wgT9w/XzdpacOOi5ciH4hcuLechSDAwXsfrGQtI2JTutY2djOx2Ow==", - "dev": true, - "requires": { - "mkdirp": "^1.0.4" - } - }, - "pump": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", - "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", - "dev": true, - "requires": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" - } - }, - "punycode": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz", - "integrity": "sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==", - "dev": true - }, - "pure-rand": { - "version": "6.0.4", - "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-6.0.4.tgz", - "integrity": "sha512-LA0Y9kxMYv47GIPJy6MI84fqTd2HmYZI83W/kM/SkKfDlajnZYfmXFTxkbY+xSBPkLJxltMa9hIkmdc29eguMA==", - "dev": true - }, - "queue-microtask": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", - "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", - "dev": true - }, - "queue-tick": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/queue-tick/-/queue-tick-1.0.1.tgz", - "integrity": "sha512-kJt5qhMxoszgU/62PLP1CJytzd2NKetjSRnyuj31fDd3Rlcz3fzlFdFLD1SItunPwyqEOkca6GbV612BWfaBag==", - "dev": true - }, - "react-is": { - "version": "18.2.0", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", - "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", - "dev": true - }, - "readable-stream": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", - "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", - "dev": true, - "requires": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - } - }, - "readdir-glob": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/readdir-glob/-/readdir-glob-1.1.2.tgz", - "integrity": "sha512-6RLVvwJtVwEDfPdn6X6Ille4/lxGl0ATOY4FN/B9nxQcgOazvvI0nodiD19ScKq0PvA/29VpaOQML36o5IzZWA==", - "dev": true, - "requires": { - "minimatch": "^5.1.0" - }, - "dependencies": { - "brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "dev": true, - "requires": { - "balanced-match": "^1.0.0" - } - }, - "minimatch": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.0.tgz", - "integrity": "sha512-9TPBGGak4nHfGZsPBohm9AWg6NoT7QTCehS3BIJABslyZbzxfV78QM2Y6+i741OPZIafFAaiiEMh5OyIrJPgtg==", - "dev": true, - "requires": { - "brace-expansion": "^2.0.1" - } - } - } - }, - "require-directory": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", - "dev": true - }, - "resolve": { - "version": "1.22.6", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.6.tgz", - "integrity": "sha512-njhxM7mV12JfufShqGy3Rz8j11RPdLy4xi15UurGJeoHLfJpVXKdh3ueuOqbYUcDZnffr6X739JBo5LzyahEsw==", - "dev": true, - "requires": { - "is-core-module": "^2.13.0", - "path-parse": "^1.0.7", - "supports-preserve-symlinks-flag": "^1.0.0" - } - }, - "resolve-cwd": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", - "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", - "dev": true, - "requires": { - "resolve-from": "^5.0.0" - }, - "dependencies": { - "resolve-from": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", - "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", - "dev": true - } - } - }, - "resolve-from": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", - "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", - "dev": true - }, - "resolve.exports": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/resolve.exports/-/resolve.exports-2.0.2.tgz", - "integrity": "sha512-X2UW6Nw3n/aMgDVy+0rSqgHlv39WZAlZrXCdnbyEiKm17DSqHX4MmQMaST3FbeWR5FTuRcUwYAziZajji0Y7mg==", - "dev": true - }, - "retry": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", - "integrity": "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==", - "dev": true - }, - "reusify": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", - "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", - "dev": true - }, - "rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "dev": true, - "requires": { - "glob": "^7.1.3" - } - }, - "run-parallel": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", - "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", - "dev": true, - "requires": { - "queue-microtask": "^1.2.2" - } - }, - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true - }, - "safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", - "dev": true - }, - "semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true - }, - "shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, - "requires": { - "shebang-regex": "^3.0.0" - } - }, - "shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true - }, - "signal-exit": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", - "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", - "dev": true - }, - "sisteransi": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", - "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", - "dev": true - }, - "slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "dev": true - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - }, - "source-map-support": { - "version": "0.5.13", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.13.tgz", - "integrity": "sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==", - "dev": true, - "requires": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" - } - }, - "split-ca": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/split-ca/-/split-ca-1.0.1.tgz", - "integrity": "sha512-Q5thBSxp5t8WPTTJQS59LrGqOZqOsrhDGDVm8azCqIBjSBd7nd9o2PM+mDulQQkh8h//4U6hFZnc/mul8t5pWQ==", - "dev": true - }, - "sprintf-js": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", - "dev": true - }, - "ssh-remote-port-forward": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/ssh-remote-port-forward/-/ssh-remote-port-forward-1.0.4.tgz", - "integrity": "sha512-x0LV1eVDwjf1gmG7TTnfqIzf+3VPRz7vrNIjX6oYLbeCrf/PeVY6hkT68Mg+q02qXxQhrLjB0jfgvhevoCRmLQ==", - "dev": true, - "requires": { - "@types/ssh2": "^0.5.48", - "ssh2": "^1.4.0" - }, - "dependencies": { - "@types/ssh2": { - "version": "0.5.52", - "resolved": "https://registry.npmjs.org/@types/ssh2/-/ssh2-0.5.52.tgz", - "integrity": "sha512-lbLLlXxdCZOSJMCInKH2+9V/77ET2J6NPQHpFI0kda61Dd1KglJs+fPQBchizmzYSOJBgdTajhPqBO1xxLywvg==", - "dev": true, - "requires": { - "@types/node": "*", - "@types/ssh2-streams": "*" - } - } - } - }, - "ssh2": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/ssh2/-/ssh2-1.11.0.tgz", - "integrity": "sha512-nfg0wZWGSsfUe/IBJkXVll3PEZ//YH2guww+mP88gTpuSU4FtZN7zu9JoeTGOyCNx2dTDtT9fOpWwlzyj4uOOw==", - "dev": true, - "requires": { - "asn1": "^0.2.4", - "bcrypt-pbkdf": "^1.0.2", - "cpu-features": "~0.0.4", - "nan": "^2.16.0" - } - }, - "stack-utils": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz", - "integrity": "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==", - "dev": true, - "requires": { - "escape-string-regexp": "^2.0.0" - } - }, - "streamx": { - "version": "2.15.1", - "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.15.1.tgz", - "integrity": "sha512-fQMzy2O/Q47rgwErk/eGeLu/roaFWV0jVsogDmrszM9uIw8L5OA+t+V93MgYlufNptfjmYR1tOMWhei/Eh7TQA==", - "dev": true, - "requires": { - "fast-fifo": "^1.1.0", - "queue-tick": "^1.0.1" - } - }, - "string_decoder": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", - "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", - "dev": true, - "requires": { - "safe-buffer": "~5.2.0" - }, - "dependencies": { - "safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "dev": true - } - } - }, - "string-length": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz", - "integrity": "sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==", - "dev": true, - "requires": { - "char-regex": "^1.0.2", - "strip-ansi": "^6.0.0" - } - }, - "string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "requires": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - } - }, - "strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "requires": { - "ansi-regex": "^5.0.1" - } - }, - "strip-bom": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", - "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", - "dev": true - }, - "strip-final-newline": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", - "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", - "dev": true - }, - "strip-json-comments": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", - "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - }, - "supports-preserve-symlinks-flag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", - "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", - "dev": true - }, - "tar-fs": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.0.4.tgz", - "integrity": "sha512-5AFQU8b9qLfZCX9zp2duONhPmZv0hGYiBPJsyUdqMjzq/mqVpy/rEUSeHk1+YitmxugaptgBh5oDGU3VsAJq4w==", - "dev": true, - "requires": { - "mkdirp-classic": "^0.5.2", - "pump": "^3.0.0", - "tar-stream": "^3.1.5" - }, - "dependencies": { - "tar-stream": { - "version": "3.1.6", - "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-3.1.6.tgz", - "integrity": "sha512-B/UyjYwPpMBv+PaFSWAmtYjwdrlEaZQEhMIBFNC5oEG8lpiW8XjcSdmEaClj28ArfKScKHs2nshz3k2le6crsg==", - "dev": true, - "requires": { - "b4a": "^1.6.4", - "fast-fifo": "^1.2.0", - "streamx": "^2.15.0" - } - } - } - }, - "tar-stream": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", - "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", - "dev": true, - "requires": { - "bl": "^4.0.3", - "end-of-stream": "^1.4.1", - "fs-constants": "^1.0.0", - "inherits": "^2.0.3", - "readable-stream": "^3.1.1" - } - }, - "test-exclude": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", - "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", - "dev": true, - "requires": { - "@istanbuljs/schema": "^0.1.2", - "glob": "^7.1.4", - "minimatch": "^3.0.4" - } - }, - "testcontainers": { - "version": "10.2.1", - "resolved": "https://registry.npmjs.org/testcontainers/-/testcontainers-10.2.1.tgz", - "integrity": "sha512-R9LUMUEkKGSL2M4cP466Jah+Vi+ZLFlvrT4BENjEKJKNzubATOmDk26RHe8DHeFT+hnMD6fvVii+McXr0UTO7g==", - "dev": true, - "requires": { - "@balena/dockerignore": "^1.0.2", - "archiver": "^5.3.1", - "async-lock": "^1.4.0", - "byline": "^5.0.0", - "debug": "^4.3.4", - "docker-compose": "^0.24.2", - "dockerode": "^3.3.5", - "get-port": "^5.1.1", - "node-fetch": "^2.6.12", - "proper-lockfile": "^4.1.2", - "properties-reader": "^2.2.0", - "ssh-remote-port-forward": "^1.0.4", - "tar-fs": "^3.0.4", - "tmp": "^0.2.1" - } - }, - "text-table": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", - "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", - "dev": true - }, - "tmp": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.1.tgz", - "integrity": "sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ==", - "dev": true, - "requires": { - "rimraf": "^3.0.0" - } - }, - "tmpl": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", - "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==", - "dev": true - }, - "to-fast-properties": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", - "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==", - "dev": true - }, - "to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, - "requires": { - "is-number": "^7.0.0" - } - }, - "tr46": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", - "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", - "dev": true - }, - "tweetnacl": { - "version": "0.14.5", - "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", - "integrity": "sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA==", - "dev": true - }, - "type-check": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", - "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", - "dev": true, - "requires": { - "prelude-ls": "^1.2.1" - } - }, - "type-detect": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", - "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", - "dev": true - }, - "type-fest": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", - "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", - "dev": true - }, - "update-browserslist-db": { - "version": "1.0.13", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.13.tgz", - "integrity": "sha512-xebP81SNcPuNpPP3uzeW1NYXxI3rxyJzF3pD6sH4jE7o/IX+WtSpwnVU+qIsDPyk0d3hmFQ7mjqc6AtV604hbg==", - "dev": true, - "requires": { - "escalade": "^3.1.1", - "picocolors": "^1.0.0" - } - }, - "uri-js": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", - "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", - "dev": true, - "requires": { - "punycode": "^2.1.0" - } - }, - "util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", - "dev": true - }, - "v8-to-istanbul": { - "version": "9.1.3", - "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.1.3.tgz", - "integrity": "sha512-9lDD+EVI2fjFsMWXc6dy5JJzBsVTcQ2fVkfBvncZ6xJWG9wtBhOldG+mHkSL0+V1K/xgZz0JDO5UT5hFwHUghg==", - "dev": true, - "requires": { - "@jridgewell/trace-mapping": "^0.3.12", - "@types/istanbul-lib-coverage": "^2.0.1", - "convert-source-map": "^2.0.0" - } - }, - "walker": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz", - "integrity": "sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==", - "dev": true, - "requires": { - "makeerror": "1.0.12" - } - }, - "webidl-conversions": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", - "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", - "dev": true - }, - "whatwg-url": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", - "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", - "dev": true, - "requires": { - "tr46": "~0.0.3", - "webidl-conversions": "^3.0.0" - } - }, - "which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, - "requires": { - "isexe": "^2.0.0" - } - }, - "wrap-ansi": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dev": true, - "requires": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - } - }, - "wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", - "dev": true - }, - "write-file-atomic": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-4.0.2.tgz", - "integrity": "sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==", - "dev": true, - "requires": { - "imurmurhash": "^0.1.4", - "signal-exit": "^3.0.7" - } - }, - "y18n": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", - "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", - "dev": true - }, - "yallist": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", - "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", - "dev": true - }, - "yaml": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.3.2.tgz", - "integrity": "sha512-N/lyzTPaJasoDmfV7YTrYCI0G/3ivm/9wdG0aHuheKowWQwGTsK0Eoiw6utmzAnI6pkJa0DUVygvp3spqqEKXg==", - "dev": true - }, - "yargs": { - "version": "17.7.2", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", - "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", - "dev": true, - "requires": { - "cliui": "^8.0.1", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.3", - "y18n": "^5.0.5", - "yargs-parser": "^21.1.1" - } - }, - "yargs-parser": { - "version": "21.1.1", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", - "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", - "dev": true - }, - "yocto-queue": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", - "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", - "dev": true - }, - "zip-stream": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/zip-stream/-/zip-stream-4.1.0.tgz", - "integrity": "sha512-zshzwQW7gG7hjpBlgeQP9RuyPGNxvJdzR8SUM3QhxCnLjWN2E7j3dOvpeDcQoETfHx0urRS7EtmVToql7YpU4A==", - "dev": true, - "requires": { - "archiver-utils": "^2.1.0", - "compress-commons": "^4.1.0", - "readable-stream": "^3.6.0" - } - } - } -} diff --git a/package.json b/package.json index 05ddc2d..b2c43ec 100644 --- a/package.json +++ b/package.json @@ -1,14 +1,27 @@ { "name": "@questdb/nodejs-client", - "version": "3.0.0", + "version": "4.0.0", "description": "QuestDB Node.js Client", - "main": "index.js", - "types": "types/index.d.ts", "scripts": { - "test": "jest", - "eslint": "npx eslint src/**", - "docs": "jsdoc src/sender.js src/options.js README.md -d docs", - "types": "npx -p typescript tsc --skipLibCheck --declaration --allowJs --emitDeclarationOnly --outDir types" + "test": "vitest", + "build": "bunchee", + "eslint": "eslint src/**", + "typecheck": "tsc --noEmit", + "docs": "pnpm run build && jsdoc ./dist/cjs/index.js README.md -d docs", + "preview:docs": "serve docs" + }, + "files": [ + "dist" + ], + "exports": { + "import": { + "types": "./dist/es/index.d.mts", + "default": "./dist/es/index.mjs" + }, + "require": { + "types": "./dist/cjs/index.d.ts", + "default": "./dist/cjs/index.js" + } }, "repository": { "type": "git", @@ -20,13 +33,19 @@ "author": "QuestDB", "license": "Apache-2.0", "homepage": "https://questdb.github.io/nodejs-questdb-client", - "jest": { - "testRunner": "jest-jasmine2" - }, "devDependencies": { - "eslint": "^8.50.0", - "jest": "^29.7.0", - "jest-jasmine2": "^29.7.0", - "testcontainers": "^10.2.1" + "@eslint/js": "^9.16.0", + "bunchee": "^6.0.3", + "eslint": "^9.16.0", + "jsdoc": "^4.0.4", + "prettier": "3.4.1", + "serve": "^14.2.4", + "testcontainers": "^10.15.0", + "typescript": "^5.7.2", + "typescript-eslint": "^8.17.0", + "vitest": "^2.1.8" + }, + "dependencies": { + "undici": "^7.1.0" } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml new file mode 100644 index 0000000..140e322 --- /dev/null +++ b/pnpm-lock.yaml @@ -0,0 +1,3965 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + dependencies: + undici: + specifier: ^7.1.0 + version: 7.1.0 + devDependencies: + '@eslint/js': + specifier: ^9.16.0 + version: 9.16.0 + bunchee: + specifier: ^6.0.3 + version: 6.0.3(typescript@5.7.2) + eslint: + specifier: ^9.16.0 + version: 9.16.0 + jsdoc: + specifier: ^4.0.4 + version: 4.0.4 + prettier: + specifier: 3.4.1 + version: 3.4.1 + serve: + specifier: ^14.2.4 + version: 14.2.4 + testcontainers: + specifier: ^10.15.0 + version: 10.15.0 + typescript: + specifier: ^5.7.2 + version: 5.7.2 + typescript-eslint: + specifier: ^8.17.0 + version: 8.17.0(eslint@9.16.0)(typescript@5.7.2) + vitest: + specifier: ^2.1.8 + version: 2.1.8(@types/node@22.10.1) + +packages: + + '@babel/code-frame@7.26.2': + resolution: {integrity: sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==} + engines: {node: '>=6.9.0'} + + '@babel/helper-string-parser@7.25.9': + resolution: {integrity: sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-identifier@7.25.9': + resolution: {integrity: sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==} + engines: {node: '>=6.9.0'} + + '@babel/parser@7.26.3': + resolution: {integrity: sha512-WJ/CvmY8Mea8iDXo6a7RK2wbmJITT5fN3BEkRuFlxVyNx8jOKIIhmC4fSkTcPcf8JyavbBwIe6OpiCOBXt/IcA==} + engines: {node: '>=6.0.0'} + hasBin: true + + '@babel/types@7.26.3': + resolution: {integrity: sha512-vN5p+1kl59GVKMvTHt55NzzmYVxprfJD+ql7U9NFIfKCBkYE55LYtS+WtPlaYOyzydrKI8Nezd+aZextrd+FMA==} + engines: {node: '>=6.9.0'} + + '@balena/dockerignore@1.0.2': + resolution: {integrity: sha512-wMue2Sy4GAVTk6Ic4tJVcnfdau+gx2EnG7S+uAEe+TWJFqE4YoWN4/H8MSLj4eYJKxGg26lZwboEniNiNwZQ6Q==} + + '@esbuild/aix-ppc64@0.21.5': + resolution: {integrity: sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==} + engines: {node: '>=12'} + cpu: [ppc64] + os: [aix] + + '@esbuild/android-arm64@0.21.5': + resolution: {integrity: sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==} + engines: {node: '>=12'} + cpu: [arm64] + os: [android] + + '@esbuild/android-arm@0.21.5': + resolution: {integrity: sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==} + engines: {node: '>=12'} + cpu: [arm] + os: [android] + + '@esbuild/android-x64@0.21.5': + resolution: {integrity: sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==} + engines: {node: '>=12'} + cpu: [x64] + os: [android] + + '@esbuild/darwin-arm64@0.21.5': + resolution: {integrity: sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==} + engines: {node: '>=12'} + cpu: [arm64] + os: [darwin] + + '@esbuild/darwin-x64@0.21.5': + resolution: {integrity: sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==} + engines: {node: '>=12'} + cpu: [x64] + os: [darwin] + + '@esbuild/freebsd-arm64@0.21.5': + resolution: {integrity: sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==} + engines: {node: '>=12'} + cpu: [arm64] + os: [freebsd] + + '@esbuild/freebsd-x64@0.21.5': + resolution: {integrity: sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [freebsd] + + '@esbuild/linux-arm64@0.21.5': + resolution: {integrity: sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==} + engines: {node: '>=12'} + cpu: [arm64] + os: [linux] + + '@esbuild/linux-arm@0.21.5': + resolution: {integrity: sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==} + engines: {node: '>=12'} + cpu: [arm] + os: [linux] + + '@esbuild/linux-ia32@0.21.5': + resolution: {integrity: sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==} + engines: {node: '>=12'} + cpu: [ia32] + os: [linux] + + '@esbuild/linux-loong64@0.21.5': + resolution: {integrity: sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==} + engines: {node: '>=12'} + cpu: [loong64] + os: [linux] + + '@esbuild/linux-mips64el@0.21.5': + resolution: {integrity: sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==} + engines: {node: '>=12'} + cpu: [mips64el] + os: [linux] + + '@esbuild/linux-ppc64@0.21.5': + resolution: {integrity: sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==} + engines: {node: '>=12'} + cpu: [ppc64] + os: [linux] + + '@esbuild/linux-riscv64@0.21.5': + resolution: {integrity: sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==} + engines: {node: '>=12'} + cpu: [riscv64] + os: [linux] + + '@esbuild/linux-s390x@0.21.5': + resolution: {integrity: sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==} + engines: {node: '>=12'} + cpu: [s390x] + os: [linux] + + '@esbuild/linux-x64@0.21.5': + resolution: {integrity: sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [linux] + + '@esbuild/netbsd-x64@0.21.5': + resolution: {integrity: sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==} + engines: {node: '>=12'} + cpu: [x64] + os: [netbsd] + + '@esbuild/openbsd-x64@0.21.5': + resolution: {integrity: sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==} + engines: {node: '>=12'} + cpu: [x64] + os: [openbsd] + + '@esbuild/sunos-x64@0.21.5': + resolution: {integrity: sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==} + engines: {node: '>=12'} + cpu: [x64] + os: [sunos] + + '@esbuild/win32-arm64@0.21.5': + resolution: {integrity: sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==} + engines: {node: '>=12'} + cpu: [arm64] + os: [win32] + + '@esbuild/win32-ia32@0.21.5': + resolution: {integrity: sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==} + engines: {node: '>=12'} + cpu: [ia32] + os: [win32] + + '@esbuild/win32-x64@0.21.5': + resolution: {integrity: sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==} + engines: {node: '>=12'} + cpu: [x64] + os: [win32] + + '@eslint-community/eslint-utils@4.4.1': + resolution: {integrity: sha512-s3O3waFUrMV8P/XaF/+ZTp1X9XBZW1a4B97ZnjQF2KYWaFD2A8KyFBsrsfSjEmjn3RGWAIuvlneuZm3CUK3jbA==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 + + '@eslint-community/regexpp@4.12.1': + resolution: {integrity: sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==} + engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} + + '@eslint/config-array@0.19.1': + resolution: {integrity: sha512-fo6Mtm5mWyKjA/Chy1BYTdn5mGJoDNjC7C64ug20ADsRDGrA85bN3uK3MaKbeRkRuuIEAR5N33Jr1pbm411/PA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/core@0.9.1': + resolution: {integrity: sha512-GuUdqkyyzQI5RMIWkHhvTWLCyLo1jNK3vzkSyaExH5kHPDHcuL2VOpHjmMY+y3+NC69qAKToBqldTBgYeLSr9Q==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/eslintrc@3.2.0': + resolution: {integrity: sha512-grOjVNN8P3hjJn/eIETF1wwd12DdnwFDoyceUJLYYdkpbwq3nLi+4fqrTAONx7XDALqlL220wC/RHSC/QTI/0w==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/js@9.16.0': + resolution: {integrity: sha512-tw2HxzQkrbeuvyj1tG2Yqq+0H9wGoI2IMk4EOsQeX+vmd75FtJAzf+gTA69WF+baUKRYQ3x2kbLE08js5OsTVg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/object-schema@2.1.5': + resolution: {integrity: sha512-o0bhxnL89h5Bae5T318nFoFzGy+YE5i/gGkoPAgkmTVdRKTiv3p8JHevPiPaMwoloKfEiiaHlawCqaZMqRm+XQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/plugin-kit@0.2.4': + resolution: {integrity: sha512-zSkKow6H5Kdm0ZUQUB2kV5JIXqoG0+uH5YADhaEHswm664N9Db8dXSi0nMJpacpMf+MyyglF1vnZohpEg5yUtg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@fastify/busboy@2.1.1': + resolution: {integrity: sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA==} + engines: {node: '>=14'} + + '@fastify/deepmerge@1.3.0': + resolution: {integrity: sha512-J8TOSBq3SoZbDhM9+R/u77hP93gz/rajSA+K2kGyijPpORPWUXHUpTaleoj+92As0S9uPRP7Oi8IqMf0u+ro6A==} + + '@humanfs/core@0.19.1': + resolution: {integrity: sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==} + engines: {node: '>=18.18.0'} + + '@humanfs/node@0.16.6': + resolution: {integrity: sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw==} + engines: {node: '>=18.18.0'} + + '@humanwhocodes/module-importer@1.0.1': + resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==} + engines: {node: '>=12.22'} + + '@humanwhocodes/retry@0.3.1': + resolution: {integrity: sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA==} + engines: {node: '>=18.18'} + + '@humanwhocodes/retry@0.4.1': + resolution: {integrity: sha512-c7hNEllBlenFTHBky65mhq8WD2kbN9Q6gk0bTk8lSBvc554jpXSkST1iePudpt7+A/AQvuHs9EMqjHDXMY1lrA==} + engines: {node: '>=18.18'} + + '@isaacs/cliui@8.0.2': + resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} + engines: {node: '>=12'} + + '@jridgewell/sourcemap-codec@1.5.0': + resolution: {integrity: sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==} + + '@jsdoc/salty@0.2.8': + resolution: {integrity: sha512-5e+SFVavj1ORKlKaKr2BmTOekmXbelU7dC0cDkQLqag7xfuTPuGMUFx7KWJuv4bYZrTsoL2Z18VVCOKYxzoHcg==} + engines: {node: '>=v12.0.0'} + + '@nodelib/fs.scandir@2.1.5': + resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} + engines: {node: '>= 8'} + + '@nodelib/fs.stat@2.0.5': + resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==} + engines: {node: '>= 8'} + + '@nodelib/fs.walk@1.2.8': + resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} + engines: {node: '>= 8'} + + '@pkgjs/parseargs@0.11.0': + resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} + engines: {node: '>=14'} + + '@rollup/plugin-commonjs@28.0.1': + resolution: {integrity: sha512-+tNWdlWKbpB3WgBN7ijjYkq9X5uhjmcvyjEght4NmH5fAU++zfQzAJ6wumLS+dNcvwEZhKx2Z+skY8m7v0wGSA==} + engines: {node: '>=16.0.0 || 14 >= 14.17'} + peerDependencies: + rollup: ^2.68.0||^3.0.0||^4.0.0 + peerDependenciesMeta: + rollup: + optional: true + + '@rollup/plugin-json@6.1.0': + resolution: {integrity: sha512-EGI2te5ENk1coGeADSIwZ7G2Q8CJS2sF120T7jLw4xFw9n7wIOXHo+kIYRAoVpJAN+kmqZSoO3Fp4JtoNF4ReA==} + engines: {node: '>=14.0.0'} + peerDependencies: + rollup: ^1.20.0||^2.0.0||^3.0.0||^4.0.0 + peerDependenciesMeta: + rollup: + optional: true + + '@rollup/plugin-node-resolve@15.3.0': + resolution: {integrity: sha512-9eO5McEICxMzJpDW9OnMYSv4Sta3hmt7VtBFz5zR9273suNOydOyq/FrGeGy+KsTRFm8w0SLVhzig2ILFT63Ag==} + engines: {node: '>=14.0.0'} + peerDependencies: + rollup: ^2.78.0||^3.0.0||^4.0.0 + peerDependenciesMeta: + rollup: + optional: true + + '@rollup/plugin-replace@6.0.1': + resolution: {integrity: sha512-2sPh9b73dj5IxuMmDAsQWVFT7mR+yoHweBaXG2W/R8vQ+IWZlnaI7BR7J6EguVQUp1hd8Z7XuozpDjEKQAAC2Q==} + engines: {node: '>=14.0.0'} + peerDependencies: + rollup: ^1.20.0||^2.0.0||^3.0.0||^4.0.0 + peerDependenciesMeta: + rollup: + optional: true + + '@rollup/plugin-wasm@6.2.2': + resolution: {integrity: sha512-gpC4R1G9Ni92ZIRTexqbhX7U+9estZrbhP+9SRb0DW9xpB9g7j34r+J2hqrcW/lRI7dJaU84MxZM0Rt82tqYPQ==} + engines: {node: '>=14.0.0'} + peerDependencies: + rollup: ^1.20.0||^2.0.0||^3.0.0||^4.0.0 + peerDependenciesMeta: + rollup: + optional: true + + '@rollup/pluginutils@5.1.3': + resolution: {integrity: sha512-Pnsb6f32CD2W3uCaLZIzDmeFyQ2b8UWMFI7xtwUezpcGBDVDW6y9XgAWIlARiGAo6eNF5FK5aQTr0LFyNyqq5A==} + engines: {node: '>=14.0.0'} + peerDependencies: + rollup: ^1.20.0||^2.0.0||^3.0.0||^4.0.0 + peerDependenciesMeta: + rollup: + optional: true + + '@rollup/rollup-android-arm-eabi@4.28.0': + resolution: {integrity: sha512-wLJuPLT6grGZsy34g4N1yRfYeouklTgPhH1gWXCYspenKYD0s3cR99ZevOGw5BexMNywkbV3UkjADisozBmpPQ==} + cpu: [arm] + os: [android] + + '@rollup/rollup-android-arm64@4.28.0': + resolution: {integrity: sha512-eiNkznlo0dLmVG/6wf+Ifi/v78G4d4QxRhuUl+s8EWZpDewgk7PX3ZyECUXU0Zq/Ca+8nU8cQpNC4Xgn2gFNDA==} + cpu: [arm64] + os: [android] + + '@rollup/rollup-darwin-arm64@4.28.0': + resolution: {integrity: sha512-lmKx9yHsppblnLQZOGxdO66gT77bvdBtr/0P+TPOseowE7D9AJoBw8ZDULRasXRWf1Z86/gcOdpBrV6VDUY36Q==} + cpu: [arm64] + os: [darwin] + + '@rollup/rollup-darwin-x64@4.28.0': + resolution: {integrity: sha512-8hxgfReVs7k9Js1uAIhS6zq3I+wKQETInnWQtgzt8JfGx51R1N6DRVy3F4o0lQwumbErRz52YqwjfvuwRxGv1w==} + cpu: [x64] + os: [darwin] + + '@rollup/rollup-freebsd-arm64@4.28.0': + resolution: {integrity: sha512-lA1zZB3bFx5oxu9fYud4+g1mt+lYXCoch0M0V/xhqLoGatbzVse0wlSQ1UYOWKpuSu3gyN4qEc0Dxf/DII1bhQ==} + cpu: [arm64] + os: [freebsd] + + '@rollup/rollup-freebsd-x64@4.28.0': + resolution: {integrity: sha512-aI2plavbUDjCQB/sRbeUZWX9qp12GfYkYSJOrdYTL/C5D53bsE2/nBPuoiJKoWp5SN78v2Vr8ZPnB+/VbQ2pFA==} + cpu: [x64] + os: [freebsd] + + '@rollup/rollup-linux-arm-gnueabihf@4.28.0': + resolution: {integrity: sha512-WXveUPKtfqtaNvpf0iOb0M6xC64GzUX/OowbqfiCSXTdi/jLlOmH0Ba94/OkiY2yTGTwteo4/dsHRfh5bDCZ+w==} + cpu: [arm] + os: [linux] + + '@rollup/rollup-linux-arm-musleabihf@4.28.0': + resolution: {integrity: sha512-yLc3O2NtOQR67lI79zsSc7lk31xjwcaocvdD1twL64PK1yNaIqCeWI9L5B4MFPAVGEVjH5k1oWSGuYX1Wutxpg==} + cpu: [arm] + os: [linux] + + '@rollup/rollup-linux-arm64-gnu@4.28.0': + resolution: {integrity: sha512-+P9G9hjEpHucHRXqesY+3X9hD2wh0iNnJXX/QhS/J5vTdG6VhNYMxJ2rJkQOxRUd17u5mbMLHM7yWGZdAASfcg==} + cpu: [arm64] + os: [linux] + + '@rollup/rollup-linux-arm64-musl@4.28.0': + resolution: {integrity: sha512-1xsm2rCKSTpKzi5/ypT5wfc+4bOGa/9yI/eaOLW0oMs7qpC542APWhl4A37AENGZ6St6GBMWhCCMM6tXgTIplw==} + cpu: [arm64] + os: [linux] + + '@rollup/rollup-linux-powerpc64le-gnu@4.28.0': + resolution: {integrity: sha512-zgWxMq8neVQeXL+ouSf6S7DoNeo6EPgi1eeqHXVKQxqPy1B2NvTbaOUWPn/7CfMKL7xvhV0/+fq/Z/J69g1WAQ==} + cpu: [ppc64] + os: [linux] + + '@rollup/rollup-linux-riscv64-gnu@4.28.0': + resolution: {integrity: sha512-VEdVYacLniRxbRJLNtzwGt5vwS0ycYshofI7cWAfj7Vg5asqj+pt+Q6x4n+AONSZW/kVm+5nklde0qs2EUwU2g==} + cpu: [riscv64] + os: [linux] + + '@rollup/rollup-linux-s390x-gnu@4.28.0': + resolution: {integrity: sha512-LQlP5t2hcDJh8HV8RELD9/xlYtEzJkm/aWGsauvdO2ulfl3QYRjqrKW+mGAIWP5kdNCBheqqqYIGElSRCaXfpw==} + cpu: [s390x] + os: [linux] + + '@rollup/rollup-linux-x64-gnu@4.28.0': + resolution: {integrity: sha512-Nl4KIzteVEKE9BdAvYoTkW19pa7LR/RBrT6F1dJCV/3pbjwDcaOq+edkP0LXuJ9kflW/xOK414X78r+K84+msw==} + cpu: [x64] + os: [linux] + + '@rollup/rollup-linux-x64-musl@4.28.0': + resolution: {integrity: sha512-eKpJr4vBDOi4goT75MvW+0dXcNUqisK4jvibY9vDdlgLx+yekxSm55StsHbxUsRxSTt3JEQvlr3cGDkzcSP8bw==} + cpu: [x64] + os: [linux] + + '@rollup/rollup-win32-arm64-msvc@4.28.0': + resolution: {integrity: sha512-Vi+WR62xWGsE/Oj+mD0FNAPY2MEox3cfyG0zLpotZdehPFXwz6lypkGs5y38Jd/NVSbOD02aVad6q6QYF7i8Bg==} + cpu: [arm64] + os: [win32] + + '@rollup/rollup-win32-ia32-msvc@4.28.0': + resolution: {integrity: sha512-kN/Vpip8emMLn/eOza+4JwqDZBL6MPNpkdaEsgUtW1NYN3DZvZqSQrbKzJcTL6hd8YNmFTn7XGWMwccOcJBL0A==} + cpu: [ia32] + os: [win32] + + '@rollup/rollup-win32-x64-msvc@4.28.0': + resolution: {integrity: sha512-Bvno2/aZT6usSa7lRDL2+hMjVAGjuqaymF1ApZm31JXzniR/hvr14jpU+/z4X6Gt5BPlzosscyJZGUvguXIqeQ==} + cpu: [x64] + os: [win32] + + '@swc/core-darwin-arm64@1.9.3': + resolution: {integrity: sha512-hGfl/KTic/QY4tB9DkTbNuxy5cV4IeejpPD4zo+Lzt4iLlDWIeANL4Fkg67FiVceNJboqg48CUX+APhDHO5G1w==} + engines: {node: '>=10'} + cpu: [arm64] + os: [darwin] + + '@swc/core-darwin-x64@1.9.3': + resolution: {integrity: sha512-IaRq05ZLdtgF5h9CzlcgaNHyg4VXuiStnOFpfNEMuI5fm5afP2S0FHq8WdakUz5WppsbddTdplL+vpeApt/WCQ==} + engines: {node: '>=10'} + cpu: [x64] + os: [darwin] + + '@swc/core-linux-arm-gnueabihf@1.9.3': + resolution: {integrity: sha512-Pbwe7xYprj/nEnZrNBvZfjnTxlBIcfApAGdz2EROhjpPj+FBqBa3wOogqbsuGGBdCphf8S+KPprL1z+oDWkmSQ==} + engines: {node: '>=10'} + cpu: [arm] + os: [linux] + + '@swc/core-linux-arm64-gnu@1.9.3': + resolution: {integrity: sha512-AQ5JZiwNGVV/2K2TVulg0mw/3LYfqpjZO6jDPtR2evNbk9Yt57YsVzS+3vHSlUBQDRV9/jqMuZYVU3P13xrk+g==} + engines: {node: '>=10'} + cpu: [arm64] + os: [linux] + + '@swc/core-linux-arm64-musl@1.9.3': + resolution: {integrity: sha512-tzVH480RY6RbMl/QRgh5HK3zn1ZTFsThuxDGo6Iuk1MdwIbdFYUY034heWUTI4u3Db97ArKh0hNL0xhO3+PZdg==} + engines: {node: '>=10'} + cpu: [arm64] + os: [linux] + + '@swc/core-linux-x64-gnu@1.9.3': + resolution: {integrity: sha512-ivXXBRDXDc9k4cdv10R21ccBmGebVOwKXT/UdH1PhxUn9m/h8erAWjz5pcELwjiMf27WokqPgaWVfaclDbgE+w==} + engines: {node: '>=10'} + cpu: [x64] + os: [linux] + + '@swc/core-linux-x64-musl@1.9.3': + resolution: {integrity: sha512-ILsGMgfnOz1HwdDz+ZgEuomIwkP1PHT6maigZxaCIuC6OPEhKE8uYna22uU63XvYcLQvZYDzpR3ms47WQPuNEg==} + engines: {node: '>=10'} + cpu: [x64] + os: [linux] + + '@swc/core-win32-arm64-msvc@1.9.3': + resolution: {integrity: sha512-e+XmltDVIHieUnNJHtspn6B+PCcFOMYXNJB1GqoCcyinkEIQNwC8KtWgMqUucUbEWJkPc35NHy9k8aCXRmw9Kg==} + engines: {node: '>=10'} + cpu: [arm64] + os: [win32] + + '@swc/core-win32-ia32-msvc@1.9.3': + resolution: {integrity: sha512-rqpzNfpAooSL4UfQnHhkW8aL+oyjqJniDP0qwZfGnjDoJSbtPysHg2LpcOBEdSnEH+uIZq6J96qf0ZFD8AGfXA==} + engines: {node: '>=10'} + cpu: [ia32] + os: [win32] + + '@swc/core-win32-x64-msvc@1.9.3': + resolution: {integrity: sha512-3YJJLQ5suIEHEKc1GHtqVq475guiyqisKSoUnoaRtxkDaW5g1yvPt9IoSLOe2mRs7+FFhGGU693RsBUSwOXSdQ==} + engines: {node: '>=10'} + cpu: [x64] + os: [win32] + + '@swc/core@1.9.3': + resolution: {integrity: sha512-oRj0AFePUhtatX+BscVhnzaAmWjpfAeySpM1TCbxA1rtBDeH/JDhi5yYzAKneDYtVtBvA7ApfeuzhMC9ye4xSg==} + engines: {node: '>=10'} + peerDependencies: + '@swc/helpers': '*' + peerDependenciesMeta: + '@swc/helpers': + optional: true + + '@swc/counter@0.1.3': + resolution: {integrity: sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==} + + '@swc/helpers@0.5.15': + resolution: {integrity: sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==} + + '@swc/types@0.1.17': + resolution: {integrity: sha512-V5gRru+aD8YVyCOMAjMpWR1Ui577DD5KSJsHP8RAxopAH22jFz6GZd/qxqjO6MJHQhcsjvjOFXyDhyLQUnMveQ==} + + '@types/docker-modem@3.0.6': + resolution: {integrity: sha512-yKpAGEuKRSS8wwx0joknWxsmLha78wNMe9R2S3UNsVOkZded8UqOrV8KoeDXoXsjndxwyF3eIhyClGbO1SEhEg==} + + '@types/dockerode@3.3.32': + resolution: {integrity: sha512-xxcG0g5AWKtNyh7I7wswLdFvym4Mlqks5ZlKzxEUrGHS0r0PUOfxm2T0mspwu10mHQqu3Ck3MI3V2HqvLWE1fg==} + + '@types/estree@1.0.6': + resolution: {integrity: sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==} + + '@types/json-schema@7.0.15': + resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} + + '@types/linkify-it@5.0.0': + resolution: {integrity: sha512-sVDA58zAw4eWAffKOaQH5/5j3XeayukzDk+ewSsnv3p4yJEZHCCzMDiZM8e0OUrRvmpGZ85jf4yDHkHsgBNr9Q==} + + '@types/markdown-it@14.1.2': + resolution: {integrity: sha512-promo4eFwuiW+TfGxhi+0x3czqTYJkG8qB17ZUJiVF10Xm7NLVRSLUsfRTU/6h1e24VvRnXCx+hG7li58lkzog==} + + '@types/mdurl@2.0.0': + resolution: {integrity: sha512-RGdgjQUZba5p6QEFAVx2OGb8rQDL/cPRG7GiedRzMcJ1tYnUANBncjbSB1NRGwbvjcPeikRABz2nshyPk1bhWg==} + + '@types/node@18.19.67': + resolution: {integrity: sha512-wI8uHusga+0ZugNp0Ol/3BqQfEcCCNfojtO6Oou9iVNGPTL6QNSdnUdqq85fRgIorLhLMuPIKpsN98QE9Nh+KQ==} + + '@types/node@22.10.1': + resolution: {integrity: sha512-qKgsUwfHZV2WCWLAnVP1JqnpE6Im6h3Y0+fYgMTasNQ7V++CBX5OT1as0g0f+OyubbFqhf6XVNIsmN4IIhEgGQ==} + + '@types/resolve@1.20.2': + resolution: {integrity: sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==} + + '@types/ssh2-streams@0.1.12': + resolution: {integrity: sha512-Sy8tpEmCce4Tq0oSOYdfqaBpA3hDM8SoxoFh5vzFsu2oL+znzGz8oVWW7xb4K920yYMUY+PIG31qZnFMfPWNCg==} + + '@types/ssh2@0.5.52': + resolution: {integrity: sha512-lbLLlXxdCZOSJMCInKH2+9V/77ET2J6NPQHpFI0kda61Dd1KglJs+fPQBchizmzYSOJBgdTajhPqBO1xxLywvg==} + + '@types/ssh2@1.15.1': + resolution: {integrity: sha512-ZIbEqKAsi5gj35y4P4vkJYly642wIbY6PqoN0xiyQGshKUGXR9WQjF/iF9mXBQ8uBKy3ezfsCkcoHKhd0BzuDA==} + + '@typescript-eslint/eslint-plugin@8.17.0': + resolution: {integrity: sha512-HU1KAdW3Tt8zQkdvNoIijfWDMvdSweFYm4hWh+KwhPstv+sCmWb89hCIP8msFm9N1R/ooh9honpSuvqKWlYy3w==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + '@typescript-eslint/parser': ^8.0.0 || ^8.0.0-alpha.0 + eslint: ^8.57.0 || ^9.0.0 + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + + '@typescript-eslint/parser@8.17.0': + resolution: {integrity: sha512-Drp39TXuUlD49F7ilHHCG7TTg8IkA+hxCuULdmzWYICxGXvDXmDmWEjJYZQYgf6l/TFfYNE167m7isnc3xlIEg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + + '@typescript-eslint/scope-manager@8.17.0': + resolution: {integrity: sha512-/ewp4XjvnxaREtqsZjF4Mfn078RD/9GmiEAtTeLQ7yFdKnqwTOgRMSvFz4et9U5RiJQ15WTGXPLj89zGusvxBg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@typescript-eslint/type-utils@8.17.0': + resolution: {integrity: sha512-q38llWJYPd63rRnJ6wY/ZQqIzPrBCkPdpIsaCfkR3Q4t3p6sb422zougfad4TFW9+ElIFLVDzWGiGAfbb/v2qw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + + '@typescript-eslint/types@8.17.0': + resolution: {integrity: sha512-gY2TVzeve3z6crqh2Ic7Cr+CAv6pfb0Egee7J5UAVWCpVvDI/F71wNfolIim4FE6hT15EbpZFVUj9j5i38jYXA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@typescript-eslint/typescript-estree@8.17.0': + resolution: {integrity: sha512-JqkOopc1nRKZpX+opvKqnM3XUlM7LpFMD0lYxTqOTKQfCWAmxw45e3qlOCsEqEB2yuacujivudOFpCnqkBDNMw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + + '@typescript-eslint/utils@8.17.0': + resolution: {integrity: sha512-bQC8BnEkxqG8HBGKwG9wXlZqg37RKSMY7v/X8VEWD8JG2JuTHuNK0VFvMPMUKQcbk6B+tf05k+4AShAEtCtJ/w==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + + '@typescript-eslint/visitor-keys@8.17.0': + resolution: {integrity: sha512-1Hm7THLpO6ww5QU6H/Qp+AusUUl+z/CAm3cNZZ0jQvon9yicgO7Rwd+/WWRpMKLYV6p2UvdbR27c86rzCPpreg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@vitest/expect@2.1.8': + resolution: {integrity: sha512-8ytZ/fFHq2g4PJVAtDX57mayemKgDR6X3Oa2Foro+EygiOJHUXhCqBAAKQYYajZpFoIfvBCF1j6R6IYRSIUFuw==} + + '@vitest/mocker@2.1.8': + resolution: {integrity: sha512-7guJ/47I6uqfttp33mgo6ga5Gr1VnL58rcqYKyShoRK9ebu8T5Rs6HN3s1NABiBeVTdWNrwUMcHH54uXZBN4zA==} + peerDependencies: + msw: ^2.4.9 + vite: ^5.0.0 + peerDependenciesMeta: + msw: + optional: true + vite: + optional: true + + '@vitest/pretty-format@2.1.8': + resolution: {integrity: sha512-9HiSZ9zpqNLKlbIDRWOnAWqgcA7xu+8YxXSekhr0Ykab7PAYFkhkwoqVArPOtJhPmYeE2YHgKZlj3CP36z2AJQ==} + + '@vitest/runner@2.1.8': + resolution: {integrity: sha512-17ub8vQstRnRlIU5k50bG+QOMLHRhYPAna5tw8tYbj+jzjcspnwnwtPtiOlkuKC4+ixDPTuLZiqiWWQ2PSXHVg==} + + '@vitest/snapshot@2.1.8': + resolution: {integrity: sha512-20T7xRFbmnkfcmgVEz+z3AU/3b0cEzZOt/zmnvZEctg64/QZbSDJEVm9fLnnlSi74KibmRsO9/Qabi+t0vCRPg==} + + '@vitest/spy@2.1.8': + resolution: {integrity: sha512-5swjf2q95gXeYPevtW0BLk6H8+bPlMb4Vw/9Em4hFxDcaOxS+e0LOX4yqNxoHzMR2akEB2xfpnWUzkZokmgWDg==} + + '@vitest/utils@2.1.8': + resolution: {integrity: sha512-dwSoui6djdwbfFmIgbIjX2ZhIoG7Ex/+xpxyiEgIGzjliY8xGkcpITKTlp6B4MgtGkF2ilvm97cPM96XZaAgcA==} + + '@zeit/schemas@2.36.0': + resolution: {integrity: sha512-7kjMwcChYEzMKjeex9ZFXkt1AyNov9R5HZtjBKVsmVpw7pa7ZtlCGvCBC2vnnXctaYN+aRI61HjIqeetZW5ROg==} + + abort-controller@3.0.0: + resolution: {integrity: sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==} + engines: {node: '>=6.5'} + + accepts@1.3.8: + resolution: {integrity: sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==} + engines: {node: '>= 0.6'} + + acorn-jsx@5.3.2: + resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} + peerDependencies: + acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 + + acorn@8.14.0: + resolution: {integrity: sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==} + engines: {node: '>=0.4.0'} + hasBin: true + + ajv@6.12.6: + resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} + + ajv@8.12.0: + resolution: {integrity: sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==} + + ansi-align@3.0.1: + resolution: {integrity: sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w==} + + ansi-regex@5.0.1: + resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} + engines: {node: '>=8'} + + ansi-regex@6.1.0: + resolution: {integrity: sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==} + engines: {node: '>=12'} + + ansi-styles@4.3.0: + resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} + engines: {node: '>=8'} + + ansi-styles@6.2.1: + resolution: {integrity: sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==} + engines: {node: '>=12'} + + arch@2.2.0: + resolution: {integrity: sha512-Of/R0wqp83cgHozfIYLbBMnej79U/SVGOOyuB3VVFv1NRM/PSFMK12x9KVtiYzJqmnU5WR2qp0Z5rHb7sWGnFQ==} + + archiver-utils@5.0.2: + resolution: {integrity: sha512-wuLJMmIBQYCsGZgYLTy5FIB2pF6Lfb6cXMSF8Qywwk3t20zWnAi7zLcQFdKQmIB8wyZpY5ER38x08GbwtR2cLA==} + engines: {node: '>= 14'} + + archiver@7.0.1: + resolution: {integrity: sha512-ZcbTaIqJOfCc03QwD468Unz/5Ir8ATtvAHsK+FdXbDIbGfihqh9mrvdcYunQzqn4HrvWWaFyaxJhGZagaJJpPQ==} + engines: {node: '>= 14'} + + arg@5.0.2: + resolution: {integrity: sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==} + + argparse@2.0.1: + resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} + + asn1@0.2.6: + resolution: {integrity: sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==} + + assertion-error@2.0.1: + resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} + engines: {node: '>=12'} + + async-lock@1.4.1: + resolution: {integrity: sha512-Az2ZTpuytrtqENulXwO3GGv1Bztugx6TT37NIo7imr/Qo0gsYiGtSdBa2B6fsXhTpVZDNfu1Qn3pk531e3q+nQ==} + + async@3.2.6: + resolution: {integrity: sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==} + + b4a@1.6.7: + resolution: {integrity: sha512-OnAYlL5b7LEkALw87fUVafQw5rVR9RjwGd4KUwNQ6DrrNmaVaUCgLipfVlzrPQ4tWOR9P0IXGNOx50jYCCdSJg==} + + balanced-match@1.0.2: + resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + + bare-events@2.5.0: + resolution: {integrity: sha512-/E8dDe9dsbLyh2qrZ64PEPadOQ0F4gbl1sUJOrmph7xOiIxfY8vwab/4bFLh4Y88/Hk/ujKcrQKc+ps0mv873A==} + + bare-fs@2.3.5: + resolution: {integrity: sha512-SlE9eTxifPDJrT6YgemQ1WGFleevzwY+XAP1Xqgl56HtcrisC2CHCZ2tq6dBpcH2TnNxwUEUGhweo+lrQtYuiw==} + + bare-os@2.4.4: + resolution: {integrity: sha512-z3UiI2yi1mK0sXeRdc4O1Kk8aOa/e+FNWZcTiPB/dfTWyLypuE99LibgRaQki914Jq//yAWylcAt+mknKdixRQ==} + + bare-path@2.1.3: + resolution: {integrity: sha512-lh/eITfU8hrj9Ru5quUp0Io1kJWIk1bTjzo7JH1P5dWmQ2EL4hFUlfI8FonAhSlgIfhn63p84CDY/x+PisgcXA==} + + bare-stream@2.4.2: + resolution: {integrity: sha512-XZ4ln/KV4KT+PXdIWTKjsLY+quqCaEtqqtgGJVPw9AoM73By03ij64YjepK0aQvHSWDb6AfAZwqKaFu68qkrdA==} + + base64-js@1.5.1: + resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} + + bcrypt-pbkdf@1.0.2: + resolution: {integrity: sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w==} + + bl@4.1.0: + resolution: {integrity: sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==} + + bluebird@3.7.2: + resolution: {integrity: sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==} + + boxen@7.0.0: + resolution: {integrity: sha512-j//dBVuyacJbvW+tvZ9HuH03fZ46QcaKvvhZickZqtB271DxJ7SNRSNxrV/dZX0085m7hISRZWbzWlJvx/rHSg==} + engines: {node: '>=14.16'} + + brace-expansion@1.1.11: + resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==} + + brace-expansion@2.0.1: + resolution: {integrity: sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==} + + braces@3.0.3: + resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} + engines: {node: '>=8'} + + buffer-crc32@1.0.0: + resolution: {integrity: sha512-Db1SbgBS/fg/392AblrMJk97KggmvYhr4pB5ZIMTWtaivCPMWLkmb7m21cJvpvgK+J3nsU2CmmixNBZx4vFj/w==} + engines: {node: '>=8.0.0'} + + buffer@5.7.1: + resolution: {integrity: sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==} + + buffer@6.0.3: + resolution: {integrity: sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==} + + buildcheck@0.0.6: + resolution: {integrity: sha512-8f9ZJCUXyT1M35Jx7MkBgmBMo3oHTTBIPLiY9xyL0pl3T5RwcPEY8cUHr5LBNfu/fk6c2T4DJZuVM/8ZZT2D2A==} + engines: {node: '>=10.0.0'} + + bunchee@6.0.3: + resolution: {integrity: sha512-Yq/srd3ocXPAHv0KEdJvhFMNUOOVVqy0kNzaGVCirk/+MfnLdvZO5uf5BHugIHe/qSvWUQTJZ3SAfB/VABONeQ==} + engines: {node: '>= 18.0.0'} + hasBin: true + peerDependencies: + typescript: ^4.1 || ^5.0 + peerDependenciesMeta: + typescript: + optional: true + + byline@5.0.0: + resolution: {integrity: sha512-s6webAy+R4SR8XVuJWt2V2rGvhnrhxN+9S15GNuTK3wKPOXFF6RNc+8ug2XhH+2s4f+uudG4kUVYmYOQWL2g0Q==} + engines: {node: '>=0.10.0'} + + bytes@3.0.0: + resolution: {integrity: sha512-pMhOfFDPiv9t5jjIXkHosWmkSyQbvsgEVNkz0ERHbuLh2T/7j4Mqqpz523Fe8MVY89KC6Sh/QfS2sM+SjgFDcw==} + engines: {node: '>= 0.8'} + + cac@6.7.14: + resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==} + engines: {node: '>=8'} + + callsites@3.1.0: + resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} + engines: {node: '>=6'} + + camelcase@7.0.1: + resolution: {integrity: sha512-xlx1yCK2Oc1APsPXDL2LdlNP6+uu8OCDdhOBSVT279M/S+y75O30C2VuD8T2ogdePBBl7PfPF4504tnLgX3zfw==} + engines: {node: '>=14.16'} + + catharsis@0.9.0: + resolution: {integrity: sha512-prMTQVpcns/tzFgFVkVp6ak6RykZyWb3gu8ckUpd6YkTlacOd3DXGJjIpD4Q6zJirizvaiAjSSHlOsA+6sNh2A==} + engines: {node: '>= 10'} + + chai@5.1.2: + resolution: {integrity: sha512-aGtmf24DW6MLHHG5gCx4zaI3uBq3KRtxeVs0DjFH6Z0rDNbsvTxFASFvdj79pxjxZ8/5u3PIiN3IwEIQkiiuPw==} + engines: {node: '>=12'} + + chalk-template@0.4.0: + resolution: {integrity: sha512-/ghrgmhfY8RaSdeo43hNXxpoHAtxdbskUHjPpfqUWGttFgycUhYPGx3YZBCnUCvOa7Doivn1IZec3DEGFoMgLg==} + engines: {node: '>=12'} + + chalk@4.1.2: + resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} + engines: {node: '>=10'} + + chalk@5.0.1: + resolution: {integrity: sha512-Fo07WOYGqMfCWHOzSXOt2CxDbC6skS/jO9ynEcmpANMoPrD+W1r1K6Vx7iNm+AQmETU1Xr2t+n8nzkV9t6xh3w==} + engines: {node: ^12.17.0 || ^14.13 || >=16.0.0} + + chalk@5.3.0: + resolution: {integrity: sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==} + engines: {node: ^12.17.0 || ^14.13 || >=16.0.0} + + check-error@2.1.1: + resolution: {integrity: sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==} + engines: {node: '>= 16'} + + chownr@1.1.4: + resolution: {integrity: sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==} + + clean-css@5.3.3: + resolution: {integrity: sha512-D5J+kHaVb/wKSFcyyV75uCn8fiY4sV38XJoe4CUyGQ+mOU/fMVYUdH1hJC+CJQ5uY3EnW27SbJYS4X8BiLrAFg==} + engines: {node: '>= 10.0'} + + cli-boxes@3.0.0: + resolution: {integrity: sha512-/lzGpEWL/8PfI0BmBOPRwp0c/wFNX1RdUML3jK/RcSBA9T8mZDdQpqYBKtCFTOfQbwPqWEOpjqW+Fnayc0969g==} + engines: {node: '>=10'} + + cli-cursor@5.0.0: + resolution: {integrity: sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==} + engines: {node: '>=18'} + + cli-spinners@2.9.2: + resolution: {integrity: sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==} + engines: {node: '>=6'} + + clipboardy@3.0.0: + resolution: {integrity: sha512-Su+uU5sr1jkUy1sGRpLKjKrvEOVXgSgiSInwa/qeID6aJ07yh+5NWc3h2QfjHjBnfX4LhtFcuAWKUsJ3r+fjbg==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + + cliui@8.0.1: + resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==} + engines: {node: '>=12'} + + color-convert@2.0.1: + resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} + engines: {node: '>=7.0.0'} + + color-name@1.1.4: + resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + + commondir@1.0.1: + resolution: {integrity: sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==} + + compress-commons@6.0.2: + resolution: {integrity: sha512-6FqVXeETqWPoGcfzrXb37E50NP0LXT8kAMu5ooZayhWWdgEY4lBEEcbQNXtkuKQsGduxiIcI4gOTsxTmuq/bSg==} + engines: {node: '>= 14'} + + compressible@2.0.18: + resolution: {integrity: sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==} + engines: {node: '>= 0.6'} + + compression@1.7.4: + resolution: {integrity: sha512-jaSIDzP9pZVS4ZfQ+TzvtiWhdpFhE2RDHz8QJkpX9SIpLq88VueF5jJw6t+6CUQcAoA6t+x89MLrWAqpfDE8iQ==} + engines: {node: '>= 0.8.0'} + + concat-map@0.0.1: + resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} + + content-disposition@0.5.2: + resolution: {integrity: sha512-kRGRZw3bLlFISDBgwTSA1TMBFN6J6GWDeubmDE3AF+3+yXL8hTWv8r5rkLbqYXY4RjPk/EzHnClI3zQf1cFmHA==} + engines: {node: '>= 0.6'} + + core-util-is@1.0.3: + resolution: {integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==} + + cpu-features@0.0.10: + resolution: {integrity: sha512-9IkYqtX3YHPCzoVg1Py+o9057a3i0fp7S530UWokCSaFVTc7CwXPRiOjRjBQQ18ZCNafx78YfnG+HALxtVmOGA==} + engines: {node: '>=10.0.0'} + + crc-32@1.2.2: + resolution: {integrity: sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==} + engines: {node: '>=0.8'} + hasBin: true + + crc32-stream@6.0.0: + resolution: {integrity: sha512-piICUB6ei4IlTv1+653yq5+KoqfBYmj9bw6LqXoOneTMDXk5nM1qt12mFW1caG3LlJXEKW1Bp0WggEmIfQB34g==} + engines: {node: '>= 14'} + + cross-spawn@7.0.6: + resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} + engines: {node: '>= 8'} + + debug@2.6.9: + resolution: {integrity: sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + debug@4.3.7: + resolution: {integrity: sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + deep-eql@5.0.2: + resolution: {integrity: sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==} + engines: {node: '>=6'} + + deep-extend@0.6.0: + resolution: {integrity: sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==} + engines: {node: '>=4.0.0'} + + deep-is@0.1.4: + resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} + + deepmerge@4.3.1: + resolution: {integrity: sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==} + engines: {node: '>=0.10.0'} + + docker-compose@0.24.8: + resolution: {integrity: sha512-plizRs/Vf15H+GCVxq2EUvyPK7ei9b/cVesHvjnX4xaXjM9spHe2Ytq0BitndFgvTJ3E3NljPNUEl7BAN43iZw==} + engines: {node: '>= 6.0.0'} + + docker-modem@3.0.8: + resolution: {integrity: sha512-f0ReSURdM3pcKPNS30mxOHSbaFLcknGmQjwSfmbcdOw1XWKXVhukM3NJHhr7NpY9BIyyWQb0EBo3KQvvuU5egQ==} + engines: {node: '>= 8.0'} + + dockerode@3.3.5: + resolution: {integrity: sha512-/0YNa3ZDNeLr/tSckmD69+Gq+qVNhvKfAHNeZJBnp7EOP6RGKV8ORrJHkUn20So5wU+xxT7+1n5u8PjHbfjbSA==} + engines: {node: '>= 8.0'} + + eastasianwidth@0.2.0: + resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} + + emoji-regex@10.4.0: + resolution: {integrity: sha512-EC+0oUMY1Rqm4O6LLrgjtYDvcVYTy7chDnM4Q7030tP4Kwj3u/pR6gP9ygnp2CJMK5Gq+9Q2oqmrFJAz01DXjw==} + + emoji-regex@8.0.0: + resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} + + emoji-regex@9.2.2: + resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} + + end-of-stream@1.4.4: + resolution: {integrity: sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==} + + entities@4.5.0: + resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==} + engines: {node: '>=0.12'} + + es-module-lexer@1.5.4: + resolution: {integrity: sha512-MVNK56NiMrOwitFB7cqDwq0CQutbw+0BvLshJSse0MUNU+y1FC3bUS/AQg7oUng+/wKrrki7JfmwtVHkVfPLlw==} + + esbuild@0.21.5: + resolution: {integrity: sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==} + engines: {node: '>=12'} + hasBin: true + + escalade@3.2.0: + resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} + engines: {node: '>=6'} + + escape-string-regexp@2.0.0: + resolution: {integrity: sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==} + engines: {node: '>=8'} + + escape-string-regexp@4.0.0: + resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} + engines: {node: '>=10'} + + eslint-scope@8.2.0: + resolution: {integrity: sha512-PHlWUfG6lvPc3yvP5A4PNyBL1W8fkDUccmI21JUu/+GKZBoH/W5u6usENXUrWFRsyoW5ACUjFGgAFQp5gUlb/A==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + 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} + + eslint-visitor-keys@4.2.0: + resolution: {integrity: sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + eslint@9.16.0: + resolution: {integrity: sha512-whp8mSQI4C8VXd+fLgSM0lh3UlmcFtVwUQjyKCFfsp+2ItAIYhlq/hqGahGqHE6cv9unM41VlqKk2VtKYR2TaA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + hasBin: true + peerDependencies: + jiti: '*' + peerDependenciesMeta: + jiti: + optional: true + + espree@10.3.0: + resolution: {integrity: sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + esquery@1.6.0: + resolution: {integrity: sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==} + engines: {node: '>=0.10'} + + esrecurse@4.3.0: + resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==} + engines: {node: '>=4.0'} + + estraverse@5.3.0: + resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} + engines: {node: '>=4.0'} + + estree-walker@2.0.2: + resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==} + + estree-walker@3.0.3: + resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} + + esutils@2.0.3: + resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} + engines: {node: '>=0.10.0'} + + event-target-shim@5.0.1: + resolution: {integrity: sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==} + engines: {node: '>=6'} + + events@3.3.0: + resolution: {integrity: sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==} + engines: {node: '>=0.8.x'} + + execa@5.1.1: + resolution: {integrity: sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==} + engines: {node: '>=10'} + + expect-type@1.1.0: + resolution: {integrity: sha512-bFi65yM+xZgk+u/KRIpekdSYkTB5W1pEf0Lt8Q8Msh7b+eQ7LXVtIB1Bkm4fvclDEL1b2CZkMhv2mOeF8tMdkA==} + engines: {node: '>=12.0.0'} + + fast-deep-equal@3.1.3: + resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} + + fast-fifo@1.3.2: + resolution: {integrity: sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==} + + fast-glob@3.3.2: + resolution: {integrity: sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==} + engines: {node: '>=8.6.0'} + + fast-json-stable-stringify@2.1.0: + resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} + + fast-levenshtein@2.0.6: + resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} + + fastq@1.17.1: + resolution: {integrity: sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==} + + fdir@6.4.2: + resolution: {integrity: sha512-KnhMXsKSPZlAhp7+IjUkRZKPb4fUyccpDrdFXbi4QL1qkmFh9kVY09Yox+n4MaOb3lHZ1Tv829C3oaaXoMYPDQ==} + peerDependencies: + picomatch: ^3 || ^4 + peerDependenciesMeta: + picomatch: + optional: true + + file-entry-cache@8.0.0: + resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==} + engines: {node: '>=16.0.0'} + + fill-range@7.1.1: + resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} + engines: {node: '>=8'} + + find-up@5.0.0: + resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} + engines: {node: '>=10'} + + flat-cache@4.0.1: + resolution: {integrity: sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==} + engines: {node: '>=16'} + + flatted@3.3.2: + resolution: {integrity: sha512-AiwGJM8YcNOaobumgtng+6NHuOqC3A7MixFeDafM3X9cIUM+xUXoS5Vfgf+OihAYe20fxqNM9yPBXJzRtZ/4eA==} + + foreground-child@3.3.0: + resolution: {integrity: sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg==} + engines: {node: '>=14'} + + fs-constants@1.0.0: + resolution: {integrity: sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==} + + fsevents@2.3.3: + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + + function-bind@1.1.2: + resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} + + get-caller-file@2.0.5: + resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} + engines: {node: 6.* || 8.* || >= 10.*} + + get-east-asian-width@1.3.0: + resolution: {integrity: sha512-vpeMIQKxczTD/0s2CdEWHcb0eeJe6TFjxb+J5xgX7hScxqrGuyjmv4c1D4A/gelKfyox0gJJwIHF+fLjeaM8kQ==} + engines: {node: '>=18'} + + get-port@5.1.1: + resolution: {integrity: sha512-g/Q1aTSDOxFpchXC4i8ZWvxA1lnPqx/JHqcpIw0/LX9T8x/GBbi6YnlN5nhaKIFkT8oFsscUKgDJYxfwfS6QsQ==} + engines: {node: '>=8'} + + get-stream@6.0.1: + resolution: {integrity: sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==} + engines: {node: '>=10'} + + get-tsconfig@4.8.1: + resolution: {integrity: sha512-k9PN+cFBmaLWtVz29SkUoqU5O0slLuHJXt/2P+tMVFT+phsSGXGkp9t3rQIqdz0e+06EHNGs3oM6ZX1s2zHxRg==} + + glob-parent@5.1.2: + resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} + engines: {node: '>= 6'} + + glob-parent@6.0.2: + resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} + engines: {node: '>=10.13.0'} + + glob@10.4.5: + resolution: {integrity: sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==} + hasBin: true + + glob@11.0.0: + resolution: {integrity: sha512-9UiX/Bl6J2yaBbxKoEBRm4Cipxgok8kQYcOPEhScPwebu2I0HoQOuYdIO6S3hLuWoZgpDpwQZMzTFxgpkyT76g==} + engines: {node: 20 || >=22} + hasBin: true + + globals@14.0.0: + resolution: {integrity: sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==} + engines: {node: '>=18'} + + graceful-fs@4.2.11: + resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} + + graphemer@1.4.0: + resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==} + + has-flag@4.0.0: + resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} + engines: {node: '>=8'} + + hasown@2.0.2: + resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} + engines: {node: '>= 0.4'} + + human-signals@2.1.0: + resolution: {integrity: sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==} + engines: {node: '>=10.17.0'} + + ieee754@1.2.1: + resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} + + ignore@5.3.2: + resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} + engines: {node: '>= 4'} + + import-fresh@3.3.0: + resolution: {integrity: sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==} + engines: {node: '>=6'} + + imurmurhash@0.1.4: + resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} + engines: {node: '>=0.8.19'} + + inherits@2.0.4: + resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} + + ini@1.3.8: + resolution: {integrity: sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==} + + is-core-module@2.15.1: + resolution: {integrity: sha512-z0vtXSwucUJtANQWldhbtbt7BnL0vxiFjIdDLAatwhDYty2bad6s+rijD6Ri4YuYJubLzIJLUidCh09e1djEVQ==} + engines: {node: '>= 0.4'} + + is-docker@2.2.1: + resolution: {integrity: sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==} + engines: {node: '>=8'} + hasBin: true + + is-extglob@2.1.1: + resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} + engines: {node: '>=0.10.0'} + + is-fullwidth-code-point@3.0.0: + resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} + engines: {node: '>=8'} + + is-glob@4.0.3: + resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} + engines: {node: '>=0.10.0'} + + is-interactive@2.0.0: + resolution: {integrity: sha512-qP1vozQRI+BMOPcjFzrjXuQvdak2pHNUMZoeG2eRbiSqyvbEf/wQtEOTOX1guk6E3t36RkaqiSt8A/6YElNxLQ==} + engines: {node: '>=12'} + + is-module@1.0.0: + resolution: {integrity: sha512-51ypPSPCoTEIN9dy5Oy+h4pShgJmPCygKfyRCISBI+JoWT/2oJvK8QPxmwv7b/p239jXrm9M1mlQbyKJ5A152g==} + + is-number@7.0.0: + resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} + engines: {node: '>=0.12.0'} + + is-port-reachable@4.0.0: + resolution: {integrity: sha512-9UoipoxYmSk6Xy7QFgRv2HDyaysmgSG75TFQs6S+3pDM7ZhKTF/bskZV+0UlABHzKjNVhPjYCLfeZUEg1wXxig==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + + is-reference@1.2.1: + resolution: {integrity: sha512-U82MsXXiFIrjCK4otLT+o2NA2Cd2g5MLoOVXUZjIOhLurrRxpEXzI8O0KZHr3IjLvlAH1kTPYSuqer5T9ZVBKQ==} + + is-stream@2.0.1: + resolution: {integrity: sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==} + engines: {node: '>=8'} + + is-unicode-supported@1.3.0: + resolution: {integrity: sha512-43r2mRvz+8JRIKnWJ+3j8JtjRKZ6GmjzfaE/qiBJnikNnYv/6bagRJ1kUhNk8R5EX/GkobD+r+sfxCPJsiKBLQ==} + engines: {node: '>=12'} + + is-unicode-supported@2.1.0: + resolution: {integrity: sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ==} + engines: {node: '>=18'} + + is-wsl@2.2.0: + resolution: {integrity: sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==} + engines: {node: '>=8'} + + isarray@1.0.0: + resolution: {integrity: sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==} + + isexe@2.0.0: + resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + + jackspeak@3.4.3: + resolution: {integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==} + + jackspeak@4.0.2: + resolution: {integrity: sha512-bZsjR/iRjl1Nk1UkjGpAzLNfQtzuijhn2g+pbZb98HQ1Gk8vM9hfbxeMBP+M2/UUdwj0RqGG3mlvk2MsAqwvEw==} + engines: {node: 20 || >=22} + + js-tokens@4.0.0: + resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} + + js-yaml@4.1.0: + resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==} + hasBin: true + + js2xmlparser@4.0.2: + resolution: {integrity: sha512-6n4D8gLlLf1n5mNLQPRfViYzu9RATblzPEtm1SthMX1Pjao0r9YI9nw7ZIfRxQMERS87mcswrg+r/OYrPRX6jA==} + + jsdoc@4.0.4: + resolution: {integrity: sha512-zeFezwyXeG4syyYHbvh1A967IAqq/67yXtXvuL5wnqCkFZe8I0vKfm+EO+YEvLguo6w9CDUbrAXVtJSHh2E8rw==} + engines: {node: '>=12.0.0'} + hasBin: true + + json-buffer@3.0.1: + resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} + + json-schema-traverse@0.4.1: + resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} + + json-schema-traverse@1.0.0: + resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==} + + json-stable-stringify-without-jsonify@1.0.1: + resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} + + keyv@4.5.4: + resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} + + klaw@3.0.0: + resolution: {integrity: sha512-0Fo5oir+O9jnXu5EefYbVK+mHMBeEVEy2cmctR1O1NECcCkPRreJKrS6Qt/j3KC2C148Dfo9i3pCmCMsdqGr0g==} + + lazystream@1.0.1: + resolution: {integrity: sha512-b94GiNHQNy6JNTrt5w6zNyffMrNkXZb3KTkCZJb2V1xaEGCk093vkZ2jk3tpaeP33/OiXC+WvK9AxUebnf5nbw==} + engines: {node: '>= 0.6.3'} + + levn@0.4.1: + resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} + engines: {node: '>= 0.8.0'} + + linkify-it@5.0.0: + resolution: {integrity: sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ==} + + locate-path@6.0.0: + resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} + engines: {node: '>=10'} + + lodash.merge@4.6.2: + resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} + + lodash@4.17.21: + resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} + + log-symbols@6.0.0: + resolution: {integrity: sha512-i24m8rpwhmPIS4zscNzK6MSEhk0DUWa/8iYQWxhffV8jkI4Phvs3F+quL5xvS0gdQR0FyTCMMH33Y78dDTzzIw==} + engines: {node: '>=18'} + + loupe@3.1.2: + resolution: {integrity: sha512-23I4pFZHmAemUnz8WZXbYRSKYj801VDaNv9ETuMh7IrMc7VuVVSo+Z9iLE3ni30+U48iDWfi30d3twAXBYmnCg==} + + lru-cache@10.4.3: + resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} + + lru-cache@11.0.2: + resolution: {integrity: sha512-123qHRfJBmo2jXDbo/a5YOQrJoHF/GNQTLzQ5+IdK5pWpceK17yRc6ozlWd25FxvGKQbIUs91fDFkXmDHTKcyA==} + engines: {node: 20 || >=22} + + magic-string@0.30.14: + resolution: {integrity: sha512-5c99P1WKTed11ZC0HMJOj6CDIue6F8ySu+bJL+85q1zBEIY8IklrJ1eiKC2NDRh3Ct3FcvmJPyQHb9erXMTJNw==} + + markdown-it-anchor@8.6.7: + resolution: {integrity: sha512-FlCHFwNnutLgVTflOYHPW2pPcl2AACqVzExlkGQNsi4CJgqOHN7YTgDd4LuhgN1BFO3TS0vLAruV1Td6dwWPJA==} + peerDependencies: + '@types/markdown-it': '*' + markdown-it: '*' + + markdown-it@14.1.0: + resolution: {integrity: sha512-a54IwgWPaeBCAAsv13YgmALOF1elABB08FxO9i+r4VFk5Vl4pKokRPeX8u5TCgSsPi6ec1otfLjdOpVcgbpshg==} + hasBin: true + + marked@4.3.0: + resolution: {integrity: sha512-PRsaiG84bK+AMvxziE/lCFss8juXjNaWzVbN5tXAm4XjeaS9NAHhop+PjQxz2A9h8Q4M/xGmzP8vqNwy6JeK0A==} + engines: {node: '>= 12'} + hasBin: true + + mdurl@2.0.0: + resolution: {integrity: sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==} + + merge-stream@2.0.0: + resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==} + + merge2@1.4.1: + resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} + engines: {node: '>= 8'} + + micromatch@4.0.8: + resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} + engines: {node: '>=8.6'} + + mime-db@1.33.0: + resolution: {integrity: sha512-BHJ/EKruNIqJf/QahvxwQZXKygOQ256myeN/Ew+THcAa5q+PjyTTMMeNQC4DZw5AwfvelsUrA6B67NKMqXDbzQ==} + engines: {node: '>= 0.6'} + + mime-db@1.52.0: + resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} + engines: {node: '>= 0.6'} + + mime-db@1.53.0: + resolution: {integrity: sha512-oHlN/w+3MQ3rba9rqFr6V/ypF10LSkdwUysQL7GkXoTgIWeV+tcXGA852TBxH+gsh8UWoyhR1hKcoMJTuWflpg==} + engines: {node: '>= 0.6'} + + mime-types@2.1.18: + resolution: {integrity: sha512-lc/aahn+t4/SWV/qcmumYjymLsWfN3ELhpmVuUFjgsORruuZPVSwAQryq+HHGvO/SI2KVX26bx+En+zhM8g8hQ==} + engines: {node: '>= 0.6'} + + mime-types@2.1.35: + resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} + engines: {node: '>= 0.6'} + + mimic-fn@2.1.0: + resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==} + engines: {node: '>=6'} + + mimic-function@5.0.1: + resolution: {integrity: sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==} + engines: {node: '>=18'} + + minimatch@10.0.1: + resolution: {integrity: sha512-ethXTt3SGGR+95gudmqJ1eNhRO7eGEGIgYA9vnPatK4/etz2MEVDno5GMCibdMTuBMyElzIlgxMna3K94XDIDQ==} + engines: {node: 20 || >=22} + + minimatch@3.1.2: + resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} + + minimatch@5.1.6: + resolution: {integrity: sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==} + engines: {node: '>=10'} + + minimatch@9.0.5: + resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==} + engines: {node: '>=16 || 14 >=14.17'} + + minimist@1.2.8: + resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} + + minipass@7.1.2: + resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==} + engines: {node: '>=16 || 14 >=14.17'} + + mkdirp-classic@0.5.3: + resolution: {integrity: sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==} + + mkdirp@1.0.4: + resolution: {integrity: sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==} + engines: {node: '>=10'} + hasBin: true + + ms@2.0.0: + resolution: {integrity: sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==} + + ms@2.1.3: + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + + nan@2.22.0: + resolution: {integrity: sha512-nbajikzWTMwsW+eSsNm3QwlOs7het9gGJU5dDZzRTQGk03vyBOauxgI4VakDzE0PtsGTmXPsXTbbjVhRwR5mpw==} + + nanoid@3.3.8: + resolution: {integrity: sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true + + natural-compare@1.4.0: + resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} + + negotiator@0.6.3: + resolution: {integrity: sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==} + engines: {node: '>= 0.6'} + + normalize-path@3.0.0: + resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} + engines: {node: '>=0.10.0'} + + npm-run-path@4.0.1: + resolution: {integrity: sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==} + engines: {node: '>=8'} + + on-headers@1.0.2: + resolution: {integrity: sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==} + engines: {node: '>= 0.8'} + + once@1.4.0: + resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} + + onetime@5.1.2: + resolution: {integrity: sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==} + engines: {node: '>=6'} + + onetime@7.0.0: + resolution: {integrity: sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==} + engines: {node: '>=18'} + + optionator@0.9.4: + resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} + engines: {node: '>= 0.8.0'} + + ora@8.1.1: + resolution: {integrity: sha512-YWielGi1XzG1UTvOaCFaNgEnuhZVMSHYkW/FQ7UX8O26PtlpdM84c0f7wLPlkvx2RfiQmnzd61d/MGxmpQeJPw==} + engines: {node: '>=18'} + + p-limit@3.1.0: + resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} + engines: {node: '>=10'} + + p-locate@5.0.0: + resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} + engines: {node: '>=10'} + + package-json-from-dist@1.0.1: + resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==} + + parent-module@1.0.1: + resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} + engines: {node: '>=6'} + + path-exists@4.0.0: + resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} + engines: {node: '>=8'} + + path-is-inside@1.0.2: + resolution: {integrity: sha512-DUWJr3+ULp4zXmol/SZkFf3JGsS9/SIv+Y3Rt93/UjPpDpklB5f1er4O3POIbUuUJ3FXgqte2Q7SrU6zAqwk8w==} + + path-key@3.1.1: + resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} + engines: {node: '>=8'} + + path-parse@1.0.7: + resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} + + path-scurry@1.11.1: + resolution: {integrity: sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==} + engines: {node: '>=16 || 14 >=14.18'} + + path-scurry@2.0.0: + resolution: {integrity: sha512-ypGJsmGtdXUOeM5u93TyeIEfEhM6s+ljAhrk5vAvSx8uyY/02OvrZnA0YNGUrPXfpJMgI1ODd3nwz8Npx4O4cg==} + engines: {node: 20 || >=22} + + path-to-regexp@3.3.0: + resolution: {integrity: sha512-qyCH421YQPS2WFDxDjftfc1ZR5WKQzVzqsp4n9M2kQhVOo/ByahFoUNJfl58kOcEGfQ//7weFTDhm+ss8Ecxgw==} + + pathe@1.1.2: + resolution: {integrity: sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==} + + pathval@2.0.0: + resolution: {integrity: sha512-vE7JKRyES09KiunauX7nd2Q9/L7lhok4smP9RZTDeD4MVs72Dp2qNFVz39Nz5a0FVEW0BJR6C0DYrq6unoziZA==} + engines: {node: '>= 14.16'} + + picocolors@1.1.1: + resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} + + picomatch@2.3.1: + resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} + engines: {node: '>=8.6'} + + picomatch@4.0.2: + resolution: {integrity: sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==} + engines: {node: '>=12'} + + postcss@8.4.49: + resolution: {integrity: sha512-OCVPnIObs4N29kxTjzLfUryOkvZEq+pf8jTF0lg8E7uETuWHA+v7j3c/xJmiqpX450191LlmZfUKkXxkTry7nA==} + engines: {node: ^10 || ^12 || >=14} + + prelude-ls@1.2.1: + resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} + engines: {node: '>= 0.8.0'} + + prettier@3.4.1: + resolution: {integrity: sha512-G+YdqtITVZmOJje6QkXQWzl3fSfMxFwm1tjTyo9exhkmWSqC4Yhd1+lug++IlR2mvRVAxEDDWYkQdeSztajqgg==} + engines: {node: '>=14'} + hasBin: true + + pretty-bytes@5.6.0: + resolution: {integrity: sha512-FFw039TmrBqFK8ma/7OL3sDz/VytdtJr044/QUJtH0wK9lb9jLq9tJyIxUwtQJHwar2BqtiA4iCWSwo9JLkzFg==} + engines: {node: '>=6'} + + process-nextick-args@2.0.1: + resolution: {integrity: sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==} + + process@0.11.10: + resolution: {integrity: sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==} + engines: {node: '>= 0.6.0'} + + proper-lockfile@4.1.2: + resolution: {integrity: sha512-TjNPblN4BwAWMXU8s9AEz4JmQxnD1NNL7bNOY/AKUzyamc379FWASUhc/K1pL2noVb+XmZKLL68cjzLsiOAMaA==} + + properties-reader@2.3.0: + resolution: {integrity: sha512-z597WicA7nDZxK12kZqHr2TcvwNU1GCfA5UwfDY/HDp3hXPoPlb5rlEx9bwGTiJnc0OqbBTkU975jDToth8Gxw==} + engines: {node: '>=14'} + + pump@3.0.2: + resolution: {integrity: sha512-tUPXtzlGM8FE3P0ZL6DVs/3P58k9nk8/jZeQCurTJylQA8qFYzHFfhBJkuqyE0FifOsQ0uKWekiZ5g8wtr28cw==} + + punycode.js@2.3.1: + resolution: {integrity: sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==} + engines: {node: '>=6'} + + punycode@2.3.1: + resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} + engines: {node: '>=6'} + + queue-microtask@1.2.3: + resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} + + queue-tick@1.0.1: + resolution: {integrity: sha512-kJt5qhMxoszgU/62PLP1CJytzd2NKetjSRnyuj31fDd3Rlcz3fzlFdFLD1SItunPwyqEOkca6GbV612BWfaBag==} + + range-parser@1.2.0: + resolution: {integrity: sha512-kA5WQoNVo4t9lNx2kQNFCxKeBl5IbbSNBl1M/tLkw9WCn+hxNBAW5Qh8gdhs63CJnhjJ2zQWFoqPJP2sK1AV5A==} + engines: {node: '>= 0.6'} + + rc@1.2.8: + resolution: {integrity: sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==} + hasBin: true + + readable-stream@2.3.8: + resolution: {integrity: sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==} + + readable-stream@3.6.2: + resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==} + engines: {node: '>= 6'} + + readable-stream@4.5.2: + resolution: {integrity: sha512-yjavECdqeZ3GLXNgRXgeQEdz9fvDDkNKyHnbHRFtOr7/LcfgBcmct7t/ET+HaCTqfh06OzoAxrkN/IfjJBVe+g==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + readdir-glob@1.1.3: + resolution: {integrity: sha512-v05I2k7xN8zXvPD9N+z/uhXPaj0sUFCe2rcWZIpBsqxfP7xXFQ0tipAd/wjj1YxWyWtUS5IDJpOG82JKt2EAVA==} + + registry-auth-token@3.3.2: + resolution: {integrity: sha512-JL39c60XlzCVgNrO+qq68FoNb56w/m7JYvGR2jT5iR1xBrUA3Mfx5Twk5rqTThPmQKMWydGmq8oFtDlxfrmxnQ==} + + registry-url@3.1.0: + resolution: {integrity: sha512-ZbgR5aZEdf4UKZVBPYIgaglBmSF2Hi94s2PcIHhRGFjKYu+chjJdYfHn4rt3hB6eCKLJ8giVIIfgMa1ehDfZKA==} + engines: {node: '>=0.10.0'} + + require-directory@2.1.1: + resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} + engines: {node: '>=0.10.0'} + + require-from-string@2.0.2: + resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} + engines: {node: '>=0.10.0'} + + requizzle@0.2.4: + resolution: {integrity: sha512-JRrFk1D4OQ4SqovXOgdav+K8EAhSB/LJZqCz8tbX0KObcdeM15Ss59ozWMBWmmINMagCwmqn4ZNryUGpBsl6Jw==} + + resolve-from@4.0.0: + resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} + engines: {node: '>=4'} + + resolve-pkg-maps@1.0.0: + resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==} + + resolve@1.22.8: + resolution: {integrity: sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==} + hasBin: true + + restore-cursor@5.1.0: + resolution: {integrity: sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==} + engines: {node: '>=18'} + + retry@0.12.0: + resolution: {integrity: sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==} + engines: {node: '>= 4'} + + reusify@1.0.4: + resolution: {integrity: sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==} + engines: {iojs: '>=1.0.0', node: '>=0.10.0'} + + rollup-plugin-dts@6.1.1: + resolution: {integrity: sha512-aSHRcJ6KG2IHIioYlvAOcEq6U99sVtqDDKVhnwt70rW6tsz3tv5OSjEiWcgzfsHdLyGXZ/3b/7b/+Za3Y6r1XA==} + engines: {node: '>=16'} + peerDependencies: + rollup: ^3.29.4 || ^4 + typescript: ^4.5 || ^5.0 + + rollup-plugin-swc3@0.11.2: + resolution: {integrity: sha512-o1ih9B806fV2wBSNk46T0cYfTF2eiiKmYXRpWw3K4j/Cp3tCAt10UCVsTqvUhGP58pcB3/GZcAVl5e7TCSKN6Q==} + engines: {node: '>=12'} + peerDependencies: + '@swc/core': '>=1.2.165' + rollup: ^2.0.0 || ^3.0.0 || ^4.0.0 + + rollup-preserve-directives@1.1.3: + resolution: {integrity: sha512-oXqxd6ZzkoQej8Qt0k+S/yvO2+S4CEVEVv2g85oL15o0cjAKTKEuo2MzyA8FcsBBXbtytBzBMFAbhvQg4YyPUQ==} + peerDependencies: + rollup: ^2.0.0 || ^3.0.0 || ^4.0.0 + + rollup@4.28.0: + resolution: {integrity: sha512-G9GOrmgWHBma4YfCcX8PjH0qhXSdH8B4HDE2o4/jaxj93S4DPCIDoLcXz99eWMji4hB29UFCEd7B2gwGJDR9cQ==} + engines: {node: '>=18.0.0', npm: '>=8.0.0'} + hasBin: true + + run-parallel@1.2.0: + resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} + + safe-buffer@5.1.2: + resolution: {integrity: sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==} + + safe-buffer@5.2.1: + resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} + + safer-buffer@2.1.2: + resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} + + semver@7.6.3: + resolution: {integrity: sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==} + engines: {node: '>=10'} + hasBin: true + + serve-handler@6.1.6: + resolution: {integrity: sha512-x5RL9Y2p5+Sh3D38Fh9i/iQ5ZK+e4xuXRd/pGbM4D13tgo/MGwbttUk8emytcr1YYzBYs+apnUngBDFYfpjPuQ==} + + serve@14.2.4: + resolution: {integrity: sha512-qy1S34PJ/fcY8gjVGszDB3EXiPSk5FKhUa7tQe0UPRddxRidc2V6cNHPNewbE1D7MAkgLuWEt3Vw56vYy73tzQ==} + engines: {node: '>= 14'} + hasBin: true + + shebang-command@2.0.0: + resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} + engines: {node: '>=8'} + + shebang-regex@3.0.0: + resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} + engines: {node: '>=8'} + + siginfo@2.0.0: + resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} + + signal-exit@3.0.7: + resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==} + + signal-exit@4.1.0: + resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} + engines: {node: '>=14'} + + source-map-js@1.2.1: + resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} + engines: {node: '>=0.10.0'} + + source-map@0.6.1: + resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} + engines: {node: '>=0.10.0'} + + split-ca@1.0.1: + resolution: {integrity: sha512-Q5thBSxp5t8WPTTJQS59LrGqOZqOsrhDGDVm8azCqIBjSBd7nd9o2PM+mDulQQkh8h//4U6hFZnc/mul8t5pWQ==} + + ssh-remote-port-forward@1.0.4: + resolution: {integrity: sha512-x0LV1eVDwjf1gmG7TTnfqIzf+3VPRz7vrNIjX6oYLbeCrf/PeVY6hkT68Mg+q02qXxQhrLjB0jfgvhevoCRmLQ==} + + ssh2@1.16.0: + resolution: {integrity: sha512-r1X4KsBGedJqo7h8F5c4Ybpcr5RjyP+aWIG007uBPRjmdQWfEiVLzSK71Zji1B9sKxwaCvD8y8cwSkYrlLiRRg==} + engines: {node: '>=10.16.0'} + + stackback@0.0.2: + resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} + + std-env@3.8.0: + resolution: {integrity: sha512-Bc3YwwCB+OzldMxOXJIIvC6cPRWr/LxOp48CdQTOkPyk/t4JWWJbrilwBd7RJzKV8QW7tJkcgAmeuLLJugl5/w==} + + stdin-discarder@0.2.2: + resolution: {integrity: sha512-UhDfHmA92YAlNnCfhmq0VeNL5bDbiZGg7sZ2IvPsXubGkiNa9EC+tUTsjBRsYUAz87btI6/1wf4XoVvQ3uRnmQ==} + engines: {node: '>=18'} + + streamx@2.21.0: + resolution: {integrity: sha512-Qz6MsDZXJ6ur9u+b+4xCG18TluU7PGlRfXVAAjNiGsFrBUt/ioyLkxbFaKJygoPs+/kW4VyBj0bSj89Qu0IGyg==} + + string-width@4.2.3: + resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} + engines: {node: '>=8'} + + string-width@5.1.2: + resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==} + engines: {node: '>=12'} + + string-width@7.2.0: + resolution: {integrity: sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==} + engines: {node: '>=18'} + + string_decoder@1.1.1: + resolution: {integrity: sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==} + + string_decoder@1.3.0: + resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==} + + strip-ansi@6.0.1: + resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} + engines: {node: '>=8'} + + strip-ansi@7.1.0: + resolution: {integrity: sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==} + engines: {node: '>=12'} + + strip-final-newline@2.0.0: + resolution: {integrity: sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==} + engines: {node: '>=6'} + + strip-json-comments@2.0.1: + resolution: {integrity: sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==} + engines: {node: '>=0.10.0'} + + strip-json-comments@3.1.1: + resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} + engines: {node: '>=8'} + + supports-color@7.2.0: + resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} + engines: {node: '>=8'} + + supports-preserve-symlinks-flag@1.0.0: + resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} + engines: {node: '>= 0.4'} + + tar-fs@2.0.1: + resolution: {integrity: sha512-6tzWDMeroL87uF/+lin46k+Q+46rAJ0SyPGz7OW7wTgblI273hsBqk2C1j0/xNadNLKDTUL9BukSjB7cwgmlPA==} + + tar-fs@3.0.6: + resolution: {integrity: sha512-iokBDQQkUyeXhgPYaZxmczGPhnhXZ0CmrqI+MOb/WFGS9DW5wnfrLgtjUJBvz50vQ3qfRwJ62QVoCFu8mPVu5w==} + + tar-stream@2.2.0: + resolution: {integrity: sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==} + engines: {node: '>=6'} + + tar-stream@3.1.7: + resolution: {integrity: sha512-qJj60CXt7IU1Ffyc3NJMjh6EkuCFej46zUqJ4J7pqYlThyd9bO0XBTmcOIhSzZJVWfsLks0+nle/j538YAW9RQ==} + + testcontainers@10.15.0: + resolution: {integrity: sha512-fQbWIdXverYhOVS7WJk3egII1b4OtUl3C9mXIJk7Q95o5HeY/PRbAif5Gxi8tzck7Lmer0rMbq2jSbSbMyYm8Q==} + + text-decoder@1.2.1: + resolution: {integrity: sha512-x9v3H/lTKIJKQQe7RPQkLfKAnc9lUTkWDypIQgTzPJAq+5/GCDHonmshfvlsNSj58yyshbIJJDLmU15qNERrXQ==} + + tinybench@2.9.0: + resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==} + + tinyexec@0.3.1: + resolution: {integrity: sha512-WiCJLEECkO18gwqIp6+hJg0//p23HXp4S+gGtAKu3mI2F2/sXC4FvHvXvB0zJVVaTPhx1/tOwdbRsa1sOBIKqQ==} + + tinypool@1.0.2: + resolution: {integrity: sha512-al6n+QEANGFOMf/dmUMsuS5/r9B06uwlyNjZZql/zv8J7ybHCgoihBNORZCY2mzUuAnomQa2JdhyHKzZxPCrFA==} + engines: {node: ^18.0.0 || >=20.0.0} + + tinyrainbow@1.2.0: + resolution: {integrity: sha512-weEDEq7Z5eTHPDh4xjX789+fHfF+P8boiFB+0vbWzpbnbsEr/GRaohi/uMKxg8RZMXnl1ItAi/IUHWMsjDV7kQ==} + engines: {node: '>=14.0.0'} + + tinyspy@3.0.2: + resolution: {integrity: sha512-n1cw8k1k0x4pgA2+9XrOkFydTerNcJ1zWCO5Nn9scWHTD+5tp8dghT2x1uduQePZTZgd3Tupf+x9BxJjeJi77Q==} + engines: {node: '>=14.0.0'} + + tmp@0.2.3: + resolution: {integrity: sha512-nZD7m9iCPC5g0pYmcaxogYKggSfLsdxl8of3Q/oIbqCqLLIO9IAF0GWjX1z9NZRHPiXv8Wex4yDCaZsgEw0Y8w==} + engines: {node: '>=14.14'} + + to-regex-range@5.0.1: + resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} + engines: {node: '>=8.0'} + + ts-api-utils@1.4.3: + resolution: {integrity: sha512-i3eMG77UTMD0hZhgRS562pv83RC6ukSAC2GMNWc+9dieh/+jDM5u5YG+NHX6VNDRHQcHwmsTHctP9LhbC3WxVw==} + engines: {node: '>=16'} + peerDependencies: + typescript: '>=4.2.0' + + tslib@2.8.1: + resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} + + tweetnacl@0.14.5: + resolution: {integrity: sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA==} + + type-check@0.4.0: + resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} + engines: {node: '>= 0.8.0'} + + type-fest@2.19.0: + resolution: {integrity: sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==} + engines: {node: '>=12.20'} + + typescript-eslint@8.17.0: + resolution: {integrity: sha512-409VXvFd/f1br1DCbuKNFqQpXICoTB+V51afcwG1pn1a3Cp92MqAUges3YjwEdQ0cMUoCIodjVDAYzyD8h3SYA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + + typescript@5.7.2: + resolution: {integrity: sha512-i5t66RHxDvVN40HfDd1PsEThGNnlMCMT3jMUuoh9/0TaqWevNontacunWyN02LA9/fIbEWlcHZcgTKb9QoaLfg==} + engines: {node: '>=14.17'} + hasBin: true + + uc.micro@2.1.0: + resolution: {integrity: sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==} + + underscore@1.13.7: + resolution: {integrity: sha512-GMXzWtsc57XAtguZgaQViUOzs0KTkk8ojr3/xAxXLITqf/3EMwxC0inyETfDFjH/Krbhuep0HNbbjI9i/q3F3g==} + + undici-types@5.26.5: + resolution: {integrity: sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==} + + undici-types@6.20.0: + resolution: {integrity: sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==} + + undici@5.28.4: + resolution: {integrity: sha512-72RFADWFqKmUb2hmmvNODKL3p9hcB6Gt2DOQMis1SEBaV6a4MH8soBvzg+95CYhCKPFedut2JY9bMfrDl9D23g==} + engines: {node: '>=14.0'} + + undici@7.1.0: + resolution: {integrity: sha512-3+mdX2R31khuLCm2mKExSlMdJsfol7bJkIMH80tdXA74W34rT1jKemUTlYR7WY3TqsV4wfOgpatWmmB2Jl1+5g==} + engines: {node: '>=20.18.1'} + + update-check@1.5.4: + resolution: {integrity: sha512-5YHsflzHP4t1G+8WGPlvKbJEbAJGCgw+Em+dGR1KmBUbr1J36SJBqlHLjR7oob7sco5hWHGQVcr9B2poIVDDTQ==} + + uri-js@4.4.1: + resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} + + util-deprecate@1.0.2: + resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} + + vary@1.1.2: + resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==} + engines: {node: '>= 0.8'} + + vite-node@2.1.8: + resolution: {integrity: sha512-uPAwSr57kYjAUux+8E2j0q0Fxpn8M9VoyfGiRI8Kfktz9NcYMCenwY5RnZxnF1WTu3TGiYipirIzacLL3VVGFg==} + engines: {node: ^18.0.0 || >=20.0.0} + hasBin: true + + vite@5.4.11: + resolution: {integrity: sha512-c7jFQRklXua0mTzneGW9QVyxFjUgwcihC4bXEtujIo2ouWCe1Ajt/amn2PCxYnhYfd5k09JX3SB7OYWFKYqj8Q==} + 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: '*' + sass-embedded: '*' + stylus: '*' + sugarss: '*' + terser: ^5.4.0 + peerDependenciesMeta: + '@types/node': + optional: true + less: + optional: true + lightningcss: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + + vitest@2.1.8: + resolution: {integrity: sha512-1vBKTZskHw/aosXqQUlVWWlGUxSJR8YtiyZDJAFeW2kPAeX6S3Sool0mjspO+kXLuxVWlEDDowBAeqeAQefqLQ==} + engines: {node: ^18.0.0 || >=20.0.0} + hasBin: true + peerDependencies: + '@edge-runtime/vm': '*' + '@types/node': ^18.0.0 || >=20.0.0 + '@vitest/browser': 2.1.8 + '@vitest/ui': 2.1.8 + 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 + + which@2.0.2: + resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} + engines: {node: '>= 8'} + hasBin: true + + why-is-node-running@2.3.0: + resolution: {integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==} + engines: {node: '>=8'} + hasBin: true + + widest-line@4.0.1: + resolution: {integrity: sha512-o0cyEG0e8GPzT4iGHphIOh0cJOV8fivsXxddQasHPHfoZf1ZexrfeA21w2NaEN1RHE+fXlfISmOE8R9N3u3Qig==} + engines: {node: '>=12'} + + word-wrap@1.2.5: + resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} + engines: {node: '>=0.10.0'} + + wrap-ansi@7.0.0: + resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} + engines: {node: '>=10'} + + wrap-ansi@8.1.0: + resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==} + engines: {node: '>=12'} + + wrappy@1.0.2: + resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} + + xmlcreate@2.0.4: + resolution: {integrity: sha512-nquOebG4sngPmGPICTS5EnxqhKbCmz5Ox5hsszI2T6U5qdrJizBc+0ilYSEjTSzU0yZcmvppztXe/5Al5fUwdg==} + + y18n@5.0.8: + resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} + engines: {node: '>=10'} + + yaml@2.6.1: + resolution: {integrity: sha512-7r0XPzioN/Q9kXBro/XPnA6kznR73DHq+GXh5ON7ZozRO6aMjbmiBuKste2wslTFkC5d1dw0GooOCepZXJ2SAg==} + engines: {node: '>= 14'} + hasBin: true + + yargs-parser@21.1.1: + resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==} + engines: {node: '>=12'} + + yargs@17.7.2: + resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==} + engines: {node: '>=12'} + + yocto-queue@0.1.0: + resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} + engines: {node: '>=10'} + + zip-stream@6.0.1: + resolution: {integrity: sha512-zK7YHHz4ZXpW89AHXUPbQVGKI7uvkd3hzusTdotCg1UxyaVtg0zFJSTfW/Dq5f7OBBVnq6cZIaC8Ti4hb6dtCA==} + engines: {node: '>= 14'} + +snapshots: + + '@babel/code-frame@7.26.2': + dependencies: + '@babel/helper-validator-identifier': 7.25.9 + js-tokens: 4.0.0 + picocolors: 1.1.1 + optional: true + + '@babel/helper-string-parser@7.25.9': {} + + '@babel/helper-validator-identifier@7.25.9': {} + + '@babel/parser@7.26.3': + dependencies: + '@babel/types': 7.26.3 + + '@babel/types@7.26.3': + dependencies: + '@babel/helper-string-parser': 7.25.9 + '@babel/helper-validator-identifier': 7.25.9 + + '@balena/dockerignore@1.0.2': {} + + '@esbuild/aix-ppc64@0.21.5': + optional: true + + '@esbuild/android-arm64@0.21.5': + optional: true + + '@esbuild/android-arm@0.21.5': + optional: true + + '@esbuild/android-x64@0.21.5': + optional: true + + '@esbuild/darwin-arm64@0.21.5': + optional: true + + '@esbuild/darwin-x64@0.21.5': + optional: true + + '@esbuild/freebsd-arm64@0.21.5': + optional: true + + '@esbuild/freebsd-x64@0.21.5': + optional: true + + '@esbuild/linux-arm64@0.21.5': + optional: true + + '@esbuild/linux-arm@0.21.5': + optional: true + + '@esbuild/linux-ia32@0.21.5': + optional: true + + '@esbuild/linux-loong64@0.21.5': + optional: true + + '@esbuild/linux-mips64el@0.21.5': + optional: true + + '@esbuild/linux-ppc64@0.21.5': + optional: true + + '@esbuild/linux-riscv64@0.21.5': + optional: true + + '@esbuild/linux-s390x@0.21.5': + optional: true + + '@esbuild/linux-x64@0.21.5': + optional: true + + '@esbuild/netbsd-x64@0.21.5': + optional: true + + '@esbuild/openbsd-x64@0.21.5': + optional: true + + '@esbuild/sunos-x64@0.21.5': + optional: true + + '@esbuild/win32-arm64@0.21.5': + optional: true + + '@esbuild/win32-ia32@0.21.5': + optional: true + + '@esbuild/win32-x64@0.21.5': + optional: true + + '@eslint-community/eslint-utils@4.4.1(eslint@9.16.0)': + dependencies: + eslint: 9.16.0 + eslint-visitor-keys: 3.4.3 + + '@eslint-community/regexpp@4.12.1': {} + + '@eslint/config-array@0.19.1': + dependencies: + '@eslint/object-schema': 2.1.5 + debug: 4.3.7 + minimatch: 3.1.2 + transitivePeerDependencies: + - supports-color + + '@eslint/core@0.9.1': + dependencies: + '@types/json-schema': 7.0.15 + + '@eslint/eslintrc@3.2.0': + dependencies: + ajv: 6.12.6 + debug: 4.3.7 + espree: 10.3.0 + globals: 14.0.0 + ignore: 5.3.2 + import-fresh: 3.3.0 + js-yaml: 4.1.0 + minimatch: 3.1.2 + strip-json-comments: 3.1.1 + transitivePeerDependencies: + - supports-color + + '@eslint/js@9.16.0': {} + + '@eslint/object-schema@2.1.5': {} + + '@eslint/plugin-kit@0.2.4': + dependencies: + levn: 0.4.1 + + '@fastify/busboy@2.1.1': {} + + '@fastify/deepmerge@1.3.0': {} + + '@humanfs/core@0.19.1': {} + + '@humanfs/node@0.16.6': + dependencies: + '@humanfs/core': 0.19.1 + '@humanwhocodes/retry': 0.3.1 + + '@humanwhocodes/module-importer@1.0.1': {} + + '@humanwhocodes/retry@0.3.1': {} + + '@humanwhocodes/retry@0.4.1': {} + + '@isaacs/cliui@8.0.2': + dependencies: + string-width: 5.1.2 + string-width-cjs: string-width@4.2.3 + strip-ansi: 7.1.0 + strip-ansi-cjs: strip-ansi@6.0.1 + wrap-ansi: 8.1.0 + wrap-ansi-cjs: wrap-ansi@7.0.0 + + '@jridgewell/sourcemap-codec@1.5.0': {} + + '@jsdoc/salty@0.2.8': + dependencies: + lodash: 4.17.21 + + '@nodelib/fs.scandir@2.1.5': + dependencies: + '@nodelib/fs.stat': 2.0.5 + run-parallel: 1.2.0 + + '@nodelib/fs.stat@2.0.5': {} + + '@nodelib/fs.walk@1.2.8': + dependencies: + '@nodelib/fs.scandir': 2.1.5 + fastq: 1.17.1 + + '@pkgjs/parseargs@0.11.0': + optional: true + + '@rollup/plugin-commonjs@28.0.1(rollup@4.28.0)': + dependencies: + '@rollup/pluginutils': 5.1.3(rollup@4.28.0) + commondir: 1.0.1 + estree-walker: 2.0.2 + fdir: 6.4.2(picomatch@4.0.2) + is-reference: 1.2.1 + magic-string: 0.30.14 + picomatch: 4.0.2 + optionalDependencies: + rollup: 4.28.0 + + '@rollup/plugin-json@6.1.0(rollup@4.28.0)': + dependencies: + '@rollup/pluginutils': 5.1.3(rollup@4.28.0) + optionalDependencies: + rollup: 4.28.0 + + '@rollup/plugin-node-resolve@15.3.0(rollup@4.28.0)': + dependencies: + '@rollup/pluginutils': 5.1.3(rollup@4.28.0) + '@types/resolve': 1.20.2 + deepmerge: 4.3.1 + is-module: 1.0.0 + resolve: 1.22.8 + optionalDependencies: + rollup: 4.28.0 + + '@rollup/plugin-replace@6.0.1(rollup@4.28.0)': + dependencies: + '@rollup/pluginutils': 5.1.3(rollup@4.28.0) + magic-string: 0.30.14 + optionalDependencies: + rollup: 4.28.0 + + '@rollup/plugin-wasm@6.2.2(rollup@4.28.0)': + dependencies: + '@rollup/pluginutils': 5.1.3(rollup@4.28.0) + optionalDependencies: + rollup: 4.28.0 + + '@rollup/pluginutils@5.1.3(rollup@4.28.0)': + dependencies: + '@types/estree': 1.0.6 + estree-walker: 2.0.2 + picomatch: 4.0.2 + optionalDependencies: + rollup: 4.28.0 + + '@rollup/rollup-android-arm-eabi@4.28.0': + optional: true + + '@rollup/rollup-android-arm64@4.28.0': + optional: true + + '@rollup/rollup-darwin-arm64@4.28.0': + optional: true + + '@rollup/rollup-darwin-x64@4.28.0': + optional: true + + '@rollup/rollup-freebsd-arm64@4.28.0': + optional: true + + '@rollup/rollup-freebsd-x64@4.28.0': + optional: true + + '@rollup/rollup-linux-arm-gnueabihf@4.28.0': + optional: true + + '@rollup/rollup-linux-arm-musleabihf@4.28.0': + optional: true + + '@rollup/rollup-linux-arm64-gnu@4.28.0': + optional: true + + '@rollup/rollup-linux-arm64-musl@4.28.0': + optional: true + + '@rollup/rollup-linux-powerpc64le-gnu@4.28.0': + optional: true + + '@rollup/rollup-linux-riscv64-gnu@4.28.0': + optional: true + + '@rollup/rollup-linux-s390x-gnu@4.28.0': + optional: true + + '@rollup/rollup-linux-x64-gnu@4.28.0': + optional: true + + '@rollup/rollup-linux-x64-musl@4.28.0': + optional: true + + '@rollup/rollup-win32-arm64-msvc@4.28.0': + optional: true + + '@rollup/rollup-win32-ia32-msvc@4.28.0': + optional: true + + '@rollup/rollup-win32-x64-msvc@4.28.0': + optional: true + + '@swc/core-darwin-arm64@1.9.3': + optional: true + + '@swc/core-darwin-x64@1.9.3': + optional: true + + '@swc/core-linux-arm-gnueabihf@1.9.3': + optional: true + + '@swc/core-linux-arm64-gnu@1.9.3': + optional: true + + '@swc/core-linux-arm64-musl@1.9.3': + optional: true + + '@swc/core-linux-x64-gnu@1.9.3': + optional: true + + '@swc/core-linux-x64-musl@1.9.3': + optional: true + + '@swc/core-win32-arm64-msvc@1.9.3': + optional: true + + '@swc/core-win32-ia32-msvc@1.9.3': + optional: true + + '@swc/core-win32-x64-msvc@1.9.3': + optional: true + + '@swc/core@1.9.3(@swc/helpers@0.5.15)': + dependencies: + '@swc/counter': 0.1.3 + '@swc/types': 0.1.17 + optionalDependencies: + '@swc/core-darwin-arm64': 1.9.3 + '@swc/core-darwin-x64': 1.9.3 + '@swc/core-linux-arm-gnueabihf': 1.9.3 + '@swc/core-linux-arm64-gnu': 1.9.3 + '@swc/core-linux-arm64-musl': 1.9.3 + '@swc/core-linux-x64-gnu': 1.9.3 + '@swc/core-linux-x64-musl': 1.9.3 + '@swc/core-win32-arm64-msvc': 1.9.3 + '@swc/core-win32-ia32-msvc': 1.9.3 + '@swc/core-win32-x64-msvc': 1.9.3 + '@swc/helpers': 0.5.15 + + '@swc/counter@0.1.3': {} + + '@swc/helpers@0.5.15': + dependencies: + tslib: 2.8.1 + + '@swc/types@0.1.17': + dependencies: + '@swc/counter': 0.1.3 + + '@types/docker-modem@3.0.6': + dependencies: + '@types/node': 22.10.1 + '@types/ssh2': 1.15.1 + + '@types/dockerode@3.3.32': + dependencies: + '@types/docker-modem': 3.0.6 + '@types/node': 22.10.1 + '@types/ssh2': 1.15.1 + + '@types/estree@1.0.6': {} + + '@types/json-schema@7.0.15': {} + + '@types/linkify-it@5.0.0': {} + + '@types/markdown-it@14.1.2': + dependencies: + '@types/linkify-it': 5.0.0 + '@types/mdurl': 2.0.0 + + '@types/mdurl@2.0.0': {} + + '@types/node@18.19.67': + dependencies: + undici-types: 5.26.5 + + '@types/node@22.10.1': + dependencies: + undici-types: 6.20.0 + + '@types/resolve@1.20.2': {} + + '@types/ssh2-streams@0.1.12': + dependencies: + '@types/node': 22.10.1 + + '@types/ssh2@0.5.52': + dependencies: + '@types/node': 22.10.1 + '@types/ssh2-streams': 0.1.12 + + '@types/ssh2@1.15.1': + dependencies: + '@types/node': 18.19.67 + + '@typescript-eslint/eslint-plugin@8.17.0(@typescript-eslint/parser@8.17.0(eslint@9.16.0)(typescript@5.7.2))(eslint@9.16.0)(typescript@5.7.2)': + dependencies: + '@eslint-community/regexpp': 4.12.1 + '@typescript-eslint/parser': 8.17.0(eslint@9.16.0)(typescript@5.7.2) + '@typescript-eslint/scope-manager': 8.17.0 + '@typescript-eslint/type-utils': 8.17.0(eslint@9.16.0)(typescript@5.7.2) + '@typescript-eslint/utils': 8.17.0(eslint@9.16.0)(typescript@5.7.2) + '@typescript-eslint/visitor-keys': 8.17.0 + eslint: 9.16.0 + graphemer: 1.4.0 + ignore: 5.3.2 + natural-compare: 1.4.0 + ts-api-utils: 1.4.3(typescript@5.7.2) + optionalDependencies: + typescript: 5.7.2 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/parser@8.17.0(eslint@9.16.0)(typescript@5.7.2)': + dependencies: + '@typescript-eslint/scope-manager': 8.17.0 + '@typescript-eslint/types': 8.17.0 + '@typescript-eslint/typescript-estree': 8.17.0(typescript@5.7.2) + '@typescript-eslint/visitor-keys': 8.17.0 + debug: 4.3.7 + eslint: 9.16.0 + optionalDependencies: + typescript: 5.7.2 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/scope-manager@8.17.0': + dependencies: + '@typescript-eslint/types': 8.17.0 + '@typescript-eslint/visitor-keys': 8.17.0 + + '@typescript-eslint/type-utils@8.17.0(eslint@9.16.0)(typescript@5.7.2)': + dependencies: + '@typescript-eslint/typescript-estree': 8.17.0(typescript@5.7.2) + '@typescript-eslint/utils': 8.17.0(eslint@9.16.0)(typescript@5.7.2) + debug: 4.3.7 + eslint: 9.16.0 + ts-api-utils: 1.4.3(typescript@5.7.2) + optionalDependencies: + typescript: 5.7.2 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/types@8.17.0': {} + + '@typescript-eslint/typescript-estree@8.17.0(typescript@5.7.2)': + dependencies: + '@typescript-eslint/types': 8.17.0 + '@typescript-eslint/visitor-keys': 8.17.0 + debug: 4.3.7 + fast-glob: 3.3.2 + is-glob: 4.0.3 + minimatch: 9.0.5 + semver: 7.6.3 + ts-api-utils: 1.4.3(typescript@5.7.2) + optionalDependencies: + typescript: 5.7.2 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/utils@8.17.0(eslint@9.16.0)(typescript@5.7.2)': + dependencies: + '@eslint-community/eslint-utils': 4.4.1(eslint@9.16.0) + '@typescript-eslint/scope-manager': 8.17.0 + '@typescript-eslint/types': 8.17.0 + '@typescript-eslint/typescript-estree': 8.17.0(typescript@5.7.2) + eslint: 9.16.0 + optionalDependencies: + typescript: 5.7.2 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/visitor-keys@8.17.0': + dependencies: + '@typescript-eslint/types': 8.17.0 + eslint-visitor-keys: 4.2.0 + + '@vitest/expect@2.1.8': + dependencies: + '@vitest/spy': 2.1.8 + '@vitest/utils': 2.1.8 + chai: 5.1.2 + tinyrainbow: 1.2.0 + + '@vitest/mocker@2.1.8(vite@5.4.11(@types/node@22.10.1))': + dependencies: + '@vitest/spy': 2.1.8 + estree-walker: 3.0.3 + magic-string: 0.30.14 + optionalDependencies: + vite: 5.4.11(@types/node@22.10.1) + + '@vitest/pretty-format@2.1.8': + dependencies: + tinyrainbow: 1.2.0 + + '@vitest/runner@2.1.8': + dependencies: + '@vitest/utils': 2.1.8 + pathe: 1.1.2 + + '@vitest/snapshot@2.1.8': + dependencies: + '@vitest/pretty-format': 2.1.8 + magic-string: 0.30.14 + pathe: 1.1.2 + + '@vitest/spy@2.1.8': + dependencies: + tinyspy: 3.0.2 + + '@vitest/utils@2.1.8': + dependencies: + '@vitest/pretty-format': 2.1.8 + loupe: 3.1.2 + tinyrainbow: 1.2.0 + + '@zeit/schemas@2.36.0': {} + + abort-controller@3.0.0: + dependencies: + event-target-shim: 5.0.1 + + accepts@1.3.8: + dependencies: + mime-types: 2.1.35 + negotiator: 0.6.3 + + acorn-jsx@5.3.2(acorn@8.14.0): + dependencies: + acorn: 8.14.0 + + acorn@8.14.0: {} + + ajv@6.12.6: + 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 + + ajv@8.12.0: + dependencies: + fast-deep-equal: 3.1.3 + json-schema-traverse: 1.0.0 + require-from-string: 2.0.2 + uri-js: 4.4.1 + + ansi-align@3.0.1: + dependencies: + string-width: 4.2.3 + + ansi-regex@5.0.1: {} + + ansi-regex@6.1.0: {} + + ansi-styles@4.3.0: + dependencies: + color-convert: 2.0.1 + + ansi-styles@6.2.1: {} + + arch@2.2.0: {} + + archiver-utils@5.0.2: + dependencies: + glob: 10.4.5 + graceful-fs: 4.2.11 + is-stream: 2.0.1 + lazystream: 1.0.1 + lodash: 4.17.21 + normalize-path: 3.0.0 + readable-stream: 4.5.2 + + archiver@7.0.1: + dependencies: + archiver-utils: 5.0.2 + async: 3.2.6 + buffer-crc32: 1.0.0 + readable-stream: 4.5.2 + readdir-glob: 1.1.3 + tar-stream: 3.1.7 + zip-stream: 6.0.1 + + arg@5.0.2: {} + + argparse@2.0.1: {} + + asn1@0.2.6: + dependencies: + safer-buffer: 2.1.2 + + assertion-error@2.0.1: {} + + async-lock@1.4.1: {} + + async@3.2.6: {} + + b4a@1.6.7: {} + + balanced-match@1.0.2: {} + + bare-events@2.5.0: + optional: true + + bare-fs@2.3.5: + dependencies: + bare-events: 2.5.0 + bare-path: 2.1.3 + bare-stream: 2.4.2 + optional: true + + bare-os@2.4.4: + optional: true + + bare-path@2.1.3: + dependencies: + bare-os: 2.4.4 + optional: true + + bare-stream@2.4.2: + dependencies: + streamx: 2.21.0 + optional: true + + base64-js@1.5.1: {} + + bcrypt-pbkdf@1.0.2: + dependencies: + tweetnacl: 0.14.5 + + bl@4.1.0: + dependencies: + buffer: 5.7.1 + inherits: 2.0.4 + readable-stream: 3.6.2 + + bluebird@3.7.2: {} + + boxen@7.0.0: + dependencies: + ansi-align: 3.0.1 + camelcase: 7.0.1 + chalk: 5.3.0 + cli-boxes: 3.0.0 + string-width: 5.1.2 + type-fest: 2.19.0 + widest-line: 4.0.1 + wrap-ansi: 8.1.0 + + brace-expansion@1.1.11: + dependencies: + balanced-match: 1.0.2 + concat-map: 0.0.1 + + brace-expansion@2.0.1: + dependencies: + balanced-match: 1.0.2 + + braces@3.0.3: + dependencies: + fill-range: 7.1.1 + + buffer-crc32@1.0.0: {} + + buffer@5.7.1: + dependencies: + base64-js: 1.5.1 + ieee754: 1.2.1 + + buffer@6.0.3: + dependencies: + base64-js: 1.5.1 + ieee754: 1.2.1 + + buildcheck@0.0.6: + optional: true + + bunchee@6.0.3(typescript@5.7.2): + dependencies: + '@rollup/plugin-commonjs': 28.0.1(rollup@4.28.0) + '@rollup/plugin-json': 6.1.0(rollup@4.28.0) + '@rollup/plugin-node-resolve': 15.3.0(rollup@4.28.0) + '@rollup/plugin-replace': 6.0.1(rollup@4.28.0) + '@rollup/plugin-wasm': 6.2.2(rollup@4.28.0) + '@rollup/pluginutils': 5.1.3(rollup@4.28.0) + '@swc/core': 1.9.3(@swc/helpers@0.5.15) + '@swc/helpers': 0.5.15 + clean-css: 5.3.3 + glob: 11.0.0 + magic-string: 0.30.14 + ora: 8.1.1 + picomatch: 4.0.2 + pretty-bytes: 5.6.0 + rollup: 4.28.0 + rollup-plugin-dts: 6.1.1(rollup@4.28.0)(typescript@5.7.2) + rollup-plugin-swc3: 0.11.2(@swc/core@1.9.3(@swc/helpers@0.5.15))(rollup@4.28.0) + rollup-preserve-directives: 1.1.3(rollup@4.28.0) + tslib: 2.8.1 + yargs: 17.7.2 + optionalDependencies: + typescript: 5.7.2 + + byline@5.0.0: {} + + bytes@3.0.0: {} + + cac@6.7.14: {} + + callsites@3.1.0: {} + + camelcase@7.0.1: {} + + catharsis@0.9.0: + dependencies: + lodash: 4.17.21 + + chai@5.1.2: + dependencies: + assertion-error: 2.0.1 + check-error: 2.1.1 + deep-eql: 5.0.2 + loupe: 3.1.2 + pathval: 2.0.0 + + chalk-template@0.4.0: + dependencies: + chalk: 4.1.2 + + chalk@4.1.2: + dependencies: + ansi-styles: 4.3.0 + supports-color: 7.2.0 + + chalk@5.0.1: {} + + chalk@5.3.0: {} + + check-error@2.1.1: {} + + chownr@1.1.4: {} + + clean-css@5.3.3: + dependencies: + source-map: 0.6.1 + + cli-boxes@3.0.0: {} + + cli-cursor@5.0.0: + dependencies: + restore-cursor: 5.1.0 + + cli-spinners@2.9.2: {} + + clipboardy@3.0.0: + dependencies: + arch: 2.2.0 + execa: 5.1.1 + is-wsl: 2.2.0 + + cliui@8.0.1: + dependencies: + string-width: 4.2.3 + strip-ansi: 6.0.1 + wrap-ansi: 7.0.0 + + color-convert@2.0.1: + dependencies: + color-name: 1.1.4 + + color-name@1.1.4: {} + + commondir@1.0.1: {} + + compress-commons@6.0.2: + dependencies: + crc-32: 1.2.2 + crc32-stream: 6.0.0 + is-stream: 2.0.1 + normalize-path: 3.0.0 + readable-stream: 4.5.2 + + compressible@2.0.18: + dependencies: + mime-db: 1.53.0 + + compression@1.7.4: + dependencies: + accepts: 1.3.8 + bytes: 3.0.0 + compressible: 2.0.18 + debug: 2.6.9 + on-headers: 1.0.2 + safe-buffer: 5.1.2 + vary: 1.1.2 + transitivePeerDependencies: + - supports-color + + concat-map@0.0.1: {} + + content-disposition@0.5.2: {} + + core-util-is@1.0.3: {} + + cpu-features@0.0.10: + dependencies: + buildcheck: 0.0.6 + nan: 2.22.0 + optional: true + + crc-32@1.2.2: {} + + crc32-stream@6.0.0: + dependencies: + crc-32: 1.2.2 + readable-stream: 4.5.2 + + cross-spawn@7.0.6: + dependencies: + path-key: 3.1.1 + shebang-command: 2.0.0 + which: 2.0.2 + + debug@2.6.9: + dependencies: + ms: 2.0.0 + + debug@4.3.7: + dependencies: + ms: 2.1.3 + + deep-eql@5.0.2: {} + + deep-extend@0.6.0: {} + + deep-is@0.1.4: {} + + deepmerge@4.3.1: {} + + docker-compose@0.24.8: + dependencies: + yaml: 2.6.1 + + docker-modem@3.0.8: + dependencies: + debug: 4.3.7 + readable-stream: 3.6.2 + split-ca: 1.0.1 + ssh2: 1.16.0 + transitivePeerDependencies: + - supports-color + + dockerode@3.3.5: + dependencies: + '@balena/dockerignore': 1.0.2 + docker-modem: 3.0.8 + tar-fs: 2.0.1 + transitivePeerDependencies: + - supports-color + + eastasianwidth@0.2.0: {} + + emoji-regex@10.4.0: {} + + emoji-regex@8.0.0: {} + + emoji-regex@9.2.2: {} + + end-of-stream@1.4.4: + dependencies: + once: 1.4.0 + + entities@4.5.0: {} + + es-module-lexer@1.5.4: {} + + esbuild@0.21.5: + optionalDependencies: + '@esbuild/aix-ppc64': 0.21.5 + '@esbuild/android-arm': 0.21.5 + '@esbuild/android-arm64': 0.21.5 + '@esbuild/android-x64': 0.21.5 + '@esbuild/darwin-arm64': 0.21.5 + '@esbuild/darwin-x64': 0.21.5 + '@esbuild/freebsd-arm64': 0.21.5 + '@esbuild/freebsd-x64': 0.21.5 + '@esbuild/linux-arm': 0.21.5 + '@esbuild/linux-arm64': 0.21.5 + '@esbuild/linux-ia32': 0.21.5 + '@esbuild/linux-loong64': 0.21.5 + '@esbuild/linux-mips64el': 0.21.5 + '@esbuild/linux-ppc64': 0.21.5 + '@esbuild/linux-riscv64': 0.21.5 + '@esbuild/linux-s390x': 0.21.5 + '@esbuild/linux-x64': 0.21.5 + '@esbuild/netbsd-x64': 0.21.5 + '@esbuild/openbsd-x64': 0.21.5 + '@esbuild/sunos-x64': 0.21.5 + '@esbuild/win32-arm64': 0.21.5 + '@esbuild/win32-ia32': 0.21.5 + '@esbuild/win32-x64': 0.21.5 + + escalade@3.2.0: {} + + escape-string-regexp@2.0.0: {} + + escape-string-regexp@4.0.0: {} + + eslint-scope@8.2.0: + dependencies: + esrecurse: 4.3.0 + estraverse: 5.3.0 + + eslint-visitor-keys@3.4.3: {} + + eslint-visitor-keys@4.2.0: {} + + eslint@9.16.0: + dependencies: + '@eslint-community/eslint-utils': 4.4.1(eslint@9.16.0) + '@eslint-community/regexpp': 4.12.1 + '@eslint/config-array': 0.19.1 + '@eslint/core': 0.9.1 + '@eslint/eslintrc': 3.2.0 + '@eslint/js': 9.16.0 + '@eslint/plugin-kit': 0.2.4 + '@humanfs/node': 0.16.6 + '@humanwhocodes/module-importer': 1.0.1 + '@humanwhocodes/retry': 0.4.1 + '@types/estree': 1.0.6 + '@types/json-schema': 7.0.15 + ajv: 6.12.6 + chalk: 4.1.2 + cross-spawn: 7.0.6 + debug: 4.3.7 + escape-string-regexp: 4.0.0 + eslint-scope: 8.2.0 + eslint-visitor-keys: 4.2.0 + espree: 10.3.0 + esquery: 1.6.0 + esutils: 2.0.3 + fast-deep-equal: 3.1.3 + file-entry-cache: 8.0.0 + find-up: 5.0.0 + glob-parent: 6.0.2 + ignore: 5.3.2 + imurmurhash: 0.1.4 + is-glob: 4.0.3 + json-stable-stringify-without-jsonify: 1.0.1 + lodash.merge: 4.6.2 + minimatch: 3.1.2 + natural-compare: 1.4.0 + optionator: 0.9.4 + transitivePeerDependencies: + - supports-color + + espree@10.3.0: + dependencies: + acorn: 8.14.0 + acorn-jsx: 5.3.2(acorn@8.14.0) + eslint-visitor-keys: 4.2.0 + + esquery@1.6.0: + dependencies: + estraverse: 5.3.0 + + esrecurse@4.3.0: + dependencies: + estraverse: 5.3.0 + + estraverse@5.3.0: {} + + estree-walker@2.0.2: {} + + estree-walker@3.0.3: + dependencies: + '@types/estree': 1.0.6 + + esutils@2.0.3: {} + + event-target-shim@5.0.1: {} + + events@3.3.0: {} + + execa@5.1.1: + dependencies: + cross-spawn: 7.0.6 + get-stream: 6.0.1 + human-signals: 2.1.0 + is-stream: 2.0.1 + merge-stream: 2.0.0 + npm-run-path: 4.0.1 + onetime: 5.1.2 + signal-exit: 3.0.7 + strip-final-newline: 2.0.0 + + expect-type@1.1.0: {} + + fast-deep-equal@3.1.3: {} + + fast-fifo@1.3.2: {} + + fast-glob@3.3.2: + 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.8 + + fast-json-stable-stringify@2.1.0: {} + + fast-levenshtein@2.0.6: {} + + fastq@1.17.1: + dependencies: + reusify: 1.0.4 + + fdir@6.4.2(picomatch@4.0.2): + optionalDependencies: + picomatch: 4.0.2 + + file-entry-cache@8.0.0: + dependencies: + flat-cache: 4.0.1 + + fill-range@7.1.1: + dependencies: + to-regex-range: 5.0.1 + + find-up@5.0.0: + dependencies: + locate-path: 6.0.0 + path-exists: 4.0.0 + + flat-cache@4.0.1: + dependencies: + flatted: 3.3.2 + keyv: 4.5.4 + + flatted@3.3.2: {} + + foreground-child@3.3.0: + dependencies: + cross-spawn: 7.0.6 + signal-exit: 4.1.0 + + fs-constants@1.0.0: {} + + fsevents@2.3.3: + optional: true + + function-bind@1.1.2: {} + + get-caller-file@2.0.5: {} + + get-east-asian-width@1.3.0: {} + + get-port@5.1.1: {} + + get-stream@6.0.1: {} + + get-tsconfig@4.8.1: + dependencies: + resolve-pkg-maps: 1.0.0 + + glob-parent@5.1.2: + dependencies: + is-glob: 4.0.3 + + glob-parent@6.0.2: + dependencies: + is-glob: 4.0.3 + + glob@10.4.5: + dependencies: + foreground-child: 3.3.0 + jackspeak: 3.4.3 + minimatch: 9.0.5 + minipass: 7.1.2 + package-json-from-dist: 1.0.1 + path-scurry: 1.11.1 + + glob@11.0.0: + dependencies: + foreground-child: 3.3.0 + jackspeak: 4.0.2 + minimatch: 10.0.1 + minipass: 7.1.2 + package-json-from-dist: 1.0.1 + path-scurry: 2.0.0 + + globals@14.0.0: {} + + graceful-fs@4.2.11: {} + + graphemer@1.4.0: {} + + has-flag@4.0.0: {} + + hasown@2.0.2: + dependencies: + function-bind: 1.1.2 + + human-signals@2.1.0: {} + + ieee754@1.2.1: {} + + ignore@5.3.2: {} + + import-fresh@3.3.0: + dependencies: + parent-module: 1.0.1 + resolve-from: 4.0.0 + + imurmurhash@0.1.4: {} + + inherits@2.0.4: {} + + ini@1.3.8: {} + + is-core-module@2.15.1: + dependencies: + hasown: 2.0.2 + + is-docker@2.2.1: {} + + is-extglob@2.1.1: {} + + is-fullwidth-code-point@3.0.0: {} + + is-glob@4.0.3: + dependencies: + is-extglob: 2.1.1 + + is-interactive@2.0.0: {} + + is-module@1.0.0: {} + + is-number@7.0.0: {} + + is-port-reachable@4.0.0: {} + + is-reference@1.2.1: + dependencies: + '@types/estree': 1.0.6 + + is-stream@2.0.1: {} + + is-unicode-supported@1.3.0: {} + + is-unicode-supported@2.1.0: {} + + is-wsl@2.2.0: + dependencies: + is-docker: 2.2.1 + + isarray@1.0.0: {} + + isexe@2.0.0: {} + + jackspeak@3.4.3: + dependencies: + '@isaacs/cliui': 8.0.2 + optionalDependencies: + '@pkgjs/parseargs': 0.11.0 + + jackspeak@4.0.2: + dependencies: + '@isaacs/cliui': 8.0.2 + + js-tokens@4.0.0: + optional: true + + js-yaml@4.1.0: + dependencies: + argparse: 2.0.1 + + js2xmlparser@4.0.2: + dependencies: + xmlcreate: 2.0.4 + + jsdoc@4.0.4: + dependencies: + '@babel/parser': 7.26.3 + '@jsdoc/salty': 0.2.8 + '@types/markdown-it': 14.1.2 + bluebird: 3.7.2 + catharsis: 0.9.0 + escape-string-regexp: 2.0.0 + js2xmlparser: 4.0.2 + klaw: 3.0.0 + markdown-it: 14.1.0 + markdown-it-anchor: 8.6.7(@types/markdown-it@14.1.2)(markdown-it@14.1.0) + marked: 4.3.0 + mkdirp: 1.0.4 + requizzle: 0.2.4 + strip-json-comments: 3.1.1 + underscore: 1.13.7 + + json-buffer@3.0.1: {} + + json-schema-traverse@0.4.1: {} + + json-schema-traverse@1.0.0: {} + + json-stable-stringify-without-jsonify@1.0.1: {} + + keyv@4.5.4: + dependencies: + json-buffer: 3.0.1 + + klaw@3.0.0: + dependencies: + graceful-fs: 4.2.11 + + lazystream@1.0.1: + dependencies: + readable-stream: 2.3.8 + + levn@0.4.1: + dependencies: + prelude-ls: 1.2.1 + type-check: 0.4.0 + + linkify-it@5.0.0: + dependencies: + uc.micro: 2.1.0 + + locate-path@6.0.0: + dependencies: + p-locate: 5.0.0 + + lodash.merge@4.6.2: {} + + lodash@4.17.21: {} + + log-symbols@6.0.0: + dependencies: + chalk: 5.3.0 + is-unicode-supported: 1.3.0 + + loupe@3.1.2: {} + + lru-cache@10.4.3: {} + + lru-cache@11.0.2: {} + + magic-string@0.30.14: + dependencies: + '@jridgewell/sourcemap-codec': 1.5.0 + + markdown-it-anchor@8.6.7(@types/markdown-it@14.1.2)(markdown-it@14.1.0): + dependencies: + '@types/markdown-it': 14.1.2 + markdown-it: 14.1.0 + + markdown-it@14.1.0: + dependencies: + argparse: 2.0.1 + entities: 4.5.0 + linkify-it: 5.0.0 + mdurl: 2.0.0 + punycode.js: 2.3.1 + uc.micro: 2.1.0 + + marked@4.3.0: {} + + mdurl@2.0.0: {} + + merge-stream@2.0.0: {} + + merge2@1.4.1: {} + + micromatch@4.0.8: + dependencies: + braces: 3.0.3 + picomatch: 2.3.1 + + mime-db@1.33.0: {} + + mime-db@1.52.0: {} + + mime-db@1.53.0: {} + + mime-types@2.1.18: + dependencies: + mime-db: 1.33.0 + + mime-types@2.1.35: + dependencies: + mime-db: 1.52.0 + + mimic-fn@2.1.0: {} + + mimic-function@5.0.1: {} + + minimatch@10.0.1: + dependencies: + brace-expansion: 2.0.1 + + minimatch@3.1.2: + dependencies: + brace-expansion: 1.1.11 + + minimatch@5.1.6: + dependencies: + brace-expansion: 2.0.1 + + minimatch@9.0.5: + dependencies: + brace-expansion: 2.0.1 + + minimist@1.2.8: {} + + minipass@7.1.2: {} + + mkdirp-classic@0.5.3: {} + + mkdirp@1.0.4: {} + + ms@2.0.0: {} + + ms@2.1.3: {} + + nan@2.22.0: + optional: true + + nanoid@3.3.8: {} + + natural-compare@1.4.0: {} + + negotiator@0.6.3: {} + + normalize-path@3.0.0: {} + + npm-run-path@4.0.1: + dependencies: + path-key: 3.1.1 + + on-headers@1.0.2: {} + + once@1.4.0: + dependencies: + wrappy: 1.0.2 + + onetime@5.1.2: + dependencies: + mimic-fn: 2.1.0 + + onetime@7.0.0: + dependencies: + mimic-function: 5.0.1 + + optionator@0.9.4: + dependencies: + deep-is: 0.1.4 + fast-levenshtein: 2.0.6 + levn: 0.4.1 + prelude-ls: 1.2.1 + type-check: 0.4.0 + word-wrap: 1.2.5 + + ora@8.1.1: + dependencies: + chalk: 5.3.0 + cli-cursor: 5.0.0 + cli-spinners: 2.9.2 + is-interactive: 2.0.0 + is-unicode-supported: 2.1.0 + log-symbols: 6.0.0 + stdin-discarder: 0.2.2 + string-width: 7.2.0 + strip-ansi: 7.1.0 + + p-limit@3.1.0: + dependencies: + yocto-queue: 0.1.0 + + p-locate@5.0.0: + dependencies: + p-limit: 3.1.0 + + package-json-from-dist@1.0.1: {} + + parent-module@1.0.1: + dependencies: + callsites: 3.1.0 + + path-exists@4.0.0: {} + + path-is-inside@1.0.2: {} + + path-key@3.1.1: {} + + path-parse@1.0.7: {} + + path-scurry@1.11.1: + dependencies: + lru-cache: 10.4.3 + minipass: 7.1.2 + + path-scurry@2.0.0: + dependencies: + lru-cache: 11.0.2 + minipass: 7.1.2 + + path-to-regexp@3.3.0: {} + + pathe@1.1.2: {} + + pathval@2.0.0: {} + + picocolors@1.1.1: {} + + picomatch@2.3.1: {} + + picomatch@4.0.2: {} + + postcss@8.4.49: + dependencies: + nanoid: 3.3.8 + picocolors: 1.1.1 + source-map-js: 1.2.1 + + prelude-ls@1.2.1: {} + + prettier@3.4.1: {} + + pretty-bytes@5.6.0: {} + + process-nextick-args@2.0.1: {} + + process@0.11.10: {} + + proper-lockfile@4.1.2: + dependencies: + graceful-fs: 4.2.11 + retry: 0.12.0 + signal-exit: 3.0.7 + + properties-reader@2.3.0: + dependencies: + mkdirp: 1.0.4 + + pump@3.0.2: + dependencies: + end-of-stream: 1.4.4 + once: 1.4.0 + + punycode.js@2.3.1: {} + + punycode@2.3.1: {} + + queue-microtask@1.2.3: {} + + queue-tick@1.0.1: {} + + range-parser@1.2.0: {} + + rc@1.2.8: + dependencies: + deep-extend: 0.6.0 + ini: 1.3.8 + minimist: 1.2.8 + strip-json-comments: 2.0.1 + + readable-stream@2.3.8: + dependencies: + core-util-is: 1.0.3 + inherits: 2.0.4 + isarray: 1.0.0 + process-nextick-args: 2.0.1 + safe-buffer: 5.1.2 + string_decoder: 1.1.1 + util-deprecate: 1.0.2 + + readable-stream@3.6.2: + dependencies: + inherits: 2.0.4 + string_decoder: 1.3.0 + util-deprecate: 1.0.2 + + readable-stream@4.5.2: + dependencies: + abort-controller: 3.0.0 + buffer: 6.0.3 + events: 3.3.0 + process: 0.11.10 + string_decoder: 1.3.0 + + readdir-glob@1.1.3: + dependencies: + minimatch: 5.1.6 + + registry-auth-token@3.3.2: + dependencies: + rc: 1.2.8 + safe-buffer: 5.2.1 + + registry-url@3.1.0: + dependencies: + rc: 1.2.8 + + require-directory@2.1.1: {} + + require-from-string@2.0.2: {} + + requizzle@0.2.4: + dependencies: + lodash: 4.17.21 + + resolve-from@4.0.0: {} + + resolve-pkg-maps@1.0.0: {} + + resolve@1.22.8: + dependencies: + is-core-module: 2.15.1 + path-parse: 1.0.7 + supports-preserve-symlinks-flag: 1.0.0 + + restore-cursor@5.1.0: + dependencies: + onetime: 7.0.0 + signal-exit: 4.1.0 + + retry@0.12.0: {} + + reusify@1.0.4: {} + + rollup-plugin-dts@6.1.1(rollup@4.28.0)(typescript@5.7.2): + dependencies: + magic-string: 0.30.14 + rollup: 4.28.0 + typescript: 5.7.2 + optionalDependencies: + '@babel/code-frame': 7.26.2 + + rollup-plugin-swc3@0.11.2(@swc/core@1.9.3(@swc/helpers@0.5.15))(rollup@4.28.0): + dependencies: + '@fastify/deepmerge': 1.3.0 + '@rollup/pluginutils': 5.1.3(rollup@4.28.0) + '@swc/core': 1.9.3(@swc/helpers@0.5.15) + get-tsconfig: 4.8.1 + rollup: 4.28.0 + rollup-preserve-directives: 1.1.3(rollup@4.28.0) + + rollup-preserve-directives@1.1.3(rollup@4.28.0): + dependencies: + magic-string: 0.30.14 + rollup: 4.28.0 + + rollup@4.28.0: + dependencies: + '@types/estree': 1.0.6 + optionalDependencies: + '@rollup/rollup-android-arm-eabi': 4.28.0 + '@rollup/rollup-android-arm64': 4.28.0 + '@rollup/rollup-darwin-arm64': 4.28.0 + '@rollup/rollup-darwin-x64': 4.28.0 + '@rollup/rollup-freebsd-arm64': 4.28.0 + '@rollup/rollup-freebsd-x64': 4.28.0 + '@rollup/rollup-linux-arm-gnueabihf': 4.28.0 + '@rollup/rollup-linux-arm-musleabihf': 4.28.0 + '@rollup/rollup-linux-arm64-gnu': 4.28.0 + '@rollup/rollup-linux-arm64-musl': 4.28.0 + '@rollup/rollup-linux-powerpc64le-gnu': 4.28.0 + '@rollup/rollup-linux-riscv64-gnu': 4.28.0 + '@rollup/rollup-linux-s390x-gnu': 4.28.0 + '@rollup/rollup-linux-x64-gnu': 4.28.0 + '@rollup/rollup-linux-x64-musl': 4.28.0 + '@rollup/rollup-win32-arm64-msvc': 4.28.0 + '@rollup/rollup-win32-ia32-msvc': 4.28.0 + '@rollup/rollup-win32-x64-msvc': 4.28.0 + fsevents: 2.3.3 + + run-parallel@1.2.0: + dependencies: + queue-microtask: 1.2.3 + + safe-buffer@5.1.2: {} + + safe-buffer@5.2.1: {} + + safer-buffer@2.1.2: {} + + semver@7.6.3: {} + + serve-handler@6.1.6: + dependencies: + bytes: 3.0.0 + content-disposition: 0.5.2 + mime-types: 2.1.18 + minimatch: 3.1.2 + path-is-inside: 1.0.2 + path-to-regexp: 3.3.0 + range-parser: 1.2.0 + + serve@14.2.4: + dependencies: + '@zeit/schemas': 2.36.0 + ajv: 8.12.0 + arg: 5.0.2 + boxen: 7.0.0 + chalk: 5.0.1 + chalk-template: 0.4.0 + clipboardy: 3.0.0 + compression: 1.7.4 + is-port-reachable: 4.0.0 + serve-handler: 6.1.6 + update-check: 1.5.4 + transitivePeerDependencies: + - supports-color + + shebang-command@2.0.0: + dependencies: + shebang-regex: 3.0.0 + + shebang-regex@3.0.0: {} + + siginfo@2.0.0: {} + + signal-exit@3.0.7: {} + + signal-exit@4.1.0: {} + + source-map-js@1.2.1: {} + + source-map@0.6.1: {} + + split-ca@1.0.1: {} + + ssh-remote-port-forward@1.0.4: + dependencies: + '@types/ssh2': 0.5.52 + ssh2: 1.16.0 + + ssh2@1.16.0: + dependencies: + asn1: 0.2.6 + bcrypt-pbkdf: 1.0.2 + optionalDependencies: + cpu-features: 0.0.10 + nan: 2.22.0 + + stackback@0.0.2: {} + + std-env@3.8.0: {} + + stdin-discarder@0.2.2: {} + + streamx@2.21.0: + dependencies: + fast-fifo: 1.3.2 + queue-tick: 1.0.1 + text-decoder: 1.2.1 + optionalDependencies: + bare-events: 2.5.0 + + string-width@4.2.3: + dependencies: + emoji-regex: 8.0.0 + is-fullwidth-code-point: 3.0.0 + strip-ansi: 6.0.1 + + string-width@5.1.2: + dependencies: + eastasianwidth: 0.2.0 + emoji-regex: 9.2.2 + strip-ansi: 7.1.0 + + string-width@7.2.0: + dependencies: + emoji-regex: 10.4.0 + get-east-asian-width: 1.3.0 + strip-ansi: 7.1.0 + + string_decoder@1.1.1: + dependencies: + safe-buffer: 5.1.2 + + string_decoder@1.3.0: + dependencies: + safe-buffer: 5.2.1 + + strip-ansi@6.0.1: + dependencies: + ansi-regex: 5.0.1 + + strip-ansi@7.1.0: + dependencies: + ansi-regex: 6.1.0 + + strip-final-newline@2.0.0: {} + + strip-json-comments@2.0.1: {} + + strip-json-comments@3.1.1: {} + + supports-color@7.2.0: + dependencies: + has-flag: 4.0.0 + + supports-preserve-symlinks-flag@1.0.0: {} + + tar-fs@2.0.1: + dependencies: + chownr: 1.1.4 + mkdirp-classic: 0.5.3 + pump: 3.0.2 + tar-stream: 2.2.0 + + tar-fs@3.0.6: + dependencies: + pump: 3.0.2 + tar-stream: 3.1.7 + optionalDependencies: + bare-fs: 2.3.5 + bare-path: 2.1.3 + + tar-stream@2.2.0: + dependencies: + bl: 4.1.0 + end-of-stream: 1.4.4 + fs-constants: 1.0.0 + inherits: 2.0.4 + readable-stream: 3.6.2 + + tar-stream@3.1.7: + dependencies: + b4a: 1.6.7 + fast-fifo: 1.3.2 + streamx: 2.21.0 + + testcontainers@10.15.0: + dependencies: + '@balena/dockerignore': 1.0.2 + '@types/dockerode': 3.3.32 + archiver: 7.0.1 + async-lock: 1.4.1 + byline: 5.0.0 + debug: 4.3.7 + docker-compose: 0.24.8 + dockerode: 3.3.5 + get-port: 5.1.1 + proper-lockfile: 4.1.2 + properties-reader: 2.3.0 + ssh-remote-port-forward: 1.0.4 + tar-fs: 3.0.6 + tmp: 0.2.3 + undici: 5.28.4 + transitivePeerDependencies: + - supports-color + + text-decoder@1.2.1: {} + + tinybench@2.9.0: {} + + tinyexec@0.3.1: {} + + tinypool@1.0.2: {} + + tinyrainbow@1.2.0: {} + + tinyspy@3.0.2: {} + + tmp@0.2.3: {} + + to-regex-range@5.0.1: + dependencies: + is-number: 7.0.0 + + ts-api-utils@1.4.3(typescript@5.7.2): + dependencies: + typescript: 5.7.2 + + tslib@2.8.1: {} + + tweetnacl@0.14.5: {} + + type-check@0.4.0: + dependencies: + prelude-ls: 1.2.1 + + type-fest@2.19.0: {} + + typescript-eslint@8.17.0(eslint@9.16.0)(typescript@5.7.2): + dependencies: + '@typescript-eslint/eslint-plugin': 8.17.0(@typescript-eslint/parser@8.17.0(eslint@9.16.0)(typescript@5.7.2))(eslint@9.16.0)(typescript@5.7.2) + '@typescript-eslint/parser': 8.17.0(eslint@9.16.0)(typescript@5.7.2) + '@typescript-eslint/utils': 8.17.0(eslint@9.16.0)(typescript@5.7.2) + eslint: 9.16.0 + optionalDependencies: + typescript: 5.7.2 + transitivePeerDependencies: + - supports-color + + typescript@5.7.2: {} + + uc.micro@2.1.0: {} + + underscore@1.13.7: {} + + undici-types@5.26.5: {} + + undici-types@6.20.0: {} + + undici@5.28.4: + dependencies: + '@fastify/busboy': 2.1.1 + + undici@7.1.0: {} + + update-check@1.5.4: + dependencies: + registry-auth-token: 3.3.2 + registry-url: 3.1.0 + + uri-js@4.4.1: + dependencies: + punycode: 2.3.1 + + util-deprecate@1.0.2: {} + + vary@1.1.2: {} + + vite-node@2.1.8(@types/node@22.10.1): + dependencies: + cac: 6.7.14 + debug: 4.3.7 + es-module-lexer: 1.5.4 + pathe: 1.1.2 + vite: 5.4.11(@types/node@22.10.1) + transitivePeerDependencies: + - '@types/node' + - less + - lightningcss + - sass + - sass-embedded + - stylus + - sugarss + - supports-color + - terser + + vite@5.4.11(@types/node@22.10.1): + dependencies: + esbuild: 0.21.5 + postcss: 8.4.49 + rollup: 4.28.0 + optionalDependencies: + '@types/node': 22.10.1 + fsevents: 2.3.3 + + vitest@2.1.8(@types/node@22.10.1): + dependencies: + '@vitest/expect': 2.1.8 + '@vitest/mocker': 2.1.8(vite@5.4.11(@types/node@22.10.1)) + '@vitest/pretty-format': 2.1.8 + '@vitest/runner': 2.1.8 + '@vitest/snapshot': 2.1.8 + '@vitest/spy': 2.1.8 + '@vitest/utils': 2.1.8 + chai: 5.1.2 + debug: 4.3.7 + expect-type: 1.1.0 + magic-string: 0.30.14 + pathe: 1.1.2 + std-env: 3.8.0 + tinybench: 2.9.0 + tinyexec: 0.3.1 + tinypool: 1.0.2 + tinyrainbow: 1.2.0 + vite: 5.4.11(@types/node@22.10.1) + vite-node: 2.1.8(@types/node@22.10.1) + why-is-node-running: 2.3.0 + optionalDependencies: + '@types/node': 22.10.1 + transitivePeerDependencies: + - less + - lightningcss + - msw + - sass + - sass-embedded + - stylus + - sugarss + - supports-color + - terser + + which@2.0.2: + dependencies: + isexe: 2.0.0 + + why-is-node-running@2.3.0: + dependencies: + siginfo: 2.0.0 + stackback: 0.0.2 + + widest-line@4.0.1: + dependencies: + string-width: 5.1.2 + + word-wrap@1.2.5: {} + + wrap-ansi@7.0.0: + dependencies: + ansi-styles: 4.3.0 + string-width: 4.2.3 + strip-ansi: 6.0.1 + + wrap-ansi@8.1.0: + dependencies: + ansi-styles: 6.2.1 + string-width: 5.1.2 + strip-ansi: 7.1.0 + + wrappy@1.0.2: {} + + xmlcreate@2.0.4: {} + + y18n@5.0.8: {} + + yaml@2.6.1: {} + + yargs-parser@21.1.1: {} + + yargs@17.7.2: + dependencies: + cliui: 8.0.1 + escalade: 3.2.0 + get-caller-file: 2.0.5 + require-directory: 2.1.1 + string-width: 4.2.3 + y18n: 5.0.8 + yargs-parser: 21.1.1 + + yocto-queue@0.1.0: {} + + zip-stream@6.0.1: + dependencies: + archiver-utils: 5.0.2 + compress-commons: 6.0.2 + readable-stream: 4.5.2 diff --git a/src/index.ts b/src/index.ts new file mode 100644 index 0000000..3f335c4 --- /dev/null +++ b/src/index.ts @@ -0,0 +1,8 @@ +/** + * A Node.js client for QuestDB. + * + * @module @questdb/nodejs-client + */ + +import { Sender } from "./sender"; +export { Sender }; diff --git a/src/logging.js b/src/logging.js deleted file mode 100644 index bf1553f..0000000 --- a/src/logging.js +++ /dev/null @@ -1,30 +0,0 @@ -'use strict'; - -const LOG_LEVELS = { - error: { log: console.error, criticality: 3 }, - warn: { log: console.warn, criticality: 2 }, - info: { log: console.info, criticality: 1 }, - debug: { log: console.debug, criticality: 0 } -}; - -const DEFAULT_CRITICALITY = LOG_LEVELS.info.criticality; - -/** - * Simple logger to write log messages to the console.
- * Supported logging levels are `error`, `warn`, `info` and `debug`.
- * Throws an error if logging level is invalid. - * - * @param {'error'|'warn'|'info'|'debug'} level - The log level of the message. - * @param {string} message - The log message. - */ -function log(level, message) { - const logLevel = LOG_LEVELS[level]; - if (!logLevel) { - throw new Error(`Invalid log level: '${level}'`); - } - if (logLevel.criticality >= DEFAULT_CRITICALITY) { - logLevel.log(message); - } -} - -exports.log = log; diff --git a/src/logging.ts b/src/logging.ts new file mode 100644 index 0000000..58b4ddf --- /dev/null +++ b/src/logging.ts @@ -0,0 +1,28 @@ +const LOG_LEVELS = { + error: { log: console.error, criticality: 3 }, + warn: { log: console.warn, criticality: 2 }, + info: { log: console.info, criticality: 1 }, + debug: { log: console.debug, criticality: 0 }, +}; + +const DEFAULT_CRITICALITY = LOG_LEVELS.info.criticality; + +/** + * Simple logger to write log messages to the console.
+ * Supported logging levels are `error`, `warn`, `info` and `debug`.
+ * Throws an error if logging level is invalid. + * + * @param {'error'|'warn'|'info'|'debug'} level - The log level of the message. + * @param {string} message - The log message. + */ +function log(level: "error" | "warn" | "info" | "debug", message: string) { + const logLevel = LOG_LEVELS[level]; + if (!logLevel) { + throw new Error(`Invalid log level: '${level}'`); + } + if (logLevel.criticality >= DEFAULT_CRITICALITY) { + logLevel.log(message); + } +} + +export { log }; diff --git a/src/options.js b/src/options.js deleted file mode 100644 index bb076cd..0000000 --- a/src/options.js +++ /dev/null @@ -1,416 +0,0 @@ -'use strict'; - -const http = require('http'); -const https = require('https'); - -const HTTP_PORT = 9000; -const TCP_PORT = 9009; - -const HTTP = 'http'; -const HTTPS = 'https'; -const TCP = 'tcp'; -const TCPS = 'tcps'; - -const ON = 'on'; -const OFF = 'off'; -const UNSAFE_OFF = 'unsafe_off'; - -/** @classdesc - * Sender configuration options.
- *
- * Properties of the object are initialized through a configuration string.
- * The configuration string has the following format: <protocol>::<key>=<value><key>=<value>...;
- * The keys are case-sensitive, the trailing semicolon is optional.
- * The values are validated, and an error is thrown if the format is invalid.
- *
- * Connection and protocol options - *
    - *
  • protocol: enum, accepted values: http, https, tcp, tcps - The protocol used to communicate with the server.
    - * When https or tcps used, the connection is secured with TLS encryption. - *
  • - *
  • addr: string - Hostname and port, separated by colon. This key is mandatory, but the port part is optional.
    - * If no port is specified, a default will be used.
    - * When the protocol is HTTP/HTTPS, the port defaults to 9000. When the protocol is TCP/TCPS, the port defaults to 9009.
    - *
    - * Examples: http::addr=localhost:9000, https::addr=localhost:9000, http::addr=localhost, tcp::addr=localhost:9009 - *
  • - *
- *
- * Authentication options - *
    - *
  • username: string - Used for authentication.
    - * For HTTP, Basic Authentication requires the password option.
    - * For TCP with JWK token authentication, token option is required. - *
  • - *
  • password: string - Password for HTTP Basic authentication, should be accompanied by the username option. - *
  • - *
  • token: string - For HTTP with Bearer authentication, this is the bearer token.
    - * For TCP with JWK token authentication, this is the private key part of the JWK token, - * and must be accompanied by the username option. - *
  • - *
- *
- * TLS options - *
    - *
  • tls_verify: enum, accepted values: on, unsafe_off - When the HTTPS or TCPS protocols are selected, TLS encryption is used.
    - * By default, the Sender will verify the server's certificate, but this check can be disabled by setting this option to off. This is useful - * non-production environments where self-signed certificates might be used, but should be avoided in production if possible. - *
  • - *
  • tls_ca: string - Path to a file containing the root CA's certificate in PEM format.
    - * Can be useful when self-signed certificates are used, otherwise should not be set. - *
  • - *
- *
- * Auto flush options - *
    - *
  • auto_flush: enum, accepted values: on, off - The Sender automatically flushes the buffer by default. This can be switched off - * by setting this option to off.
    - * When disabled, the flush() method of the Sender has to be called explicitly to make sure data is sent to the server.
    - * Manual buffer flushing can be useful, especially when we want to use transactions. When the HTTP protocol is used, each flush results in a single HTTP - * request, which becomes a single transaction on the server side. The transaction either succeeds, and all rows sent in the request are - * inserted; or it fails, and none of the rows make it into the database. - *
  • - *
  • auto_flush_rows: integer - The number of rows that will trigger a flush. When set to 0, row-based flushing is disabled.
    - * The Sender will default this parameter to 75000 rows when HTTP protocol is used, and to 600 in case of TCP protocol. - *
  • - *
  • auto_flush_interval: integer - The number of milliseconds that will trigger a flush, default value is 1000. - * When set to 0, interval-based flushing is disabled.
    - * Note that the setting is checked only when a new row is added to the buffer. There is no timer registered to flush the buffer automatically. - *
  • - *
- *
- * Buffer sizing options - *
    - *
  • init_buf_size: integer - Initial buffer size, defaults to 64 KiB in the Sender. - *
  • - *
  • max_buf_size: integer - Maximum buffer size, defaults to 100 MiB in the Sender.
    - * If the buffer would need to be extended beyond the maximum size, an error is thrown. - *
  • - *
- *
- * HTTP request specific options - *
    - *
  • request_timeout: integer - The time in milliseconds to wait for a response from the server, set to 10 seconds by default.
    - * This is in addition to the calculation derived from the request_min_throughput parameter. - *
  • - *
  • request_min_throughput: integer - Minimum expected throughput in bytes per second for HTTP requests, set to 100 KiB/s seconds by default.
    - * If the throughput is lower than this value, the connection will time out. This is used to calculate an additional - * timeout on top of request_timeout. This is useful for large requests. You can set this value to 0 to disable this logic. - *
  • - *
  • retry_timeout: integer - The time in milliseconds to continue retrying after a failed HTTP request, set to 10 seconds by default.
    - * The interval between retries is an exponential backoff starting at 10ms and doubling after each failed attempt up to a maximum of 1 second. - *
  • - *
- *
- * Other options - *
    - *
  • max_name_len: integer - The maximum length of a table or column name, the Sender defaults this parameter to 127.
    - * Recommended to use the same setting as the server, which also uses 127 by default. - *
  • - *
  • copy_buffer: enum, accepted values: on, off - By default, the Sender creates a new buffer for every flush() call, - * and the data to be sent to the server is copied into this new buffer. - * Setting the flag to off results in reusing the same buffer instance for each flush() call.
    - * Use this flag only if calls to the client are serialised. - *
  • - *
- */ -class SenderOptions { - - protocol; - addr; - host; // derived from addr - port; // derived from addr - - // replaces `auth` and `jwk` options - username; - password; - token; - token_x; // allowed, but ignored - token_y; // allowed, but ignored - - auto_flush; - auto_flush_rows; - auto_flush_interval; - - // replaces `copyBuffer` option - copy_buffer; - - request_min_throughput; - request_timeout; - retry_timeout; - - // replaces `bufferSize` option - init_buf_size; - max_buf_size; - - tls_verify; - tls_ca; - tls_roots; // not supported - tls_roots_password; // not supported - - max_name_len; - - log; - agent; - - /** - * Creates a Sender options object by parsing the provided configuration string. - * - * @param {string} configurationString - Configuration string.
- * @param {object} extraOptions - Optional extra configuration.
- * - 'log' is a logging function used by the Sender.
- * Prototype: (level: 'error'|'warn'|'info'|'debug', message: string) => void.
- * - 'agent' is a custom http/https agent used by the Sender when http/https transport is used.
- * A http.Agent or https.Agent object is expected. - */ - constructor(configurationString, extraOptions = undefined) { - parseConfigurationString(this, configurationString); - - if (extraOptions) { - if (extraOptions.log && typeof extraOptions.log !== 'function') { - throw new Error('Invalid logging function'); - } - this.log = extraOptions.log; - - if (extraOptions.agent && !(extraOptions.agent instanceof http.Agent) && !(extraOptions.agent instanceof https.Agent)) { - throw new Error('Invalid http/https agent'); - } - this.agent = extraOptions.agent; - } - } - - /** - * Creates a Sender options object by parsing the provided configuration string. - * - * @param {string} configurationString - Configuration string.
- * @param {object} extraOptions - Optional extra configuration.
- * - 'log' is a logging function used by the Sender.
- * Prototype: (level: 'error'|'warn'|'info'|'debug', message: string) => void.
- * - 'agent' is a custom http/https agent used by the Sender when http/https transport is used.
- * A http.Agent or https.Agent object is expected. - * - * @return {SenderOptions} A Sender configuration object initialized from the provided configuration string. - */ - static fromConfig(configurationString, extraOptions = undefined) { - return new SenderOptions(configurationString, extraOptions); - } - - /** - * Creates a Sender options object by parsing the configuration string set in the QDB_CLIENT_CONF environment variable. - * - * @param {object} extraOptions - Optional extra configuration.
- * - 'log' is a logging function used by the Sender.
- * Prototype: (level: 'error'|'warn'|'info'|'debug', message: string) => void.
- * - 'agent' is a custom http/https agent used by the Sender when http/https transport is used.
- * A http.Agent or https.Agent object is expected. - * - * @return {SenderOptions} A Sender configuration object initialized from the QDB_CLIENT_CONF environment variable. - */ - static fromEnv(extraOptions = undefined) { - return SenderOptions.fromConfig(process.env.QDB_CLIENT_CONF, extraOptions); - } -} - -function parseConfigurationString(options, configString) { - if (!configString) { - throw new Error('Configuration string is missing or empty'); - } - - const position = parseProtocol(options, configString); - parseSettings(options, configString, position); - parseAddress(options); - parseBufferSizes(options); - parseAutoFlushOptions(options); - parseTlsOptions(options); - parseRequestTimeoutOptions(options); - parseMaxNameLength(options); - parseCopyBuffer(options); -} - -function parseSettings(options, configString, position) { - let index = configString.indexOf(';', position); - while (index > -1) { - if (index + 1 < configString.length && configString.charAt(index + 1) === ';') { - index = configString.indexOf(';', index + 2); - continue; - } - - parseSetting(options, configString, position, index); - - position = index + 1; - index = configString.indexOf(';', position); - } - if (position < configString.length) { - parseSetting(options, configString, position, configString.length); - } -} - -function parseSetting(options, configString, position, index) { - const setting = configString.slice(position, index).replaceAll(';;', ';'); - const equalsIndex = setting.indexOf('='); - if (equalsIndex < 0) { - throw new Error(`Missing '=' sign in '${setting}'`); - } - const key = setting.slice(0, equalsIndex); - const value = setting.slice(equalsIndex + 1); - validateConfigKey(key); - validateConfigValue(key, value); - options[key] = value; -} - -const ValidConfigKeys = [ - 'addr', - 'username', 'password', 'token', 'token_x', 'token_y', - 'auto_flush', 'auto_flush_rows', 'auto_flush_interval', - 'copy_buffer', - 'request_min_throughput', 'request_timeout', 'retry_timeout', - 'init_buf_size', 'max_buf_size', - 'max_name_len', - 'tls_verify', 'tls_ca', 'tls_roots', 'tls_roots_password' -]; - -function validateConfigKey(key) { - if (!ValidConfigKeys.includes(key)) { - throw new Error(`Unknown configuration key: '${key}'`); - } -} - -function validateConfigValue(key, value) { - if (!value) { - throw new Error(`Invalid configuration, value is not set for '${key}'`); - } - for (let i = 0; i < value.length; i++) { - const unicode = value.codePointAt(i); - if (unicode < 0x20 || (unicode > 0x7E && unicode < 0xA0)) { - throw new Error(`Invalid configuration, control characters are not allowed: '${value}'`); - } - } -} - -function parseProtocol(options, configString) { - let index = configString.indexOf('::'); - if (index < 0) { - throw new Error('Missing protocol, configuration string format: \'protocol::key1=value1;key2=value2;key3=value3;\''); - } - - options.protocol = configString.slice(0, index); - switch (options.protocol) { - case HTTP: - case HTTPS: - case TCP: - case TCPS: - break; - default: - throw new Error(`Invalid protocol: '${options.protocol}', accepted protocols: 'http', 'https', 'tcp', 'tcps'`); - } - return index + 2; -} - -function parseAddress(options) { - if (!options.addr) { - throw new Error('Invalid configuration, \'addr\' is required'); - } - - const index = options.addr.indexOf(':'); - if (index < 0) { - options.host = options.addr; - switch (options.protocol) { - case HTTP: - case HTTPS: - options.port = HTTP_PORT; - return; - case TCP: - case TCPS: - options.port = TCP_PORT; - return; - default: - throw new Error(`Invalid protocol: '${options.protocol}', accepted protocols: 'http', 'https', 'tcp', 'tcps'`); - } - } - - options.host = options.addr.slice(0, index); - if (!options.host) { - throw new Error(`Host name is required`); - } - - const portStr = options.addr.slice(index + 1); - if (!portStr) { - throw new Error(`Port is required`); - } - options.port = Number(portStr); - if (isNaN(options.port)) { - throw new Error(`Invalid port: '${portStr}'`); - } - if (!Number.isInteger(options.port) || options.port < 1) { - throw new Error(`Invalid port: ${options.port}`); - } -} - -function parseBufferSizes(options) { - parseInteger(options, 'init_buf_size', 'initial buffer size', 1); - parseInteger(options, 'max_buf_size', 'max buffer size', 1); -} - -function parseAutoFlushOptions(options) { - parseBoolean(options, 'auto_flush', 'auto flush'); - parseInteger(options, 'auto_flush_rows', 'auto flush rows', 0); - parseInteger(options, 'auto_flush_interval', 'auto flush interval', 0); -} - -function parseTlsOptions(options) { - parseBoolean(options, 'tls_verify', 'TLS verify', UNSAFE_OFF); - - if (options.tls_roots || options.tls_roots_password) { - throw new Error('\'tls_roots\' and \'tls_roots_password\' options are not supported, please, ' + - 'use the \'tls_ca\' option or the NODE_EXTRA_CA_CERTS environment variable instead'); - } -} - -function parseRequestTimeoutOptions(options) { - parseInteger(options, 'request_min_throughput', 'request min throughput', 1); - parseInteger(options, 'request_timeout', 'request timeout', 1); - parseInteger(options, 'retry_timeout', 'retry timeout', 0); -} - -function parseMaxNameLength(options) { - parseInteger(options, 'max_name_len', 'max name length', 1); -} - -function parseCopyBuffer(options) { - parseBoolean(options, 'copy_buffer', 'copy buffer'); -} - -function parseBoolean(options, property, description, offValue = OFF) { - if (options[property]) { - const property_str = options[property]; - switch (property_str) { - case ON: - options[property] = true; - break; - case offValue: - options[property] = false; - break; - default: - throw new Error(`Invalid ${description} option: '${property_str}'`); - } - } -} - -function parseInteger(options, property, description, lowerBound) { - if (options[property]) { - const property_str = options[property]; - options[property] = Number(property_str); - if (isNaN(options[property])) { - throw new Error(`Invalid ${description} option, not a number: '${property_str}'`); - } - if (!Number.isInteger(options[property]) || options[property] < lowerBound) { - throw new Error(`Invalid ${description} option: ${options[property]}`); - } - } -} - -exports.SenderOptions = SenderOptions; -exports.HTTP = HTTP; -exports.HTTPS = HTTPS; -exports.TCP = TCP; -exports.TCPS = TCPS; diff --git a/src/options.ts b/src/options.ts new file mode 100644 index 0000000..b0d6d5d --- /dev/null +++ b/src/options.ts @@ -0,0 +1,483 @@ +import { PathOrFileDescriptor } from "fs"; +import { Agent } from "undici"; + +const HTTP_PORT = 9000; +const TCP_PORT = 9009; + +const HTTP = "http"; +const HTTPS = "https"; +const TCP = "tcp"; +const TCPS = "tcps"; + +const ON = "on"; +const OFF = "off"; +const UNSAFE_OFF = "unsafe_off"; + +/** @classdesc + * Sender configuration options.
+ *
+ * Properties of the object are initialized through a configuration string.
+ * The configuration string has the following format: <protocol>::<key>=<value><key>=<value>...;
+ * The keys are case-sensitive, the trailing semicolon is optional.
+ * The values are validated, and an error is thrown if the format is invalid.
+ *
+ * Connection and protocol options + *
    + *
  • protocol: enum, accepted values: http, https, tcp, tcps - The protocol used to communicate with the server.
    + * When https or tcps used, the connection is secured with TLS encryption. + *
  • + *
  • addr: string - Hostname and port, separated by colon. This key is mandatory, but the port part is optional.
    + * If no port is specified, a default will be used.
    + * When the protocol is HTTP/HTTPS, the port defaults to 9000. When the protocol is TCP/TCPS, the port defaults to 9009.
    + *
    + * Examples: http::addr=localhost:9000, https::addr=localhost:9000, http::addr=localhost, tcp::addr=localhost:9009 + *
  • + *
+ *
+ * Authentication options + *
    + *
  • username: string - Used for authentication.
    + * For HTTP, Basic Authentication requires the password option.
    + * For TCP with JWK token authentication, token option is required. + *
  • + *
  • password: string - Password for HTTP Basic authentication, should be accompanied by the username option. + *
  • + *
  • token: string - For HTTP with Bearer authentication, this is the bearer token.
    + * For TCP with JWK token authentication, this is the private key part of the JWK token, + * and must be accompanied by the username option. + *
  • + *
+ *
+ * TLS options + *
    + *
  • tls_verify: enum, accepted values: on, unsafe_off - When the HTTPS or TCPS protocols are selected, TLS encryption is used.
    + * By default, the Sender will verify the server's certificate, but this check can be disabled by setting this option to off. This is useful + * non-production environments where self-signed certificates might be used, but should be avoided in production if possible. + *
  • + *
  • tls_ca: string - Path to a file containing the root CA's certificate in PEM format.
    + * Can be useful when self-signed certificates are used, otherwise should not be set. + *
  • + *
+ *
+ * Auto flush options + *
    + *
  • auto_flush: enum, accepted values: on, off - The Sender automatically flushes the buffer by default. This can be switched off + * by setting this option to off.
    + * When disabled, the flush() method of the Sender has to be called explicitly to make sure data is sent to the server.
    + * Manual buffer flushing can be useful, especially when we want to use transactions. When the HTTP protocol is used, each flush results in a single HTTP + * request, which becomes a single transaction on the server side. The transaction either succeeds, and all rows sent in the request are + * inserted; or it fails, and none of the rows make it into the database. + *
  • + *
  • auto_flush_rows: integer - The number of rows that will trigger a flush. When set to 0, row-based flushing is disabled.
    + * The Sender will default this parameter to 75000 rows when HTTP protocol is used, and to 600 in case of TCP protocol. + *
  • + *
  • auto_flush_interval: integer - The number of milliseconds that will trigger a flush, default value is 1000. + * When set to 0, interval-based flushing is disabled.
    + * Note that the setting is checked only when a new row is added to the buffer. There is no timer registered to flush the buffer automatically. + *
  • + *
+ *
+ * Buffer sizing options + *
    + *
  • init_buf_size: integer - Initial buffer size, defaults to 64 KiB in the Sender. + *
  • + *
  • max_buf_size: integer - Maximum buffer size, defaults to 100 MiB in the Sender.
    + * If the buffer would need to be extended beyond the maximum size, an error is thrown. + *
  • + *
+ *
+ * HTTP request specific options + *
    + *
  • request_timeout: integer - The time in milliseconds to wait for a response from the server, set to 10 seconds by default.
    + * This is in addition to the calculation derived from the request_min_throughput parameter. + *
  • + *
  • request_min_throughput: integer - Minimum expected throughput in bytes per second for HTTP requests, set to 100 KiB/s seconds by default.
    + * If the throughput is lower than this value, the connection will time out. This is used to calculate an additional + * timeout on top of request_timeout. This is useful for large requests. You can set this value to 0 to disable this logic. + *
  • + *
  • retry_timeout: integer - The time in milliseconds to continue retrying after a failed HTTP request, set to 10 seconds by default.
    + * The interval between retries is an exponential backoff starting at 10ms and doubling after each failed attempt up to a maximum of 1 second. + *
  • + *
+ *
+ * Other options + *
    + *
  • max_name_len: integer - The maximum length of a table or column name, the Sender defaults this parameter to 127.
    + * Recommended to use the same setting as the server, which also uses 127 by default. + *
  • + *
  • copy_buffer: enum, accepted values: on, off - By default, the Sender creates a new buffer for every flush() call, + * and the data to be sent to the server is copied into this new buffer. + * Setting the flag to off results in reusing the same buffer instance for each flush() call.
    + * Use this flag only if calls to the client are serialised. + *
  • + *
+ */ +class SenderOptions { + protocol: string; + addr?: string; + host?: string; // derived from addr + port?: number; // derived from addr + + // replaces `auth` and `jwk` options + username?: string; + password?: string; + token?: string; + token_x?: string; // allowed, but ignored + token_y?: string; // allowed, but ignored + + auto_flush?: boolean; + auto_flush_rows?: number; + auto_flush_interval?: number; + + // replaces `copyBuffer` option + copy_buffer?: string | boolean | null; + + request_min_throughput?: number; + request_timeout?: number; + retry_timeout?: number; + + // replaces `bufferSize` option + init_buf_size?: number | null; + max_buf_size?: number | null; + + tls_verify?: boolean; + tls_ca?: PathOrFileDescriptor; + tls_roots?: never; // not supported + tls_roots_password?: never; // not supported + + max_name_len?: number; + + log?: + | ((level: "error" | "warn" | "info" | "debug", message: string) => void) + | null; + agent?: Agent; + + auth?: { + username?: string; + keyId?: string; + password?: string; + token?: string; + }; + jwk?: Record; + + /** + * Creates a Sender options object by parsing the provided configuration string. + * + * @param {string} configurationString - Configuration string.
+ * @param {object} extraOptions - Optional extra configuration.
+ * - 'log' is a logging function used by the Sender.
+ * Prototype: (level: 'error'|'warn'|'info'|'debug', message: string) => void.
+ * - 'agent' is a custom http/https agent used by the Sender when http/https transport is used.
+ * A http.Agent or https.Agent object is expected. + */ + constructor(configurationString: string, extraOptions = undefined) { + parseConfigurationString(this, configurationString); + + if (extraOptions) { + if (extraOptions.log && typeof extraOptions.log !== "function") { + throw new Error("Invalid logging function"); + } + this.log = extraOptions.log; + + if (extraOptions.agent && !(extraOptions.agent instanceof Agent)) { + throw new Error("Invalid http/https agent"); + } + this.agent = extraOptions.agent; + } + } + + /** + * Creates a Sender options object by parsing the provided configuration string. + * + * @param {string} configurationString - Configuration string.
+ * @param {object} extraOptions - Optional extra configuration.
+ * - 'log' is a logging function used by the Sender.
+ * Prototype: (level: 'error'|'warn'|'info'|'debug', message: string) => void.
+ * - 'agent' is a custom http/https agent used by the Sender when http/https transport is used.
+ * A http.Agent or https.Agent object is expected. + * + * @return {SenderOptions} A Sender configuration object initialized from the provided configuration string. + */ + static fromConfig( + configurationString: string, + extraOptions: { + log?: ( + level: "error" | "warn" | "info" | "debug", + message: string, + ) => void; + agent?: Agent; + } = undefined, + ) { + return new SenderOptions(configurationString, extraOptions); + } + + /** + * Creates a Sender options object by parsing the configuration string set in the QDB_CLIENT_CONF environment variable. + * + * @param {object} extraOptions - Optional extra configuration.
+ * - 'log' is a logging function used by the Sender.
+ }in /**br> + * - 'agent' is a custom http/https agent used by the Sender when http/https transport is used.
+ * A http.Agent or https.Agent object is expected. + * + * @return {SenderOptions} A Sender configuration object initialized from the QDB_CLIENT_CONF environment variable. + */ + static fromEnv(extraOptions = undefined) { + return SenderOptions.fromConfig(process.env.QDB_CLIENT_CONF, extraOptions); + } +} + +function parseConfigurationString( + options: SenderOptions, + configString: string, +) { + if (!configString) { + throw new Error("Configuration string is missing or empty"); + } + + const position = parseProtocol(options, configString); + parseSettings(options, configString, position); + parseAddress(options); + parseBufferSizes(options); + parseAutoFlushOptions(options); + parseTlsOptions(options); + parseRequestTimeoutOptions(options); + parseMaxNameLength(options); + parseCopyBuffer(options); +} + +function parseSettings( + options: SenderOptions, + configString: string, + position: number, +) { + let index = configString.indexOf(";", position); + while (index > -1) { + if ( + index + 1 < configString.length && + configString.charAt(index + 1) === ";" + ) { + index = configString.indexOf(";", index + 2); + continue; + } + + parseSetting(options, configString, position, index); + + position = index + 1; + index = configString.indexOf(";", position); + } + if (position < configString.length) { + parseSetting(options, configString, position, configString.length); + } +} + +function parseSetting( + options: SenderOptions, + configString: string, + position: number, + index: number, +) { + const setting = configString.slice(position, index).replaceAll(";;", ";"); + const equalsIndex = setting.indexOf("="); + if (equalsIndex < 0) { + throw new Error(`Missing '=' sign in '${setting}'`); + } + const key = setting.slice(0, equalsIndex); + const value = setting.slice(equalsIndex + 1); + validateConfigKey(key); + validateConfigValue(key, value); + options[key] = value; +} + +const ValidConfigKeys = [ + "addr", + "username", + "password", + "token", + "token_x", + "token_y", + "auto_flush", + "auto_flush_rows", + "auto_flush_interval", + "copy_buffer", + "request_min_throughput", + "request_timeout", + "retry_timeout", + "init_buf_size", + "max_buf_size", + "max_name_len", + "tls_verify", + "tls_ca", + "tls_roots", + "tls_roots_password", +]; + +function validateConfigKey(key: string) { + if (!ValidConfigKeys.includes(key)) { + throw new Error(`Unknown configuration key: '${key}'`); + } +} + +function validateConfigValue(key: string, value: string) { + if (!value) { + throw new Error(`Invalid configuration, value is not set for '${key}'`); + } + for (let i = 0; i < value.length; i++) { + const unicode = value.codePointAt(i); + if (unicode < 0x20 || (unicode > 0x7e && unicode < 0xa0)) { + throw new Error( + `Invalid configuration, control characters are not allowed: '${value}'`, + ); + } + } +} + +function parseProtocol( + options: SenderOptions, + configString: string | string[], +) { + const index = configString.indexOf("::"); + if (index < 0) { + throw new Error( + "Missing protocol, configuration string format: 'protocol::key1=value1;key2=value2;key3=value3;'", + ); + } + + options.protocol = configString.slice(0, index) as string; + switch (options.protocol) { + case HTTP: + case HTTPS: + case TCP: + case TCPS: + break; + default: + throw new Error( + `Invalid protocol: '${options.protocol}', accepted protocols: 'http', 'https', 'tcp', 'tcps'`, + ); + } + return index + 2; +} + +function parseAddress(options: SenderOptions) { + if (!options.addr) { + throw new Error("Invalid configuration, 'addr' is required"); + } + + const index = options.addr.indexOf(":"); + if (index < 0) { + options.host = options.addr; + switch (options.protocol) { + case HTTP: + case HTTPS: + options.port = HTTP_PORT; + return; + case TCP: + case TCPS: + options.port = TCP_PORT; + return; + default: + throw new Error( + `Invalid protocol: '${options.protocol}', accepted protocols: 'http', 'https', 'tcp', 'tcps'`, + ); + } + } + + options.host = options.addr.slice(0, index); + if (!options.host) { + throw new Error(`Host name is required`); + } + + const portStr = options.addr.slice(index + 1); + if (!portStr) { + throw new Error(`Port is required`); + } + options.port = Number(portStr); + if (isNaN(options.port)) { + throw new Error(`Invalid port: '${portStr}'`); + } + if (!Number.isInteger(options.port) || options.port < 1) { + throw new Error(`Invalid port: ${options.port}`); + } +} + +function parseBufferSizes(options: SenderOptions) { + parseInteger(options, "init_buf_size", "initial buffer size", 1); + parseInteger(options, "max_buf_size", "max buffer size", 1); +} + +function parseAutoFlushOptions(options: SenderOptions) { + parseBoolean(options, "auto_flush", "auto flush"); + parseInteger(options, "auto_flush_rows", "auto flush rows", 0); + parseInteger(options, "auto_flush_interval", "auto flush interval", 0); +} + +function parseTlsOptions(options: SenderOptions) { + parseBoolean(options, "tls_verify", "TLS verify", UNSAFE_OFF); + + if (options.tls_roots || options.tls_roots_password) { + throw new Error( + "'tls_roots' and 'tls_roots_password' options are not supported, please, " + + "use the 'tls_ca' option or the NODE_EXTRA_CA_CERTS environment variable instead", + ); + } +} + +function parseRequestTimeoutOptions(options: SenderOptions) { + parseInteger(options, "request_min_throughput", "request min throughput", 1); + parseInteger(options, "request_timeout", "request timeout", 1); + parseInteger(options, "retry_timeout", "retry timeout", 0); +} + +function parseMaxNameLength(options: SenderOptions) { + parseInteger(options, "max_name_len", "max name length", 1); +} + +function parseCopyBuffer(options: SenderOptions) { + parseBoolean(options, "copy_buffer", "copy buffer"); +} + +function parseBoolean( + options: SenderOptions, + property: string, + description: string, + offValue = OFF, +) { + if (options[property]) { + const property_str = options[property]; + switch (property_str) { + case ON: + options[property] = true; + break; + case offValue: + options[property] = false; + break; + default: + throw new Error(`Invalid ${description} option: '${property_str}'`); + } + } +} + +function parseInteger( + options: SenderOptions, + property: string, + description: string, + lowerBound: number, +) { + if (options[property]) { + const property_str = options[property]; + options[property] = Number(property_str); + if (isNaN(options[property])) { + throw new Error( + `Invalid ${description} option, not a number: '${property_str}'`, + ); + } + if ( + !Number.isInteger(options[property]) || + options[property] < lowerBound + ) { + throw new Error(`Invalid ${description} option: ${options[property]}`); + } + } +} + +export { SenderOptions, HTTP, HTTPS, TCP, TCPS }; diff --git a/src/sender.js b/src/sender.js deleted file mode 100644 index baf954d..0000000 --- a/src/sender.js +++ /dev/null @@ -1,963 +0,0 @@ -'use strict'; - -/* eslint-disable no-unused-vars */ - -const { readFileSync } = require("fs"); -const { Buffer } = require('buffer'); -const { log } = require('./logging'); -const { validateTableName, validateColumnName } = require('./validation'); -const { SenderOptions, HTTP, HTTPS, TCP, TCPS } = require('./options'); -const http = require('http'); -const https = require('https'); -const net = require('net'); -const tls = require('tls'); -const crypto = require('crypto'); - -const HTTP_NO_CONTENT = 204; // success - -const DEFAULT_HTTP_AUTO_FLUSH_ROWS = 75000; -const DEFAULT_TCP_AUTO_FLUSH_ROWS = 600; -const DEFAULT_AUTO_FLUSH_INTERVAL = 1000; // 1 sec - -const DEFAULT_MAX_NAME_LENGTH = 127; - -const DEFAULT_REQUEST_MIN_THROUGHPUT = 102400; // 100 KB/sec -const DEFAULT_REQUEST_TIMEOUT = 10000; // 10 sec -const DEFAULT_RETRY_TIMEOUT = 10000; // 10 sec - -const DEFAULT_BUFFER_SIZE = 65536; // 64 KB -const DEFAULT_MAX_BUFFER_SIZE = 104857600; // 100 MB - -// default options for HTTP agent -// - persistent connections with 1 minute idle timeout, server side has 5 minutes set by default -// - max open connections is set to 256, same as server side default -const DEFAULT_HTTP_AGENT_CONFIG = { - maxSockets: 256, - keepAlive: true, - timeout: 60000 // 1 min -} - -// an arbitrary public key, not used in authentication -// only used to construct a valid JWK token which is accepted by the crypto API -const PUBLIC_KEY = { - x: 'aultdA0PjhD_cWViqKKyL5chm6H1n-BiZBo_48T-uqc', - y: '__ptaol41JWSpTTL525yVEfzmY8A6Vi_QrW1FjKcHMg' -}; - -/** @classdesc - * The QuestDB client's API provides methods to connect to the database, ingest data, and close the connection. - * The supported protocols are HTTP and TCP. HTTP is preferred as it provides feedback in the HTTP response.
- * Based on benchmarks HTTP also provides higher throughput, if configured to ingest data in bigger batches. - *

- * The client supports authentication.
- * Authentication details can be passed to the Sender in its configuration options.
- * The client supports Basic username/password and Bearer token authentication methods when used with HTTP protocol, - * and JWK token authentication when ingesting data via TCP.
- * Please, note that authentication is enabled by default in QuestDB Enterprise only.
- * Details on how to configure authentication in the open source version of - * QuestDB: {@link https://questdb.io/docs/reference/api/ilp/authenticate} - *

- *

- * The client also supports TLS encryption for both, HTTP and TCP transports to provide a secure connection.
- * Please, note that the open source version of QuestDB does not support TLS, and requires an external reverse-proxy, - * such as Nginx to enable encryption. - *

- *

- * The client uses a buffer to store data. It automatically flushes the buffer by sending its content to the server. - * Auto flushing can be disabled via configuration options to gain control over transactions. Initial and maximum - * buffer sizes can also be set. - *

- *

- * It is recommended that the Sender is created by using one of the static factory methods, - * Sender.fromConfig(configString, extraOptions) or Sender.fromEnv(extraOptions)). - * If the Sender is created via its constructor, at least the SenderOptions configuration object should be - * initialized from a configuration string to make sure that the parameters are validated.
- * Detailed description of the Sender's configuration options can be found in - * the SenderOptions documentation. - *

- *

- * Extra options can be provided to the Sender in the extraOptions configuration object.
- * A custom logging function and a custom HTTP(S) agent can be passed to the Sender in this object.
- * The logger implementation provides the option to direct log messages to the same place where the host application's - * log is saved. The default logger writes to the console.
- * The custom HTTP(S) agent option becomes handy if there is a need to modify the default options set for the - * HTTP(S) connections. A popular setting would be disabling persistent connections, in this case an agent can be - * passed to the Sender with keepAlive set to false.
- * For example: Sender.fromConfig(`http::addr=host:port`, { agent: new http.Agent({ keepAlive: false })})
- * If no custom agent is configured, the Sender will use its own agent which overrides some default values - * of http.Agent/https.Agent. The Sender's own agent uses persistent connections with 1 minute idle - * timeout, and limits the number of open connections to the server, which is set to 256 for each host. - *

- */ -class Sender { - /** @private */ static DEFAULT_HTTP_AGENT; - /** @private */ static DEFAULT_HTTPS_AGENT; - - /** @private */ http; // true if the protocol is HTTP/HTTPS, false if it is TCP/TCPS - /** @private */ secure; // true if the protocol is HTTPS or TCPS, false otherwise - /** @private */ host; - /** @private */ port; - - /** @private */ socket; - - /** @private */ username; - /** @private */ password; - /** @private */ token; - - /** @private */ tlsVerify; - /** @private */ tlsCA; - - /** @private */ bufferSize; - /** @private */ maxBufferSize; - /** @private */ buffer; - /** @private */ toBuffer; - /** @private */ doResolve; - /** @private */ position; - /** @private */ endOfLastRow; - - /** @private */ autoFlush; - /** @private */ autoFlushRows; - /** @private */ autoFlushInterval; - /** @private */ lastFlushTime; - /** @private */ pendingRowCount; - - /** @private */ requestMinThroughput; - /** @private */ requestTimeout; - /** @private */ retryTimeout; - - /** @private */ hasTable; - /** @private */ hasSymbols; - /** @private */ hasColumns; - - /** @private */ maxNameLength; - - /** @private */ log; - /** @private */ agent; - - /** - * Creates an instance of Sender. - * - * @param {SenderOptions} options - Sender configuration object.
- * See SenderOptions documentation for detailed description of configuration options.
- */ - constructor(options) { - if (!options || !options.protocol) { - throw new Error('The \'protocol\' option is mandatory'); - } - replaceDeprecatedOptions(options); - - this.log = typeof options.log === 'function' ? options.log : log; - - switch (options.protocol) { - case HTTP: - this.http = true; - this.secure = false; - this.agent = options.agent instanceof http.Agent ? options.agent : this.getDefaultHttpAgent(); - break; - case HTTPS: - this.http = true; - this.secure = true; - this.agent = options.agent instanceof https.Agent ? options.agent : this.getDefaultHttpsAgent(); - break; - case TCP: - this.http = false; - this.secure = false; - break; - case TCPS: - this.http = false; - this.secure = true; - break; - default: - throw new Error(`Invalid protocol: '${options.protocol}'`); - } - - if (this.http) { - this.username = options.username; - this.password = options.password; - this.token = options.token; - if (!options.port) { - options.port = 9000; - } - } else { - if (!options.auth && !options.jwk) { - constructAuth(options); - } - this.jwk = constructJwk(options); - if (!options.port) { - options.port = 9009; - } - } - - this.host = options.host; - this.port = options.port; - - this.tlsVerify = isBoolean(options.tls_verify) ? options.tls_verify : true; - this.tlsCA = options.tls_ca ? readFileSync(options.tls_ca) : undefined; - - this.autoFlush = isBoolean(options.auto_flush) ? options.auto_flush : true; - this.autoFlushRows = isInteger(options.auto_flush_rows, 0) ? options.auto_flush_rows : (this.http ? DEFAULT_HTTP_AUTO_FLUSH_ROWS : DEFAULT_TCP_AUTO_FLUSH_ROWS); - this.autoFlushInterval = isInteger(options.auto_flush_interval, 0) ? options.auto_flush_interval : DEFAULT_AUTO_FLUSH_INTERVAL; - - this.maxNameLength = isInteger(options.max_name_len, 1) ? options.max_name_len : DEFAULT_MAX_NAME_LENGTH; - - this.requestMinThroughput = isInteger(options.request_min_throughput, 1) ? options.request_min_throughput : DEFAULT_REQUEST_MIN_THROUGHPUT; - this.requestTimeout = isInteger(options.request_timeout, 1) ? options.request_timeout : DEFAULT_REQUEST_TIMEOUT; - this.retryTimeout = isInteger(options.retry_timeout, 0) ? options.retry_timeout : DEFAULT_RETRY_TIMEOUT; - - const noCopy = isBoolean(options.copy_buffer) && !options.copy_buffer; - this.toBuffer = noCopy ? this.toBufferView : this.toBufferNew; - this.doResolve = noCopy - ? (resolve) => { - compact(this); - resolve(true); - } - : (resolve) => { - resolve(true); - } - this.maxBufferSize = isInteger(options.max_buf_size, 1) ? options.max_buf_size : DEFAULT_MAX_BUFFER_SIZE; - this.resize(isInteger(options.init_buf_size, 1) ? options.init_buf_size : DEFAULT_BUFFER_SIZE); - this.reset(); - } - - /** - * Creates a Sender options object by parsing the provided configuration string. - * - * @param {string} configurationString - Configuration string.
- * @param {object} extraOptions - Optional extra configuration.
- * - 'log' is a logging function used by the Sender.
- * Prototype: (level: 'error'|'warn'|'info'|'debug', message: string) => void.
- * - 'agent' is a custom http/https agent used by the Sender when http/https transport is used.
- * A http.Agent or https.Agent object is expected. - * - * @return {Sender} A Sender object initialized from the provided configuration string. - */ - static fromConfig(configurationString, extraOptions = undefined) { - return new Sender(SenderOptions.fromConfig(configurationString, extraOptions)); - } - - /** - * Creates a Sender options object by parsing the configuration string set in the QDB_CLIENT_CONF environment variable. - * - * @param {object} extraOptions - Optional extra configuration.
- * - 'log' is a logging function used by the Sender.
- * Prototype: (level: 'error'|'warn'|'info'|'debug', message: string) => void.
- * - 'agent' is a custom http/https agent used by the Sender when http/https transport is used.
- * A http.Agent or https.Agent object is expected. - * - * @return {Sender} A Sender object initialized from the QDB_CLIENT_CONF environment variable. - */ - static fromEnv(extraOptions = undefined) { - return new Sender(SenderOptions.fromConfig(process.env.QDB_CLIENT_CONF, extraOptions)); - } - - /** - * Extends the size of the sender's buffer.
- * Can be used to increase the size of buffer if overflown. - * The buffer's content is copied into the new buffer. - * - * @param {number} bufferSize - New size of the buffer used by the sender, provided in bytes. - */ - resize(bufferSize) { - if (bufferSize > this.maxBufferSize) { - throw new Error(`Max buffer size is ${this.maxBufferSize} bytes, requested buffer size: ${bufferSize}`); - } - this.bufferSize = bufferSize; - // Allocating an extra byte because Buffer.write() does not fail if the length of the data to be written is - // longer than the size of the buffer. It simply just writes whatever it can, and returns. - // If we can write into the extra byte, that indicates buffer overflow. - // See the check in our write() function. - const newBuffer = Buffer.alloc(this.bufferSize + 1, 0, 'utf8'); - if (this.buffer) { - this.buffer.copy(newBuffer); - } - this.buffer = newBuffer; - } - - /** - * Resets the buffer, data added to the buffer will be lost.
- * In other words it clears the buffer and sets the writing position to the beginning of the buffer. - * - * @return {Sender} Returns with a reference to this sender. - */ - reset() { - this.position = 0; - this.lastFlushTime = Date.now(); - this.pendingRowCount = 0; - startNewRow(this); - return this; - } - - /** - * Creates a TCP connection to the database. - * - * @param {net.NetConnectOpts | tls.ConnectionOptions} connectOptions - Connection options, host and port are required. - * - * @return {Promise} Resolves to true if the client is connected. - */ - connect(connectOptions = undefined) { - if (this.http) { - throw new Error('\'connect()\' should be called only if the sender connects via TCP'); - } - - if (!connectOptions) { - connectOptions = { - host: this.host, - port: this.port, - ca: this.tlsCA - } - } - if (!connectOptions.host) { - throw new Error('Hostname is not set'); - } - if (!connectOptions.port) { - throw new Error('Port is not set'); - } - - let self = this; - return new Promise((resolve, reject) => { - if (this.socket) { - throw new Error('Sender connected already'); - } - - let authenticated = false; - let data; - - this.socket = !this.secure - ? net.connect(connectOptions) - : tls.connect(connectOptions, () => { - if (authenticated) { - resolve(true); - } - }); - this.socket.setKeepAlive(true); - - this.socket.on('data', async raw => { - data = !data ? raw : Buffer.concat([data, raw]); - if (!authenticated) { - authenticated = await authenticate(self, data); - if (authenticated) { - resolve(true); - } - } else { - this.log('warn', `Received unexpected data: ${data}`); - } - }) - .on('ready', async () => { - this.log('info', `Successfully connected to ${connectOptions.host}:${connectOptions.port}`); - if (self.jwk) { - this.log('info', `Authenticating with ${connectOptions.host}:${connectOptions.port}`); - await self.socket.write(`${self.jwk.kid}\n`, err => { - if (err) { - reject(err); - } - }); - } else { - authenticated = true; - if (!self.secure || !self.tlsVerify) { - resolve(true); - } - } - }) - .on('error', err => { - self.log('error', err); - if (err.code !== 'SELF_SIGNED_CERT_IN_CHAIN' || self.tlsVerify) { - reject(err); - } - }); - }); - } - - /** - * @ignore - * @return {http.Agent} Returns the default http agent. - */ - getDefaultHttpAgent() { - if (!Sender.DEFAULT_HTTP_AGENT) { - Sender.DEFAULT_HTTP_AGENT = new http.Agent(DEFAULT_HTTP_AGENT_CONFIG); - } - return Sender.DEFAULT_HTTP_AGENT; - } - - /** - * @ignore - * @return {https.Agent} Returns the default https agent. - */ - getDefaultHttpsAgent() { - if (!Sender.DEFAULT_HTTPS_AGENT) { - Sender.DEFAULT_HTTPS_AGENT = new https.Agent(DEFAULT_HTTP_AGENT_CONFIG); - } - return Sender.DEFAULT_HTTPS_AGENT; - } - - /** - * Closes the TCP connection to the database.
- * Data sitting in the Sender's buffer will be lost unless flush() is called before close(). - */ - async close() { - if (this.socket) { - const address = this.socket.remoteAddress; - const port = this.socket.remotePort; - this.socket.destroy(); - this.socket = null; - this.log('info', `Connection to ${address}:${port} is closed`); - } - } - - /** - * Sends the buffer's content to the database and compacts the buffer. - * If the last row is not finished it stays in the sender's buffer. - * - * @return {Promise} Resolves to true when there was data in the buffer to send. - */ - async flush() { - const data = this.toBuffer(this.endOfLastRow); - if (!data) { - return false; - } - - if (this.http) { - const request = this.secure ? https.request : http.request; - const options = createRequestOptions(this, data); - return sendHttp(this, request, options, data, this.retryTimeout); - } else { - if (!this.socket) { - throw new Error('Sender is not connected'); - } - return sendTcp(this, data); - } - } - - /** - * @ignore - * @return {Buffer} Returns a cropped buffer ready to send to the server or null if there is nothing to send. - * The returned buffer is backed by the sender's buffer. - */ - toBufferView(pos = this.position) { - return pos > 0 - ? this.buffer.subarray(0, pos) - : null; - } - - /** - * @ignore - * @return {Buffer} Returns a cropped buffer ready to send to the server or null if there is nothing to send. - * The returned buffer is a copy of the sender's buffer. - */ - toBufferNew(pos = this.position) { - if (pos > 0) { - const data = Buffer.allocUnsafe(pos); - this.buffer.copy(data, 0, 0, pos); - compact(this); - return data; - } - return null; - } - - /** - * Write the table name into the buffer of the sender. - * - * @param {string} table - Table name. - * @return {Sender} Returns with a reference to this sender. - */ - table(table) { - if (typeof table !== 'string') { - throw new Error(`Table name must be a string, received ${typeof table}`); - } - if (this.hasTable) { - throw new Error('Table name has already been set'); - } - validateTableName(table, this.maxNameLength); - checkCapacity(this, [table]); - writeEscaped(this, table); - this.hasTable = true; - return this; - } - - /** - * Write a symbol name and value into the buffer of the sender. - * - * @param {string} name - Symbol name. - * @param {any} value - Symbol value, toString() will be called to extract the actual symbol value from the parameter. - * @return {Sender} Returns with a reference to this sender. - */ - symbol(name, value) { - if (typeof name !== 'string') { - throw new Error(`Symbol name must be a string, received ${typeof name}`); - } - if (!this.hasTable || this.hasColumns) { - throw new Error('Symbol can be added only after table name is set and before any column added'); - } - const valueStr = value.toString(); - checkCapacity(this, [name, valueStr], 2 + name.length + valueStr.length); - write(this, ','); - validateColumnName(name, this.maxNameLength); - writeEscaped(this, name); - write(this, '='); - writeEscaped(this, valueStr); - this.hasSymbols = true; - return this; - } - - /** - * Write a string column with its value into the buffer of the sender. - * - * @param {string} name - Column name. - * @param {string} value - Column value, accepts only string values. - * @return {Sender} Returns with a reference to this sender. - */ - stringColumn(name, value) { - writeColumn(this, name, value, () => { - checkCapacity(this, [value], 2 + value.length); - write(this, '"'); - writeEscaped(this, value, true); - write(this, '"'); - }, 'string'); - return this; - } - - /** - * Write a boolean column with its value into the buffer of the sender. - * - * @param {string} name - Column name. - * @param {boolean} value - Column value, accepts only boolean values. - * @return {Sender} Returns with a reference to this sender. - */ - booleanColumn(name, value) { - writeColumn(this, name, value, () => { - checkCapacity(this, [], 1); - write(this, value ? 't' : 'f'); - }, 'boolean'); - return this; - } - - /** - * Write a float column with its value into the buffer of the sender. - * - * @param {string} name - Column name. - * @param {number} value - Column value, accepts only number values. - * @return {Sender} Returns with a reference to this sender. - */ - floatColumn(name, value) { - writeColumn(this, name, value, () => { - const valueStr = value.toString(); - checkCapacity(this, [valueStr], valueStr.length); - write(this, valueStr); - }, 'number'); - return this; - } - - /** - * Write an integer column with its value into the buffer of the sender. - * - * @param {string} name - Column name. - * @param {number} value - Column value, accepts only number values. - * @return {Sender} Returns with a reference to this sender. - */ - intColumn(name, value) { - if (!Number.isInteger(value)) { - throw new Error(`Value must be an integer, received ${value}`); - } - writeColumn(this, name, value, () => { - const valueStr = value.toString(); - checkCapacity(this, [valueStr], 1 + valueStr.length); - write(this, valueStr); - write(this, 'i'); - }); - return this; - } - - /** - * Write a timestamp column with its value into the buffer of the sender. - * - * @param {string} name - Column name. - * @param {number | bigint} value - Epoch timestamp, accepts numbers or BigInts. - * @param {string} [unit=us] - Timestamp unit. Supported values: 'ns' - nanoseconds, 'us' - microseconds, 'ms' - milliseconds. Defaults to 'us'. - * @return {Sender} Returns with a reference to this sender. - */ - timestampColumn(name, value, unit = 'us') { - if (typeof value !== 'bigint' && !Number.isInteger(value)) { - throw new Error(`Value must be an integer or BigInt, received ${value}`); - } - writeColumn(this, name, value, () => { - const valueMicros = timestampToMicros(BigInt(value), unit); - const valueStr = valueMicros.toString(); - checkCapacity(this, [valueStr], 1 + valueStr.length); - write(this, valueStr); - write(this, 't'); - }); - return this; - } - - /** - * Closing the row after writing the designated timestamp into the buffer of the sender. - * - * @param {number | bigint} timestamp - Designated epoch timestamp, accepts numbers or BigInts. - * @param {string} [unit=us] - Timestamp unit. Supported values: 'ns' - nanoseconds, 'us' - microseconds, 'ms' - milliseconds. Defaults to 'us'. - */ - async at(timestamp, unit = 'us') { - if (!this.hasSymbols && !this.hasColumns) { - throw new Error('The row must have a symbol or column set before it is closed'); - } - if (typeof timestamp !== 'bigint' && !Number.isInteger(timestamp)) { - throw new Error(`Designated timestamp must be an integer or BigInt, received ${timestamp}`); - } - const timestampNanos = timestampToNanos(BigInt(timestamp), unit); - const timestampStr = timestampNanos.toString(); - checkCapacity(this, [], 2 + timestampStr.length); - write(this, ' '); - write(this, timestampStr); - write(this, '\n'); - this.pendingRowCount++; - startNewRow(this); - await autoFlush(this); - } - - /** - * Closing the row without writing designated timestamp into the buffer of the sender.
- * Designated timestamp will be populated by the server on this record. - */ - async atNow() { - if (!this.hasSymbols && !this.hasColumns) { - throw new Error('The row must have a symbol or column set before it is closed'); - } - checkCapacity(this, [], 1); - write(this, '\n'); - this.pendingRowCount++; - startNewRow(this); - await autoFlush(this); - } -} - -function isBoolean(value) { - return typeof value === 'boolean'; -} - -function isInteger(value, lowerBound) { - return typeof value === 'number' && Number.isInteger(value) && value >= lowerBound; -} - -async function authenticate(sender, challenge) { - // Check for trailing \n which ends the challenge - if (challenge.slice(-1).readInt8() === 10) { - const keyObject = await crypto.createPrivateKey( - {'key': sender.jwk, 'format': 'jwk'} - ); - const signature = await crypto.sign( - 'RSA-SHA256', - challenge.slice(0, challenge.length - 1), - keyObject - ); - - return new Promise((resolve, reject) => { - sender.socket.write(`${Buffer.from(signature).toString('base64')}\n`, err => { - err ? reject(err) : resolve(true); - }); - }); - } - return false; -} - -function startNewRow(sender) { - sender.endOfLastRow = sender.position; - sender.hasTable = false; - sender.hasSymbols = false; - sender.hasColumns = false; -} - -function createRequestOptions(sender, data) { - const timeoutMillis = (data.length / sender.requestMinThroughput) * 1000 + sender.requestTimeout; - const options = { - hostname: sender.host, - port: sender.port, - agent: sender.agent, - path: '/write?precision=n', - method: 'POST', - timeout: timeoutMillis - }; - if (sender.secure) { - options.rejectUnauthorized = sender.tlsVerify; - options.ca = sender.tlsCA; - } - return options; -} - -function sendHttp(sender, request, options, data, retryTimeout, retryBegin = -1, retryInterval = -1) { - return new Promise((resolve, reject) => { - let statusCode = -1; - const req = request(options, response => { - statusCode = response.statusCode; - - const body = []; - response - .on('data', chunk => { - body.push(chunk); - }) - .on('error', err => { - sender.log('error', `resp err=${err}`); - }); - - if (statusCode === HTTP_NO_CONTENT) { - response.on('end', () => { - if (body.length > 0) { - sender.log('warn', `Unexpected message from server: ${Buffer.concat(body)}`); - } - sender.doResolve(resolve); - }); - } else { - req.destroy(new Error(`HTTP request failed, statusCode=${statusCode}, error=${Buffer.concat(body)}`)); - } - }); - - if (sender.token) { - req.setHeader('Authorization', 'Bearer ' + sender.token); - } else if (sender.username && sender.password) { - req.setHeader('Authorization', 'Basic ' + Buffer.from(sender.username + ':' + sender.password).toString('base64')); - } - - req.on('timeout', () => { - // set a retryable error code - statusCode = 524; - req.destroy(new Error('HTTP request timeout, no response from server in time')); - }); - req.on('error', err => { - // if the error is thrown while the request is sent, statusCode is -1 => no retry - // request timeout comes through with statusCode 524 => retry - // if the error is thrown while the response is processed, the statusCode is taken from the response => retry depends on statusCode - if (isRetryable(statusCode) && retryTimeout > 0) { - if (retryBegin < 0) { - retryBegin = Date.now(); - retryInterval = 10; - } else { - const elapsed = Date.now() - retryBegin; - if (elapsed > retryTimeout) { - reject(err); - return; - } - } - const jitter = Math.floor(Math.random() * 10) - 5; - setTimeout(() => { - retryInterval = Math.min(retryInterval * 2, 1000); - sendHttp(sender, request, options, data, retryTimeout, retryBegin, retryInterval) - .then(() => resolve(true)) - .catch(e => reject(e)); - }, retryInterval + jitter); - } else { - reject(err); - } - }); - req.write(data, err => err ? reject(err) : () => {}); - req.end(); - }); -} - -/* -We are retrying on the following response codes (copied from the Rust client): -500: Internal Server Error -503: Service Unavailable -504: Gateway Timeout - -// Unofficial extensions -507: Insufficient Storage -509: Bandwidth Limit Exceeded -523: Origin is Unreachable -524: A Timeout Occurred -529: Site is overloaded -599: Network Connect Timeout Error -*/ -function isRetryable(statusCode) { - return [500, 503, 504, 507, 509, 523, 524, 529, 599].includes(statusCode); -} - -async function autoFlush(sender) { - if (sender.autoFlush && sender.pendingRowCount > 0 && ( - (sender.autoFlushRows > 0 && sender.pendingRowCount >= sender.autoFlushRows) || - (sender.autoFlushInterval > 0 && Date.now() - sender.lastFlushTime >= sender.autoFlushInterval) - )) { - await sender.flush(); - } -} - -function sendTcp(sender, data) { - return new Promise((resolve, reject) => { - sender.socket.write(data, err => { - err ? reject(err) : sender.doResolve(resolve); - }); - }); -} - -function checkCapacity(sender, data, base = 0) { - let length = base; - for (const str of data) { - length += Buffer.byteLength(str, 'utf8'); - } - if (sender.position + length > sender.bufferSize) { - let newSize = sender.bufferSize; - do { - newSize += sender.bufferSize; - } while(sender.position + length > newSize); - sender.resize(newSize); - } -} - -function compact(sender) { - if (sender.endOfLastRow > 0) { - sender.buffer.copy(sender.buffer, 0, sender.endOfLastRow, sender.position); - sender.position = sender.position - sender.endOfLastRow; - sender.endOfLastRow = 0; - - sender.lastFlushTime = Date.now(); - sender.pendingRowCount = 0; - } -} - -function writeColumn(sender, name, value, writeValue, valueType) { - if (typeof name !== 'string') { - throw new Error(`Column name must be a string, received ${typeof name}`); - } - if (valueType != null && typeof value !== valueType) { - throw new Error(`Column value must be of type ${valueType}, received ${typeof value}`); - } - if (!sender.hasTable) { - throw new Error('Column can be set only after table name is set'); - } - checkCapacity(sender, [name], 2 + name.length); - write(sender, sender.hasColumns ? ',' : ' '); - validateColumnName(name, sender.maxNameLength); - writeEscaped(sender, name); - write(sender, '='); - writeValue(); - sender.hasColumns = true; -} - -function write(sender, data) { - sender.position += sender.buffer.write(data, sender.position); - if (sender.position > sender.bufferSize) { - throw new Error(`Buffer overflow [position=${sender.position}, bufferSize=${sender.bufferSize}]`); - } -} - -function writeEscaped(sender, data, quoted = false) { - for (const ch of data) { - if (ch > '\\') { - write(sender, ch); - continue; - } - - switch (ch) { - case ' ': - case ',': - case '=': - if (!quoted) { - write(sender, '\\'); - } - write(sender, ch); - break; - case '\n': - case '\r': - write(sender, '\\'); - write(sender, ch); - break; - case '"': - if (quoted) { - write(sender, '\\'); - } - write(sender, ch); - break; - case '\\': - write(sender, '\\\\'); - break; - default: - write(sender, ch); - break; - } - } -} - -function timestampToMicros(timestamp, unit) { - switch (unit) { - case 'ns': - return timestamp / 1000n; - case 'us': - return timestamp; - case 'ms': - return timestamp * 1000n; - default: - throw new Error('Unknown timestamp unit: ' + unit); - } -} - -function timestampToNanos(timestamp, unit) { - switch (unit) { - case 'ns': - return timestamp; - case 'us': - return timestamp * 1000n; - case 'ms': - return timestamp * 1000_000n; - default: - throw new Error('Unknown timestamp unit: ' + unit); - } -} - -function replaceDeprecatedOptions(options) { - // deal with deprecated options - if (options.copyBuffer) { - options.copy_buffer = options.copyBuffer; - options.copyBuffer = undefined; - } - if (options.bufferSize) { - options.init_buf_size = options.bufferSize; - options.bufferSize = undefined; - } -} - -function constructAuth(options) { - if (!options.username && !options.token && !options.password) { - // no intention to authenticate - return; - } - if (!options.username || !options.token) { - throw new Error('TCP transport requires a username and a private key for authentication, ' + - 'please, specify the \'username\' and \'token\' config options'); - } - - options.auth = { - keyId: options.username, - token: options.token - }; -} - -function constructJwk(options) { - if (options.auth) { - if (!options.auth.keyId) { - throw new Error('Missing username, please, specify the \'keyId\' property of the \'auth\' config option. ' + - 'For example: new Sender({protocol: \'tcp\', host: \'host\', auth: {keyId: \'username\', token: \'private key\'}})'); - } - if (typeof options.auth.keyId !== 'string') { - throw new Error('Please, specify the \'keyId\' property of the \'auth\' config option as a string. ' + - 'For example: new Sender({protocol: \'tcp\', host: \'host\', auth: {keyId: \'username\', token: \'private key\'}})'); - } - if (!options.auth.token) { - throw new Error('Missing private key, please, specify the \'token\' property of the \'auth\' config option. ' + - 'For example: new Sender({protocol: \'tcp\', host: \'host\', auth: {keyId: \'username\', token: \'private key\'}})'); - } - if (typeof options.auth.token !== 'string') { - throw new Error('Please, specify the \'token\' property of the \'auth\' config option as a string. ' + - 'For example: new Sender({protocol: \'tcp\', host: \'host\', auth: {keyId: \'username\', token: \'private key\'}})'); - } - - return { - kid: options.auth.keyId, - d: options.auth.token, - ...PUBLIC_KEY, - kty: 'EC', - crv: 'P-256' - }; - } else { - return options.jwk; - } -} - -exports.Sender = Sender; -exports.DEFAULT_BUFFER_SIZE = DEFAULT_BUFFER_SIZE; -exports.DEFAULT_MAX_BUFFER_SIZE = DEFAULT_MAX_BUFFER_SIZE; diff --git a/src/sender.ts b/src/sender.ts new file mode 100644 index 0000000..410bf8c --- /dev/null +++ b/src/sender.ts @@ -0,0 +1,1105 @@ +// @ts-check +import { readFileSync } from "node:fs"; +import { Buffer } from "node:buffer"; +import net from "node:net"; +import tls from "node:tls"; +import crypto from "node:crypto"; +import { Agent, RetryAgent } from "undici"; + +import { log } from "./logging"; +import { validateTableName, validateColumnName } from "./validation"; +import { SenderOptions, HTTP, HTTPS, TCP, TCPS } from "./options"; + +const HTTP_NO_CONTENT = 204; // success + +const DEFAULT_HTTP_AUTO_FLUSH_ROWS = 75000; +const DEFAULT_TCP_AUTO_FLUSH_ROWS = 600; +const DEFAULT_AUTO_FLUSH_INTERVAL = 1000; // 1 sec + +const DEFAULT_MAX_NAME_LENGTH = 127; + +const DEFAULT_REQUEST_MIN_THROUGHPUT = 102400; // 100 KB/sec +const DEFAULT_REQUEST_TIMEOUT = 10000; // 10 sec +const DEFAULT_RETRY_TIMEOUT = 10000; // 10 sec + +const DEFAULT_BUFFER_SIZE = 65536; // 64 KB +const DEFAULT_MAX_BUFFER_SIZE = 104857600; // 100 MB + +/** @type {Agent.Options} */ +const DEFAULT_HTTP_OPTIONS: Agent.Options = { + connect: { + keepAlive: true, + }, + pipelining: 1, + keepAliveTimeout: 60000, // 1 minute +}; +// an arbitrary public key, not used in authentication +// only used to construct a valid JWK token which is accepted by the crypto API +const PUBLIC_KEY = { + x: "aultdA0PjhD_cWViqKKyL5chm6H1n-BiZBo_48T-uqc", + y: "__ptaol41JWSpTTL525yVEfzmY8A6Vi_QrW1FjKcHMg", +}; + +/* +We are retrying on the following response codes (copied from the Rust client): +500: Internal Server Error +503: Service Unavailable +504: Gateway Timeout + +// Unofficial extensions +507: Insufficient Storage +509: Bandwidth Limit Exceeded +523: Origin is Unreachable +524: A Timeout Occurred +529: Site is overloaded +599: Network Connect Timeout Error +*/ +const RETRIABLE_STATUS_CODES = [500, 503, 504, 507, 509, 523, 524, 529, 599]; + +/** @classdesc + * The QuestDB client's API provides methods to connect to the database, ingest data, and close the connection. + * The supported protocols are HTTP and TCP. HTTP is preferred as it provides feedback in the HTTP response.
+ * Based on benchmarks HTTP also provides higher throughput, if configured to ingest data in bigger batches. + *

+ * The client supports authentication.
+ * Authentication details can be passed to the Sender in its configuration options.
+ * The client supports Basic username/password and Bearer token authentication methods when used with HTTP protocol, + * and JWK token authentication when ingesting data via TCP.
+ * Please, note that authentication is enabled by default in QuestDB Enterprise only.
+ * Details on how to configure authentication in the open source version of + * QuestDB: {@link https://questdb.io/docs/reference/api/ilp/authenticate} + *

+ *

+ * The client also supports TLS encryption for both, HTTP and TCP transports to provide a secure connection.
+ * Please, note that the open source version of QuestDB does not support TLS, and requires an external reverse-proxy, + * such as Nginx to enable encryption. + *

+ *

+ * The client uses a buffer to store data. It automatically flushes the buffer by sending its content to the server. + * Auto flushing can be disabled via configuration options to gain control over transactions. Initial and maximum + * buffer sizes can also be set. + *

+ *

+ * It is recommended that the Sender is created by using one of the static factory methods, + * Sender.fromConfig(configString, extraOptions) or Sender.fromEnv(extraOptions)). + * If the Sender is created via its constructor, at least the SenderOptions configuration object should be + * initialized from a configuration string to make sure that the parameters are validated.
+ * Detailed description of the Sender's configuration options can be found in + * the SenderOptions documentation. + *

+ *

+ * Extra options can be provided to the Sender in the extraOptions configuration object.
+ * A custom logging function and a custom HTTP(S) agent can be passed to the Sender in this object.
+ * The logger implementation provides the option to direct log messages to the same place where the host application's + * log is saved. The default logger writes to the console.
+ * The custom HTTP(S) agent option becomes handy if there is a need to modify the default options set for the + * HTTP(S) connections. A popular setting would be disabling persistent connections, in this case an agent can be + * passed to the Sender with keepAlive set to false.
+ * For example: Sender.fromConfig(`http::addr=host:port`, { agent: new undici.Agent({ connect: { keepAlive: false } })})
+ * If no custom agent is configured, the Sender will use its own agent which overrides some default values + * of undici.Agent. The Sender's own agent uses persistent connections with 1 minute idle timeout, pipelines requests default to 1. + *

+ */ +class Sender { + /** @private */ static DEFAULT_HTTP_AGENT; + /** @private */ static DEFAULT_HTTPS_AGENT; + + /** @private */ http; // true if the protocol is HTTP/HTTPS, false if it is TCP/TCPS + /** @private */ secure; // true if the protocol is HTTPS or TCPS, false otherwise + /** @private */ host; + /** @private */ port; + + /** @private */ socket; + + /** @private */ username; + /** @private */ password; + /** @private */ token; + + /** @private */ tlsVerify; + /** @private */ tlsCA; + + /** @private */ bufferSize; + /** @private */ maxBufferSize; + /** @private */ buffer; + /** @private */ toBuffer; + /** @private */ doResolve; + /** @private */ position; + /** @private */ endOfLastRow; + + /** @private */ autoFlush; + /** @private */ autoFlushRows; + /** @private */ autoFlushInterval; + /** @private */ lastFlushTime; + /** @private */ pendingRowCount; + + /** @private */ requestMinThroughput; + /** @private */ requestTimeout; + /** @private */ retryTimeout; + + /** @private */ hasTable; + /** @private */ hasSymbols; + /** @private */ hasColumns; + + /** @private */ maxNameLength; + + /** @private */ log; + /** @private */ agent; + /** @private */ jwk; + + /** + * Creates an instance of Sender. + * + * @param {SenderOptions} options - Sender configuration object.
+ * See SenderOptions documentation for detailed description of configuration options.
+ */ + constructor(options: SenderOptions) { + if (!options || !options.protocol) { + throw new Error("The 'protocol' option is mandatory"); + } + replaceDeprecatedOptions(options); + + this.log = typeof options.log === "function" ? options.log : log; + + switch (options.protocol) { + case HTTP: + this.http = true; + this.secure = false; + this.agent = + options.agent instanceof Agent + ? options.agent + : this.getDefaultHttpAgent(); + break; + case HTTPS: + this.http = true; + this.secure = true; + this.agent = + options.agent instanceof Agent + ? options.agent + : this.getDefaultHttpAgent(); + break; + case TCP: + this.http = false; + this.secure = false; + break; + case TCPS: + this.http = false; + this.secure = true; + break; + default: + throw new Error(`Invalid protocol: '${options.protocol}'`); + } + + if (this.http) { + this.username = options.username; + this.password = options.password; + this.token = options.token; + if (!options.port) { + options.port = 9000; + } + } else { + if (!options.auth && !options.jwk) { + constructAuth(options); + } + this.jwk = constructJwk(options); + if (!options.port) { + options.port = 9009; + } + } + + this.host = options.host; + this.port = options.port; + + this.tlsVerify = isBoolean(options.tls_verify) ? options.tls_verify : true; + this.tlsCA = options.tls_ca ? readFileSync(options.tls_ca) : undefined; + + this.autoFlush = isBoolean(options.auto_flush) ? options.auto_flush : true; + this.autoFlushRows = isInteger(options.auto_flush_rows, 0) + ? options.auto_flush_rows + : this.http + ? DEFAULT_HTTP_AUTO_FLUSH_ROWS + : DEFAULT_TCP_AUTO_FLUSH_ROWS; + this.autoFlushInterval = isInteger(options.auto_flush_interval, 0) + ? options.auto_flush_interval + : DEFAULT_AUTO_FLUSH_INTERVAL; + + this.maxNameLength = isInteger(options.max_name_len, 1) + ? options.max_name_len + : DEFAULT_MAX_NAME_LENGTH; + + this.requestMinThroughput = isInteger(options.request_min_throughput, 0) + ? options.request_min_throughput + : DEFAULT_REQUEST_MIN_THROUGHPUT; + this.requestTimeout = isInteger(options.request_timeout, 1) + ? options.request_timeout + : DEFAULT_REQUEST_TIMEOUT; + this.retryTimeout = isInteger(options.retry_timeout, 0) + ? options.retry_timeout + : DEFAULT_RETRY_TIMEOUT; + + const noCopy = isBoolean(options.copy_buffer) && !options.copy_buffer; + this.toBuffer = noCopy ? this.toBufferView : this.toBufferNew; + this.doResolve = noCopy + ? (resolve) => { + compact(this); + resolve(true); + } + : (resolve) => { + resolve(true); + }; + this.maxBufferSize = isInteger(options.max_buf_size, 1) + ? options.max_buf_size + : DEFAULT_MAX_BUFFER_SIZE; + this.resize( + isInteger(options.init_buf_size, 1) + ? options.init_buf_size + : DEFAULT_BUFFER_SIZE, + ); + this.reset(); + } + + /** + * Creates a Sender options object by parsing the provided configuration string. + * + * @param {string} configurationString - Configuration string.
+ * @param {object} extraOptions - Optional extra configuration.
+ * - 'log' is a logging function used by the Sender.
+ * Prototype: (level: 'error'|'warn'|'info'|'debug', message: string) => void.
+ * - 'agent' is a custom Undici agent used by the Sender when http/https transport is used.
+ * A undici.Agent object is expected. + * + * @return {Sender} A Sender object initialized from the provided configuration string. + */ + static fromConfig( + configurationString: string, + extraOptions: object = undefined, + ): Sender { + return new Sender( + SenderOptions.fromConfig(configurationString, extraOptions), + ); + } + + /** + * Creates a Sender options object by parsing the configuration string set in the QDB_CLIENT_CONF environment variable. + * + * @param {object} extraOptions - Optional extra configuration.
+ * - 'log' is a logging function used by the Sender.
+ * Prototype: (level: 'error'|'warn'|'info'|'debug', message: string) => void.
+ * - 'agent' is a custom Undici agent used by the Sender when http/https transport is used.
+ * A undici.Agent object is expected. + * + * @return {Sender} A Sender object initialized from the QDB_CLIENT_CONF environment variable. + */ + static fromEnv(extraOptions: object = undefined): Sender { + return new Sender( + SenderOptions.fromConfig(process.env.QDB_CLIENT_CONF, extraOptions), + ); + } + + /** + * Extends the size of the sender's buffer.
+ * Can be used to increase the size of buffer if overflown. + * The buffer's content is copied into the new buffer. + * + * @param {number} bufferSize - New size of the buffer used by the sender, provided in bytes. + */ + resize(bufferSize: number) { + if (bufferSize > this.maxBufferSize) { + throw new Error( + `Max buffer size is ${this.maxBufferSize} bytes, requested buffer size: ${bufferSize}`, + ); + } + this.bufferSize = bufferSize; + // Allocating an extra byte because Buffer.write() does not fail if the length of the data to be written is + // longer than the size of the buffer. It simply just writes whatever it can, and returns. + // If we can write into the extra byte, that indicates buffer overflow. + // See the check in our write() function. + const newBuffer = Buffer.alloc(this.bufferSize + 1, 0, "utf8"); + if (this.buffer) { + this.buffer.copy(newBuffer); + } + this.buffer = newBuffer; + } + + /** + * Resets the buffer, data added to the buffer will be lost.
+ * In other words it clears the buffer and sets the writing position to the beginning of the buffer. + * + * @return {Sender} Returns with a reference to this sender. + */ + reset(): Sender { + this.position = 0; + this.lastFlushTime = Date.now(); + this.pendingRowCount = 0; + startNewRow(this); + return this; + } + + /** + * Creates a TCP connection to the database. + * + * @param {net.NetConnectOpts | tls.ConnectionOptions} connectOptions - Connection options, host and port are required. + * + * @return {Promise} Resolves to true if the client is connected. + */ + connect( + connectOptions: net.NetConnectOpts | tls.ConnectionOptions = undefined, + ): Promise { + if (this.http) { + throw new Error( + "'connect()' should be called only if the sender connects via TCP", + ); + } + + if (!connectOptions) { + connectOptions = { + host: this.host, + port: this.port, + ca: this.tlsCA, + }; + } + if (!(connectOptions as tls.ConnectionOptions).host) { + throw new Error("Hostname is not set"); + } + if (!(connectOptions as tls.ConnectionOptions).port) { + throw new Error("Port is not set"); + } + + return new Promise((resolve, reject) => { + if (this.socket) { + throw new Error("Sender connected already"); + } + + let authenticated: boolean = false; + let data; + + this.socket = !this.secure + ? net.connect(connectOptions as net.NetConnectOpts) + : tls.connect(connectOptions, () => { + if (authenticated) { + resolve(true); + } + }); + this.socket.setKeepAlive(true); + + this.socket + .on("data", async (raw) => { + data = !data ? raw : Buffer.concat([data, raw]); + if (!authenticated) { + authenticated = await authenticate(this, data); + if (authenticated) { + resolve(true); + } + } else { + this.log("warn", `Received unexpected data: ${data}`); + } + }) + .on("ready", async () => { + this.log( + "info", + `Successfully connected to ${(connectOptions as tls.ConnectionOptions).host}:${(connectOptions as tls.ConnectionOptions).port}`, + ); + if (this.jwk) { + this.log( + "info", + `Authenticating with ${(connectOptions as tls.ConnectionOptions).host}:${(connectOptions as tls.ConnectionOptions).port}`, + ); + await this.socket.write(`${this.jwk.kid}\n`, (err) => { + if (err) { + reject(err); + } + }); + } else { + authenticated = true; + if (!this.secure || !this.tlsVerify) { + resolve(true); + } + } + }) + .on("error", (err) => { + this.log("error", err); + if (err.code !== "SELF_SIGNED_CERT_IN_CHAIN" || this.tlsVerify) { + reject(err); + } + }); + }); + } + + /** + * @ignore + * @return {Agent} Returns the default http agent. + */ + getDefaultHttpAgent(): Agent { + if (!Sender.DEFAULT_HTTP_AGENT) { + Sender.DEFAULT_HTTP_AGENT = new Agent(DEFAULT_HTTP_OPTIONS); + } + return Sender.DEFAULT_HTTP_AGENT; + } + + /** + * Closes the TCP connection to the database.
+ * Data sitting in the Sender's buffer will be lost unless flush() is called before close(). + */ + async close() { + if (this.socket) { + const address = this.socket.remoteAddress; + const port = this.socket.remotePort; + this.socket.destroy(); + this.socket = null; + this.log("info", `Connection to ${address}:${port} is closed`); + } + } + + /** + * Sends the buffer's content to the database and compacts the buffer. + * If the last row is not finished it stays in the sender's buffer. + * + * @return {Promise} Resolves to true when there was data in the buffer to send. + */ + async flush(): Promise { + const data = this.toBuffer(this.endOfLastRow); + if (!data) { + return false; + } + + if (this.http) { + // const request = this.secure ? https.request : http.request; + const options = createRequestOptions(this, data); + return sendHttp(this, options, data, this.retryTimeout); + } else { + if (!this.socket) { + throw new Error("Sender is not connected"); + } + return sendTcp(this, data); + } + } + + /** + * @ignore + * @return {Buffer} Returns a cropped buffer ready to send to the server or null if there is nothing to send. + * The returned buffer is backed by the sender's buffer. + */ + toBufferView(pos = this.position): Buffer { + return pos > 0 ? this.buffer.subarray(0, pos) : null; + } + + /** + * @ignore + * @return {Buffer|null} Returns a cropped buffer ready to send to the server or null if there is nothing to send. + * The returned buffer is a copy of the sender's buffer. + */ + toBufferNew(pos = this.position): Buffer | null { + if (pos > 0) { + const data = Buffer.allocUnsafe(pos); + this.buffer.copy(data, 0, 0, pos); + compact(this); + return data; + } + return null; + } + + /** + * Write the table name into the buffer of the sender. + * + * @param {string} table - Table name. + * @return {Sender} Returns with a reference to this sender. + */ + table(table: string): Sender { + if (typeof table !== "string") { + throw new Error(`Table name must be a string, received ${typeof table}`); + } + if (this.hasTable) { + throw new Error("Table name has already been set"); + } + validateTableName(table, this.maxNameLength); + checkCapacity(this, [table]); + writeEscaped(this, table); + this.hasTable = true; + return this; + } + + /** + * Write a symbol name and value into the buffer of the sender. + * + * @param {string} name - Symbol name. + * @param {any} value - Symbol value, toString() will be called to extract the actual symbol value from the parameter. + * @return {Sender} Returns with a reference to this sender. + */ + symbol(name: string, value: T): Sender { + if (typeof name !== "string") { + throw new Error(`Symbol name must be a string, received ${typeof name}`); + } + if (!this.hasTable || this.hasColumns) { + throw new Error( + "Symbol can be added only after table name is set and before any column added", + ); + } + const valueStr = value.toString(); + checkCapacity(this, [name, valueStr], 2 + name.length + valueStr.length); + write(this, ","); + validateColumnName(name, this.maxNameLength); + writeEscaped(this, name); + write(this, "="); + writeEscaped(this, valueStr); + this.hasSymbols = true; + return this; + } + + /** + * Write a string column with its value into the buffer of the sender. + * + * @param {string} name - Column name. + * @param {string} value - Column value, accepts only string values. + * @return {Sender} Returns with a reference to this sender. + */ + stringColumn(name: string, value: string): Sender { + writeColumn( + this, + name, + value, + () => { + checkCapacity(this, [value], 2 + value.length); + write(this, '"'); + writeEscaped(this, value, true); + write(this, '"'); + }, + "string", + ); + return this; + } + + /** + * Write a boolean column with its value into the buffer of the sender. + * + * @param {string} name - Column name. + * @param {boolean} value - Column value, accepts only boolean values. + * @return {Sender} Returns with a reference to this sender. + */ + booleanColumn(name: string, value: boolean): Sender { + writeColumn( + this, + name, + value, + () => { + checkCapacity(this, [], 1); + write(this, value ? "t" : "f"); + }, + "boolean", + ); + return this; + } + + /** + * Write a float column with its value into the buffer of the sender. + * + * @param {string} name - Column name. + * @param {number} value - Column value, accepts only number values. + * @return {Sender} Returns with a reference to this sender. + */ + floatColumn(name: string, value: number): Sender { + writeColumn( + this, + name, + value, + () => { + const valueStr = value.toString(); + checkCapacity(this, [valueStr], valueStr.length); + write(this, valueStr); + }, + "number", + ); + return this; + } + + /** + * Write an integer column with its value into the buffer of the sender. + * + * @param {string} name - Column name. + * @param {number} value - Column value, accepts only number values. + * @return {Sender} Returns with a reference to this sender. + */ + intColumn(name: string, value: number): Sender { + if (!Number.isInteger(value)) { + throw new Error(`Value must be an integer, received ${value}`); + } + writeColumn(this, name, value, () => { + const valueStr = value.toString(); + checkCapacity(this, [valueStr], 1 + valueStr.length); + write(this, valueStr); + write(this, "i"); + }); + return this; + } + + /** + * Write a timestamp column with its value into the buffer of the sender. + * + * @param {string} name - Column name. + * @param {number | bigint} value - Epoch timestamp, accepts numbers or BigInts. + * @param {string} [unit=us] - Timestamp unit. Supported values: 'ns' - nanoseconds, 'us' - microseconds, 'ms' - milliseconds. Defaults to 'us'. + * @return {Sender} Returns with a reference to this sender. + */ + timestampColumn( + name: string, + value: number | bigint, + unit: "ns" | "us" | "ms" = "us", + ): Sender { + if (typeof value !== "bigint" && !Number.isInteger(value)) { + throw new Error(`Value must be an integer or BigInt, received ${value}`); + } + writeColumn(this, name, value, () => { + const valueMicros = timestampToMicros(BigInt(value), unit); + const valueStr = valueMicros.toString(); + checkCapacity(this, [valueStr], 1 + valueStr.length); + write(this, valueStr); + write(this, "t"); + }); + return this; + } + + /** + * Closing the row after writing the designated timestamp into the buffer of the sender. + * + * @param {number | bigint} timestamp - Designated epoch timestamp, accepts numbers or BigInts. + * @param {string} [unit=us] - Timestamp unit. Supported values: 'ns' - nanoseconds, 'us' - microseconds, 'ms' - milliseconds. Defaults to 'us'. + */ + async at(timestamp: number | bigint, unit: "ns" | "us" | "ms" = "us") { + if (!this.hasSymbols && !this.hasColumns) { + throw new Error( + "The row must have a symbol or column set before it is closed", + ); + } + if (typeof timestamp !== "bigint" && !Number.isInteger(timestamp)) { + throw new Error( + `Designated timestamp must be an integer or BigInt, received ${timestamp}`, + ); + } + const timestampNanos = timestampToNanos(BigInt(timestamp), unit); + const timestampStr = timestampNanos.toString(); + checkCapacity(this, [], 2 + timestampStr.length); + write(this, " "); + write(this, timestampStr); + write(this, "\n"); + this.pendingRowCount++; + startNewRow(this); + await autoFlush(this); + } + + /** + * Closing the row without writing designated timestamp into the buffer of the sender.
+ * Designated timestamp will be populated by the server on this record. + */ + async atNow() { + if (!this.hasSymbols && !this.hasColumns) { + throw new Error( + "The row must have a symbol or column set before it is closed", + ); + } + checkCapacity(this, [], 1); + write(this, "\n"); + this.pendingRowCount++; + startNewRow(this); + await autoFlush(this); + } +} + +function isBoolean(value: unknown): value is boolean { + return typeof value === "boolean"; +} + +function isInteger(value: unknown, lowerBound: number): value is number { + return ( + typeof value === "number" && Number.isInteger(value) && value >= lowerBound + ); +} + +async function authenticate( + sender: Sender, + challenge: Buffer, +): Promise { + // Check for trailing \n which ends the challenge + if (challenge.subarray(-1).readInt8() === 10) { + const keyObject = crypto.createPrivateKey({ + key: sender.jwk, + format: "jwk", + }); + const signature = crypto.sign( + "RSA-SHA256", + challenge.subarray(0, challenge.length - 1), + keyObject, + ); + + return new Promise((resolve, reject) => { + sender.socket.write( + `${Buffer.from(signature).toString("base64")}\n`, + (err) => { + if (err) { + reject(err); + } else { + resolve(true); + } + }, + ); + }); + } + return false; +} + +function startNewRow(sender: Sender) { + sender.endOfLastRow = sender.position; + sender.hasTable = false; + sender.hasSymbols = false; + sender.hasColumns = false; +} + +type InternalHttpOptions = { + hostname: string; + port: number; + agent: Agent; + protocol: string; + path: string; + method: string; + timeout: number; +}; +function createRequestOptions( + sender: Sender, + data: Buffer, +): InternalHttpOptions { + const timeoutMillis = + (data.length / sender.requestMinThroughput) * 1000 + sender.requestTimeout; + const options: InternalHttpOptions = { + hostname: sender.host, + port: sender.port, + agent: sender.agent, + protocol: sender.secure ? "https" : "http", + path: "/write?precision=n", + method: "POST", + timeout: timeoutMillis, + }; + + return options; +} + +async function sendHttp( + sender: Sender, + options: InternalHttpOptions, + data: Buffer, + retryTimeout: number, +) { + const retryBegin = Date.now(); + const headers: Record = {}; + + if (sender.secure) { + sender.agent = new Agent({ + ...DEFAULT_HTTP_OPTIONS, + connect: { + ...DEFAULT_HTTP_OPTIONS.connect, + requestCert: sender.tlsVerify, + rejectUnauthorized: sender.tlsVerify, + ca: sender.tlsCA, + }, + }); + } + + const dispatcher = new RetryAgent(sender.agent, { + maxRetries: Infinity, // We'll control retries based on time + minTimeout: 10, // Initial retry interval in milliseconds + maxTimeout: 1000, // Maximum retry interval in milliseconds + timeoutFactor: 2, // Exponential backoff factor + retryAfter: true, // Enable automatic retry after 'Retry-After' header + methods: ["GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS", "HEAD"], + statusCodes: RETRIABLE_STATUS_CODES, + errorCodes: [ + "ECONNRESET", + "EAI_AGAIN", + "ECONNREFUSED", + "ETIMEDOUT", + "EPIPE", + "UND_ERR_CONNECT_TIMEOUT", + "UND_ERR_HEADERS_TIMEOUT", + "UND_ERR_BODY_TIMEOUT", + ], + retry(err, context, callback) { + const elapsed = Date.now() - retryBegin; + if (elapsed > retryTimeout) { + // Stop retrying if the total retry timeout is exceeded + return callback(err); + } + + return callback(null); + }, + }); + + if (sender.token) { + headers["Authorization"] = "Bearer " + sender.token; + } else if (sender.username && sender.password) { + headers["Authorization"] = + "Basic " + + Buffer.from(sender.username + ":" + sender.password).toString("base64"); + } + + try { + const { statusCode, body } = await dispatcher.request({ + origin: `${options.protocol}://${options.hostname}:${options.port}`, + path: options.path, + method: options.method, + headers, + body: data, + headersTimeout: sender.requestTimeout, + }); + + const responseBody = await body.arrayBuffer(); + if (statusCode === HTTP_NO_CONTENT) { + if (responseBody.byteLength > 0) { + sender.log( + "warn", + `Unexpected message from server: ${responseBody.toString()}`, + ); + } + return true; + } else { + const error = new Error( + `HTTP request failed, statusCode=${statusCode}, error=${responseBody.toString()}`, + ); + throw error; + } + } catch (err) { + if (err.code === "UND_ERR_HEADERS_TIMEOUT") { + sender.log( + "error", + `HTTP request timeout, no response from server in time`, + ); + throw new Error(`HTTP request timeout, no response from server in time`); + } + + sender.log("error", `HTTP request failed, statusCode=500, error=`); + throw new Error( + `HTTP request failed, statusCode=500, error=${err.message}`, + ); + } +} + +async function autoFlush(sender: Sender) { + if ( + sender.autoFlush && + sender.pendingRowCount > 0 && + ((sender.autoFlushRows > 0 && + sender.pendingRowCount >= sender.autoFlushRows) || + (sender.autoFlushInterval > 0 && + Date.now() - sender.lastFlushTime >= sender.autoFlushInterval)) + ) { + await sender.flush(); + } +} + +function sendTcp(sender: Sender, data: Buffer) { + return new Promise((resolve, reject) => { + sender.socket.write(data, (err) => { + if (err) { + reject(err); + } else { + sender.doResolve(resolve); + } + }); + }); +} + +function checkCapacity(sender: Sender, data: string[], base = 0) { + let length = base; + for (const str of data) { + length += Buffer.byteLength(str, "utf8"); + } + if (sender.position + length > sender.bufferSize) { + let newSize = sender.bufferSize; + do { + newSize += sender.bufferSize; + } while (sender.position + length > newSize); + sender.resize(newSize); + } +} + +function compact(sender: Sender) { + if (sender.endOfLastRow > 0) { + sender.buffer.copy(sender.buffer, 0, sender.endOfLastRow, sender.position); + sender.position = sender.position - sender.endOfLastRow; + sender.endOfLastRow = 0; + + sender.lastFlushTime = Date.now(); + sender.pendingRowCount = 0; + } +} + +function writeColumn( + sender: Sender, + name: string, + value: unknown, + writeValue: () => void, + valueType?: string | null, +) { + if (typeof name !== "string") { + throw new Error(`Column name must be a string, received ${typeof name}`); + } + if (valueType != null && typeof value !== valueType) { + throw new Error( + `Column value must be of type ${valueType}, received ${typeof value}`, + ); + } + if (!sender.hasTable) { + throw new Error("Column can be set only after table name is set"); + } + checkCapacity(sender, [name], 2 + name.length); + write(sender, sender.hasColumns ? "," : " "); + validateColumnName(name, sender.maxNameLength); + writeEscaped(sender, name); + write(sender, "="); + writeValue(); + sender.hasColumns = true; +} + +function write(sender: Sender, data: string) { + sender.position += sender.buffer.write(data, sender.position); + if (sender.position > sender.bufferSize) { + throw new Error( + `Buffer overflow [position=${sender.position}, bufferSize=${sender.bufferSize}]`, + ); + } +} + +function writeEscaped(sender: Sender, data: string, quoted = false) { + for (const ch of data) { + if (ch > "\\") { + write(sender, ch); + continue; + } + + switch (ch) { + case " ": + case ",": + case "=": + if (!quoted) { + write(sender, "\\"); + } + write(sender, ch); + break; + case "\n": + case "\r": + write(sender, "\\"); + write(sender, ch); + break; + case '"': + if (quoted) { + write(sender, "\\"); + } + write(sender, ch); + break; + case "\\": + write(sender, "\\\\"); + break; + default: + write(sender, ch); + break; + } + } +} + +function timestampToMicros(timestamp: bigint, unit: "ns" | "us" | "ms") { + switch (unit) { + case "ns": + return timestamp / 1000n; + case "us": + return timestamp; + case "ms": + return timestamp * 1000n; + default: + throw new Error("Unknown timestamp unit: " + unit); + } +} + +function timestampToNanos(timestamp: bigint, unit: "ns" | "us" | "ms") { + switch (unit) { + case "ns": + return timestamp; + case "us": + return timestamp * 1000n; + case "ms": + return timestamp * 1000_000n; + default: + throw new Error("Unknown timestamp unit: " + unit); + } +} + +type DeprecatedOptions = { + /** @deprecated */ + copyBuffer?: boolean; + /** @deprecated */ + bufferSize?: number; +}; +function replaceDeprecatedOptions(options: SenderOptions & DeprecatedOptions) { + // deal with deprecated options + if (options.copyBuffer) { + options.copy_buffer = options.copyBuffer; + options.copyBuffer = undefined; + } + if (options.bufferSize) { + options.init_buf_size = options.bufferSize; + options.bufferSize = undefined; + } +} + +function constructAuth(options: SenderOptions) { + if (!options.username && !options.token && !options.password) { + // no intention to authenticate + return; + } + if (!options.username || !options.token) { + throw new Error( + "TCP transport requires a username and a private key for authentication, " + + "please, specify the 'username' and 'token' config options", + ); + } + + options.auth = { + keyId: options.username, + token: options.token, + }; +} + +function constructJwk(options: SenderOptions) { + if (options.auth) { + if (!options.auth.keyId) { + throw new Error( + "Missing username, please, specify the 'keyId' property of the 'auth' config option. " + + "For example: new Sender({protocol: 'tcp', host: 'host', auth: {keyId: 'username', token: 'private key'}})", + ); + } + if (typeof options.auth.keyId !== "string") { + throw new Error( + "Please, specify the 'keyId' property of the 'auth' config option as a string. " + + "For example: new Sender({protocol: 'tcp', host: 'host', auth: {keyId: 'username', token: 'private key'}})", + ); + } + if (!options.auth.token) { + throw new Error( + "Missing private key, please, specify the 'token' property of the 'auth' config option. " + + "For example: new Sender({protocol: 'tcp', host: 'host', auth: {keyId: 'username', token: 'private key'}})", + ); + } + if (typeof options.auth.token !== "string") { + throw new Error( + "Please, specify the 'token' property of the 'auth' config option as a string. " + + "For example: new Sender({protocol: 'tcp', host: 'host', auth: {keyId: 'username', token: 'private key'}})", + ); + } + + return { + kid: options.auth.keyId, + d: options.auth.token, + ...PUBLIC_KEY, + kty: "EC", + crv: "P-256", + }; + } else { + return options.jwk; + } +} + +export { Sender, DEFAULT_BUFFER_SIZE, DEFAULT_MAX_BUFFER_SIZE }; diff --git a/src/validation.js b/src/validation.js deleted file mode 100644 index d31d129..0000000 --- a/src/validation.js +++ /dev/null @@ -1,123 +0,0 @@ -'use strict'; - -/** - * Validates a table name.
- * Throws an error if table name is invalid. - * - * @param {string} name - The table name to validate. - * @param {number} maxNameLength - The maximum length of table names. - */ -function validateTableName(name, maxNameLength) { - const len = name.length; - if (len > maxNameLength) { - throw new Error(`Table name is too long, max length is ${maxNameLength}`); - } - if (len === 0) { - throw new Error("Empty string is not allowed as table name"); - } - for (let i = 0; i < len; i++) { - let ch = name[i]; - switch (ch) { - case '.': - if (i === 0 || i === len - 1 || name[i - 1] === '.') - // single dot is allowed in the middle only - // starting with a dot hides directory in Linux - // ending with a dot can be trimmed by some Windows versions / file systems - // double or triple dot looks suspicious - // single dot allowed as compatibility, - // when someone uploads 'file_name.csv' the file name used as the table name - throw new Error('Table name cannot start or end with a dot, and only a single dot allowed'); - break; - case '?': - case ',': - case '\'': - case '"': - case '\\': - case '/': - case ':': - case ')': - case '(': - case '+': - case '*': - case '%': - case '~': - case '\u0000': - case '\u0001': - case '\u0002': - case '\u0003': - case '\u0004': - case '\u0005': - case '\u0006': - case '\u0007': - case '\u0008': - case '\u0009': // control characters, except \n. - case '\u000B': // new line allowed for compatibility, there are tests to make sure it works - case '\u000c': - case '\r': - case '\n': - case '\u000e': - case '\u000f': - case '\u007f': - case '\ufeff': // UTF-8 BOM (Byte Order Mark) can appear at the beginning of a character stream - throw new Error(`Invalid character in table name: ${ch}`); - } - } -} - -/** - * Validates a column name.
- * Throws an error if column name is invalid. - * - * @param {string} name - The column name to validate. - * @param {number} maxNameLength - The maximum length of column names. - */ -function validateColumnName(name, maxNameLength) { - const len = name.length; - if (len > maxNameLength) { - throw new Error(`Column name is too long, max length is ${maxNameLength}`); - } - if (len === 0) { - throw new Error('Empty string is not allowed as column name'); - } - for (const ch of name) { - switch (ch) { - case '?': - case '.': - case ',': - case '\'': - case '"': - case '\\': - case '/': - case ':': - case ')': - case '(': - case '+': - case '-': - case '*': - case '%': - case '~': - case '\u0000': - case '\u0001': - case '\u0002': - case '\u0003': - case '\u0004': - case '\u0005': - case '\u0006': - case '\u0007': - case '\u0008': - case '\u0009': // control characters, except \n - case '\u000B': - case '\u000c': - case '\r': - case '\n': - case '\u000e': - case '\u000f': - case '\u007f': - case '\ufeff': // UTF-8 BOM (Byte Order Mark) can appear at the beginning of a character stream - throw new Error(`Invalid character in column name: ${ch}`); - } - } -} - -exports.validateTableName = validateTableName; -exports.validateColumnName = validateColumnName; diff --git a/src/validation.ts b/src/validation.ts new file mode 100644 index 0000000..7ebea0b --- /dev/null +++ b/src/validation.ts @@ -0,0 +1,122 @@ +/** + * Validates a table name.
+ * Throws an error if table name is invalid. + * + * @param {string} name - The table name to validate. + * @param {number} maxNameLength - The maximum length of table names. + */ +function validateTableName(name: string, maxNameLength: number) { + const len = name.length; + if (len > maxNameLength) { + throw new Error(`Table name is too long, max length is ${maxNameLength}`); + } + if (len === 0) { + throw new Error("Empty string is not allowed as table name"); + } + for (let i = 0; i < len; i++) { + const ch = name[i]; + switch (ch) { + case ".": + if (i === 0 || i === len - 1 || name[i - 1] === ".") + // single dot is allowed in the middle only + // starting with a dot hides directory in Linux + // ending with a dot can be trimmed by some Windows versions / file systems + // double or triple dot looks suspicious + // single dot allowed as compatibility, + // when someone uploads 'file_name.csv' the file name used as the table name + throw new Error( + "Table name cannot start or end with a dot, and only a single dot allowed", + ); + break; + case "?": + case ",": + case "'": + case '"': + case "\\": + case "/": + case ":": + case ")": + case "(": + case "+": + case "*": + case "%": + case "~": + case "\u0000": + case "\u0001": + case "\u0002": + case "\u0003": + case "\u0004": + case "\u0005": + case "\u0006": + case "\u0007": + case "\u0008": + case "\u0009": // control characters, except \n. + case "\u000B": // new line allowed for compatibility, there are tests to make sure it works + case "\u000c": + case "\r": + case "\n": + case "\u000e": + case "\u000f": + case "\u007f": + case "\ufeff": // UTF-8 BOM (Byte Order Mark) can appear at the beginning of a character stream + throw new Error(`Invalid character in table name: ${ch}`); + } + } +} + +/** + * Validates a column name.
+ * Throws an error if column name is invalid. + * + * @param {string} name - The column name to validate. + * @param {number} maxNameLength - The maximum length of column names. + */ +function validateColumnName(name: string, maxNameLength: number) { + const len = name.length; + if (len > maxNameLength) { + throw new Error(`Column name is too long, max length is ${maxNameLength}`); + } + if (len === 0) { + throw new Error("Empty string is not allowed as column name"); + } + for (const ch of name) { + switch (ch) { + case "?": + case ".": + case ",": + case "'": + case '"': + case "\\": + case "/": + case ":": + case ")": + case "(": + case "+": + case "-": + case "*": + case "%": + case "~": + case "\u0000": + case "\u0001": + case "\u0002": + case "\u0003": + case "\u0004": + case "\u0005": + case "\u0006": + case "\u0007": + case "\u0008": + case "\u0009": // control characters, except \n + case "\u000B": + case "\u000c": + case "\r": + case "\n": + case "\u000e": + case "\u000f": + case "\u007f": + case "\ufeff": // UTF-8 BOM (Byte Order Mark) can appear at the beginning of a character stream + throw new Error(`Invalid character in column name: ${ch}`); + } + } +} + +export { validateTableName, validateColumnName }; diff --git a/test/_utils_/mockhttp.ts b/test/_utils_/mockhttp.ts new file mode 100644 index 0000000..0d83640 --- /dev/null +++ b/test/_utils_/mockhttp.ts @@ -0,0 +1,93 @@ +import http from "node:http"; +import https from "node:https"; + +class MockHttp { + server; + mockConfig; + numOfRequests; + + constructor() { + this.reset(); + } + + reset(mockConfig = {}) { + this.mockConfig = mockConfig; + this.numOfRequests = 0; + } + + async start(listenPort, secure = false, options?: Record) { + const serverCreator = secure ? https.createServer : http.createServer; + // @ts-expect-error - Testing different options, so typing is not important + this.server = serverCreator(options, (req, res) => { + const authFailed = checkAuthHeader(this.mockConfig, req); + + const body: Uint8Array[] = []; + req.on("data", (chunk: Uint8Array) => { + body.push(chunk); + }); + + req.on("end", async () => { + console.info(`Received data: ${Buffer.concat(body)}`); + this.numOfRequests++; + + const delay = + this.mockConfig.responseDelays && + this.mockConfig.responseDelays.length > 0 + ? this.mockConfig.responseDelays.pop() + : undefined; + if (delay) { + await sleep(delay); + } + + const responseCode = authFailed + ? 401 + : this.mockConfig.responseCodes && + this.mockConfig.responseCodes.length > 0 + ? this.mockConfig.responseCodes.pop() + : 204; + res.writeHead(responseCode); + res.end(); + }); + }); + + this.server.listen(listenPort, () => { + console.info(`Server is running on port ${listenPort}`); + }); + } + + async stop() { + if (this.server) { + this.server.close(); + } + } +} + +function checkAuthHeader(mockConfig, req) { + let authFailed = false; + const header = (req.headers.authorization || "").split(/\s+/); + switch (header[0]) { + case "Basic": { + const auth = Buffer.from(header[1], "base64").toString().split(/:/); + if (mockConfig.username !== auth[0] || mockConfig.password !== auth[1]) { + authFailed = true; + } + break; + } + case "Bearer": + if (mockConfig.token !== header[1]) { + authFailed = true; + } + break; + default: + if (mockConfig.username || mockConfig.password || mockConfig.token) { + authFailed = true; + } + } + return authFailed; +} + +function sleep(ms) { + return new Promise((resolve) => setTimeout(resolve, ms)); +} + +export { MockHttp }; diff --git a/test/_utils_/mockproxy.ts b/test/_utils_/mockproxy.ts new file mode 100644 index 0000000..f26e404 --- /dev/null +++ b/test/_utils_/mockproxy.ts @@ -0,0 +1,60 @@ +import { write, listen, shutdown } from "./proxyfunctions"; + +const CHALLENGE_LENGTH = 512; + +class MockProxy { + mockConfig: { + auth?: boolean; + assertions?: boolean; + }; + dataSentToRemote: string[]; + hasSentChallenge: boolean; + client: unknown; + + constructor(mockConfig) { + if (!mockConfig) { + throw new Error("Missing mock config"); + } + this.mockConfig = mockConfig; + this.dataSentToRemote = []; + } + + async start(listenPort: number, tlsOptions?: Record) { + await listen( + this, + listenPort, + async (data) => { + console.info(`received from client: ${data}`); + if (this.mockConfig.assertions) { + this.dataSentToRemote.push(data.toString()); + } + if (this.mockConfig.auth && !this.hasSentChallenge) { + await write(this.client, mockChallenge()); + this.hasSentChallenge = true; + } + }, + tlsOptions, + ); + } + + async stop() { + await shutdown(this); + } + + getDataSentToRemote() { + if (!this.mockConfig.assertions) { + throw new Error("Should be called only when assertions switched on"); + } + return this.dataSentToRemote; + } +} + +function mockChallenge() { + let challenge = ""; + for (let i = 0; i < CHALLENGE_LENGTH - 1; i++) { + challenge += "a"; + } + return challenge + "\n"; +} + +export { MockProxy }; diff --git a/test/_utils_/proxy.ts b/test/_utils_/proxy.ts new file mode 100644 index 0000000..00394bf --- /dev/null +++ b/test/_utils_/proxy.ts @@ -0,0 +1,53 @@ +import { Socket } from "node:net"; +import { write, listen, shutdown, connect, close } from "./proxyfunctions"; + +// handles only a single client +// client -> server (Proxy) -> remote (QuestDB) +// client <- server (Proxy) <- remote (QuestDB) +class Proxy { + client: unknown; + remote: Socket; + + constructor() { + this.remote = new Socket(); + + this.remote.on("data", async (data: unknown) => { + console.info(`received from remote, forwarding to client: ${data}`); + await write(this.client, data); + }); + + this.remote.on("close", () => { + console.info("remote connection closed"); + }); + + this.remote.on("error", (err: unknown) => { + console.error(`remote connection: ${err}`); + }); + } + + async start(listenPort: unknown, remotePort: unknown, remoteHost: unknown, tlsOptions: Record) { + return new Promise((resolve) => { + this.remote.on("ready", async () => { + console.info("remote connection ready"); + await listen( + this, + listenPort, + async (data: unknown) => { + console.info(`received from client, forwarding to remote: ${data}`); + await write(this.remote, data); + }, + tlsOptions, + ); + resolve(); + }); + + connect(this, remotePort, remoteHost); + }); + } + + async stop() { + await shutdown(this, async () => await close()); + } +} + +export { Proxy }; diff --git a/test/_utils_/proxyfunctions.ts b/test/_utils_/proxyfunctions.ts new file mode 100644 index 0000000..0d5fab9 --- /dev/null +++ b/test/_utils_/proxyfunctions.ts @@ -0,0 +1,70 @@ +import net from "node:net"; +import tls from "node:tls"; + +const LOCALHOST = "localhost"; + +async function write(socket, data) { + return new Promise((resolve, reject) => { + socket.write(data, "utf8", (err) => { + if (err) { + reject(err) + } else { + resolve(); + } + }); + }); +} + +async function listen(proxy, listenPort, dataHandler, tlsOptions) { + return new Promise((resolve) => { + const clientConnHandler = (client) => { + console.info("client connected"); + if (proxy.client) { + console.error("There is already a client connected"); + process.exit(1); + } + proxy.client = client; + + client.on("data", dataHandler); + }; + + proxy.server = tlsOptions + ? tls.createServer(tlsOptions, clientConnHandler) + : net.createServer(clientConnHandler); + + proxy.server.on("error", (err) => { + console.error(`server error: ${err}`); + }); + + proxy.server.listen(listenPort, LOCALHOST, () => { + console.info(`listening for clients on ${listenPort}`); + resolve(); + }); + }); +} + +async function shutdown(proxy, onServerClose = async () => { }) { + console.info("closing proxy"); + return new Promise((resolve) => { + proxy.server.close(async () => { + await onServerClose(); + resolve(); + }); + }); +} + +async function connect(proxy, remotePort, remoteHost) { + console.info(`opening remote connection to ${remoteHost}:${remotePort}`); + return new Promise((resolve) => { + proxy.remote.connect(remotePort, remoteHost, () => resolve()); + }); +} + +async function close() { + console.info("closing remote connection"); + return new Promise((resolve) => { + resolve(); + }); +} + +export { write, listen, shutdown, connect, close }; diff --git a/test/logging.test.js b/test/logging.test.js deleted file mode 100644 index 06e7091..0000000 --- a/test/logging.test.js +++ /dev/null @@ -1,73 +0,0 @@ -'use strict'; - -describe('Default logging suite', function () { - let error, warn, info, debug; - let log; - - beforeAll(() => { - error = jest.spyOn(console, 'error').mockImplementation(() => {}); - warn = jest.spyOn(console, 'warn').mockImplementation(() => {}); - info = jest.spyOn(console, 'info').mockImplementation(() => {}); - debug = jest.spyOn(console, 'debug').mockImplementation(() => {}); - log = require('../src/logging').log; - }); - - afterAll(() => { - error.mockReset(); - warn.mockReset(); - info.mockReset(); - debug.mockReset(); - }); - - afterEach(() => { - error.mockClear(); - warn.mockClear(); - info.mockClear(); - debug.mockClear(); - }); - - it('can log error level messages', function () { - const testMessage = 'ERROR ERROR ERROR'; - log('error', testMessage); - expect(error).toHaveBeenCalledTimes(1); - expect(warn).toHaveBeenCalledTimes(0); - expect(info).toHaveBeenCalledTimes(0); - expect(debug).toHaveBeenCalledTimes(0); - expect(error).toHaveBeenCalledWith(testMessage); - }); - - it('can log warn level messages', function () { - const testMessage = 'WARN WARN WARN'; - log('warn', testMessage); - expect(error).toHaveBeenCalledTimes(0); - expect(warn).toHaveBeenCalledTimes(1); - expect(info).toHaveBeenCalledTimes(0); - expect(debug).toHaveBeenCalledTimes(0); - expect(warn).toHaveBeenCalledWith(testMessage); - }); - - it('can log info level messages', function () { - const testMessage = 'INFO INFO INFO'; - log('info', testMessage); - expect(error).toHaveBeenCalledTimes(0); - expect(warn).toHaveBeenCalledTimes(0); - expect(info).toHaveBeenCalledTimes(1); - expect(debug).toHaveBeenCalledTimes(0); - expect(info).toHaveBeenCalledWith(testMessage); - }); - - it('cannot log debug level messages', function () { - const testMessage = 'DEBUG DEBUG DEBUG'; - log('debug', testMessage); - expect(error).toHaveBeenCalledTimes(0); - expect(warn).toHaveBeenCalledTimes(0); - expect(info).toHaveBeenCalledTimes(0); - expect(debug).toHaveBeenCalledTimes(0); - }); - - it('throws exception if log level is not supported', function () { - expect( - () => log('trace', 'TRACE TRACE TRACE') - ).toThrow('Invalid log level: \'trace\''); - }); -}); diff --git a/test/logging.test.ts b/test/logging.test.ts new file mode 100644 index 0000000..4384e60 --- /dev/null +++ b/test/logging.test.ts @@ -0,0 +1,84 @@ +import { + describe, + it, + beforeAll, + afterAll, + afterEach, + expect, + vi, +} from "vitest"; + +describe("Default logging suite", function () { + const error = vi.spyOn(console, "error").mockImplementation(() => { }); + const warn = vi.spyOn(console, "warn").mockImplementation(() => { }); + const info = vi.spyOn(console, "info").mockImplementation(() => { }); + const debug = vi.spyOn(console, "debug").mockImplementation(() => { }); + let log: ( + level: "error" | "warn" | "info" | "debug", + message: string, + ) => void; + + beforeAll(async () => { + log = (await import("../src/logging")).log; + }); + + afterAll(() => { + error.mockReset(); + warn.mockReset(); + info.mockReset(); + debug.mockReset(); + }); + + afterEach(() => { + error.mockClear(); + warn.mockClear(); + info.mockClear(); + debug.mockClear(); + }); + + it("can log error level messages", function () { + const testMessage = "ERROR ERROR ERROR"; + log("error", testMessage); + expect(error).toHaveBeenCalledTimes(1); + expect(warn).toHaveBeenCalledTimes(0); + expect(info).toHaveBeenCalledTimes(0); + expect(debug).toHaveBeenCalledTimes(0); + expect(error).toHaveBeenCalledWith(testMessage); + }); + + it("can log warn level messages", function () { + const testMessage = "WARN WARN WARN"; + log("warn", testMessage); + expect(error).toHaveBeenCalledTimes(0); + expect(warn).toHaveBeenCalledTimes(1); + expect(info).toHaveBeenCalledTimes(0); + expect(debug).toHaveBeenCalledTimes(0); + expect(warn).toHaveBeenCalledWith(testMessage); + }); + + it("can log info level messages", function () { + const testMessage = "INFO INFO INFO"; + log("info", testMessage); + expect(error).toHaveBeenCalledTimes(0); + expect(warn).toHaveBeenCalledTimes(0); + expect(info).toHaveBeenCalledTimes(1); + expect(debug).toHaveBeenCalledTimes(0); + expect(info).toHaveBeenCalledWith(testMessage); + }); + + it("cannot log debug level messages", function () { + const testMessage = "DEBUG DEBUG DEBUG"; + log("debug", testMessage); + expect(error).toHaveBeenCalledTimes(0); + expect(warn).toHaveBeenCalledTimes(0); + expect(info).toHaveBeenCalledTimes(0); + expect(debug).toHaveBeenCalledTimes(0); + }); + + it("throws exception if log level is not supported", function () { + // @ts-expect-error - Testing invalid log level + expect(() => log("trace", "TRACE TRACE TRACE")).toThrow( + "Invalid log level: 'trace'", + ); + }); +}); diff --git a/test/mockhttp.js b/test/mockhttp.js deleted file mode 100644 index b2ca968..0000000 --- a/test/mockhttp.js +++ /dev/null @@ -1,86 +0,0 @@ -'use strict'; - -const http = require('http'); -const https = require('https'); - -class MockHttp { - server; - mockConfig; - numOfRequests; - - constructor() { - this.reset(); - } - - reset(mockConfig = {}) { - this.mockConfig = mockConfig; - this.numOfRequests = 0; - } - - async start(listenPort, secure = false, options = undefined) { - const createServer = secure ? https.createServer : http.createServer; - this.server = createServer(options, (req, res) => { - const authFailed = checkAuthHeader(this.mockConfig, req); - - const body = []; - req.on('data', chunk => { - body.push(chunk); - }); - - req.on('end', async () => { - console.info(`Received data: ${Buffer.concat(body)}`); - this.numOfRequests++; - - const delay = this.mockConfig.responseDelays && this.mockConfig.responseDelays.length > 0 ? this.mockConfig.responseDelays.pop() : undefined; - if (delay) { - await sleep(delay); - } - - const responseCode = authFailed ? 401 : ( - this.mockConfig.responseCodes && this.mockConfig.responseCodes.length > 0 ? this.mockConfig.responseCodes.pop() : 204 - ); - res.writeHead(responseCode); - res.end(); - }); - }) - - this.server.listen(listenPort, () => { - console.info(`Server is running on port ${listenPort}`); - }); - } - - async stop() { - if (this.server) { - this.server.close(); - } - } -} - -function checkAuthHeader(mockConfig, req) { - let authFailed = false; - const header = (req.headers.authorization || '').split(/\s+/); - switch (header[0]) { - case 'Basic': - const auth = Buffer.from(header[1], 'base64').toString().split(/:/); - if (mockConfig.username !== auth[0] || mockConfig.password !== auth[1]) { - authFailed = true; - } - break; - case 'Bearer': - if (mockConfig.token !== header[1]) { - authFailed = true; - } - break; - default: - if (mockConfig.username || mockConfig.password || mockConfig.token) { - authFailed = true; - } - } - return authFailed; -} - -function sleep(ms) { - return new Promise(resolve => setTimeout(resolve, ms)); -} - -exports.MockHttp = MockHttp; diff --git a/test/mockproxy.js b/test/mockproxy.js deleted file mode 100644 index ec422f6..0000000 --- a/test/mockproxy.js +++ /dev/null @@ -1,49 +0,0 @@ -'use strict'; - -const { write, listen, shutdown } = require('./proxyfunctions'); - -const CHALLENGE_LENGTH = 512; - -class MockProxy { - constructor(mockConfig) { - if (!mockConfig) { - throw new Error('Missing mock config'); - } - this.mockConfig = mockConfig; - this.dataSentToRemote = []; - } - - async start(listenPort, tlsOptions = undefined) { - await listen(this, listenPort, async data => { - console.info(`received from client: ${data}`); - if (this.mockConfig.assertions) { - this.dataSentToRemote.push(data.toString()); - } - if (this.mockConfig.auth && !this.hasSentChallenge) { - await write(this.client, mockChallenge()); - this.hasSentChallenge = true; - } - }, tlsOptions); - } - - async stop() { - await shutdown(this); - } - - getDataSentToRemote() { - if (!this.mockConfig.assertions) { - throw new Error('Should be called only when assertions switched on'); - } - return this.dataSentToRemote; - } -} - -function mockChallenge() { - let challenge = ''; - for (let i = 0; i < CHALLENGE_LENGTH - 1; i++) { - challenge += 'a'; - } - return challenge + '\n'; -} - -exports.MockProxy = MockProxy; diff --git a/test/options.test.js b/test/options.test.js deleted file mode 100644 index 0f64a40..0000000 --- a/test/options.test.js +++ /dev/null @@ -1,638 +0,0 @@ -'use strict'; - -const { SenderOptions } = require('../src/options'); -const http = require("http"); - -describe('Configuration string parser suite', function () { - it('can parse a basic config string', function () { - const options = SenderOptions.fromConfig('https::addr=host;username=user1;password=pwd;'); - expect(options.protocol).toBe('https'); - expect(options.addr).toBe('host'); - expect(options.username).toBe('user1'); - expect(options.password).toBe('pwd'); - }); - - it('can parse a config string from environment variable', async function () { - process.env.QDB_CLIENT_CONF = 'tcp::addr=host;'; - const options = SenderOptions.fromEnv(); - expect(options.protocol).toBe('tcp'); - expect(options.addr).toBe('host'); - }); - - it('accepts only lowercase protocols', function () { - let options = SenderOptions.fromConfig('tcp::addr=host;'); - expect(options.protocol).toBe('tcp'); - - options = SenderOptions.fromConfig('tcps::addr=host;'); - expect(options.protocol).toBe('tcps'); - - options = SenderOptions.fromConfig('http::addr=host;'); - expect(options.protocol).toBe('http'); - - options = SenderOptions.fromConfig('https::addr=host;'); - expect(options.protocol).toBe('https'); - - expect( - () => SenderOptions.fromConfig('HTTP::') - ).toThrow('Invalid protocol: \'HTTP\', accepted protocols: \'http\', \'https\', \'tcp\', \'tcps\''); - expect( - () => SenderOptions.fromConfig('Http::') - ).toThrow('Invalid protocol: \'Http\', accepted protocols: \'http\', \'https\', \'tcp\', \'tcps\''); - expect( - () => SenderOptions.fromConfig('HtTps::') - ).toThrow('Invalid protocol: \'HtTps\', accepted protocols: \'http\', \'https\', \'tcp\', \'tcps\''); - - expect( - () => SenderOptions.fromConfig('TCP::') - ).toThrow('Invalid protocol: \'TCP\', accepted protocols: \'http\', \'https\', \'tcp\', \'tcps\''); - expect( - () => SenderOptions.fromConfig('TcP::') - ).toThrow('Invalid protocol: \'TcP\', accepted protocols: \'http\', \'https\', \'tcp\', \'tcps\''); - expect( - () => SenderOptions.fromConfig('Tcps::') - ).toThrow('Invalid protocol: \'Tcps\', accepted protocols: \'http\', \'https\', \'tcp\', \'tcps\''); - }); - - it('considers that keys and values are case-sensitive', function () { - const options = SenderOptions.fromConfig('tcps::addr=Host;username=useR1;token=TOKEN;'); - expect(options.protocol).toBe('tcps'); - expect(options.addr).toBe('Host'); - expect(options.username).toBe('useR1'); - expect(options.token).toBe('TOKEN'); - - expect( - () => SenderOptions.fromConfig('tcps::addr=Host;UserNAME=useR1;PaSswOrD=pWd;') - ).toThrow('Unknown configuration key: \'UserNAME\''); - expect( - () => SenderOptions.fromConfig('tcps::addr=Host;PaSswOrD=pWd;') - ).toThrow('Unknown configuration key: \'PaSswOrD\''); - }); - - it('can parse with or without the last semicolon', function () { - let options = SenderOptions.fromConfig('https::addr=host:9002'); - expect(options.protocol).toBe('https'); - expect(options.addr).toBe('host:9002'); - - options = SenderOptions.fromConfig('https::addr=host:9002;'); - expect(options.protocol).toBe('https'); - expect(options.addr).toBe('host:9002'); - - options = SenderOptions.fromConfig('https::addr=host:9002;token=abcde'); - expect(options.protocol).toBe('https'); - expect(options.addr).toBe('host:9002'); - expect(options.token).toBe('abcde'); - - options = SenderOptions.fromConfig('https::addr=host:9002;token=abcde;'); - expect(options.protocol).toBe('https'); - expect(options.addr).toBe('host:9002'); - expect(options.token).toBe('abcde'); - - options = SenderOptions.fromConfig('https::addr=host:9002;token=abcde;;'); - expect(options.protocol).toBe('https'); - expect(options.addr).toBe('host:9002'); - expect(options.token).toBe('abcde;'); - - options = SenderOptions.fromConfig('https::addr=host:9002;token=abcde;;;'); - expect(options.protocol).toBe('https'); - expect(options.addr).toBe('host:9002'); - expect(options.token).toBe('abcde;'); - }); - - it('can parse escaped config string values', function () { - const options = SenderOptions.fromConfig('https::addr=host:9002;username=us;;;;;;er;;1;;;password=p;;wd;'); - expect(options.protocol).toBe('https'); - expect(options.addr).toBe('host:9002'); - expect(options.username).toBe('us;;;er;1;'); - expect(options.password).toBe('p;wd'); - }); - - it('can parse the address', function () { - let options = SenderOptions.fromConfig('https::addr=host1:9002;token=resttoken123;'); - expect(options.protocol).toBe('https'); - expect(options.addr).toBe('host1:9002'); - expect(options.host).toBe('host1'); - expect(options.port).toBe(9002); - expect(options.token).toBe('resttoken123'); - - options = SenderOptions.fromConfig('tcps::addr=host2:9005;username=user1;token=jwkprivkey123;'); - expect(options.protocol).toBe('tcps'); - expect(options.addr).toBe('host2:9005'); - expect(options.host).toBe('host2'); - expect(options.port).toBe(9005); - expect(options.username).toBe('user1'); - expect(options.token).toBe('jwkprivkey123'); - }); - - it('can default the port', function () { - let options = SenderOptions.fromConfig('https::addr=hostname;token=resttoken123;'); - expect(options.protocol).toBe('https'); - expect(options.addr).toBe('hostname'); - expect(options.host).toBe('hostname'); - expect(options.port).toBe(9000); - expect(options.token).toBe('resttoken123'); - - options = SenderOptions.fromConfig('http::addr=hostname;token=resttoken123;'); - expect(options.protocol).toBe('http'); - expect(options.addr).toBe('hostname'); - expect(options.host).toBe('hostname'); - expect(options.port).toBe(9000); - expect(options.token).toBe('resttoken123'); - - options = SenderOptions.fromConfig('tcps::addr=hostname;username=user1;token=jwkprivkey123;'); - expect(options.protocol).toBe('tcps'); - expect(options.addr).toBe('hostname'); - expect(options.host).toBe('hostname'); - expect(options.port).toBe(9009); - expect(options.username).toBe('user1'); - expect(options.token).toBe('jwkprivkey123'); - - options = SenderOptions.fromConfig('tcp::addr=hostname;username=user1;token=jwkprivkey123;'); - expect(options.protocol).toBe('tcp'); - expect(options.addr).toBe('hostname'); - expect(options.host).toBe('hostname'); - expect(options.port).toBe(9009); - expect(options.username).toBe('user1'); - expect(options.token).toBe('jwkprivkey123'); - }); - - it('fails if port is not a positive integer', function () { - expect( - () => SenderOptions.fromConfig('tcp::addr=host:;') - ).toThrow('Port is required'); - expect( - () => SenderOptions.fromConfig('tcp::addr=host:0') - ).toThrow('Invalid port: 0'); - expect( - () => SenderOptions.fromConfig('tcp::addr=host:0.2') - ).toThrow('Invalid port: 0.2'); - expect( - () => SenderOptions.fromConfig('tcp::addr=host:-2') - ).toThrow('Invalid port: -2'); - expect( - () => SenderOptions.fromConfig('tcp::addr=host:!;') - ).toThrow('Invalid port: \'!\''); - expect( - () => SenderOptions.fromConfig('tcp::addr=host:9009x;') - ).toThrow('Invalid port: \'9009x\''); - expect( - () => SenderOptions.fromConfig('tcp::addr=host:900 9;') - ).toThrow('Invalid port: \'900 9\''); - }); - - it('fails if init_buf_size is not a positive integer', function () { - expect( - () => SenderOptions.fromConfig('tcp::addr=host;init_buf_size=;') - ).toThrow('Invalid configuration, value is not set for \'init_buf_size\''); - expect( - () => SenderOptions.fromConfig('tcp::addr=host;init_buf_size=1024a;') - ).toThrow('Invalid initial buffer size option, not a number: \'1024a\''); - expect( - () => SenderOptions.fromConfig('tcp::addr=host;init_buf_size=102 4;') - ).toThrow('Invalid initial buffer size option, not a number: \'102 4\''); - expect( - () => SenderOptions.fromConfig('tcp::addr=host;init_buf_size=0;') - ).toThrow('Invalid initial buffer size option: 0'); - }); - - it('fails if max_buf_size is not a positive integer', function () { - expect( - () => SenderOptions.fromConfig('tcp::addr=host;max_buf_size=;') - ).toThrow('Invalid configuration, value is not set for \'max_buf_size\''); - expect( - () => SenderOptions.fromConfig('tcp::addr=host;max_buf_size=1024a;') - ).toThrow('Invalid max buffer size option, not a number: \'1024a\''); - expect( - () => SenderOptions.fromConfig('tcp::addr=host;max_buf_size=102 4;') - ).toThrow('Invalid max buffer size option, not a number: \'102 4\''); - expect( - () => SenderOptions.fromConfig('tcp::addr=host;max_buf_size=0;') - ).toThrow('Invalid max buffer size option: 0'); - }); - - it('rejects missing or empty hostname', function () { - expect( - () => SenderOptions.fromConfig('http::') - ).toThrow('Invalid configuration, \'addr\' is required'); - expect( - () => SenderOptions.fromConfig('http::;') - ).toThrow('Missing \'=\' sign in \'\''); - expect( - () => SenderOptions.fromConfig('http::addr=;') - ).toThrow('Invalid configuration, value is not set for \'addr\''); - expect( - () => SenderOptions.fromConfig('http::addr=;username=user1;') - ).toThrow('Invalid configuration, value is not set for \'addr\''); - expect( - () => SenderOptions.fromConfig('http::username=user1;addr=;') - ).toThrow('Invalid configuration, value is not set for \'addr\''); - expect( - () => SenderOptions.fromConfig('http::addr=:9000;') - ).toThrow('Host name is required'); - - let options = SenderOptions.fromConfig('http::addr=x;'); - expect(options.protocol).toBe('http'); - expect(options.host).toBe('x'); - expect(options.host).toBe('x'); - }); - - it('does not default optional fields', function () { - const options = SenderOptions.fromConfig('https::addr=host:9000;token=abcdef123;'); - expect(options.protocol).toBe('https'); - expect(options.token).toBe('abcdef123'); - expect(options.username).toBe(undefined); - expect(options.password).toBe(undefined); - }); - - it('rejects invalid config value', function () { - expect( - () => SenderOptions.fromConfig('http::addr=host:9000;username=;') - ).toThrow('Invalid configuration, value is not set for \'username\''); - - expect( - () => SenderOptions.fromConfig('http::addr=host:9000;username=user\t;') - ).toThrow('Invalid configuration, control characters are not allowed: \'user\t\''); - expect( - () => SenderOptions.fromConfig('http::addr=host:9000;username=user\n;') - ).toThrow('Invalid configuration, control characters are not allowed: \'user\n\''); - - let options = SenderOptions.fromConfig('http::addr=host:9000;username=us\x7Eer;'); - expect(options.protocol).toBe('http'); - expect(options.addr).toBe('host:9000'); - expect(options.username).toBe('us\x7Eer'); - - expect( - () => SenderOptions.fromConfig('http::addr=host:9000;username=us\x7Fer;') - ).toThrow('Invalid configuration, control characters are not allowed: \'us\x7Fer\''); - expect( - () => SenderOptions.fromConfig('http::addr=host:9000;username=us\x9Fer;') - ).toThrow('Invalid configuration, control characters are not allowed: \'us\x9Fer\''); - - options = SenderOptions.fromConfig('http::addr=host:9000;username=us\xA0er;'); - expect(options.protocol).toBe('http'); - expect(options.addr).toBe('host:9000'); - expect(options.username).toBe('us\xA0er'); - }); - - it('reject invalid config keys', function () { - expect( - () => SenderOptions.fromConfig('http::addr=host:9000;username=user1;pass=pwd;') - ).toThrow('Unknown configuration key: \'pass\''); - expect( - () => SenderOptions.fromConfig('http::addr=host:9000;user=user1;password=pwd;') - ).toThrow('Unknown configuration key: \'user\''); - expect( - () => SenderOptions.fromConfig('http::addr=host:9000;username =user1;password=pwd;') - ).toThrow('Unknown configuration key: \'username \''); - expect( - () => SenderOptions.fromConfig('http::addr=host:9000; username=user1;password=pwd;') - ).toThrow('Unknown configuration key: \' username\''); - expect( - () => SenderOptions.fromConfig('http::addr=host:9000;user name=user1;password=pwd;') - ).toThrow('Unknown configuration key: \'user name\''); - }); - - it('rejects keys without value', function () { - expect( - () => SenderOptions.fromConfig('http::addr;username=user1') - ).toThrow('Missing \'=\' sign in \'addr\''); - expect( - () => SenderOptions.fromConfig('http::addr=host:9000;username;') - ).toThrow('Missing \'=\' sign in \'username\''); - }); - - it('throws error if protocol is invalid', function () { - expect( - () => SenderOptions.fromConfig('::addr=host;username=user1;password=pwd;') - ).toThrow('Invalid protocol: \'\', accepted protocols: \'http\', \'https\', \'tcp\', \'tcps\''); - expect( - () => SenderOptions.fromConfig('htt::addr=host;username=user1;password=pwd;') - ).toThrow('Invalid protocol: \'htt\', accepted protocols: \'http\', \'https\', \'tcp\', \'tcps\''); - }); - - it('throws error if protocol is missing', function () { - expect( - () => SenderOptions.fromConfig('addr=host;username=user1;password=pwd;') - ).toThrow('Missing protocol, configuration string format: \'protocol::key1=value1;key2=value2;key3=value3;\''); - expect( - () => SenderOptions.fromConfig('https:addr=host;username=user1;password=pwd;') - ).toThrow('Missing protocol, configuration string format: \'protocol::key1=value1;key2=value2;key3=value3;\''); - expect( - () => SenderOptions.fromConfig('https addr=host;username=user1;password=pwd;') - ).toThrow('Missing protocol, configuration string format: \'protocol::key1=value1;key2=value2;key3=value3;\''); - }); - - it('throws error if configuration string is missing', function () { - expect( - () => SenderOptions.fromConfig() - ).toThrow('Configuration string is missing'); - expect( - () => SenderOptions.fromConfig('') - ).toThrow('Configuration string is missing'); - expect( - () => SenderOptions.fromConfig(null) - ).toThrow('Configuration string is missing'); - expect( - () => SenderOptions.fromConfig(undefined) - ).toThrow('Configuration string is missing'); - }); - - it('can parse auto_flush config', function () { - let options = SenderOptions.fromConfig('http::addr=host:9000;auto_flush=on;'); - expect(options.protocol).toBe('http'); - expect(options.host).toBe('host'); - expect(options.port).toBe(9000); - expect(options.auto_flush).toBe(true); - - options = SenderOptions.fromConfig('http::addr=host:9000;auto_flush=off;'); - expect(options.protocol).toBe('http'); - expect(options.host).toBe('host'); - expect(options.port).toBe(9000); - expect(options.auto_flush).toBe(false); - - expect( - () => SenderOptions.fromConfig('http::addr=host:9000;auto_flush=ON;') - ).toThrow('Invalid auto flush option: \'ON\''); - expect( - () => SenderOptions.fromConfig('http::addr=host:9000;auto_flush=On;') - ).toThrow('Invalid auto flush option: \'On\''); - expect( - () => SenderOptions.fromConfig('http::addr=host:9000;auto_flush=true;') - ).toThrow('Invalid auto flush option: \'true\''); - expect( - () => SenderOptions.fromConfig('http::addr=host:9000;auto_flush=OFF;') - ).toThrow('Invalid auto flush option: \'OFF\''); - expect( - () => SenderOptions.fromConfig('http::addr=host:9000;auto_flush=Off;') - ).toThrow('Invalid auto flush option: \'Off\''); - expect( - () => SenderOptions.fromConfig('http::addr=host:9000;auto_flush=false;') - ).toThrow('Invalid auto flush option: \'false\''); - }); - - it('can parse auto_flush_rows config', function () { - let options = SenderOptions.fromConfig('http::addr=host:9000;auto_flush_rows=123;'); - expect(options.protocol).toBe('http'); - expect(options.auto_flush_rows).toBe(123); - - options = SenderOptions.fromConfig('http::addr=host:9000;auto_flush_rows=0;'); - expect(options.protocol).toBe('http'); - expect(options.auto_flush_rows).toBe(0); - - expect( - () => SenderOptions.fromConfig('http::addr=host:9000;auto_flush_rows=-123;') - ).toThrow('Invalid auto flush rows option: -123'); - expect( - () => SenderOptions.fromConfig('http::addr=host:9000;auto_flush_rows=1.23;') - ).toThrow('Invalid auto flush rows option: 1.23'); - expect( - () => SenderOptions.fromConfig('http::addr=host:9000;auto_flush_rows=123x;') - ).toThrow('Invalid auto flush rows option, not a number: \'123x\''); - expect( - () => SenderOptions.fromConfig('http::addr=host:9000;auto_flush_rows=a123;') - ).toThrow('Invalid auto flush rows option, not a number: \'a123\''); - expect( - () => SenderOptions.fromConfig('http::addr=host:9000;auto_flush_rows=1w23;') - ).toThrow('Invalid auto flush rows option, not a number: \'1w23\''); - }); - - it('can parse auto_flush_interval config', function () { - let options = SenderOptions.fromConfig('http::addr=host:9000;auto_flush_interval=30'); - expect(options.protocol).toBe('http'); - expect(options.auto_flush_interval).toBe(30); - - options = SenderOptions.fromConfig('http::addr=host:9000;auto_flush_interval=0'); - expect(options.protocol).toBe('http'); - expect(options.auto_flush_interval).toBe(0); - - expect( - () => SenderOptions.fromConfig('http::addr=host:9000;auto_flush_interval=-60') - ).toThrow('Invalid auto flush interval option: -60'); - expect( - () => SenderOptions.fromConfig('http::addr=host:9000;auto_flush_interval=-6.0') - ).toThrow('Invalid auto flush interval option: -6'); - expect( - () => SenderOptions.fromConfig('http::addr=host:9000;auto_flush_interval=60x') - ).toThrow('Invalid auto flush interval option, not a number: \'60x\''); - expect( - () => SenderOptions.fromConfig('http::addr=host:9000;auto_flush_interval=a60') - ).toThrow('Invalid auto flush interval option, not a number: \'a60\''); - expect( - () => SenderOptions.fromConfig('http::addr=host:9000;auto_flush_interval=6w0') - ).toThrow('Invalid auto flush interval option, not a number: \'6w0\''); - }); - - it('can parse tls_verify config', function () { - let options = SenderOptions.fromConfig('http::addr=host:9000;tls_verify=on'); - expect(options.protocol).toBe('http'); - expect(options.host).toBe('host'); - expect(options.port).toBe(9000); - expect(options.tls_verify).toBe(true); - - options = SenderOptions.fromConfig('http::addr=host:9000;tls_verify=unsafe_off'); - expect(options.protocol).toBe('http'); - expect(options.host).toBe('host'); - expect(options.port).toBe(9000); - expect(options.tls_verify).toBe(false); - - expect( - () => SenderOptions.fromConfig('http::addr=host:9000;tls_verify=ON') - ).toThrow('Invalid TLS verify option: \'ON\''); - expect( - () => SenderOptions.fromConfig('http::addr=host:9000;tls_verify=On') - ).toThrow('Invalid TLS verify option: \'On\''); - expect( - () => SenderOptions.fromConfig('http::addr=host:9000;tls_verify=true') - ).toThrow('Invalid TLS verify option: \'true\''); - expect( - () => SenderOptions.fromConfig('http::addr=host:9000;tls_verify=OFF') - ).toThrow('Invalid TLS verify option: \'OFF\''); - expect( - () => SenderOptions.fromConfig('http::addr=host:9000;tls_verify=Off') - ).toThrow('Invalid TLS verify option: \'Off\''); - expect( - () => SenderOptions.fromConfig('http::addr=host:9000;tls_verify=UNSAFE_OFF') - ).toThrow('Invalid TLS verify option: \'UNSAFE_OFF\''); - expect( - () => SenderOptions.fromConfig('http::addr=host:9000;tls_verify=Unsafe_Off') - ).toThrow('Invalid TLS verify option: \'Unsafe_Off\''); - expect( - () => SenderOptions.fromConfig('http::addr=host:9000;tls_verify=false') - ).toThrow('Invalid TLS verify option: \'false\''); - }); - - it('fails with tls_roots or tls_roots_password config', function () { - expect( - () => SenderOptions.fromConfig('http::addr=host:9000;tls_roots=/whatever/path') - ).toThrow('\'tls_roots\' and \'tls_roots_password\' options are not supported, please, use the \'tls_ca\' option or the NODE_EXTRA_CA_CERTS environment variable instead'); - expect( - () => SenderOptions.fromConfig('http::addr=host:9000;tls_roots_password=pwd') - ).toThrow('\'tls_roots\' and \'tls_roots_password\' options are not supported, please, use the \'tls_ca\' option or the NODE_EXTRA_CA_CERTS environment variable instead'); - }); - - it('can parse request_min_throughput config', function () { - let options = SenderOptions.fromConfig('http::addr=host:9000;request_min_throughput=300'); - expect(options.protocol).toBe('http'); - expect(options.request_min_throughput).toBe(300); - - expect( - () => SenderOptions.fromConfig('http::addr=host:9000;request_min_throughput=0') - ).toThrow('Invalid request min throughput option: 0'); - expect( - () => SenderOptions.fromConfig('http::addr=host:9000;request_min_throughput=0.5') - ).toThrow('Invalid request min throughput option: 0.5'); - expect( - () => SenderOptions.fromConfig('http::addr=host:9000;request_min_throughput=-60') - ).toThrow('Invalid request min throughput option: -60'); - expect( - () => SenderOptions.fromConfig('http::addr=host:9000;request_min_throughput=60x') - ).toThrow('Invalid request min throughput option, not a number: \'60x\''); - expect( - () => SenderOptions.fromConfig('http::addr=host:9000;request_min_throughput=a60') - ).toThrow('Invalid request min throughput option, not a number: \'a60\''); - expect( - () => SenderOptions.fromConfig('http::addr=host:9000;request_min_throughput=6w0') - ).toThrow('Invalid request min throughput option, not a number: \'6w0\''); - }); - - it('can parse request_timeout config', function () { - let options = SenderOptions.fromConfig('http::addr=host:9000;request_timeout=30'); - expect(options.protocol).toBe('http'); - expect(options.request_timeout).toBe(30); - - expect( - () => SenderOptions.fromConfig('http::addr=host:9000;request_timeout=0') - ).toThrow('Invalid request timeout option: 0'); - expect( - () => SenderOptions.fromConfig('http::addr=host:9000;request_timeout=10.32') - ).toThrow('Invalid request timeout option: 10.32'); - expect( - () => SenderOptions.fromConfig('http::addr=host:9000;request_timeout=-60') - ).toThrow('Invalid request timeout option: -60'); - expect( - () => SenderOptions.fromConfig('http::addr=host:9000;request_timeout=60x') - ).toThrow('Invalid request timeout option, not a number: \'60x\''); - expect( - () => SenderOptions.fromConfig('http::addr=host:9000;request_timeout=a60') - ).toThrow('Invalid request timeout option, not a number: \'a60\''); - expect( - () => SenderOptions.fromConfig('http::addr=host:9000;request_timeout=6w0') - ).toThrow('Invalid request timeout option, not a number: \'6w0\''); - }); - - it('can parse retry_timeout config', function () { - let options = SenderOptions.fromConfig('http::addr=host:9000;retry_timeout=60'); - expect(options.protocol).toBe('http'); - expect(options.retry_timeout).toBe(60); - - options = SenderOptions.fromConfig('http::addr=host:9000;retry_timeout=0'); - expect(options.protocol).toBe('http'); - expect(options.retry_timeout).toBe(0); - - expect( - () => SenderOptions.fromConfig('http::addr=host:9000;retry_timeout=-60') - ).toThrow('Invalid retry timeout option: -60'); - expect( - () => SenderOptions.fromConfig('http::addr=host:9000;retry_timeout=-60.444') - ).toThrow('Invalid retry timeout option: -60.444'); - expect( - () => SenderOptions.fromConfig('http::addr=host:9000;retry_timeout=60x') - ).toThrow('Invalid retry timeout option, not a number: \'60x\''); - expect( - () => SenderOptions.fromConfig('http::addr=host:9000;retry_timeout=a60') - ).toThrow('Invalid retry timeout option, not a number: \'a60\''); - expect( - () => SenderOptions.fromConfig('http::addr=host:9000;retry_timeout=6w0') - ).toThrow('Invalid retry timeout option, not a number: \'6w0\''); - }); - - it('can parse copy_buffer config', function () { - let options = SenderOptions.fromConfig('http::addr=host:9000;copy_buffer=on;'); - expect(options.protocol).toBe('http'); - expect(options.host).toBe('host'); - expect(options.port).toBe(9000); - expect(options.copy_buffer).toBe(true); - - options = SenderOptions.fromConfig('http::addr=host:9000;copy_buffer=off;'); - expect(options.protocol).toBe('http'); - expect(options.host).toBe('host'); - expect(options.port).toBe(9000); - expect(options.copy_buffer).toBe(false); - - expect( - () => SenderOptions.fromConfig('http::addr=host:9000;copy_buffer=ON;') - ).toThrow('Invalid copy buffer option: \'ON\''); - expect( - () => SenderOptions.fromConfig('http::addr=host:9000;copy_buffer=On;') - ).toThrow('Invalid copy buffer option: \'On\''); - expect( - () => SenderOptions.fromConfig('http::addr=host:9000;copy_buffer=true;') - ).toThrow('Invalid copy buffer option: \'true\''); - expect( - () => SenderOptions.fromConfig('http::addr=host:9000;copy_buffer=OFF;') - ).toThrow('Invalid copy buffer option: \'OFF\''); - expect( - () => SenderOptions.fromConfig('http::addr=host:9000;copy_buffer=Off;') - ).toThrow('Invalid copy buffer option: \'Off\''); - expect( - () => SenderOptions.fromConfig('http::addr=host:9000;copy_buffer=false;') - ).toThrow('Invalid copy buffer option: \'false\''); - }); - - it('can parse max_name_len config', function () { - let options = SenderOptions.fromConfig('http::addr=host:9000;max_name_len=30'); - expect(options.protocol).toBe('http'); - expect(options.max_name_len).toBe(30); - - expect( - () => SenderOptions.fromConfig('http::addr=host:9000;max_name_len=0') - ).toThrow('Invalid max name length option: 0'); - expect( - () => SenderOptions.fromConfig('http::addr=host:9000;max_name_len=10.32') - ).toThrow('Invalid max name length option: 10.32'); - expect( - () => SenderOptions.fromConfig('http::addr=host:9000;max_name_len=-60') - ).toThrow('Invalid max name length option: -60'); - expect( - () => SenderOptions.fromConfig('http::addr=host:9000;max_name_len=60x') - ).toThrow('Invalid max name length option, not a number: \'60x\''); - expect( - () => SenderOptions.fromConfig('http::addr=host:9000;max_name_len=a60') - ).toThrow('Invalid max name length option, not a number: \'a60\''); - expect( - () => SenderOptions.fromConfig('http::addr=host:9000;max_name_len=6w0') - ).toThrow('Invalid max name length option, not a number: \'6w0\''); - }); - - it('can take a custom logger', function () { - let options = SenderOptions.fromConfig('http::addr=host:9000', { log: console.log }); - expect(options.protocol).toBe('http'); - expect(options.log).toBe(console.log); - - expect( - () => SenderOptions.fromConfig('http::addr=host:9000', { log: 1234 }) - ).toThrow('Invalid logging function'); - expect( - () => SenderOptions.fromConfig('http::addr=host:9000', { log: 'hoppa' }) - ).toThrow('Invalid logging function'); - }); - - it('can take a custom agent', function () { - const agent = new http.Agent({ keepAlive: true }); - - const options = SenderOptions.fromConfig('http::addr=host:9000', { agent: agent }); - expect(options.protocol).toBe('http'); - expect(options.agent.keepAlive).toBe(true); - - agent.destroy(); - - expect( - () => SenderOptions.fromConfig('http::addr=host:9000', { agent: { keepAlive: true } }) - ).toThrow('Invalid http/https agent'); - expect( - () => SenderOptions.fromConfig('http::addr=host:9000', { agent: 4567 }) - ).toThrow('Invalid http/https agent'); - expect( - () => SenderOptions.fromConfig('http::addr=host:9000', { agent: 'hopp' }) - ).toThrow('Invalid http/https agent'); - }); -}); diff --git a/test/options.test.ts b/test/options.test.ts new file mode 100644 index 0000000..9ef1c2f --- /dev/null +++ b/test/options.test.ts @@ -0,0 +1,738 @@ +import { describe, it, expect } from "vitest"; +import { SenderOptions } from "../src/options"; +import { Agent } from "undici"; + +describe("Configuration string parser suite", function () { + it("can parse a basic config string", function () { + const options = SenderOptions.fromConfig( + "https::addr=host;username=user1;password=pwd;", + ); + expect(options.protocol).toBe("https"); + expect(options.addr).toBe("host"); + expect(options.username).toBe("user1"); + expect(options.password).toBe("pwd"); + }); + + it("can parse a config string from environment variable", async function () { + process.env.QDB_CLIENT_CONF = "tcp::addr=host;"; + const options = SenderOptions.fromEnv(); + expect(options.protocol).toBe("tcp"); + expect(options.addr).toBe("host"); + }); + + it("accepts only lowercase protocols", function () { + let options = SenderOptions.fromConfig("tcp::addr=host;"); + expect(options.protocol).toBe("tcp"); + + options = SenderOptions.fromConfig("tcps::addr=host;"); + expect(options.protocol).toBe("tcps"); + + options = SenderOptions.fromConfig("http::addr=host;"); + expect(options.protocol).toBe("http"); + + options = SenderOptions.fromConfig("https::addr=host;"); + expect(options.protocol).toBe("https"); + + expect(() => SenderOptions.fromConfig("HTTP::")).toThrow( + "Invalid protocol: 'HTTP', accepted protocols: 'http', 'https', 'tcp', 'tcps'", + ); + expect(() => SenderOptions.fromConfig("Http::")).toThrow( + "Invalid protocol: 'Http', accepted protocols: 'http', 'https', 'tcp', 'tcps'", + ); + expect(() => SenderOptions.fromConfig("HtTps::")).toThrow( + "Invalid protocol: 'HtTps', accepted protocols: 'http', 'https', 'tcp', 'tcps'", + ); + + expect(() => SenderOptions.fromConfig("TCP::")).toThrow( + "Invalid protocol: 'TCP', accepted protocols: 'http', 'https', 'tcp', 'tcps'", + ); + expect(() => SenderOptions.fromConfig("TcP::")).toThrow( + "Invalid protocol: 'TcP', accepted protocols: 'http', 'https', 'tcp', 'tcps'", + ); + expect(() => SenderOptions.fromConfig("Tcps::")).toThrow( + "Invalid protocol: 'Tcps', accepted protocols: 'http', 'https', 'tcp', 'tcps'", + ); + }); + + it("considers that keys and values are case-sensitive", function () { + const options = SenderOptions.fromConfig( + "tcps::addr=Host;username=useR1;token=TOKEN;", + ); + expect(options.protocol).toBe("tcps"); + expect(options.addr).toBe("Host"); + expect(options.username).toBe("useR1"); + expect(options.token).toBe("TOKEN"); + + expect(() => + SenderOptions.fromConfig("tcps::addr=Host;UserNAME=useR1;PaSswOrD=pWd;"), + ).toThrow("Unknown configuration key: 'UserNAME'"); + expect(() => + SenderOptions.fromConfig("tcps::addr=Host;PaSswOrD=pWd;"), + ).toThrow("Unknown configuration key: 'PaSswOrD'"); + }); + + it("can parse with or without the last semicolon", function () { + let options = SenderOptions.fromConfig("https::addr=host:9002"); + expect(options.protocol).toBe("https"); + expect(options.addr).toBe("host:9002"); + + options = SenderOptions.fromConfig("https::addr=host:9002;"); + expect(options.protocol).toBe("https"); + expect(options.addr).toBe("host:9002"); + + options = SenderOptions.fromConfig("https::addr=host:9002;token=abcde"); + expect(options.protocol).toBe("https"); + expect(options.addr).toBe("host:9002"); + expect(options.token).toBe("abcde"); + + options = SenderOptions.fromConfig("https::addr=host:9002;token=abcde;"); + expect(options.protocol).toBe("https"); + expect(options.addr).toBe("host:9002"); + expect(options.token).toBe("abcde"); + + options = SenderOptions.fromConfig("https::addr=host:9002;token=abcde;;"); + expect(options.protocol).toBe("https"); + expect(options.addr).toBe("host:9002"); + expect(options.token).toBe("abcde;"); + + options = SenderOptions.fromConfig("https::addr=host:9002;token=abcde;;;"); + expect(options.protocol).toBe("https"); + expect(options.addr).toBe("host:9002"); + expect(options.token).toBe("abcde;"); + }); + + it("can parse escaped config string values", function () { + const options = SenderOptions.fromConfig( + "https::addr=host:9002;username=us;;;;;;er;;1;;;password=p;;wd;", + ); + expect(options.protocol).toBe("https"); + expect(options.addr).toBe("host:9002"); + expect(options.username).toBe("us;;;er;1;"); + expect(options.password).toBe("p;wd"); + }); + + it("can parse the address", function () { + let options = SenderOptions.fromConfig( + "https::addr=host1:9002;token=resttoken123;", + ); + expect(options.protocol).toBe("https"); + expect(options.addr).toBe("host1:9002"); + expect(options.host).toBe("host1"); + expect(options.port).toBe(9002); + expect(options.token).toBe("resttoken123"); + + options = SenderOptions.fromConfig( + "tcps::addr=host2:9005;username=user1;token=jwkprivkey123;", + ); + expect(options.protocol).toBe("tcps"); + expect(options.addr).toBe("host2:9005"); + expect(options.host).toBe("host2"); + expect(options.port).toBe(9005); + expect(options.username).toBe("user1"); + expect(options.token).toBe("jwkprivkey123"); + }); + + it("can default the port", function () { + let options = SenderOptions.fromConfig( + "https::addr=hostname;token=resttoken123;", + ); + expect(options.protocol).toBe("https"); + expect(options.addr).toBe("hostname"); + expect(options.host).toBe("hostname"); + expect(options.port).toBe(9000); + expect(options.token).toBe("resttoken123"); + + options = SenderOptions.fromConfig( + "http::addr=hostname;token=resttoken123;", + ); + expect(options.protocol).toBe("http"); + expect(options.addr).toBe("hostname"); + expect(options.host).toBe("hostname"); + expect(options.port).toBe(9000); + expect(options.token).toBe("resttoken123"); + + options = SenderOptions.fromConfig( + "tcps::addr=hostname;username=user1;token=jwkprivkey123;", + ); + expect(options.protocol).toBe("tcps"); + expect(options.addr).toBe("hostname"); + expect(options.host).toBe("hostname"); + expect(options.port).toBe(9009); + expect(options.username).toBe("user1"); + expect(options.token).toBe("jwkprivkey123"); + + options = SenderOptions.fromConfig( + "tcp::addr=hostname;username=user1;token=jwkprivkey123;", + ); + expect(options.protocol).toBe("tcp"); + expect(options.addr).toBe("hostname"); + expect(options.host).toBe("hostname"); + expect(options.port).toBe(9009); + expect(options.username).toBe("user1"); + expect(options.token).toBe("jwkprivkey123"); + }); + + it("fails if port is not a positive integer", function () { + expect(() => SenderOptions.fromConfig("tcp::addr=host:;")).toThrow( + "Port is required", + ); + expect(() => SenderOptions.fromConfig("tcp::addr=host:0")).toThrow( + "Invalid port: 0", + ); + expect(() => SenderOptions.fromConfig("tcp::addr=host:0.2")).toThrow( + "Invalid port: 0.2", + ); + expect(() => SenderOptions.fromConfig("tcp::addr=host:-2")).toThrow( + "Invalid port: -2", + ); + expect(() => SenderOptions.fromConfig("tcp::addr=host:!;")).toThrow( + "Invalid port: '!'", + ); + expect(() => SenderOptions.fromConfig("tcp::addr=host:9009x;")).toThrow( + "Invalid port: '9009x'", + ); + expect(() => SenderOptions.fromConfig("tcp::addr=host:900 9;")).toThrow( + "Invalid port: '900 9'", + ); + }); + + it("fails if init_buf_size is not a positive integer", function () { + expect(() => + SenderOptions.fromConfig("tcp::addr=host;init_buf_size=;"), + ).toThrow("Invalid configuration, value is not set for 'init_buf_size'"); + expect(() => + SenderOptions.fromConfig("tcp::addr=host;init_buf_size=1024a;"), + ).toThrow("Invalid initial buffer size option, not a number: '1024a'"); + expect(() => + SenderOptions.fromConfig("tcp::addr=host;init_buf_size=102 4;"), + ).toThrow("Invalid initial buffer size option, not a number: '102 4'"); + expect(() => + SenderOptions.fromConfig("tcp::addr=host;init_buf_size=0;"), + ).toThrow("Invalid initial buffer size option: 0"); + }); + + it("fails if max_buf_size is not a positive integer", function () { + expect(() => + SenderOptions.fromConfig("tcp::addr=host;max_buf_size=;"), + ).toThrow("Invalid configuration, value is not set for 'max_buf_size'"); + expect(() => + SenderOptions.fromConfig("tcp::addr=host;max_buf_size=1024a;"), + ).toThrow("Invalid max buffer size option, not a number: '1024a'"); + expect(() => + SenderOptions.fromConfig("tcp::addr=host;max_buf_size=102 4;"), + ).toThrow("Invalid max buffer size option, not a number: '102 4'"); + expect(() => + SenderOptions.fromConfig("tcp::addr=host;max_buf_size=0;"), + ).toThrow("Invalid max buffer size option: 0"); + }); + + it("rejects missing or empty hostname", function () { + expect(() => SenderOptions.fromConfig("http::")).toThrow( + "Invalid configuration, 'addr' is required", + ); + expect(() => SenderOptions.fromConfig("http::;")).toThrow( + "Missing '=' sign in ''", + ); + expect(() => SenderOptions.fromConfig("http::addr=;")).toThrow( + "Invalid configuration, value is not set for 'addr'", + ); + expect(() => + SenderOptions.fromConfig("http::addr=;username=user1;"), + ).toThrow("Invalid configuration, value is not set for 'addr'"); + expect(() => + SenderOptions.fromConfig("http::username=user1;addr=;"), + ).toThrow("Invalid configuration, value is not set for 'addr'"); + expect(() => SenderOptions.fromConfig("http::addr=:9000;")).toThrow( + "Host name is required", + ); + + const options = SenderOptions.fromConfig("http::addr=x;"); + expect(options.protocol).toBe("http"); + expect(options.host).toBe("x"); + expect(options.host).toBe("x"); + }); + + it("does not default optional fields", function () { + const options = SenderOptions.fromConfig( + "https::addr=host:9000;token=abcdef123;", + ); + expect(options.protocol).toBe("https"); + expect(options.token).toBe("abcdef123"); + expect(options.username).toBe(undefined); + expect(options.password).toBe(undefined); + }); + + it("rejects invalid config value", function () { + expect(() => + SenderOptions.fromConfig("http::addr=host:9000;username=;"), + ).toThrow("Invalid configuration, value is not set for 'username'"); + + expect(() => + SenderOptions.fromConfig("http::addr=host:9000;username=user\t;"), + ).toThrow( + "Invalid configuration, control characters are not allowed: 'user\t'", + ); + expect(() => + SenderOptions.fromConfig("http::addr=host:9000;username=user\n;"), + ).toThrow( + "Invalid configuration, control characters are not allowed: 'user\n'", + ); + + let options = SenderOptions.fromConfig( + "http::addr=host:9000;username=us\x7Eer;", + ); + expect(options.protocol).toBe("http"); + expect(options.addr).toBe("host:9000"); + expect(options.username).toBe("us\x7Eer"); + + expect(() => + SenderOptions.fromConfig("http::addr=host:9000;username=us\x7Fer;"), + ).toThrow( + "Invalid configuration, control characters are not allowed: 'us\x7Fer'", + ); + expect(() => + SenderOptions.fromConfig("http::addr=host:9000;username=us\x9Fer;"), + ).toThrow( + "Invalid configuration, control characters are not allowed: 'us\x9Fer'", + ); + + options = SenderOptions.fromConfig( + "http::addr=host:9000;username=us\xA0er;", + ); + expect(options.protocol).toBe("http"); + expect(options.addr).toBe("host:9000"); + expect(options.username).toBe("us\xA0er"); + }); + + it("reject invalid config keys", function () { + expect(() => + SenderOptions.fromConfig("http::addr=host:9000;username=user1;pass=pwd;"), + ).toThrow("Unknown configuration key: 'pass'"); + expect(() => + SenderOptions.fromConfig("http::addr=host:9000;user=user1;password=pwd;"), + ).toThrow("Unknown configuration key: 'user'"); + expect(() => + SenderOptions.fromConfig( + "http::addr=host:9000;username =user1;password=pwd;", + ), + ).toThrow("Unknown configuration key: 'username '"); + expect(() => + SenderOptions.fromConfig( + "http::addr=host:9000; username=user1;password=pwd;", + ), + ).toThrow("Unknown configuration key: ' username'"); + expect(() => + SenderOptions.fromConfig( + "http::addr=host:9000;user name=user1;password=pwd;", + ), + ).toThrow("Unknown configuration key: 'user name'"); + }); + + it("rejects keys without value", function () { + expect(() => SenderOptions.fromConfig("http::addr;username=user1")).toThrow( + "Missing '=' sign in 'addr'", + ); + expect(() => + SenderOptions.fromConfig("http::addr=host:9000;username;"), + ).toThrow("Missing '=' sign in 'username'"); + }); + + it("throws error if protocol is invalid", function () { + expect(() => + SenderOptions.fromConfig("::addr=host;username=user1;password=pwd;"), + ).toThrow( + "Invalid protocol: '', accepted protocols: 'http', 'https', 'tcp', 'tcps'", + ); + expect(() => + SenderOptions.fromConfig("htt::addr=host;username=user1;password=pwd;"), + ).toThrow( + "Invalid protocol: 'htt', accepted protocols: 'http', 'https', 'tcp', 'tcps'", + ); + }); + + it("throws error if protocol is missing", function () { + expect(() => + SenderOptions.fromConfig("addr=host;username=user1;password=pwd;"), + ).toThrow( + "Missing protocol, configuration string format: 'protocol::key1=value1;key2=value2;key3=value3;'", + ); + expect(() => + SenderOptions.fromConfig("https:addr=host;username=user1;password=pwd;"), + ).toThrow( + "Missing protocol, configuration string format: 'protocol::key1=value1;key2=value2;key3=value3;'", + ); + expect(() => + SenderOptions.fromConfig("https addr=host;username=user1;password=pwd;"), + ).toThrow( + "Missing protocol, configuration string format: 'protocol::key1=value1;key2=value2;key3=value3;'", + ); + }); + + it("throws error if configuration string is missing", function () { + // @ts-expect-error - Testing invalid input + expect(() => SenderOptions.fromConfig()).toThrow( + "Configuration string is missing", + ); + expect(() => SenderOptions.fromConfig("")).toThrow( + "Configuration string is missing", + ); + // @ts-expect-error - Testing invalid input + expect(() => SenderOptions.fromConfig(null)).toThrow( + "Configuration string is missing", + ); + // @ts-expect-error - Testing invalid input + expect(() => SenderOptions.fromConfig(undefined)).toThrow( + "Configuration string is missing", + ); + }); + + it("can parse auto_flush config", function () { + let options = SenderOptions.fromConfig( + "http::addr=host:9000;auto_flush=on;", + ); + expect(options.protocol).toBe("http"); + expect(options.host).toBe("host"); + expect(options.port).toBe(9000); + expect(options.auto_flush).toBe(true); + + options = SenderOptions.fromConfig("http::addr=host:9000;auto_flush=off;"); + expect(options.protocol).toBe("http"); + expect(options.host).toBe("host"); + expect(options.port).toBe(9000); + expect(options.auto_flush).toBe(false); + + expect(() => + SenderOptions.fromConfig("http::addr=host:9000;auto_flush=ON;"), + ).toThrow("Invalid auto flush option: 'ON'"); + expect(() => + SenderOptions.fromConfig("http::addr=host:9000;auto_flush=On;"), + ).toThrow("Invalid auto flush option: 'On'"); + expect(() => + SenderOptions.fromConfig("http::addr=host:9000;auto_flush=true;"), + ).toThrow("Invalid auto flush option: 'true'"); + expect(() => + SenderOptions.fromConfig("http::addr=host:9000;auto_flush=OFF;"), + ).toThrow("Invalid auto flush option: 'OFF'"); + expect(() => + SenderOptions.fromConfig("http::addr=host:9000;auto_flush=Off;"), + ).toThrow("Invalid auto flush option: 'Off'"); + expect(() => + SenderOptions.fromConfig("http::addr=host:9000;auto_flush=false;"), + ).toThrow("Invalid auto flush option: 'false'"); + }); + + it("can parse auto_flush_rows config", function () { + let options = SenderOptions.fromConfig( + "http::addr=host:9000;auto_flush_rows=123;", + ); + expect(options.protocol).toBe("http"); + expect(options.auto_flush_rows).toBe(123); + + options = SenderOptions.fromConfig( + "http::addr=host:9000;auto_flush_rows=0;", + ); + expect(options.protocol).toBe("http"); + expect(options.auto_flush_rows).toBe(0); + + expect(() => + SenderOptions.fromConfig("http::addr=host:9000;auto_flush_rows=-123;"), + ).toThrow("Invalid auto flush rows option: -123"); + expect(() => + SenderOptions.fromConfig("http::addr=host:9000;auto_flush_rows=1.23;"), + ).toThrow("Invalid auto flush rows option: 1.23"); + expect(() => + SenderOptions.fromConfig("http::addr=host:9000;auto_flush_rows=123x;"), + ).toThrow("Invalid auto flush rows option, not a number: '123x'"); + expect(() => + SenderOptions.fromConfig("http::addr=host:9000;auto_flush_rows=a123;"), + ).toThrow("Invalid auto flush rows option, not a number: 'a123'"); + expect(() => + SenderOptions.fromConfig("http::addr=host:9000;auto_flush_rows=1w23;"), + ).toThrow("Invalid auto flush rows option, not a number: '1w23'"); + }); + + it("can parse auto_flush_interval config", function () { + let options = SenderOptions.fromConfig( + "http::addr=host:9000;auto_flush_interval=30", + ); + expect(options.protocol).toBe("http"); + expect(options.auto_flush_interval).toBe(30); + + options = SenderOptions.fromConfig( + "http::addr=host:9000;auto_flush_interval=0", + ); + expect(options.protocol).toBe("http"); + expect(options.auto_flush_interval).toBe(0); + + expect(() => + SenderOptions.fromConfig("http::addr=host:9000;auto_flush_interval=-60"), + ).toThrow("Invalid auto flush interval option: -60"); + expect(() => + SenderOptions.fromConfig("http::addr=host:9000;auto_flush_interval=-6.0"), + ).toThrow("Invalid auto flush interval option: -6"); + expect(() => + SenderOptions.fromConfig("http::addr=host:9000;auto_flush_interval=60x"), + ).toThrow("Invalid auto flush interval option, not a number: '60x'"); + expect(() => + SenderOptions.fromConfig("http::addr=host:9000;auto_flush_interval=a60"), + ).toThrow("Invalid auto flush interval option, not a number: 'a60'"); + expect(() => + SenderOptions.fromConfig("http::addr=host:9000;auto_flush_interval=6w0"), + ).toThrow("Invalid auto flush interval option, not a number: '6w0'"); + }); + + it("can parse tls_verify config", function () { + let options = SenderOptions.fromConfig( + "http::addr=host:9000;tls_verify=on", + ); + expect(options.protocol).toBe("http"); + expect(options.host).toBe("host"); + expect(options.port).toBe(9000); + expect(options.tls_verify).toBe(true); + + options = SenderOptions.fromConfig( + "http::addr=host:9000;tls_verify=unsafe_off", + ); + expect(options.protocol).toBe("http"); + expect(options.host).toBe("host"); + expect(options.port).toBe(9000); + expect(options.tls_verify).toBe(false); + + expect(() => + SenderOptions.fromConfig("http::addr=host:9000;tls_verify=ON"), + ).toThrow("Invalid TLS verify option: 'ON'"); + expect(() => + SenderOptions.fromConfig("http::addr=host:9000;tls_verify=On"), + ).toThrow("Invalid TLS verify option: 'On'"); + expect(() => + SenderOptions.fromConfig("http::addr=host:9000;tls_verify=true"), + ).toThrow("Invalid TLS verify option: 'true'"); + expect(() => + SenderOptions.fromConfig("http::addr=host:9000;tls_verify=OFF"), + ).toThrow("Invalid TLS verify option: 'OFF'"); + expect(() => + SenderOptions.fromConfig("http::addr=host:9000;tls_verify=Off"), + ).toThrow("Invalid TLS verify option: 'Off'"); + expect(() => + SenderOptions.fromConfig("http::addr=host:9000;tls_verify=UNSAFE_OFF"), + ).toThrow("Invalid TLS verify option: 'UNSAFE_OFF'"); + expect(() => + SenderOptions.fromConfig("http::addr=host:9000;tls_verify=Unsafe_Off"), + ).toThrow("Invalid TLS verify option: 'Unsafe_Off'"); + expect(() => + SenderOptions.fromConfig("http::addr=host:9000;tls_verify=false"), + ).toThrow("Invalid TLS verify option: 'false'"); + }); + + it("fails with tls_roots or tls_roots_password config", function () { + expect(() => + SenderOptions.fromConfig("http::addr=host:9000;tls_roots=/whatever/path"), + ).toThrow( + "'tls_roots' and 'tls_roots_password' options are not supported, please, use the 'tls_ca' option or the NODE_EXTRA_CA_CERTS environment variable instead", + ); + expect(() => + SenderOptions.fromConfig("http::addr=host:9000;tls_roots_password=pwd"), + ).toThrow( + "'tls_roots' and 'tls_roots_password' options are not supported, please, use the 'tls_ca' option or the NODE_EXTRA_CA_CERTS environment variable instead", + ); + }); + + it("can parse request_min_throughput config", function () { + const options = SenderOptions.fromConfig( + "http::addr=host:9000;request_min_throughput=300", + ); + expect(options.protocol).toBe("http"); + expect(options.request_min_throughput).toBe(300); + + expect(() => + SenderOptions.fromConfig("http::addr=host:9000;request_min_throughput=0"), + ).toThrow("Invalid request min throughput option: 0"); + expect(() => + SenderOptions.fromConfig( + "http::addr=host:9000;request_min_throughput=0.5", + ), + ).toThrow("Invalid request min throughput option: 0.5"); + expect(() => + SenderOptions.fromConfig( + "http::addr=host:9000;request_min_throughput=-60", + ), + ).toThrow("Invalid request min throughput option: -60"); + expect(() => + SenderOptions.fromConfig( + "http::addr=host:9000;request_min_throughput=60x", + ), + ).toThrow("Invalid request min throughput option, not a number: '60x'"); + expect(() => + SenderOptions.fromConfig( + "http::addr=host:9000;request_min_throughput=a60", + ), + ).toThrow("Invalid request min throughput option, not a number: 'a60'"); + expect(() => + SenderOptions.fromConfig( + "http::addr=host:9000;request_min_throughput=6w0", + ), + ).toThrow("Invalid request min throughput option, not a number: '6w0'"); + }); + + it("can parse request_timeout config", function () { + const options = SenderOptions.fromConfig( + "http::addr=host:9000;request_timeout=30", + ); + expect(options.protocol).toBe("http"); + expect(options.request_timeout).toBe(30); + + expect(() => + SenderOptions.fromConfig("http::addr=host:9000;request_timeout=0"), + ).toThrow("Invalid request timeout option: 0"); + expect(() => + SenderOptions.fromConfig("http::addr=host:9000;request_timeout=10.32"), + ).toThrow("Invalid request timeout option: 10.32"); + expect(() => + SenderOptions.fromConfig("http::addr=host:9000;request_timeout=-60"), + ).toThrow("Invalid request timeout option: -60"); + expect(() => + SenderOptions.fromConfig("http::addr=host:9000;request_timeout=60x"), + ).toThrow("Invalid request timeout option, not a number: '60x'"); + expect(() => + SenderOptions.fromConfig("http::addr=host:9000;request_timeout=a60"), + ).toThrow("Invalid request timeout option, not a number: 'a60'"); + expect(() => + SenderOptions.fromConfig("http::addr=host:9000;request_timeout=6w0"), + ).toThrow("Invalid request timeout option, not a number: '6w0'"); + }); + + it("can parse retry_timeout config", function () { + let options = SenderOptions.fromConfig( + "http::addr=host:9000;retry_timeout=60", + ); + expect(options.protocol).toBe("http"); + expect(options.retry_timeout).toBe(60); + + options = SenderOptions.fromConfig("http::addr=host:9000;retry_timeout=0"); + expect(options.protocol).toBe("http"); + expect(options.retry_timeout).toBe(0); + + expect(() => + SenderOptions.fromConfig("http::addr=host:9000;retry_timeout=-60"), + ).toThrow("Invalid retry timeout option: -60"); + expect(() => + SenderOptions.fromConfig("http::addr=host:9000;retry_timeout=-60.444"), + ).toThrow("Invalid retry timeout option: -60.444"); + expect(() => + SenderOptions.fromConfig("http::addr=host:9000;retry_timeout=60x"), + ).toThrow("Invalid retry timeout option, not a number: '60x'"); + expect(() => + SenderOptions.fromConfig("http::addr=host:9000;retry_timeout=a60"), + ).toThrow("Invalid retry timeout option, not a number: 'a60'"); + expect(() => + SenderOptions.fromConfig("http::addr=host:9000;retry_timeout=6w0"), + ).toThrow("Invalid retry timeout option, not a number: '6w0'"); + }); + + it("can parse copy_buffer config", function () { + let options = SenderOptions.fromConfig( + "http::addr=host:9000;copy_buffer=on;", + ); + expect(options.protocol).toBe("http"); + expect(options.host).toBe("host"); + expect(options.port).toBe(9000); + expect(options.copy_buffer).toBe(true); + + options = SenderOptions.fromConfig("http::addr=host:9000;copy_buffer=off;"); + expect(options.protocol).toBe("http"); + expect(options.host).toBe("host"); + expect(options.port).toBe(9000); + expect(options.copy_buffer).toBe(false); + + expect(() => + SenderOptions.fromConfig("http::addr=host:9000;copy_buffer=ON;"), + ).toThrow("Invalid copy buffer option: 'ON'"); + expect(() => + SenderOptions.fromConfig("http::addr=host:9000;copy_buffer=On;"), + ).toThrow("Invalid copy buffer option: 'On'"); + expect(() => + SenderOptions.fromConfig("http::addr=host:9000;copy_buffer=true;"), + ).toThrow("Invalid copy buffer option: 'true'"); + expect(() => + SenderOptions.fromConfig("http::addr=host:9000;copy_buffer=OFF;"), + ).toThrow("Invalid copy buffer option: 'OFF'"); + expect(() => + SenderOptions.fromConfig("http::addr=host:9000;copy_buffer=Off;"), + ).toThrow("Invalid copy buffer option: 'Off'"); + expect(() => + SenderOptions.fromConfig("http::addr=host:9000;copy_buffer=false;"), + ).toThrow("Invalid copy buffer option: 'false'"); + }); + + it("can parse max_name_len config", function () { + const options = SenderOptions.fromConfig( + "http::addr=host:9000;max_name_len=30", + ); + expect(options.protocol).toBe("http"); + expect(options.max_name_len).toBe(30); + + expect(() => + SenderOptions.fromConfig("http::addr=host:9000;max_name_len=0"), + ).toThrow("Invalid max name length option: 0"); + expect(() => + SenderOptions.fromConfig("http::addr=host:9000;max_name_len=10.32"), + ).toThrow("Invalid max name length option: 10.32"); + expect(() => + SenderOptions.fromConfig("http::addr=host:9000;max_name_len=-60"), + ).toThrow("Invalid max name length option: -60"); + expect(() => + SenderOptions.fromConfig("http::addr=host:9000;max_name_len=60x"), + ).toThrow("Invalid max name length option, not a number: '60x'"); + expect(() => + SenderOptions.fromConfig("http::addr=host:9000;max_name_len=a60"), + ).toThrow("Invalid max name length option, not a number: 'a60'"); + expect(() => + SenderOptions.fromConfig("http::addr=host:9000;max_name_len=6w0"), + ).toThrow("Invalid max name length option, not a number: '6w0'"); + }); + + it("can take a custom logger", function () { + const options = SenderOptions.fromConfig("http::addr=host:9000", { + log: console.log, + }); + expect(options.protocol).toBe("http"); + expect(options.log).toBe(console.log); + + expect(() => + // @ts-expect-error - Testing invalid input + SenderOptions.fromConfig("http::addr=host:9000", { log: 1234 }), + ).toThrow("Invalid logging function"); + expect(() => + // @ts-expect-error - Testing invalid input + SenderOptions.fromConfig("http::addr=host:9000", { log: "hoppa" }), + ).toThrow("Invalid logging function"); + }); + + it("can take a custom agent", function () { + const agent = new Agent({ connect: { keepAlive: true } }); + + const options = SenderOptions.fromConfig("http::addr=host:9000", { + agent: agent, + }); + expect(options.protocol).toBe("http"); + const symbols = Object.getOwnPropertySymbols(options.agent); + expect(agent[symbols[6]]).toEqual({ connect: { keepAlive: true } }); + + agent.destroy(); + + expect(() => + SenderOptions.fromConfig("http::addr=host:9000", { + // @ts-expect-error - Testing invalid input + agent: { keepAlive: true }, + }), + ).toThrow("Invalid http/https agent"); + expect(() => + // @ts-expect-error - Testing invalid input + SenderOptions.fromConfig("http::addr=host:9000", { agent: 4567 }), + ).toThrow("Invalid http/https agent"); + expect(() => + // @ts-expect-error - Testing invalid input + SenderOptions.fromConfig("http::addr=host:9000", { agent: "hopp" }), + ).toThrow("Invalid http/https agent"); + }); +}); diff --git a/test/proxy.js b/test/proxy.js deleted file mode 100644 index dea5232..0000000 --- a/test/proxy.js +++ /dev/null @@ -1,47 +0,0 @@ -'use strict'; - -const { Socket } = require('net'); -const { write, listen, shutdown, connect, close } = require('./proxyfunctions'); - -// handles only a single client -// client -> server (Proxy) -> remote (QuestDB) -// client <- server (Proxy) <- remote (QuestDB) -class Proxy { - constructor() { - this.remote = new Socket(); - - this.remote.on('data', async data => { - console.info(`received from remote, forwarding to client: ${data}`); - await write(this.client, data); - }); - - this.remote.on('close', () => { - console.info('remote connection closed'); - }); - - this.remote.on('error', err => { - console.error(`remote connection: ${err}`); - }); - } - - async start(listenPort, remotePort, remoteHost, tlsOptions = undefined) { - return new Promise(resolve => { - this.remote.on('ready', async () => { - console.info('remote connection ready'); - await listen(this, listenPort, async data => { - console.info(`received from client, forwarding to remote: ${data}`); - await write(this.remote, data); - }, tlsOptions); - resolve(); - }); - - connect(this, remotePort, remoteHost); - }); - } - - async stop() { - await shutdown(this, async () => await close(this)); - } -} - -exports.Proxy = Proxy; diff --git a/test/proxyfunctions.js b/test/proxyfunctions.js deleted file mode 100644 index 95952c4..0000000 --- a/test/proxyfunctions.js +++ /dev/null @@ -1,73 +0,0 @@ -'use strict'; - -const net = require('net'); -const tls = require('tls'); - -const LOCALHOST = 'localhost'; - -async function write(socket, data) { - return new Promise((resolve, reject) => { - socket.write(data, 'utf8', err => { - err ? reject(err) : resolve(); - }); - }); -} - -async function listen(proxy, listenPort, dataHandler, tlsOptions) { - return new Promise(resolve => { - const clientConnHandler = client => { - console.info('client connected'); - if (proxy.client) { - console.error('There is already a client connected'); - process.exit(1); - } - proxy.client = client; - - client.on('data', dataHandler); - } - - proxy.server = tlsOptions - ? tls.createServer(tlsOptions, clientConnHandler) - : net.createServer(clientConnHandler); - - proxy.server.on('error', err => { - console.error(`server error: ${err}`); - }); - - proxy.server.listen(listenPort, LOCALHOST, () => { - console.info(`listening for clients on ${listenPort}`); - resolve(); - }); - }); -} - -async function shutdown(proxy, onServerClose = async () => {}) { - console.info('closing proxy') - return new Promise(resolve => { - proxy.server.close(async () => { - await onServerClose(); - resolve(); - }); - }); -} - -async function connect(proxy, remotePort, remoteHost) { - console.info(`opening remote connection to ${remoteHost}:${remotePort}`) - return new Promise(resolve => { - proxy.remote.connect(remotePort, remoteHost, () => resolve()); - }); -} - -async function close(proxy) { - console.info('closing remote connection') - return new Promise(resolve => { - proxy.remote.destroy(); - resolve(); - }); -} - -exports.write = write; -exports.listen = listen; -exports.shutdown = shutdown; -exports.connect = connect; -exports.close = close; diff --git a/test/sender.test.js b/test/sender.test.js deleted file mode 100644 index 3eb5e88..0000000 --- a/test/sender.test.js +++ /dev/null @@ -1,1700 +0,0 @@ -'use strict'; - -const { Sender } = require('../index'); -const { DEFAULT_BUFFER_SIZE, DEFAULT_MAX_BUFFER_SIZE } = require('../src/sender'); -const { log } = require('../src/logging'); -const { MockProxy } = require('./mockproxy'); -const { readFileSync} = require('fs'); -const { GenericContainer } = require('testcontainers'); -const http = require('http'); -const {MockHttp} = require("./mockhttp"); - -const HTTP_OK = 200; - -const QUESTDB_HTTP_PORT = 9000; -const QUESTDB_ILP_PORT = 9009; -const MOCK_HTTP_PORT = 9099; -const MOCK_HTTPS_PORT = 9098; -const PROXY_PORT = 9088; -const PROXY_HOST = 'localhost'; - -const proxyOptions = { - key: readFileSync('test/certs/server/server.key'), - cert: readFileSync('test/certs/server/server.crt'), - ca: readFileSync('test/certs/ca/ca.crt') -} - -const USER_NAME = 'testapp'; -const PRIVATE_KEY = '9b9x5WhJywDEuo1KGQWSPNxtX-6X6R2BRCKhYMMY6n8'; -const AUTH = { - keyId: USER_NAME, - token: PRIVATE_KEY -} - -async function sleep(ms) { - return new Promise(resolve => setTimeout(resolve, ms)); -} - -describe('Sender configuration options suite', function () { - it('creates a sender from a configuration string', async function () { - await Sender.fromConfig('tcps::addr=hostname;').close(); - }); - - it('creates a sender from a configuration string picked up from env', async function () { - process.env.QDB_CLIENT_CONF = 'https::addr=hostname;'; - await Sender.fromEnv().close(); - }); - - it('throws exception if the username or the token is missing when TCP transport is used', async function () { - try { - await Sender.fromConfig('tcp::addr=hostname;username=bobo;').close(); - fail('Expected error is not thrown'); - } catch(err) { - expect(err.message).toBe('TCP transport requires a username and a private key for authentication, please, specify the \'username\' and \'token\' config options'); - } - - try { - await Sender.fromConfig('tcp::addr=hostname;token=bobo_token;').close(); - fail('Expected error is not thrown'); - } catch(err) { - expect(err.message).toBe('TCP transport requires a username and a private key for authentication, please, specify the \'username\' and \'token\' config options'); - } - }); - - it('throws exception if tls_roots or tls_roots_password is used', async function () { - try { - await Sender.fromConfig('tcps::addr=hostname;username=bobo;tls_roots=bla;').close(); - fail('Expected error is not thrown'); - } catch(err) { - expect(err.message).toBe('\'tls_roots\' and \'tls_roots_password\' options are not supported, please, use the \'tls_ca\' option or the NODE_EXTRA_CA_CERTS environment variable instead'); - } - - try { - await Sender.fromConfig('tcps::addr=hostname;token=bobo_token;tls_roots_password=bla;').close(); - fail('Expected error is not thrown'); - } catch(err) { - expect(err.message).toBe('\'tls_roots\' and \'tls_roots_password\' options are not supported, please, use the \'tls_ca\' option or the NODE_EXTRA_CA_CERTS environment variable instead'); - } - }); - - it('throws exception if connect() is called when http transport is used', async function () { - let sender; - try { - sender = Sender.fromConfig('http::addr=hostname'); - await sender.connect(); - fail('Expected error is not thrown'); - } catch(err) { - expect(err.message).toBe('\'connect()\' should be called only if the sender connects via TCP'); - } - await sender.close(); - }); -}); - -describe('Sender options test suite', function () { - it('fails if no options defined', async function () { - try { - await new Sender().close(); - fail('Expected error is not thrown'); - } catch(err) { - expect(err.message).toBe('The \'protocol\' option is mandatory'); - } - }); - - it('fails if options are null', async function () { - try { - await new Sender(null).close(); - fail('Expected error is not thrown'); - } catch(err) { - expect(err.message).toBe('The \'protocol\' option is mandatory'); - } - }); - - it('fails if options are undefined', async function () { - try { - await new Sender(undefined).close(); - fail('Expected error is not thrown'); - } catch(err) { - expect(err.message).toBe('The \'protocol\' option is mandatory'); - } - }); - - it('fails if options are empty', async function () { - try { - await new Sender({}).close(); - fail('Expected error is not thrown'); - } catch(err) { - expect(err.message).toBe('The \'protocol\' option is mandatory'); - } - }); - - it('fails if protocol option is missing', async function () { - try { - await new Sender({host: 'host'}).close(); - fail('Expected error is not thrown'); - } catch(err) { - expect(err.message).toBe('The \'protocol\' option is mandatory'); - } - }); - - it('fails if protocol option is invalid', async function () { - try { - await new Sender({protocol: 'abcd'}).close(); - fail('Expected error is not thrown'); - } catch(err) { - expect(err.message).toBe('Invalid protocol: \'abcd\''); - } - }); - - it('does copy the buffer during flush() if copyBuffer is not set', async function () { - const sender = new Sender({protocol: 'http', host: 'host'}); - expect(sender.toBuffer).toBe(sender.toBufferNew); - await sender.close(); - }); - - it('does copy the buffer during flush() if copyBuffer is set to true', async function () { - const sender = new Sender({protocol: 'http', host: 'host', copy_buffer: true}); - expect(sender.toBuffer).toBe(sender.toBufferNew); - await sender.close(); - }); - - it('does copy the buffer during flush() if copyBuffer is not a boolean', async function () { - const sender = new Sender({protocol: 'http', host: 'host', copy_buffer: ''}); - expect(sender.toBuffer).toBe(sender.toBufferNew); - await sender.close(); - }); - - it('does not copy the buffer during flush() if copyBuffer is set to false', async function () { - const sender = new Sender({protocol: 'http', host: 'host', copy_buffer: false}); - expect(sender.toBuffer).toBe(sender.toBufferView); - await sender.close(); - }); - - it('does not copy the buffer during flush() if copyBuffer is set to null', async function () { - const sender = new Sender({protocol: 'http', host: 'host', copy_buffer: null}); - expect(sender.toBuffer).toBe(sender.toBufferNew); - await sender.close(); - }); - - it('does not copy the buffer during flush() if copyBuffer is undefined', async function () { - const sender = new Sender({protocol: 'http', host: 'host', copy_buffer: undefined}); - expect(sender.toBuffer).toBe(sender.toBufferNew); - await sender.close(); - }); - - it('sets default buffer size if bufferSize is not set', async function () { - const sender = new Sender({protocol: 'http', host: 'host', copy_buffer: true}); - expect(sender.bufferSize).toBe(DEFAULT_BUFFER_SIZE); - await sender.close(); - }); - - it('sets the requested buffer size if bufferSize is set', async function () { - const sender = new Sender({protocol: 'http', host: 'host', init_buf_size: 1024}); - expect(sender.bufferSize).toBe(1024); - await sender.close(); - }); - - it('sets default buffer size if bufferSize is set to null', async function () { - const sender = new Sender({protocol: 'http', host: 'host', init_buf_size: null}); - expect(sender.bufferSize).toBe(DEFAULT_BUFFER_SIZE); - await sender.close(); - }); - - it('sets default buffer size if bufferSize is set to undefined', async function () { - const sender = new Sender({protocol: 'http', host: 'host', init_buf_size: undefined}); - expect(sender.bufferSize).toBe(DEFAULT_BUFFER_SIZE); - await sender.close(); - }); - - it('sets default buffer size if bufferSize is not a number', async function () { - const sender = new Sender({protocol: 'http', host: 'host', init_buf_size: '1024'}); - expect(sender.bufferSize).toBe(DEFAULT_BUFFER_SIZE); - await sender.close(); - }); - - it('sets default max buffer size if max_buf_size is not set', async function () { - const sender = new Sender({protocol: 'http', host: 'host'}); - expect(sender.maxBufferSize).toBe(DEFAULT_MAX_BUFFER_SIZE); - await sender.close(); - }); - - it('sets the requested max buffer size if max_buf_size is set', async function () { - const sender = new Sender({protocol: 'http', host: 'host', max_buf_size: 131072}); - expect(sender.maxBufferSize).toBe(131072); - await sender.close(); - }); - - it('throws error if initial buffer size is greater than max_buf_size', async function () { - try { - await new Sender({protocol: 'http', host: 'host', max_buf_size: 8192, init_buf_size: 16384}).close(); - fail('Expected error is not thrown'); - } catch (err) { - expect(err.message).toBe('Max buffer size is 8192 bytes, requested buffer size: 16384'); - } - }); - - it('sets default max buffer size if max_buf_size is set to null', async function () { - const sender = new Sender({protocol: 'http', host: 'host', max_buf_size: null}); - expect(sender.maxBufferSize).toBe(DEFAULT_MAX_BUFFER_SIZE); - await sender.close(); - }); - - it('sets default max buffer size if max_buf_size is set to undefined', async function () { - const sender = new Sender({protocol: 'http', host: 'host', max_buf_size: undefined}); - expect(sender.maxBufferSize).toBe(DEFAULT_MAX_BUFFER_SIZE); - await sender.close(); - }); - - it('sets default max buffer size if max_buf_size is not a number', async function () { - const sender = new Sender({protocol: 'http', host: 'host', max_buf_size: '1024'}); - expect(sender.maxBufferSize).toBe(DEFAULT_MAX_BUFFER_SIZE); - await sender.close(); - }); - - it('uses default logger if log function is not set', async function () { - const sender = new Sender({protocol: 'http', host: 'host', }); - expect(sender.log).toBe(log); - await sender.close(); - }); - - it('uses the required log function if it is set', async function () { - const testFunc = () => {}; - const sender = new Sender({protocol: 'http', host: 'host', log: testFunc}); - expect(sender.log).toBe(testFunc); - await sender.close(); - }); - - it('uses default logger if log is set to null', async function () { - const sender = new Sender({protocol: 'http', host: 'host', log: null}); - expect(sender.log).toBe(log); - await sender.close(); - }); - - it('uses default logger if log is set to undefined', async function () { - const sender = new Sender({protocol: 'http', host: 'host', log: undefined}); - expect(sender.log).toBe(log); - await sender.close(); - }); - - it('uses default logger if log is not a function', async function () { - const sender = new Sender({protocol: 'http', host: 'host', log: ''}); - expect(sender.log).toBe(log); - await sender.close(); - }); -}); - -describe('Sender auth config checks suite', function () { - it('requires a username for authentication', async function () { - try { - await new Sender({ - protocol: 'tcp', host: 'host', - auth: { - token: 'privateKey' - } - }).close(); - fail('it should not be able to create the sender'); - } catch(err) { - expect(err.message).toBe('Missing username, please, specify the \'keyId\' property of the \'auth\' config option. ' + - 'For example: new Sender({protocol: \'tcp\', host: \'host\', auth: {keyId: \'username\', token: \'private key\'}})'); - } - }); - - it('requires a non-empty username', async function () { - try { - await new Sender({ - protocol: 'tcp', host: 'host', - auth: { - keyId: '', - token: 'privateKey' - } - }).close(); - fail('it should not be able to create the sender'); - } catch(err) { - expect(err.message).toBe('Missing username, please, specify the \'keyId\' property of the \'auth\' config option. ' + - 'For example: new Sender({protocol: \'tcp\', host: \'host\', auth: {keyId: \'username\', token: \'private key\'}})'); - } - }); - - it('requires that the username is a string', async function () { - try { - await new Sender({ - protocol: 'tcp', host: 'host', - auth: { - keyId: 23, - token: 'privateKey' - } - }).close(); - fail('it should not be able to create the sender'); - } catch(err) { - expect(err.message).toBe('Please, specify the \'keyId\' property of the \'auth\' config option as a string. ' + - 'For example: new Sender({protocol: \'tcp\', host: \'host\', auth: {keyId: \'username\', token: \'private key\'}})'); - } - }); - - it('requires a private key for authentication', async function () { - try { - await new Sender({ - protocol: 'tcp', host: 'host', - auth: { - keyId: 'username' - } - }).close(); - fail('it should not be able to create the sender'); - } catch(err) { - expect(err.message).toBe('Missing private key, please, specify the \'token\' property of the \'auth\' config option. ' + - 'For example: new Sender({protocol: \'tcp\', host: \'host\', auth: {keyId: \'username\', token: \'private key\'}})'); - } - }); - - it('requires a non-empty private key', async function () { - try { - await new Sender({ - protocol: 'tcp', host: 'host', - auth: { - keyId: 'username', - token: '' - } - }).close(); - fail('it should not be able to create the sender'); - } catch(err) { - expect(err.message).toBe('Missing private key, please, specify the \'token\' property of the \'auth\' config option. ' + - 'For example: new Sender({protocol: \'tcp\', host: \'host\', auth: {keyId: \'username\', token: \'private key\'}})'); - } - }); - - it('requires that the private key is a string', async function () { - try { - await new Sender({ - protocol: 'tcp', host: 'host', - auth: { - keyId: 'username', - token: true - } - }).close(); - fail('it should not be able to create the sender'); - } catch(err) { - expect(err.message).toBe('Please, specify the \'token\' property of the \'auth\' config option as a string. ' + - 'For example: new Sender({protocol: \'tcp\', host: \'host\', auth: {keyId: \'username\', token: \'private key\'}})'); - } - }); -}); - -describe('Sender HTTP suite', function () { - async function sendData(sender) { - await sender.table('test').symbol('location', 'us').floatColumn('temperature', 17.1).at(1658484765000000000n, 'ns'); - await sender.flush(); - } - - const mockHttp = new MockHttp(); - const mockHttps = new MockHttp(); - - beforeAll(async function () { - await mockHttp.start(MOCK_HTTP_PORT); - await mockHttps.start(MOCK_HTTPS_PORT, true, proxyOptions); - }); - - afterAll(async function () { - await mockHttp.stop(); - await mockHttps.stop(); - }); - - it('can ingest via HTTP', async function () { - mockHttp.reset(); - - const sender = Sender.fromConfig(`http::addr=${PROXY_HOST}:${MOCK_HTTP_PORT}`); - await sendData(sender); - expect(mockHttp.numOfRequests).toBe(1); - - expect(sender.agent.maxSockets).toBe(256); - - await sender.close(); - }); - - it('supports custom http agent', async function () { - mockHttp.reset(); - const agent = new http.Agent({ maxSockets: 128 }); - - const sender = Sender.fromConfig(`http::addr=${PROXY_HOST}:${MOCK_HTTP_PORT}`, { agent: agent }); - await sendData(sender); - expect(mockHttp.numOfRequests).toBe(1); - - expect(sender.agent.maxSockets).toBe(128); - - await sender.close(); - agent.destroy(); - }); - - it('can ingest via HTTPS', async function () { - mockHttps.reset(); - - const senderCertCheckFail = Sender.fromConfig(`https::addr=${PROXY_HOST}:${MOCK_HTTPS_PORT}`); - try { - await sendData(senderCertCheckFail); - fail('Request should have failed'); - } catch (err) { - expect(err.message).toMatch(/^self[ -]signed certificate in certificate chain$/); - } - await senderCertCheckFail.close(); - - const senderWithCA = Sender.fromConfig(`https::addr=${PROXY_HOST}:${MOCK_HTTPS_PORT};tls_ca=test/certs/ca/ca.crt`); - await sendData(senderWithCA); - expect(mockHttps.numOfRequests).toBe(1); - await senderWithCA.close(); - - const senderVerifyOff = Sender.fromConfig(`https::addr=${PROXY_HOST}:${MOCK_HTTPS_PORT};tls_verify=unsafe_off`); - await sendData(senderVerifyOff); - expect(mockHttps.numOfRequests).toBe(2); - await senderVerifyOff.close(); - }); - - it('can ingest via HTTP with basic auth', async function () { - mockHttp.reset({username: 'user1', password: 'pwd'}); - - const sender = Sender.fromConfig(`http::addr=${PROXY_HOST}:${MOCK_HTTP_PORT};username=user1;password=pwd`); - await sendData(sender); - expect(mockHttp.numOfRequests).toBe(1); - await sender.close(); - - const senderFailPwd = Sender.fromConfig(`http::addr=${PROXY_HOST}:${MOCK_HTTP_PORT};username=user1;password=xyz`); - try { - await sendData(senderFailPwd); - fail('Request should have failed'); - } catch (err) { - expect(err.message).toBe('HTTP request failed, statusCode=401, error='); - } - await senderFailPwd.close(); - - const senderFailMissingPwd = Sender.fromConfig(`http::addr=${PROXY_HOST}:${MOCK_HTTP_PORT};username=user1z`); - try { - await sendData(senderFailMissingPwd); - fail('Request should have failed'); - } catch (err) { - expect(err.message).toBe('HTTP request failed, statusCode=401, error='); - } - await senderFailMissingPwd.close(); - - const senderFailUsername = Sender.fromConfig(`http::addr=${PROXY_HOST}:${MOCK_HTTP_PORT};username=xyz;password=pwd`); - try { - await sendData(senderFailUsername); - fail('Request should have failed'); - } catch (err) { - expect(err.message).toBe('HTTP request failed, statusCode=401, error='); - } - await senderFailUsername.close(); - - const senderFailMissingUsername = Sender.fromConfig(`http::addr=${PROXY_HOST}:${MOCK_HTTP_PORT};password=pwd`); - try { - await sendData(senderFailMissingUsername); - fail('Request should have failed'); - } catch (err) { - expect(err.message).toBe('HTTP request failed, statusCode=401, error='); - } - await senderFailMissingUsername.close(); - - const senderFailMissing = Sender.fromConfig(`http::addr=${PROXY_HOST}:${MOCK_HTTP_PORT}`); - try { - await sendData(senderFailMissing); - fail('Request should have failed'); - } catch (err) { - expect(err.message).toBe('HTTP request failed, statusCode=401, error='); - } - await senderFailMissing.close(); - }); - - it('can ingest via HTTP with token auth', async function () { - mockHttp.reset({token: 'abcdefghijkl123'}); - - const sender = Sender.fromConfig(`http::addr=${PROXY_HOST}:${MOCK_HTTP_PORT};token=abcdefghijkl123`); - await sendData(sender); - expect(mockHttp.numOfRequests).toBe(1); - await sender.close(); - - const senderFailToken = Sender.fromConfig(`http::addr=${PROXY_HOST}:${MOCK_HTTP_PORT};token=xyz`); - try { - await sendData(senderFailToken); - fail('Request should have failed'); - } catch (err) { - expect(err.message).toBe('HTTP request failed, statusCode=401, error='); - } - await senderFailToken.close(); - - const senderFailMissing = Sender.fromConfig(`http::addr=${PROXY_HOST}:${MOCK_HTTP_PORT}`); - try { - await sendData(senderFailMissing); - fail('Request should have failed'); - } catch (err) { - expect(err.message).toBe('HTTP request failed, statusCode=401, error='); - } - await senderFailMissing.close(); - }); - - it('can retry via HTTP', async function () { - mockHttp.reset({responseCodes: [204, 500, 523, 504, 500]}); - - const sender = Sender.fromConfig(`http::addr=${PROXY_HOST}:${MOCK_HTTP_PORT}`); - await sendData(sender); - expect(mockHttp.numOfRequests).toBe(5); - - await sender.close(); - }); - - it('fails when retry timeout expires', async function () { - // artificial delay (responseDelays) is same as retry timeout - // should result in the request failing on the second try - mockHttp.reset({ - responseCodes: [204, 500, 503], - responseDelays: [1000, 1000, 1000] - }); - - const sender = Sender.fromConfig(`http::addr=${PROXY_HOST}:${MOCK_HTTP_PORT};retry_timeout=1000`); - try { - await sendData(sender); - fail('Request should have failed'); - } catch(err) { - expect(err.message).toBe('HTTP request failed, statusCode=500, error='); - } - - await sender.close(); - }); - - it('fails when HTTP request times out', async function () { - // artificial delay (responseDelays) is greater than request timeout, and retry is switched off - // should result in the request failing with timeout - mockHttp.reset({ - responseCodes: [204], - responseDelays: [500] - }); - - const sender = Sender.fromConfig(`http::addr=${PROXY_HOST}:${MOCK_HTTP_PORT};retry_timeout=0;request_timeout=100`); - try { - await sendData(sender); - fail('Request should have failed'); - } catch(err) { - expect(err.message).toBe('HTTP request timeout, no response from server in time'); - } - - await sender.close(); - }); - - it('succeeds on the third request after two timeouts', async function () { - mockHttp.reset({ - responseCodes: [204, 504, 504], - responseDelays: [2000, 2000] - }); - - const sender = Sender.fromConfig(`http::addr=${PROXY_HOST}:${MOCK_HTTP_PORT};retry_timeout=30000;request_timeout=1000`); - await sendData(sender); - - await sender.close(); - }); - - it('accepts custom http agent', async function () { - mockHttp.reset(); - const agent = new http.Agent({ keepAlive: false, maxSockets: 2 }); - - const num = 300; - const senders = []; - const promises = []; - for (let i = 0; i < num; i++) { - const sender = Sender.fromConfig(`http::addr=${PROXY_HOST}:${MOCK_HTTP_PORT}`, { agent: agent}); - senders.push(sender); - const promise = sendData(sender); - promises.push(promise); - } - await Promise.all(promises); - expect(mockHttp.numOfRequests).toBe(num); - - expect(agent.totalSocketCount).toBeLessThan(3); - - for (const sender of senders) { - await sender.close(); - } - agent.destroy(); - }); -}); - -describe('Sender connection suite', function () { - async function createProxy(auth = false, tlsOptions = undefined) { - const mockConfig = { auth: auth, assertions: true }; - const proxy = new MockProxy(mockConfig); - await proxy.start(PROXY_PORT, tlsOptions); - expect(proxy.mockConfig).toBe(mockConfig); - expect(proxy.dataSentToRemote).toStrictEqual([]); - return proxy; - } - - async function createSender(auth = undefined, secure = false) { - const sender = new Sender({ - protocol: (secure ? 'tcps' : 'tcp'), - port: PROXY_PORT, - host: PROXY_HOST, - auth: auth, - tls_ca: 'test/certs/ca/ca.crt' - }); - const connected = await sender.connect(); - expect(connected).toBe(true); - return sender; - } - - async function sendData(sender) { - await sender.table('test').symbol('location', 'us').floatColumn('temperature', 17.1).at(1658484765000000000n, 'ns'); - await sender.flush(); - } - - async function assertSentData(proxy, authenticated, expected, timeout = 60000) { - const interval = 100; - const num = timeout / interval; - let actual; - for (let i = 0; i < num; i++) { - const dataSentToRemote = proxy.getDataSentToRemote().join('').split('\n'); - if (authenticated) { - dataSentToRemote.splice(1, 1); - } - actual = dataSentToRemote.join('\n'); - if (actual === expected) { - return new Promise(resolve => resolve(null)); - } - await sleep(interval); - } - return new Promise(resolve => resolve(`data assert failed [expected=${expected}, actual=${actual}]`)); - } - - it('can authenticate', async function () { - const proxy = await createProxy(true); - const sender = await createSender(AUTH); - await assertSentData(proxy, true, 'testapp\n'); - await sender.close(); - await proxy.stop(); - }); - - it('can authenticate with a different private key', async function () { - const proxy = await createProxy(true); - const sender = await createSender({ - keyId: 'user1', - token: 'zhPiK3BkYMYJvRf5sqyrWNJwjDKHOWHnRbmQggUll6A' - }); - await assertSentData(proxy, true, 'user1\n'); - await sender.close(); - await proxy.stop(); - }); - - it('is backwards compatible and still can authenticate with full JWK', async function () { - const JWK = { - x: 'BtUXC_K3oAyGlsuPjTgkiwirMUJhuRQDfcUHeyoxFxU', - y: 'R8SOup-rrNofB7wJagy4HrJhTVfrVKmj061lNRk3bF8', - kid: 'user2', - kty: 'EC', - d: 'hsg6Zm4kSBlIEvKUWT3kif-2y2Wxw-iWaGrJxrPXQhs', - crv: 'P-256' - } - - const proxy = await createProxy(true); - const sender = new Sender({ - protocol: 'tcp', - port: PROXY_PORT, - host: PROXY_HOST, - ca: readFileSync('test/certs/ca/ca.crt'), - jwk: JWK}); - const connected = await sender.connect(); - expect(connected).toBe(true); - await assertSentData(proxy, true, 'user2\n'); - await sender.close(); - await proxy.stop(); - }); - - it('can connect unauthenticated', async function () { - const proxy = await createProxy(); - const sender = await createSender(); - await assertSentData(proxy, false, ''); - await sender.close(); - await proxy.stop(); - }); - - it('can authenticate and send data to server', async function () { - const proxy = await createProxy(true); - const sender = await createSender(AUTH); - await sendData(sender); - await assertSentData(proxy, true, 'testapp\ntest,location=us temperature=17.1 1658484765000000000\n'); - await sender.close(); - await proxy.stop(); - }); - - it('can connect unauthenticated and send data to server', async function () { - const proxy = await createProxy(); - const sender = await createSender(); - await sendData(sender); - await assertSentData(proxy, false, 'test,location=us temperature=17.1 1658484765000000000\n'); - await sender.close(); - await proxy.stop(); - }); - - it('can authenticate and send data to server via secure connection', async function () { - const proxy = await createProxy(true, proxyOptions); - const sender = await createSender(AUTH, true); - await sendData(sender); - await assertSentData(proxy, true, 'testapp\ntest,location=us temperature=17.1 1658484765000000000\n'); - await sender.close(); - await proxy.stop(); - }); - - it('can connect unauthenticated and send data to server via secure connection', async function () { - const proxy = await createProxy(false, proxyOptions); - const sender = await createSender(null, true); - await sendData(sender); - await assertSentData(proxy, false, 'test,location=us temperature=17.1 1658484765000000000\n'); - await sender.close(); - await proxy.stop(); - }); - - it('fails to connect without hostname and port', async function () { - const sender = new Sender({protocol: 'tcp'}); - try { - await sender.connect(); - fail('it should not be able to connect'); - } catch(err) { - expect(err.message).toBe('Hostname is not set'); - } - await sender.close(); - }); - - it('fails to send data if not connected', async function () { - const sender = new Sender({protocol: 'tcp', host: 'localhost'}); - try { - await sender.table('test').symbol('location', 'us').atNow(); - await sender.flush(); - fail('it should not be able to send data'); - } catch(err) { - expect(err.message).toBe('Sender is not connected'); - } - await sender.close(); - }); - - it('guards against multiple connect calls', async function () { - const proxy = await createProxy(true, proxyOptions); - const sender = await createSender(AUTH, true); - try { - await sender.connect(); - fail('it should not be able to connect again'); - } catch(err) { - expect(err.message).toBe('Sender connected already'); - } - await sender.close(); - await proxy.stop(); - }); - - it('guards against concurrent connect calls', async function () { - const proxy = await createProxy(true, proxyOptions); - const sender = new Sender({ - protocol: 'tcps', - port: PROXY_PORT, - host: PROXY_HOST, - auth: AUTH, - ca: readFileSync('test/certs/ca/ca.crt') - }); - try { - await Promise.all([sender.connect(), sender.connect()]); - fail('it should not be able to connect twice'); - } catch(err) { - expect(err.message).toBe('Sender connected already'); - } - await sender.close(); - await proxy.stop(); - }); - - it('can disable the server certificate check' , async function () { - const proxy = await createProxy(true, proxyOptions); - const senderCertCheckFail = Sender.fromConfig(`tcps::addr=${PROXY_HOST}:${PROXY_PORT}`); - try { - await senderCertCheckFail.connect(); - fail('it should not be able to connect'); - } catch (err) { - expect(err.message).toMatch(/^self[ -]signed certificate in certificate chain$/); - } - await senderCertCheckFail.close(); - - const senderCertCheckOn = Sender.fromConfig(`tcps::addr=${PROXY_HOST}:${PROXY_PORT};tls_ca=test/certs/ca/ca.crt`); - await senderCertCheckOn.connect(); - await senderCertCheckOn.close(); - - const senderCertCheckOff = Sender.fromConfig(`tcps::addr=${PROXY_HOST}:${PROXY_PORT};tls_verify=unsafe_off`); - await senderCertCheckOff.connect(); - await senderCertCheckOff.close(); - await proxy.stop(); - }); - - it('can handle unfinished rows during flush()', async function () { - const proxy = await createProxy(true, proxyOptions); - const sender = await createSender(AUTH, true); - sender.table('test').symbol('location', 'us'); - const sent = await sender.flush(); - expect(sent).toBe(false); - await assertSentData(proxy, true, 'testapp\n'); - await sender.close(); - await proxy.stop(); - }); - - it('supports custom logger', async function () { - const expectedMessages = [ - 'Successfully connected to localhost:9088', - /^Connection to .*1:9088 is closed$/ - ]; - const log = (level, message) => { - expect(level).toBe('info'); - expect(message).toMatch(expectedMessages.shift()); - }; - const proxy = await createProxy(); - const sender = new Sender({ - protocol: 'tcp', - port: PROXY_PORT, - host: PROXY_HOST, - log: log}); - await sender.connect(); - await sendData(sender); - await assertSentData(proxy, false, 'test,location=us temperature=17.1 1658484765000000000\n'); - await sender.close(); - await proxy.stop(); - }); -}); - -describe('Client interop test suite', function () { - it('runs client tests as per json test config', async function () { - let testCases = JSON.parse(readFileSync('./questdb-client-test/ilp-client-interop-test.json').toString()); - - loopTestCase: - for (const testCase of testCases) { - console.info(`test name: ${testCase.testName}`); - const sender = new Sender({protocol: 'tcp', host: 'host', init_buf_size: 1024}); - try { - sender.table(testCase.table); - for (const symbol of testCase.symbols) { - sender.symbol(symbol.name, symbol.value); - } - for (const column of testCase.columns) { - switch (column.type) { - case 'STRING': - sender.stringColumn(column.name, column.value); - break; - case 'LONG': - sender.intColumn(column.name, column.value); - break; - case 'DOUBLE': - sender.floatColumn(column.name, column.value); - break; - case 'BOOLEAN': - sender.booleanColumn(column.name, column.value); - break; - case 'TIMESTAMP': - sender.timestampColumn(column.name, column.value); - break; - default: - throw new Error('Unsupported column type'); - } - } - await sender.atNow(); - } catch (e) { - if (testCase.result.status !== 'ERROR') { - fail('Did not expect error: ' + e.message); - break; - } - await sender.close(); - continue; - } - - const buffer = sender.toBufferView(); - if (testCase.result.status === 'SUCCESS') { - if (testCase.result.line) { - expect(buffer.toString()).toBe(testCase.result.line + '\n'); - } else { - for (const line of testCase.result.anyLines) { - if (buffer.toString() === line + '\n') { - // test passed - await sender.close(); - continue loopTestCase; - } - } - fail('Line is not matching any of the expected results: ' + buffer.toString()); - } - } else { - fail('Expected error missing, instead we have a line: ' + buffer.toString()); - break; - } - - await sender.close(); - } - }); -}); - -describe('Sender message builder test suite (anything not covered in client interop test suite)', function () { - it('throws on invalid timestamp unit', async function () { - const sender = new Sender({protocol: 'tcp', host: 'host', init_buf_size: 1024}); - try { - await sender.table('tableName') - .booleanColumn('boolCol', true) - .timestampColumn('timestampCol', 1658484765000000, 'foobar') - .atNow(); - fail('Expected error is not thrown'); - } catch (err) { - expect(err.message).toBe('Unknown timestamp unit: foobar'); - } - await sender.close(); - }); - - it('supports json object', async function () { - const pages = []; - for (let i = 0; i < 4; i++) { - const pageProducts = [ - {"id": "46022e96-076f-457f-b630-51b82b871618" + i, "gridId": "46022e96-076f-457f-b630-51b82b871618"}, - {"id": "55615358-4af1-4179-9153-faaa57d71e55", "gridId": "55615358-4af1-4179-9153-faaa57d71e55"}, - {"id": "365b9cdf-3d4e-4135-9cb0-f1a65601c840", "gridId": "365b9cdf-3d4e-4135-9cb0-f1a65601c840"}, - {"id": "0b67ddf2-8e69-4482-bf0c-bb987ee5c280", "gridId": "0b67ddf2-8e69-4482-bf0c-bb987ee5c280" + i}]; - pages.push(pageProducts); - } - - const sender = new Sender({protocol: 'tcp', host: 'host', bufferSize: 256}); - for (const p of pages) { - await sender.table('tableName') - .stringColumn('page_products', JSON.stringify(p || [])) - .booleanColumn('boolCol', true) - .atNow(); - } - expect(sender.toBufferView().toString()).toBe( - 'tableName page_products="[{\\"id\\":\\"46022e96-076f-457f-b630-51b82b8716180\\",\\"gridId\\":\\"46022e96-076f-457f-b630-51b82b871618\\"},{\\"id\\":\\"55615358-4af1-4179-9153-faaa57d71e55\\",\\"gridId\\":\\"55615358-4af1-4179-9153-faaa57d71e55\\"},{\\"id\\":\\"365b9cdf-3d4e-4135-9cb0-f1a65601c840\\",\\"gridId\\":\\"365b9cdf-3d4e-4135-9cb0-f1a65601c840\\"},{\\"id\\":\\"0b67ddf2-8e69-4482-bf0c-bb987ee5c280\\",\\"gridId\\":\\"0b67ddf2-8e69-4482-bf0c-bb987ee5c2800\\"}]",boolCol=t\n' + - 'tableName page_products="[{\\"id\\":\\"46022e96-076f-457f-b630-51b82b8716181\\",\\"gridId\\":\\"46022e96-076f-457f-b630-51b82b871618\\"},{\\"id\\":\\"55615358-4af1-4179-9153-faaa57d71e55\\",\\"gridId\\":\\"55615358-4af1-4179-9153-faaa57d71e55\\"},{\\"id\\":\\"365b9cdf-3d4e-4135-9cb0-f1a65601c840\\",\\"gridId\\":\\"365b9cdf-3d4e-4135-9cb0-f1a65601c840\\"},{\\"id\\":\\"0b67ddf2-8e69-4482-bf0c-bb987ee5c280\\",\\"gridId\\":\\"0b67ddf2-8e69-4482-bf0c-bb987ee5c2801\\"}]",boolCol=t\n' + - 'tableName page_products="[{\\"id\\":\\"46022e96-076f-457f-b630-51b82b8716182\\",\\"gridId\\":\\"46022e96-076f-457f-b630-51b82b871618\\"},{\\"id\\":\\"55615358-4af1-4179-9153-faaa57d71e55\\",\\"gridId\\":\\"55615358-4af1-4179-9153-faaa57d71e55\\"},{\\"id\\":\\"365b9cdf-3d4e-4135-9cb0-f1a65601c840\\",\\"gridId\\":\\"365b9cdf-3d4e-4135-9cb0-f1a65601c840\\"},{\\"id\\":\\"0b67ddf2-8e69-4482-bf0c-bb987ee5c280\\",\\"gridId\\":\\"0b67ddf2-8e69-4482-bf0c-bb987ee5c2802\\"}]",boolCol=t\n' + - 'tableName page_products="[{\\"id\\":\\"46022e96-076f-457f-b630-51b82b8716183\\",\\"gridId\\":\\"46022e96-076f-457f-b630-51b82b871618\\"},{\\"id\\":\\"55615358-4af1-4179-9153-faaa57d71e55\\",\\"gridId\\":\\"55615358-4af1-4179-9153-faaa57d71e55\\"},{\\"id\\":\\"365b9cdf-3d4e-4135-9cb0-f1a65601c840\\",\\"gridId\\":\\"365b9cdf-3d4e-4135-9cb0-f1a65601c840\\"},{\\"id\\":\\"0b67ddf2-8e69-4482-bf0c-bb987ee5c280\\",\\"gridId\\":\\"0b67ddf2-8e69-4482-bf0c-bb987ee5c2803\\"}]",boolCol=t\n' - ); - await sender.close(); - }); - - it('supports timestamp field as number', async function () { - const sender = new Sender({protocol: 'tcp', host: 'host', init_buf_size: 1024}); - await sender.table('tableName') - .booleanColumn('boolCol', true) - .timestampColumn('timestampCol', 1658484765000000) - .atNow(); - expect(sender.toBufferView().toString()).toBe( - 'tableName boolCol=t,timestampCol=1658484765000000t\n' - ); - await sender.close(); - }); - - it('supports timestamp field as ns number', async function () { - const sender = new Sender({protocol: 'tcp', host: 'host', init_buf_size: 1024}); - await sender.table('tableName') - .booleanColumn('boolCol', true) - .timestampColumn('timestampCol', 1658484765000000, 'ns') - .atNow(); - expect(sender.toBufferView().toString()).toBe( - 'tableName boolCol=t,timestampCol=1658484765000t\n' - ); - await sender.close(); - }); - - it('supports timestamp field as us number', async function () { - const sender = new Sender({protocol: 'tcp', host: 'host', init_buf_size: 1024}); - await sender.table('tableName') - .booleanColumn('boolCol', true) - .timestampColumn('timestampCol', 1658484765000000, 'us') - .atNow(); - expect(sender.toBufferView().toString()).toBe( - 'tableName boolCol=t,timestampCol=1658484765000000t\n' - ); - await sender.close(); - }); - - it('supports timestamp field as ms number', async function () { - const sender = new Sender({protocol: 'tcp', host: 'host', init_buf_size: 1024}); - await sender.table('tableName') - .booleanColumn('boolCol', true) - .timestampColumn('timestampCol', 1658484765000, 'ms') - .atNow(); - expect(sender.toBufferView().toString()).toBe( - 'tableName boolCol=t,timestampCol=1658484765000000t\n' - ); - await sender.close(); - }); - - it('supports timestamp field as BigInt', async function () { - const sender = new Sender({protocol: 'tcp', host: 'host', init_buf_size: 1024}); - await sender.table('tableName') - .booleanColumn('boolCol', true) - .timestampColumn('timestampCol', 1658484765000000n) - .atNow(); - expect(sender.toBufferView().toString()).toBe( - 'tableName boolCol=t,timestampCol=1658484765000000t\n' - ); - await sender.close(); - }); - - it('supports timestamp field as ns BigInt', async function () { - const sender = new Sender({protocol: 'tcp', host: 'host', init_buf_size: 1024}); - await sender.table('tableName') - .booleanColumn('boolCol', true) - .timestampColumn('timestampCol', 1658484765000000000n, 'ns') - .atNow(); - expect(sender.toBufferView().toString()).toBe( - 'tableName boolCol=t,timestampCol=1658484765000000t\n' - ); - await sender.close(); - }); - - it('supports timestamp field as us BigInt', async function () { - const sender = new Sender({protocol: 'tcp', host: 'host', init_buf_size: 1024}); - await sender.table('tableName') - .booleanColumn('boolCol', true) - .timestampColumn('timestampCol', 1658484765000000n, 'us') - .atNow(); - expect(sender.toBufferView().toString()).toBe( - 'tableName boolCol=t,timestampCol=1658484765000000t\n' - ); - await sender.close(); - }); - - it('supports timestamp field as ms BigInt', async function () { - const sender = new Sender({protocol: 'tcp', host: 'host', init_buf_size: 1024}); - await sender.table('tableName') - .booleanColumn('boolCol', true) - .timestampColumn('timestampCol', 1658484765000n, 'ms') - .atNow(); - expect(sender.toBufferView().toString()).toBe( - 'tableName boolCol=t,timestampCol=1658484765000000t\n' - ); - await sender.close(); - }); - - it('throws on invalid designated timestamp unit', async function () { - const sender = new Sender({protocol: 'tcp', host: 'host', init_buf_size: 1024}); - try { - await sender.table('tableName') - .booleanColumn('boolCol', true) - .timestampColumn('timestampCol', 1658484765000000) - .at(1658484769000000, 'foobar'); - fail('Expected error is not thrown'); - } catch(err) { - expect(err.message).toBe('Unknown timestamp unit: foobar'); - } - await sender.close(); - }); - - it('supports setting designated us timestamp as number from client', async function () { - const sender = new Sender({protocol: 'tcp', host: 'host', init_buf_size: 1024}); - await sender.table('tableName') - .booleanColumn('boolCol', true) - .timestampColumn('timestampCol', 1658484765000000) - .at(1658484769000000, 'us'); - expect(sender.toBufferView().toString()).toBe( - 'tableName boolCol=t,timestampCol=1658484765000000t 1658484769000000000\n' - ); - await sender.close(); - }); - - it('supports setting designated ms timestamp as number from client', async function () { - const sender = new Sender({protocol: 'tcp', host: 'host', init_buf_size: 1024}); - await sender.table('tableName') - .booleanColumn('boolCol', true) - .timestampColumn('timestampCol', 1658484765000000) - .at(1658484769000, 'ms'); - expect(sender.toBufferView().toString()).toBe( - 'tableName boolCol=t,timestampCol=1658484765000000t 1658484769000000000\n' - ); - await sender.close(); - }); - - it('supports setting designated timestamp as BigInt from client', async function () { - const sender = new Sender({protocol: 'tcp', host: 'host', init_buf_size: 1024}); - await sender.table('tableName') - .booleanColumn('boolCol', true) - .timestampColumn('timestampCol', 1658484765000000) - .at(1658484769000000n); - expect(sender.toBufferView().toString()).toBe( - 'tableName boolCol=t,timestampCol=1658484765000000t 1658484769000000000\n' - ); - await sender.close(); - }); - - it('supports setting designated ns timestamp as BigInt from client', async function () { - const sender = new Sender({protocol: 'tcp', host: 'host', init_buf_size: 1024}); - await sender.table('tableName') - .booleanColumn('boolCol', true) - .timestampColumn('timestampCol', 1658484765000000) - .at(1658484769000000123n, 'ns'); - expect(sender.toBufferView().toString()).toBe( - 'tableName boolCol=t,timestampCol=1658484765000000t 1658484769000000123\n' - ); - await sender.close(); - }); - - it('supports setting designated us timestamp as BigInt from client', async function () { - const sender = new Sender({protocol: 'tcp', host: 'host', init_buf_size: 1024}); - await sender.table('tableName') - .booleanColumn('boolCol', true) - .timestampColumn('timestampCol', 1658484765000000) - .at(1658484769000000n, 'us'); - expect(sender.toBufferView().toString()).toBe( - 'tableName boolCol=t,timestampCol=1658484765000000t 1658484769000000000\n' - ); - await sender.close(); - }); - - it('supports setting designated ms timestamp as BigInt from client', async function () { - const sender = new Sender({protocol: 'tcp', host: 'host', init_buf_size: 1024}); - await sender.table('tableName') - .booleanColumn('boolCol', true) - .timestampColumn('timestampCol', 1658484765000000) - .at(1658484769000n, 'ms'); - expect(sender.toBufferView().toString()).toBe( - 'tableName boolCol=t,timestampCol=1658484765000000t 1658484769000000000\n' - ); - await sender.close(); - }); - - it('throws exception if table name is not a string', async function () { - const sender = new Sender({protocol: 'tcp', host: 'host', init_buf_size: 1024}); - expect( - () => sender.table(23456) - ).toThrow('Table name must be a string, received number'); - await sender.close(); - }); - - it('throws exception if table name is too long', async function () { - const sender = new Sender({protocol: 'tcp', host: 'host', init_buf_size: 1024}); - expect( - () => sender.table('123456789012345678901234567890123456789012345678901234567890' - + '12345678901234567890123456789012345678901234567890123456789012345678') - ).toThrow('Table name is too long, max length is 127'); - await sender.close(); - }); - - it('throws exception if table name is set more times', async function () { - const sender = new Sender({protocol: 'tcp', host: 'host', init_buf_size: 1024}); - expect( - () => sender.table('tableName') - .symbol('name', 'value') - .table('newTableName') - ).toThrow('Table name has already been set'); - await sender.close(); - }); - - it('throws exception if symbol name is not a string', async function () { - const sender = new Sender({protocol: 'tcp', host: 'host', init_buf_size: 1024}); - expect( - () => sender.table('tableName') - .symbol(12345.5656, 'value') - ).toThrow('Symbol name must be a string, received number'); - await sender.close(); - }); - - it('throws exception if symbol name is empty string', async function () { - const sender = new Sender({protocol: 'tcp', host: 'host', init_buf_size: 1024}); - expect( - () => sender.table('tableName') - .symbol('', 'value') - ).toThrow('Empty string is not allowed as column name'); - await sender.close(); - }); - - it('throws exception if column name is not a string', async function () { - const sender = new Sender({protocol: 'tcp', host: 'host', init_buf_size: 1024}); - expect( - () => sender.table('tableName') - .stringColumn(12345.5656, 'value') - ).toThrow('Column name must be a string, received number'); - await sender.close(); - }); - - it('throws exception if column name is empty string', async function () { - const sender = new Sender({protocol: 'tcp', host: 'host', init_buf_size: 1024}); - expect( - () => sender.table('tableName') - .stringColumn('', 'value') - ).toThrow('Empty string is not allowed as column name'); - await sender.close(); - }); - - it('throws exception if column name is too long', async function () { - const sender = new Sender({protocol: 'tcp', host: 'host', init_buf_size: 1024}); - expect( - () => sender.table('tableName') - .stringColumn('123456789012345678901234567890123456789012345678901234567890' - + '12345678901234567890123456789012345678901234567890123456789012345678', 'value') - ).toThrow('Column name is too long, max length is 127'); - await sender.close(); - }); - - it('throws exception if column value is not the right type', async function () { - const sender = new Sender({protocol: 'tcp', host: 'host', init_buf_size: 1024}); - expect( - () => sender.table('tableName') - .stringColumn('columnName', false) - ).toThrow('Column value must be of type string, received boolean'); - await sender.close(); - }); - - it('throws exception if adding column without setting table name', async function () { - const sender = new Sender({protocol: 'tcp', host: 'host', init_buf_size: 1024}); - expect( - () => sender.floatColumn('name', 12.459) - ).toThrow('Column can be set only after table name is set'); - await sender.close(); - }); - - it('throws exception if adding symbol without setting table name', async function () { - const sender = new Sender({protocol: 'tcp', host: 'host', init_buf_size: 1024}); - expect( - () => sender.symbol('name', 'value') - ).toThrow('Symbol can be added only after table name is set and before any column added'); - await sender.close(); - }); - - it('throws exception if adding symbol after columns', async function () { - const sender = new Sender({protocol: 'tcp', host: 'host', init_buf_size: 1024}); - expect( - () => sender.table('tableName') - .stringColumn('name', 'value') - .symbol('symbolName', 'symbolValue') - ).toThrow('Symbol can be added only after table name is set and before any column added'); - await sender.close(); - }); - - it('returns null if preparing an empty buffer for send', async function () { - const sender = new Sender({protocol: 'tcp', host: 'host', init_buf_size: 1024}); - expect(sender.toBufferView()).toBe(null); - await sender.close(); - }); - - it('ignores unfinished rows when preparing a buffer for send', async function () { - const sender = new Sender({protocol: 'tcp', host: 'host', init_buf_size: 1024}); - sender.table('tableName') - .symbol('name', 'value'); - await sender.at(1234567890n, 'ns'); - sender.table('tableName') - .symbol('name', 'value2'); - expect( - sender.toBufferView(sender.endOfLastRow).toString() - ).toBe('tableName,name=value 1234567890\n'); - await sender.close(); - }); - - it('throws exception if a float is passed as integer field', async function () { - const sender = new Sender({protocol: 'tcp', host: 'host', init_buf_size: 1024}); - expect( - () => sender.table('tableName') - .intColumn('intField', 123.222) - ).toThrow('Value must be an integer, received 123.222'); - await sender.close(); - }); - - it('throws exception if a float is passed as timestamp field', async function () { - const sender = new Sender({protocol: 'tcp', host: 'host', init_buf_size: 1024}); - expect( - () => sender.table('tableName') - .timestampColumn('intField', 123.222) - ).toThrow('Value must be an integer or BigInt, received 123.222'); - await sender.close(); - }); - - it('throws exception if designated timestamp is not an integer or bigint', async function () { - const sender = new Sender({protocol: 'tcp', host: 'host', init_buf_size: 1024}); - try { - await sender.table('tableName') - .symbol('name', 'value') - .at(23232322323.05); - } catch (e) { - expect(e.message).toEqual('Designated timestamp must be an integer or BigInt, received 23232322323.05'); - } - await sender.close(); - }); - - it('throws exception if designated timestamp is invalid', async function () { - const sender = new Sender({protocol: 'tcp', host: 'host', init_buf_size: 1024}); - try { - await sender.table('tableName') - .symbol('name', 'value') - .at('invalid_dts'); - } catch (e) { - expect(e.message).toEqual('Designated timestamp must be an integer or BigInt, received invalid_dts'); - } - await sender.close(); - }); - - it('throws exception if designated timestamp is set without any fields added', async function () { - const sender = new Sender({protocol: 'tcp', host: 'host', init_buf_size: 1024}); - try { - await sender.table('tableName') - .at(12345678n, 'ns'); - } catch (e) { - expect(e.message).toEqual('The row must have a symbol or column set before it is closed'); - } - await sender.close(); - }); - - it('extends the size of the buffer if data does not fit', async function () { - const sender = new Sender({protocol: 'tcp', host: 'host', init_buf_size: 8}); - expect(sender.bufferSize).toBe(8); - expect(sender.position).toBe(0); - sender.table('tableName'); - expect(sender.bufferSize).toBe(16); - expect(sender.position).toBe('tableName'.length); - sender.intColumn('intField', 123); - expect(sender.bufferSize).toBe(32); - expect(sender.position).toBe('tableName intField=123i'.length); - await sender.atNow(); - expect(sender.bufferSize).toBe(32); - expect(sender.position).toBe('tableName intField=123i\n'.length); - expect(sender.toBufferView().toString()).toBe( - 'tableName intField=123i\n' - ); - - await sender.table('table2') - .intColumn('intField', 125) - .stringColumn('strField', 'test') - .atNow(); - expect(sender.bufferSize).toBe(64); - expect(sender.position).toBe('tableName intField=123i\ntable2 intField=125i,strField="test"\n'.length); - expect(sender.toBufferView().toString()).toBe( - 'tableName intField=123i\ntable2 intField=125i,strField="test"\n' - ); - await sender.close(); - }); - - it('throws exception if tries to extend the size of the buffer above max buffer size', async function () { - const sender = Sender.fromConfig('tcp::addr=host;init_buf_size=8;max_buf_size=48;') - expect(sender.bufferSize).toBe(8); - expect(sender.position).toBe(0); - sender.table('tableName'); - expect(sender.bufferSize).toBe(16); - expect(sender.position).toBe('tableName'.length); - sender.intColumn('intField', 123); - expect(sender.bufferSize).toBe(32); - expect(sender.position).toBe('tableName intField=123i'.length); - await sender.atNow(); - expect(sender.bufferSize).toBe(32); - expect(sender.position).toBe('tableName intField=123i\n'.length); - expect(sender.toBufferView().toString()).toBe( - 'tableName intField=123i\n' - ); - - try { - await sender.table('table2') - .intColumn('intField', 125) - .stringColumn('strField', 'test') - .atNow(); - fail('Expected error is not thrown'); - } catch (err) { - expect(err.message).toBe('Max buffer size is 48 bytes, requested buffer size: 64'); - } - await sender.close(); - }); - - it('is possible to clear the buffer by calling reset()', async function () { - const sender = new Sender({protocol: 'tcp', host: 'host', init_buf_size: 1024}); - await sender.table('tableName') - .booleanColumn('boolCol', true) - .timestampColumn('timestampCol', 1658484765000000) - .atNow(); - await sender.table('tableName') - .booleanColumn('boolCol', false) - .timestampColumn('timestampCol', 1658484766000000) - .atNow(); - expect(sender.toBufferView().toString()).toBe( - 'tableName boolCol=t,timestampCol=1658484765000000t\n' - + 'tableName boolCol=f,timestampCol=1658484766000000t\n' - ); - - sender.reset(); - await sender.table('tableName') - .floatColumn('floatCol', 1234567890) - .timestampColumn('timestampCol', 1658484767000000) - .atNow(); - expect(sender.toBufferView().toString()).toBe( - 'tableName floatCol=1234567890,timestampCol=1658484767000000t\n' - ); - await sender.close(); - }); -}); - -describe('Sender tests with containerized QuestDB instance', () => { - let container; - - async function query(container, query) { - const options = { - hostname: container.getHost(), - port: container.getMappedPort(QUESTDB_HTTP_PORT), - path: `/exec?query=${encodeURIComponent(query)}`, - method: 'GET', - }; - - return new Promise((resolve, reject) => { - const req = http.request(options, response => { - if (response.statusCode === HTTP_OK) { - const body = []; - response.on('data', data => { - body.push(data); - }).on('end', () => { - resolve(JSON.parse(Buffer.concat(body).toString())); - }); - } else { - reject(new Error(`HTTP request failed, statusCode=${response.statusCode}, query=${query}`)); - } - }); - - req.on('error', error => { - reject(error); - }); - - req.end(); - }); - } - - async function runSelect(container, select, expectedCount, timeout = 60000) { - const interval = 500; - const num = timeout / interval; - let selectResult; - for (let i = 0; i < num; i++) { - selectResult = await query(container, select); - if (selectResult && selectResult.count >= expectedCount) { - return selectResult; - } - await sleep(interval); - } - throw new Error(`Timed out while waiting for ${expectedCount} rows, select='${select}'`); - } - - function getFieldsString(schema) { - let fields = ''; - for (const element of schema) { - fields += `${element.name} ${element.type}, `; - } - return fields.substring(0, fields.length - 2); - } - - beforeAll(async () => { - jest.setTimeout(3000000); - container = await new GenericContainer('questdb/questdb:nightly') - .withExposedPorts(QUESTDB_HTTP_PORT, QUESTDB_ILP_PORT) - .start(); - - const stream = await container.logs(); - stream - .on('data', line => console.log(line)) - .on('err', line => console.error(line)) - .on('end', () => console.log('Stream closed')); - }); - - afterAll(async () => { - await container.stop(); - }); - - it('can ingest data via TCP and run queries', async () => { - const sender = new Sender({ - protocol: 'tcp', - host: container.getHost(), - port: container.getMappedPort(QUESTDB_ILP_PORT)} - ); - await sender.connect(); - - const tableName = 'test_tcp'; - const schema = [ - {name: 'location', type: 'SYMBOL'}, - {name: 'temperature', type: 'DOUBLE'}, - {name: 'timestamp', type: 'TIMESTAMP'} - ]; - - // create table - let createTableResult = await query(container, - `CREATE TABLE ${tableName}(${getFieldsString(schema)}) TIMESTAMP (timestamp) PARTITION BY DAY BYPASS WAL;` - ); - expect(createTableResult.ddl).toBe('OK'); - - // alter table - let alterTableResult = await query(container, - `ALTER TABLE ${tableName} SET PARAM maxUncommittedRows = 1;` - ); - expect(alterTableResult.ddl).toBe('OK'); - - // ingest via client - await sender.table(tableName).symbol('location', 'us').floatColumn('temperature', 17.1).at(1658484765000000000n, 'ns'); - await sender.flush(); - - // query table - const select1Result = await runSelect(container, tableName, 1); - expect(select1Result.query).toBe(tableName); - expect(select1Result.count).toBe(1); - expect(select1Result.columns).toStrictEqual(schema); - expect(select1Result.dataset).toStrictEqual([ - ['us',17.1,'2022-07-22T10:12:45.000000Z'] - ]); - - // ingest via client, add new column - await sender.table(tableName).symbol('location', 'us').floatColumn('temperature', 17.3).at(1658484765000666000n, 'ns'); - await sender.table(tableName).symbol('location', 'emea').floatColumn('temperature', 17.4).at(1658484765000999000n, 'ns'); - await sender.table(tableName).symbol('location', 'emea').symbol('city', 'london').floatColumn('temperature', 18.8).at(1658484765001234000n, 'ns'); - await sender.flush(); - - // query table - const select2Result = await runSelect(container, tableName, 4); - expect(select2Result.query).toBe(tableName); - expect(select2Result.count).toBe(4); - expect(select2Result.columns).toStrictEqual([ - {name: 'location', type: 'SYMBOL'}, - {name: 'temperature', type: 'DOUBLE'}, - {name: 'timestamp', type: 'TIMESTAMP'}, - {name: 'city', type: 'SYMBOL'} - ]); - expect(select2Result.dataset).toStrictEqual([ - ['us',17.1,'2022-07-22T10:12:45.000000Z',null], - ['us',17.3,'2022-07-22T10:12:45.000666Z',null], - ['emea',17.4,'2022-07-22T10:12:45.000999Z',null], - ['emea',18.8,'2022-07-22T10:12:45.001234Z','london'] - ]); - - await sender.close(); - }); - - it('can ingest data via HTTP with auto flush rows', async () => { - const sender = Sender.fromConfig(`http::addr=${container.getHost()}:${container.getMappedPort(QUESTDB_HTTP_PORT)};auto_flush_interval=0;auto_flush_rows=1`); - - const tableName = 'test_http_rows'; - const schema = [ - {name: 'location', type: 'SYMBOL'}, - {name: 'temperature', type: 'DOUBLE'}, - {name: 'timestamp', type: 'TIMESTAMP'} - ]; - - // ingest via client - await sender.table(tableName).symbol('location', 'us').floatColumn('temperature', 17.1).at(1658484765000000000n, 'ns'); - - // query table - const select1Result = await runSelect(container, tableName, 1); - expect(select1Result.query).toBe(tableName); - expect(select1Result.count).toBe(1); - expect(select1Result.columns).toStrictEqual(schema); - expect(select1Result.dataset).toStrictEqual([ - ['us',17.1,'2022-07-22T10:12:45.000000Z'] - ]); - - // ingest via client, add new column - await sender.table(tableName).symbol('location', 'us').floatColumn('temperature', 17.36).at(1658484765000666000n, 'ns'); - await sender.table(tableName).symbol('location', 'emea').floatColumn('temperature', 17.41).at(1658484765000999000n, 'ns'); - await sender.table(tableName).symbol('location', 'emea').symbol('city', 'london').floatColumn('temperature', 18.81).at(1658484765001234000n, 'ns'); - - // query table - const select2Result = await runSelect(container, tableName, 4); - expect(select2Result.query).toBe(tableName); - expect(select2Result.count).toBe(4); - expect(select2Result.columns).toStrictEqual([ - {name: 'location', type: 'SYMBOL'}, - {name: 'temperature', type: 'DOUBLE'}, - {name: 'timestamp', type: 'TIMESTAMP'}, - {name: 'city', type: 'SYMBOL'} - ]); - expect(select2Result.dataset).toStrictEqual([ - ['us',17.1,'2022-07-22T10:12:45.000000Z',null], - ['us',17.36,'2022-07-22T10:12:45.000666Z',null], - ['emea',17.41,'2022-07-22T10:12:45.000999Z',null], - ['emea',18.81,'2022-07-22T10:12:45.001234Z','london'] - ]); - - await sender.close(); - }); - - it('can ingest data via HTTP with auto flush interval', async () => { - const sender = Sender.fromConfig(`http::addr=${container.getHost()}:${container.getMappedPort(QUESTDB_HTTP_PORT)};auto_flush_interval=1;auto_flush_rows=0`); - - const tableName = 'test_http_interval'; - const schema = [ - {name: 'location', type: 'SYMBOL'}, - {name: 'temperature', type: 'DOUBLE'}, - {name: 'timestamp', type: 'TIMESTAMP'} - ]; - - // wait longer than the set auto flush interval to make sure there is a flush - await sleep(10); - - // ingest via client - await sender.table(tableName).symbol('location', 'us').floatColumn('temperature', 17.1).at(1658484765000000000n, 'ns'); - - // query table - const select1Result = await runSelect(container, tableName, 1); - expect(select1Result.query).toBe(tableName); - expect(select1Result.count).toBe(1); - expect(select1Result.columns).toStrictEqual(schema); - expect(select1Result.dataset).toStrictEqual([ - ['us',17.1,'2022-07-22T10:12:45.000000Z'] - ]); - - // ingest via client, add new column - await sleep(10); - await sender.table(tableName).symbol('location', 'us').floatColumn('temperature', 17.36).at(1658484765000666000n, 'ns'); - await sleep(10); - await sender.table(tableName).symbol('location', 'emea').floatColumn('temperature', 17.41).at(1658484765000999000n, 'ns'); - await sleep(10); - await sender.table(tableName).symbol('location', 'emea').symbol('city', 'london').floatColumn('temperature', 18.81).at(1658484765001234000n, 'ns'); - - // query table - const select2Result = await runSelect(container, tableName, 4); - expect(select2Result.query).toBe(tableName); - expect(select2Result.count).toBe(4); - expect(select2Result.columns).toStrictEqual([ - {name: 'location', type: 'SYMBOL'}, - {name: 'temperature', type: 'DOUBLE'}, - {name: 'timestamp', type: 'TIMESTAMP'}, - {name: 'city', type: 'SYMBOL'} - ]); - expect(select2Result.dataset).toStrictEqual([ - ['us',17.1,'2022-07-22T10:12:45.000000Z',null], - ['us',17.36,'2022-07-22T10:12:45.000666Z',null], - ['emea',17.41,'2022-07-22T10:12:45.000999Z',null], - ['emea',18.81,'2022-07-22T10:12:45.001234Z','london'] - ]); - - await sender.close(); - }); - - it('does not duplicate rows if await is missing when calling flush', async () => { - // setting copyBuffer to make sure promises send data from their own local buffer - const sender = new Sender({ protocol: 'tcp', host: container.getHost(), port: container.getMappedPort(QUESTDB_ILP_PORT), copy_buffer: true }); - await sender.connect(); - - const tableName = 'test2'; - const schema = [ - {name: 'location', type: 'SYMBOL'}, - {name: 'temperature', type: 'DOUBLE'}, - {name: 'timestamp', type: 'TIMESTAMP'} - ]; - - // create table - let createTableResult = await query(container, - `CREATE TABLE ${tableName}(${getFieldsString(schema)}) TIMESTAMP (timestamp) PARTITION BY DAY BYPASS WAL;` - ); - expect(createTableResult.ddl).toBe('OK'); - - // alter table - let alterTableResult = await query(container, - `ALTER TABLE ${tableName} SET PARAM maxUncommittedRows = 1;` - ); - expect(alterTableResult.ddl).toBe('OK'); - - // ingest via client - const numOfRows = 100; - for (let i = 0; i < numOfRows; i++) { - await sender.table(tableName).symbol('location', 'us').floatColumn('temperature', i).at(1658484765000000000n, 'ns'); - // missing await is intentional - await sender.flush(); - } - - // query table - const selectQuery = `${tableName} order by temperature`; - const selectResult = await runSelect(container, selectQuery, numOfRows); - expect(selectResult.query).toBe(selectQuery); - expect(selectResult.count).toBe(numOfRows); - expect(selectResult.columns).toStrictEqual(schema); - - const expectedData = []; - for (let i = 0; i < numOfRows; i++) { - expectedData.push(['us',i,'2022-07-22T10:12:45.000000Z']); - } - expect(selectResult.dataset).toStrictEqual(expectedData); - - await sender.close(); - }); -}); diff --git a/test/sender.test.ts b/test/sender.test.ts new file mode 100644 index 0000000..d585b29 --- /dev/null +++ b/test/sender.test.ts @@ -0,0 +1,2152 @@ +import { Sender } from "../src/index"; +import { describe, it, expect, beforeAll, afterAll } from "vitest"; +import { DEFAULT_BUFFER_SIZE, DEFAULT_MAX_BUFFER_SIZE } from "../src/sender"; +import { readFileSync } from "fs"; +import { MockProxy } from "./_utils_/mockproxy"; +import { MockHttp } from "./_utils_/mockhttp"; +import { GenericContainer } from "testcontainers"; +import http from "http"; +import { Agent } from "undici"; +import { SenderOptions } from "../src/options"; + +const HTTP_OK = 200; + +const QUESTDB_HTTP_PORT = 9000; +const QUESTDB_ILP_PORT = 9009; +const MOCK_HTTP_PORT = 9099; +const MOCK_HTTPS_PORT = 9098; +const PROXY_PORT = 9088; +const PROXY_HOST = "localhost"; + +const proxyOptions = { + key: readFileSync("test/certs/server/server.key"), + cert: readFileSync("test/certs/server/server.crt"), + ca: readFileSync("test/certs/ca/ca.crt"), +}; + +const USER_NAME = "testapp"; +const PRIVATE_KEY = "9b9x5WhJywDEuo1KGQWSPNxtX-6X6R2BRCKhYMMY6n8"; +const AUTH: SenderOptions["auth"] = { + keyId: USER_NAME, + token: PRIVATE_KEY, +}; + +async function sleep(ms) { + return new Promise((resolve) => setTimeout(resolve, ms)); +} + +describe("Sender configuration options suite", function () { + it("creates a sender from a configuration string", async function () { + await Sender.fromConfig("tcps::addr=hostname;").close(); + }); + + it("creates a sender from a configuration string picked up from env", async function () { + process.env.QDB_CLIENT_CONF = "https::addr=hostname;"; + await Sender.fromEnv().close(); + }); + + it("throws exception if the username or the token is missing when TCP transport is used", async function () { + try { + await Sender.fromConfig("tcp::addr=hostname;username=bobo;").close(); + } catch (err) { + expect(err.message).toBe( + "TCP transport requires a username and a private key for authentication, please, specify the 'username' and 'token' config options", + ); + } + + try { + await Sender.fromConfig("tcp::addr=hostname;token=bobo_token;").close(); + } catch (err) { + expect(err.message).toBe( + "TCP transport requires a username and a private key for authentication, please, specify the 'username' and 'token' config options", + ); + } + }); + + it("throws exception if tls_roots or tls_roots_password is used", async function () { + try { + await Sender.fromConfig( + "tcps::addr=hostname;username=bobo;tls_roots=bla;", + ).close(); + } catch (err) { + expect(err.message).toBe( + "'tls_roots' and 'tls_roots_password' options are not supported, please, use the 'tls_ca' option or the NODE_EXTRA_CA_CERTS environment variable instead", + ); + } + + try { + await Sender.fromConfig( + "tcps::addr=hostname;token=bobo_token;tls_roots_password=bla;", + ).close(); + } catch (err) { + expect(err.message).toBe( + "'tls_roots' and 'tls_roots_password' options are not supported, please, use the 'tls_ca' option or the NODE_EXTRA_CA_CERTS environment variable instead", + ); + } + }); + + it("throws exception if connect() is called when http transport is used", async function () { + let sender; + try { + sender = Sender.fromConfig("http::addr=hostname"); + await sender.connect(); + } catch (err) { + expect(err.message).toBe( + "'connect()' should be called only if the sender connects via TCP", + ); + } + await sender.close(); + }); +}); + +describe("Sender options test suite", function () { + it("fails if no options defined", async function () { + try { + // @ts-expect-error - Testing invalid options + await new Sender().close(); + } catch (err) { + expect(err.message).toBe("The 'protocol' option is mandatory"); + } + }); + + it("fails if options are null", async function () { + try { + // @ts-expect-error - Testing invalid options + await new Sender(null).close(); + } catch (err) { + expect(err.message).toBe("The 'protocol' option is mandatory"); + } + }); + + it("fails if options are undefined", async function () { + try { + // @ts-expect-error - Testing invalid options + await new Sender(undefined).close(); + } catch (err) { + expect(err.message).toBe("The 'protocol' option is mandatory"); + } + }); + + it("fails if options are empty", async function () { + try { + // @ts-expect-error - Testing invalid options + await new Sender({}).close(); + } catch (err) { + expect(err.message).toBe("The 'protocol' option is mandatory"); + } + }); + + it("fails if protocol option is missing", async function () { + try { + // @ts-expect-error - Testing invalid options + await new Sender({ host: "host" }).close(); + } catch (err) { + expect(err.message).toBe("The 'protocol' option is mandatory"); + } + }); + + it("fails if protocol option is invalid", async function () { + try { + await new Sender({ protocol: "abcd" }).close(); + } catch (err) { + expect(err.message).toBe("Invalid protocol: 'abcd'"); + } + }); + + it("does copy the buffer during flush() if copyBuffer is not set", async function () { + const sender = new Sender({ protocol: "http", host: "host" }); + expect(sender.toBuffer).toBe(sender.toBufferNew); + await sender.close(); + }); + + it("does copy the buffer during flush() if copyBuffer is set to true", async function () { + const sender = new Sender({ + protocol: "http", + host: "host", + copy_buffer: true, + }); + expect(sender.toBuffer).toBe(sender.toBufferNew); + await sender.close(); + }); + + it("does copy the buffer during flush() if copyBuffer is not a boolean", async function () { + const sender = new Sender({ + protocol: "http", + host: "host", + copy_buffer: "", + }); + expect(sender.toBuffer).toBe(sender.toBufferNew); + await sender.close(); + }); + + it("does not copy the buffer during flush() if copyBuffer is set to false", async function () { + const sender = new Sender({ + protocol: "http", + host: "host", + copy_buffer: false, + }); + expect(sender.toBuffer).toBe(sender.toBufferView); + await sender.close(); + }); + + it("does not copy the buffer during flush() if copyBuffer is set to null", async function () { + const sender = new Sender({ + protocol: "http", + host: "host", + copy_buffer: null, + }); + expect(sender.toBuffer).toBe(sender.toBufferNew); + await sender.close(); + }); + + it("does not copy the buffer during flush() if copyBuffer is undefined", async function () { + const sender = new Sender({ + protocol: "http", + host: "host", + copy_buffer: undefined, + }); + expect(sender.toBuffer).toBe(sender.toBufferNew); + await sender.close(); + }); + + it("sets default buffer size if bufferSize is not set", async function () { + const sender = new Sender({ + protocol: "http", + host: "host", + copy_buffer: true, + }); + expect(sender.bufferSize).toBe(DEFAULT_BUFFER_SIZE); + await sender.close(); + }); + + it("sets the requested buffer size if bufferSize is set", async function () { + const sender = new Sender({ + protocol: "http", + host: "host", + init_buf_size: 1024, + }); + expect(sender.bufferSize).toBe(1024); + await sender.close(); + }); + + it("sets default buffer size if bufferSize is set to null", async function () { + const sender = new Sender({ + protocol: "http", + host: "host", + init_buf_size: null, + }); + expect(sender.bufferSize).toBe(DEFAULT_BUFFER_SIZE); + await sender.close(); + }); + + it("sets default buffer size if bufferSize is set to undefined", async function () { + const sender = new Sender({ + protocol: "http", + host: "host", + init_buf_size: undefined, + }); + expect(sender.bufferSize).toBe(DEFAULT_BUFFER_SIZE); + await sender.close(); + }); + + it("sets default buffer size if bufferSize is not a number", async function () { + const sender = new Sender({ + protocol: "http", + host: "host", + // @ts-expect-error - Testing invalid options + init_buf_size: "1024", + }); + expect(sender.bufferSize).toBe(DEFAULT_BUFFER_SIZE); + await sender.close(); + }); + + it("sets default max buffer size if max_buf_size is not set", async function () { + const sender = new Sender({ protocol: "http", host: "host" }); + expect(sender.maxBufferSize).toBe(DEFAULT_MAX_BUFFER_SIZE); + await sender.close(); + }); + + it("sets the requested max buffer size if max_buf_size is set", async function () { + const sender = new Sender({ + protocol: "http", + host: "host", + max_buf_size: 131072, + }); + expect(sender.maxBufferSize).toBe(131072); + await sender.close(); + }); + + it("throws error if initial buffer size is greater than max_buf_size", async function () { + try { + await new Sender({ + protocol: "http", + host: "host", + max_buf_size: 8192, + init_buf_size: 16384, + }).close(); + } catch (err) { + expect(err.message).toBe( + "Max buffer size is 8192 bytes, requested buffer size: 16384", + ); + } + }); + + it("sets default max buffer size if max_buf_size is set to null", async function () { + const sender = new Sender({ + protocol: "http", + host: "host", + max_buf_size: null, + }); + expect(sender.maxBufferSize).toBe(DEFAULT_MAX_BUFFER_SIZE); + await sender.close(); + }); + + it("sets default max buffer size if max_buf_size is set to undefined", async function () { + const sender = new Sender({ + protocol: "http", + host: "host", + max_buf_size: undefined, + }); + expect(sender.maxBufferSize).toBe(DEFAULT_MAX_BUFFER_SIZE); + await sender.close(); + }); + + it("sets default max buffer size if max_buf_size is not a number", async function () { + const sender = new Sender({ + protocol: "http", + host: "host", + // @ts-expect-error - Testing invalid vlaue + max_buf_size: "1024", + }); + expect(sender.maxBufferSize).toBe(DEFAULT_MAX_BUFFER_SIZE); + await sender.close(); + }); + + it("uses default logger if log function is not set", async function () { + const sender = new Sender({ protocol: "http", host: "host" }); + expect(JSON.stringify(sender.log)).toEqual(JSON.stringify(sender.log)); + await sender.close(); + }); + + it("uses the required log function if it is set", async function () { + const testFunc = () => { }; + const sender = new Sender({ + protocol: "http", + host: "host", + log: testFunc, + }); + expect(JSON.stringify(sender.log)).toEqual(JSON.stringify(sender.log)); + await sender.close(); + }); + + it("uses default logger if log is set to null", async function () { + const sender = new Sender({ protocol: "http", host: "host", log: null }); + expect(JSON.stringify(sender.log)).toEqual(JSON.stringify(sender.log)); + await sender.close(); + }); + + it("uses default logger if log is set to undefined", async function () { + const sender = new Sender({ + protocol: "http", + host: "host", + log: undefined, + }); + expect(JSON.stringify(sender.log)).toEqual(JSON.stringify(sender.log)); + await sender.close(); + }); + + it("uses default logger if log is not a function", async function () { + // @ts-expect-error - Testing invalid options + const sender = new Sender({ protocol: "http", host: "host", log: "" }); + expect(JSON.stringify(sender.log)).toEqual(JSON.stringify(sender.log)); + await sender.close(); + }); +}); + +describe("Sender auth config checks suite", function () { + it("requires a username for authentication", async function () { + try { + await new Sender({ + protocol: "tcp", + host: "host", + auth: { + token: "privateKey", + }, + }).close(); + // fail('it should not be able to create the sender'); + } catch (err) { + expect(err.message).toBe( + "Missing username, please, specify the 'keyId' property of the 'auth' config option. " + + "For example: new Sender({protocol: 'tcp', host: 'host', auth: {keyId: 'username', token: 'private key'}})", + ); + } + }); + + it("requires a non-empty username", async function () { + try { + await new Sender({ + protocol: "tcp", + host: "host", + auth: { + keyId: "", + token: "privateKey", + }, + }).close(); + // fail('it should not be able to create the sender'); + } catch (err) { + expect(err.message).toBe( + "Missing username, please, specify the 'keyId' property of the 'auth' config option. " + + "For example: new Sender({protocol: 'tcp', host: 'host', auth: {keyId: 'username', token: 'private key'}})", + ); + } + }); + + it("requires that the username is a string", async function () { + try { + await new Sender({ + protocol: "tcp", + host: "host", + auth: { + // @ts-expect-error - Testing invalid options + keyId: 23, + token: "privateKey", + }, + }).close(); + // fail('it should not be able to create the sender'); + } catch (err) { + expect(err.message).toBe( + "Please, specify the 'keyId' property of the 'auth' config option as a string. " + + "For example: new Sender({protocol: 'tcp', host: 'host', auth: {keyId: 'username', token: 'private key'}})", + ); + } + }); + + it("requires a private key for authentication", async function () { + try { + await new Sender({ + protocol: "tcp", + host: "host", + auth: { + keyId: "username", + }, + }).close(); + // fail('it should not be able to create the sender'); + } catch (err) { + expect(err.message).toBe( + "Missing private key, please, specify the 'token' property of the 'auth' config option. " + + "For example: new Sender({protocol: 'tcp', host: 'host', auth: {keyId: 'username', token: 'private key'}})", + ); + } + }); + + it("requires a non-empty private key", async function () { + try { + await new Sender({ + protocol: "tcp", + host: "host", + auth: { + keyId: "username", + token: "", + }, + }).close(); + // fail('it should not be able to create the sender'); + } catch (err) { + expect(err.message).toBe( + "Missing private key, please, specify the 'token' property of the 'auth' config option. " + + "For example: new Sender({protocol: 'tcp', host: 'host', auth: {keyId: 'username', token: 'private key'}})", + ); + } + }); + + it("requires that the private key is a string", async function () { + try { + await new Sender({ + protocol: "tcp", + host: "host", + auth: { + keyId: "username", + // @ts-expect-error - Testing invalid options + token: true, + }, + }).close(); + // fail('it should not be able to create the sender'); + } catch (err) { + expect(err.message).toBe( + "Please, specify the 'token' property of the 'auth' config option as a string. " + + "For example: new Sender({protocol: 'tcp', host: 'host', auth: {keyId: 'username', token: 'private key'}})", + ); + } + }); +}); + +describe("Sender HTTP suite", function () { + async function sendData(sender) { + await sender + .table("test") + .symbol("location", "us") + .floatColumn("temperature", 17.1) + .at(1658484765000000000n, "ns"); + await sender.flush(); + } + + const mockHttp = new MockHttp(); + const mockHttps = new MockHttp(); + + beforeAll(async function () { + await mockHttp.start(MOCK_HTTP_PORT); + await mockHttps.start(MOCK_HTTPS_PORT, true, proxyOptions); + }); + + afterAll(async function () { + await mockHttp.stop(); + await mockHttps.stop(); + }); + + it("can ingest via HTTP", async function () { + mockHttp.reset(); + + const sender = Sender.fromConfig( + `http::addr=${PROXY_HOST}:${MOCK_HTTP_PORT}`, + ); + await sendData(sender); + expect(mockHttp.numOfRequests).toBe(1); + + await sender.close(); + }); + + it("supports custom http agent", async function () { + mockHttp.reset(); + const agent = new Agent({ pipelining: 3 }); + + const sender = Sender.fromConfig( + `http::addr=${PROXY_HOST}:${MOCK_HTTP_PORT}`, + { agent: agent }, + ); + await sendData(sender); + expect(mockHttp.numOfRequests).toBe(1); + + const symbols = Object.getOwnPropertySymbols(sender.agent); + expect(sender.agent[symbols[6]]).toEqual({ pipelining: 3 }); + + await sender.close(); + agent.destroy(); + }); + + it("can ingest via HTTPS", async function () { + mockHttps.reset(); + + const senderCertCheckFail = Sender.fromConfig( + `https::addr=${PROXY_HOST}:${MOCK_HTTPS_PORT}`, + ); + await expect(sendData(senderCertCheckFail)).rejects.toThrowError( + "HTTP request failed, statusCode=500, error=self-signed certificate in certificate chain", + ); + await senderCertCheckFail.close(); + + const senderWithCA = Sender.fromConfig( + `https::addr=${PROXY_HOST}:${MOCK_HTTPS_PORT};tls_ca=test/certs/ca/ca.crt`, + ); + await sendData(senderWithCA); + expect(mockHttps.numOfRequests).toEqual(1); + await senderWithCA.close(); + + const senderVerifyOff = Sender.fromConfig( + `https::addr=${PROXY_HOST}:${MOCK_HTTPS_PORT};tls_verify=unsafe_off`, + ); + await sendData(senderVerifyOff); + expect(mockHttps.numOfRequests).toEqual(2); + await senderVerifyOff.close(); + }); + + it("can ingest via HTTP with basic auth", async function () { + mockHttp.reset({ username: "user1", password: "pwd" }); + + const sender = Sender.fromConfig( + `http::addr=${PROXY_HOST}:${MOCK_HTTP_PORT};username=user1;password=pwd`, + ); + await sendData(sender); + expect(mockHttp.numOfRequests).toEqual(1); + await sender.close(); + + const senderFailPwd = Sender.fromConfig( + `http::addr=${PROXY_HOST}:${MOCK_HTTP_PORT};username=user1;password=xyz`, + ); + await expect(sendData(senderFailPwd)).rejects.toThrowError( + "HTTP request failed, statusCode=401", + ); + await senderFailPwd.close(); + + const senderFailMissingPwd = Sender.fromConfig( + `http::addr=${PROXY_HOST}:${MOCK_HTTP_PORT};username=user1z`, + ); + await expect(sendData(senderFailMissingPwd)).rejects.toThrowError( + "HTTP request failed, statusCode=401", + ); + await senderFailMissingPwd.close(); + + const senderFailUsername = Sender.fromConfig( + `http::addr=${PROXY_HOST}:${MOCK_HTTP_PORT};username=xyz;password=pwd`, + ); + await expect(sendData(senderFailUsername)).rejects.toThrowError( + "HTTP request failed, statusCode=401", + ); + await senderFailUsername.close(); + + const senderFailMissingUsername = Sender.fromConfig( + `http::addr=${PROXY_HOST}:${MOCK_HTTP_PORT};password=pwd`, + ); + await expect(sendData(senderFailMissingUsername)).rejects.toThrowError( + "HTTP request failed, statusCode=401", + ); + await senderFailMissingUsername.close(); + + const senderFailMissing = Sender.fromConfig( + `http::addr=${PROXY_HOST}:${MOCK_HTTP_PORT}`, + ); + await expect(sendData(senderFailMissing)).rejects.toThrowError( + "HTTP request failed, statusCode=401", + ); + await senderFailMissing.close(); + }); + + it("can ingest via HTTP with token auth", async function () { + mockHttp.reset({ token: "abcdefghijkl123" }); + + const sender = Sender.fromConfig( + `http::addr=${PROXY_HOST}:${MOCK_HTTP_PORT};token=abcdefghijkl123`, + ); + await sendData(sender); + expect(mockHttp.numOfRequests).toBe(1); + await sender.close(); + + const senderFailToken = Sender.fromConfig( + `http::addr=${PROXY_HOST}:${MOCK_HTTP_PORT};token=xyz`, + ); + await expect(sendData(senderFailToken)).rejects.toThrowError( + "HTTP request failed, statusCode=401", + ); + await senderFailToken.close(); + + const senderFailMissing = Sender.fromConfig( + `http::addr=${PROXY_HOST}:${MOCK_HTTP_PORT}`, + ); + await expect(sendData(senderFailMissing)).rejects.toThrowError( + "HTTP request failed, statusCode=401", + ); + await senderFailMissing.close(); + }); + + it("can retry via HTTP", async function () { + mockHttp.reset({ responseCodes: [204, 500, 523, 504, 500] }); + + const sender = Sender.fromConfig( + `http::addr=${PROXY_HOST}:${MOCK_HTTP_PORT}`, + ); + await sendData(sender); + expect(mockHttp.numOfRequests).toBe(5); + + await sender.close(); + }); + + it("fails when retry timeout expires", async function () { + // artificial delay (responseDelays) is same as retry timeout + // should result in the request failing on the second try + mockHttp.reset({ + responseCodes: [204, 500, 503], + responseDelays: [1000, 1000, 1000], + }); + + const sender = Sender.fromConfig( + `http::addr=${PROXY_HOST}:${MOCK_HTTP_PORT};retry_timeout=1000`, + ); + await expect(sendData(sender)).rejects.toThrowError( + "HTTP request failed, statusCode=500, error=", + ); + await sender.close(); + }); + + it("fails when HTTP request times out", async function () { + // artificial delay (responseDelays) is greater than request timeout, and retry is switched off + // should result in the request failing with timeout + mockHttp.reset({ + responseCodes: [204], + responseDelays: [1000], + }); + + const sender = Sender.fromConfig( + `http::addr=${PROXY_HOST}:${MOCK_HTTP_PORT};retry_timeout=0;request_timeout=100`, + ); + await expect(sendData(sender)).rejects.toThrowError( + "HTTP request timeout, no response from server in time", + ); + await sender.close(); + }); + + it("succeeds on the third request after two timeouts", async function () { + mockHttp.reset({ + responseCodes: [204, 504, 504], + responseDelays: [2000, 2000], + }); + + const sender = Sender.fromConfig( + `http::addr=${PROXY_HOST}:${MOCK_HTTP_PORT};retry_timeout=30000;request_timeout=1000`, + ); + await sendData(sender); + + await sender.close(); + }); + + it("accepts custom http agent", async function () { + mockHttp.reset(); + const agent = new Agent({ connect: { keepAlive: false } }); + + const num = 300; + const senders: Sender[] = []; + const promises: Promise[] = []; + for (let i = 0; i < num; i++) { + const sender = Sender.fromConfig( + `http::addr=${PROXY_HOST}:${MOCK_HTTP_PORT}`, + { agent: agent }, + ); + senders.push(sender); + const promise = sendData(sender); + promises.push(promise); + } + await Promise.all(promises); + expect(mockHttp.numOfRequests).toBe(num); + + for (const sender of senders) { + await sender.close(); + } + agent.destroy(); + }); +}); + +describe("Sender connection suite", function () { + async function createProxy( + auth = false, + tlsOptions?: Record, + ) { + const mockConfig = { auth: auth, assertions: true }; + const proxy = new MockProxy(mockConfig); + await proxy.start(PROXY_PORT, tlsOptions); + expect(proxy.mockConfig).toBe(mockConfig); + expect(proxy.dataSentToRemote).toStrictEqual([]); + return proxy; + } + + async function createSender(auth: SenderOptions["auth"], secure = false) { + const sender = new Sender({ + protocol: secure ? "tcps" : "tcp", + port: PROXY_PORT, + host: PROXY_HOST, + auth: auth, + tls_ca: "test/certs/ca/ca.crt", + }); + const connected = await sender.connect(); + expect(connected).toBe(true); + return sender; + } + + async function sendData(sender) { + await sender + .table("test") + .symbol("location", "us") + .floatColumn("temperature", 17.1) + .at(1658484765000000000n, "ns"); + await sender.flush(); + } + + async function assertSentData( + proxy, + authenticated, + expected, + timeout = 60000, + ) { + const interval = 100; + const num = timeout / interval; + let actual; + for (let i = 0; i < num; i++) { + const dataSentToRemote = proxy.getDataSentToRemote().join("").split("\n"); + if (authenticated) { + dataSentToRemote.splice(1, 1); + } + actual = dataSentToRemote.join("\n"); + if (actual === expected) { + return new Promise((resolve) => resolve(null)); + } + await sleep(interval); + } + return new Promise((resolve) => + resolve(`data assert failed [expected=${expected}, actual=${actual}]`), + ); + } + + it("can authenticate", async function () { + const proxy = await createProxy(true); + const sender = await createSender(AUTH); + await assertSentData(proxy, true, "testapp\n"); + await sender.close(); + await proxy.stop(); + }); + + it("can authenticate with a different private key", async function () { + const proxy = await createProxy(true); + const sender = await createSender({ + keyId: "user1", + token: "zhPiK3BkYMYJvRf5sqyrWNJwjDKHOWHnRbmQggUll6A", + }); + await assertSentData(proxy, true, "user1\n"); + await sender.close(); + await proxy.stop(); + }); + + it("is backwards compatible and still can authenticate with full JWK", async function () { + const JWK = { + x: "BtUXC_K3oAyGlsuPjTgkiwirMUJhuRQDfcUHeyoxFxU", + y: "R8SOup-rrNofB7wJagy4HrJhTVfrVKmj061lNRk3bF8", + kid: "user2", + kty: "EC", + d: "hsg6Zm4kSBlIEvKUWT3kif-2y2Wxw-iWaGrJxrPXQhs", + crv: "P-256", + }; + + const proxy = await createProxy(true); + const sender = new Sender({ + protocol: "tcp", + port: PROXY_PORT, + host: PROXY_HOST, + // @ts-expect-error invalid options + ca: readFileSync("test/certs/ca/ca.crt"), + jwk: JWK, + }); + const connected = await sender.connect(); + expect(connected).toBe(true); + await assertSentData(proxy, true, "user2\n"); + await sender.close(); + await proxy.stop(); + }); + + it("can connect unauthenticated", async function () { + const proxy = await createProxy(); + // @ts-expect-error invalid options + const sender = await createSender(); + await assertSentData(proxy, false, ""); + await sender.close(); + await proxy.stop(); + }); + + it("can authenticate and send data to server", async function () { + const proxy = await createProxy(true); + const sender = await createSender(AUTH); + await sendData(sender); + await assertSentData( + proxy, + true, + "testapp\ntest,location=us temperature=17.1 1658484765000000000\n", + ); + await sender.close(); + await proxy.stop(); + }); + + it("can connect unauthenticated and send data to server", async function () { + const proxy = await createProxy(); + // @ts-expect-error invalid options + const sender = await createSender(); + await sendData(sender); + await assertSentData( + proxy, + false, + "test,location=us temperature=17.1 1658484765000000000\n", + ); + await sender.close(); + await proxy.stop(); + }); + + it("can authenticate and send data to server via secure connection", async function () { + const proxy = await createProxy(true, proxyOptions); + const sender = await createSender(AUTH, true); + await sendData(sender); + await assertSentData( + proxy, + true, + "testapp\ntest,location=us temperature=17.1 1658484765000000000\n", + ); + await sender.close(); + await proxy.stop(); + }); + + it("can connect unauthenticated and send data to server via secure connection", async function () { + const proxy = await createProxy(false, proxyOptions); + // @ts-expect-error invalid options + const sender = await createSender(null, true); + await sendData(sender); + await assertSentData( + proxy, + false, + "test,location=us temperature=17.1 1658484765000000000\n", + ); + await sender.close(); + await proxy.stop(); + }); + + it("fails to connect without hostname and port", async function () { + const sender = new Sender({ protocol: "tcp" }); + try { + await sender.connect(); + // fail('it should not be able to connect'); + } catch (err) { + expect(err.message).toBe("Hostname is not set"); + } + await sender.close(); + }); + + it("fails to send data if not connected", async function () { + const sender = new Sender({ protocol: "tcp", host: "localhost" }); + try { + await sender.table("test").symbol("location", "us").atNow(); + await sender.flush(); + // fail('it should not be able to send data'); + } catch (err) { + expect(err.message).toBe("Sender is not connected"); + } + await sender.close(); + }); + + it("guards against multiple connect calls", async function () { + const proxy = await createProxy(true, proxyOptions); + const sender = await createSender(AUTH, true); + try { + await sender.connect(); + // fail('it should not be able to connect again'); + } catch (err) { + expect(err.message).toBe("Sender connected already"); + } + await sender.close(); + await proxy.stop(); + }); + + it("guards against concurrent connect calls", async function () { + const proxy = await createProxy(true, proxyOptions); + const sender = new Sender({ + protocol: "tcps", + port: PROXY_PORT, + host: PROXY_HOST, + auth: AUTH, + // @ts-expect-error invalid options + ca: readFileSync("test/certs/ca/ca.crt"), + }); + try { + await Promise.all([sender.connect(), sender.connect()]); + // fail('it should not be able to connect twice'); + } catch (err) { + expect(err.message).toBe("Sender connected already"); + } + await sender.close(); + await proxy.stop(); + }); + + it("can disable the server certificate check", async function () { + const proxy = await createProxy(true, proxyOptions); + const senderCertCheckFail = Sender.fromConfig( + `tcps::addr=${PROXY_HOST}:${PROXY_PORT}`, + ); + try { + await senderCertCheckFail.connect(); + // fail('it should not be able to connect'); + } catch (err) { + expect(err.message).toMatch( + /^self[ -]signed certificate in certificate chain$/, + ); + } + await senderCertCheckFail.close(); + + const senderCertCheckOn = Sender.fromConfig( + `tcps::addr=${PROXY_HOST}:${PROXY_PORT};tls_ca=test/certs/ca/ca.crt`, + ); + await senderCertCheckOn.connect(); + await senderCertCheckOn.close(); + + const senderCertCheckOff = Sender.fromConfig( + `tcps::addr=${PROXY_HOST}:${PROXY_PORT};tls_verify=unsafe_off`, + ); + await senderCertCheckOff.connect(); + await senderCertCheckOff.close(); + await proxy.stop(); + }); + + it("can handle unfinished rows during flush()", async function () { + const proxy = await createProxy(true, proxyOptions); + const sender = await createSender(AUTH, true); + sender.table("test").symbol("location", "us"); + const sent = await sender.flush(); + expect(sent).toBe(false); + await assertSentData(proxy, true, "testapp\n"); + await sender.close(); + await proxy.stop(); + }); + + it("supports custom logger", async function () { + const expectedMessages = [ + "Successfully connected to localhost:9088", + /^Connection to .*1:9088 is closed$/, + ]; + const log = (level, message) => { + expect(level).toBe("info"); + // @ts-expect-error invalid options + expect(message).toMatch(expectedMessages.shift()); + }; + const proxy = await createProxy(); + const sender = new Sender({ + protocol: "tcp", + port: PROXY_PORT, + host: PROXY_HOST, + log: log, + }); + await sender.connect(); + await sendData(sender); + await assertSentData( + proxy, + false, + "test,location=us temperature=17.1 1658484765000000000\n", + ); + await sender.close(); + await proxy.stop(); + }); +}); + +describe("Client interop test suite", function () { + it("runs client tests as per json test config", async function () { + const testCases = JSON.parse( + readFileSync( + "./questdb-client-test/ilp-client-interop-test.json", + ).toString(), + ); + + loopTestCase: for (const testCase of testCases) { + console.info(`test name: ${testCase.testName}`); + const sender = new Sender({ + protocol: "tcp", + host: "host", + init_buf_size: 1024, + }); + try { + sender.table(testCase.table); + for (const symbol of testCase.symbols) { + sender.symbol(symbol.name, symbol.value); + } + for (const column of testCase.columns) { + switch (column.type) { + case "STRING": + sender.stringColumn(column.name, column.value); + break; + case "LONG": + sender.intColumn(column.name, column.value); + break; + case "DOUBLE": + sender.floatColumn(column.name, column.value); + break; + case "BOOLEAN": + sender.booleanColumn(column.name, column.value); + break; + case "TIMESTAMP": + sender.timestampColumn(column.name, column.value); + break; + default: + throw new Error("Unsupported column type"); + } + } + await sender.atNow(); + } catch { + if (testCase.result.status !== "ERROR") { + // fail('Did not expect error: ' + e.message); + break; + } + await sender.close(); + continue; + } + + const buffer = sender.toBufferView(); + if (testCase.result.status === "SUCCESS") { + if (testCase.result.line) { + expect(buffer.toString()).toBe(testCase.result.line + "\n"); + } else { + for (const line of testCase.result.anyLines) { + if (buffer.toString() === line + "\n") { + // test passed + await sender.close(); + continue loopTestCase; + } + } + // fail('Line is not matching any of the expected results: ' + buffer.toString()); + } + } else { + // fail('Expected error missing, instead we have a line: ' + buffer.toString()); + break; + } + + await sender.close(); + } + }); +}); + +describe("Sender message builder test suite (anything not covered in client interop test suite)", function () { + it("throws on invalid timestamp unit", async function () { + const sender = new Sender({ + protocol: "tcp", + host: "host", + init_buf_size: 1024, + }); + try { + await sender + .table("tableName") + .booleanColumn("boolCol", true) + // @ts-expect-error - Testing invalid options + .timestampColumn("timestampCol", 1658484765000000, "foobar") + .atNow(); + } catch (err) { + expect(err.message).toBe("Unknown timestamp unit: foobar"); + } + await sender.close(); + }); + + it("supports json object", async function () { + const pages: Array<{ + id: string; + gridId: string; + }>[] = []; + for (let i = 0; i < 4; i++) { + const pageProducts: Array<{ + id: string; + gridId: string; + }> = [ + { + id: "46022e96-076f-457f-b630-51b82b871618" + i, + gridId: "46022e96-076f-457f-b630-51b82b871618", + }, + { + id: "55615358-4af1-4179-9153-faaa57d71e55", + gridId: "55615358-4af1-4179-9153-faaa57d71e55", + }, + { + id: "365b9cdf-3d4e-4135-9cb0-f1a65601c840", + gridId: "365b9cdf-3d4e-4135-9cb0-f1a65601c840", + }, + { + id: "0b67ddf2-8e69-4482-bf0c-bb987ee5c280", + gridId: "0b67ddf2-8e69-4482-bf0c-bb987ee5c280" + i, + }, + ]; + pages.push(pageProducts); + } + + const sender = new Sender({ + protocol: "tcp", + host: "host", + // @ts-expect-error - Technically it's a private field, but I'm not sure + bufferSize: 256, + }); + for (const p of pages) { + await sender + .table("tableName") + .stringColumn("page_products", JSON.stringify(p || [])) + .booleanColumn("boolCol", true) + .atNow(); + } + expect(sender.toBufferView().toString()).toBe( + 'tableName page_products="[{\\"id\\":\\"46022e96-076f-457f-b630-51b82b8716180\\",\\"gridId\\":\\"46022e96-076f-457f-b630-51b82b871618\\"},{\\"id\\":\\"55615358-4af1-4179-9153-faaa57d71e55\\",\\"gridId\\":\\"55615358-4af1-4179-9153-faaa57d71e55\\"},{\\"id\\":\\"365b9cdf-3d4e-4135-9cb0-f1a65601c840\\",\\"gridId\\":\\"365b9cdf-3d4e-4135-9cb0-f1a65601c840\\"},{\\"id\\":\\"0b67ddf2-8e69-4482-bf0c-bb987ee5c280\\",\\"gridId\\":\\"0b67ddf2-8e69-4482-bf0c-bb987ee5c2800\\"}]",boolCol=t\n' + + 'tableName page_products="[{\\"id\\":\\"46022e96-076f-457f-b630-51b82b8716181\\",\\"gridId\\":\\"46022e96-076f-457f-b630-51b82b871618\\"},{\\"id\\":\\"55615358-4af1-4179-9153-faaa57d71e55\\",\\"gridId\\":\\"55615358-4af1-4179-9153-faaa57d71e55\\"},{\\"id\\":\\"365b9cdf-3d4e-4135-9cb0-f1a65601c840\\",\\"gridId\\":\\"365b9cdf-3d4e-4135-9cb0-f1a65601c840\\"},{\\"id\\":\\"0b67ddf2-8e69-4482-bf0c-bb987ee5c280\\",\\"gridId\\":\\"0b67ddf2-8e69-4482-bf0c-bb987ee5c2801\\"}]",boolCol=t\n' + + 'tableName page_products="[{\\"id\\":\\"46022e96-076f-457f-b630-51b82b8716182\\",\\"gridId\\":\\"46022e96-076f-457f-b630-51b82b871618\\"},{\\"id\\":\\"55615358-4af1-4179-9153-faaa57d71e55\\",\\"gridId\\":\\"55615358-4af1-4179-9153-faaa57d71e55\\"},{\\"id\\":\\"365b9cdf-3d4e-4135-9cb0-f1a65601c840\\",\\"gridId\\":\\"365b9cdf-3d4e-4135-9cb0-f1a65601c840\\"},{\\"id\\":\\"0b67ddf2-8e69-4482-bf0c-bb987ee5c280\\",\\"gridId\\":\\"0b67ddf2-8e69-4482-bf0c-bb987ee5c2802\\"}]",boolCol=t\n' + + 'tableName page_products="[{\\"id\\":\\"46022e96-076f-457f-b630-51b82b8716183\\",\\"gridId\\":\\"46022e96-076f-457f-b630-51b82b871618\\"},{\\"id\\":\\"55615358-4af1-4179-9153-faaa57d71e55\\",\\"gridId\\":\\"55615358-4af1-4179-9153-faaa57d71e55\\"},{\\"id\\":\\"365b9cdf-3d4e-4135-9cb0-f1a65601c840\\",\\"gridId\\":\\"365b9cdf-3d4e-4135-9cb0-f1a65601c840\\"},{\\"id\\":\\"0b67ddf2-8e69-4482-bf0c-bb987ee5c280\\",\\"gridId\\":\\"0b67ddf2-8e69-4482-bf0c-bb987ee5c2803\\"}]",boolCol=t\n', + ); + await sender.close(); + }); + + it("supports timestamp field as number", async function () { + const sender = new Sender({ + protocol: "tcp", + host: "host", + init_buf_size: 1024, + }); + await sender + .table("tableName") + .booleanColumn("boolCol", true) + .timestampColumn("timestampCol", 1658484765000000) + .atNow(); + expect(sender.toBufferView().toString()).toBe( + "tableName boolCol=t,timestampCol=1658484765000000t\n", + ); + await sender.close(); + }); + + it("supports timestamp field as ns number", async function () { + const sender = new Sender({ + protocol: "tcp", + host: "host", + init_buf_size: 1024, + }); + await sender + .table("tableName") + .booleanColumn("boolCol", true) + .timestampColumn("timestampCol", 1658484765000000, "ns") + .atNow(); + expect(sender.toBufferView().toString()).toBe( + "tableName boolCol=t,timestampCol=1658484765000t\n", + ); + await sender.close(); + }); + + it("supports timestamp field as us number", async function () { + const sender = new Sender({ + protocol: "tcp", + host: "host", + init_buf_size: 1024, + }); + await sender + .table("tableName") + .booleanColumn("boolCol", true) + .timestampColumn("timestampCol", 1658484765000000, "us") + .atNow(); + expect(sender.toBufferView().toString()).toBe( + "tableName boolCol=t,timestampCol=1658484765000000t\n", + ); + await sender.close(); + }); + + it("supports timestamp field as ms number", async function () { + const sender = new Sender({ + protocol: "tcp", + host: "host", + init_buf_size: 1024, + }); + await sender + .table("tableName") + .booleanColumn("boolCol", true) + .timestampColumn("timestampCol", 1658484765000, "ms") + .atNow(); + expect(sender.toBufferView().toString()).toBe( + "tableName boolCol=t,timestampCol=1658484765000000t\n", + ); + await sender.close(); + }); + + it("supports timestamp field as BigInt", async function () { + const sender = new Sender({ + protocol: "tcp", + host: "host", + init_buf_size: 1024, + }); + await sender + .table("tableName") + .booleanColumn("boolCol", true) + .timestampColumn("timestampCol", 1658484765000000n) + .atNow(); + expect(sender.toBufferView().toString()).toBe( + "tableName boolCol=t,timestampCol=1658484765000000t\n", + ); + await sender.close(); + }); + + it("supports timestamp field as ns BigInt", async function () { + const sender = new Sender({ + protocol: "tcp", + host: "host", + init_buf_size: 1024, + }); + await sender + .table("tableName") + .booleanColumn("boolCol", true) + .timestampColumn("timestampCol", 1658484765000000000n, "ns") + .atNow(); + expect(sender.toBufferView().toString()).toBe( + "tableName boolCol=t,timestampCol=1658484765000000t\n", + ); + await sender.close(); + }); + + it("supports timestamp field as us BigInt", async function () { + const sender = new Sender({ + protocol: "tcp", + host: "host", + init_buf_size: 1024, + }); + await sender + .table("tableName") + .booleanColumn("boolCol", true) + .timestampColumn("timestampCol", 1658484765000000n, "us") + .atNow(); + expect(sender.toBufferView().toString()).toBe( + "tableName boolCol=t,timestampCol=1658484765000000t\n", + ); + await sender.close(); + }); + + it("supports timestamp field as ms BigInt", async function () { + const sender = new Sender({ + protocol: "tcp", + host: "host", + init_buf_size: 1024, + }); + await sender + .table("tableName") + .booleanColumn("boolCol", true) + .timestampColumn("timestampCol", 1658484765000n, "ms") + .atNow(); + expect(sender.toBufferView().toString()).toBe( + "tableName boolCol=t,timestampCol=1658484765000000t\n", + ); + await sender.close(); + }); + + it("throws on invalid designated timestamp unit", async function () { + const sender = new Sender({ + protocol: "tcp", + host: "host", + init_buf_size: 1024, + }); + try { + await sender + .table("tableName") + .booleanColumn("boolCol", true) + .timestampColumn("timestampCol", 1658484765000000) + // @ts-expect-error - Testing invalid options + .at(1658484769000000, "foobar"); + } catch (err) { + expect(err.message).toBe("Unknown timestamp unit: foobar"); + } + await sender.close(); + }); + + it("supports setting designated us timestamp as number from client", async function () { + const sender = new Sender({ + protocol: "tcp", + host: "host", + init_buf_size: 1024, + }); + await sender + .table("tableName") + .booleanColumn("boolCol", true) + .timestampColumn("timestampCol", 1658484765000000) + .at(1658484769000000, "us"); + expect(sender.toBufferView().toString()).toBe( + "tableName boolCol=t,timestampCol=1658484765000000t 1658484769000000000\n", + ); + await sender.close(); + }); + + it("supports setting designated ms timestamp as number from client", async function () { + const sender = new Sender({ + protocol: "tcp", + host: "host", + init_buf_size: 1024, + }); + await sender + .table("tableName") + .booleanColumn("boolCol", true) + .timestampColumn("timestampCol", 1658484765000000) + .at(1658484769000, "ms"); + expect(sender.toBufferView().toString()).toBe( + "tableName boolCol=t,timestampCol=1658484765000000t 1658484769000000000\n", + ); + await sender.close(); + }); + + it("supports setting designated timestamp as BigInt from client", async function () { + const sender = new Sender({ + protocol: "tcp", + host: "host", + init_buf_size: 1024, + }); + await sender + .table("tableName") + .booleanColumn("boolCol", true) + .timestampColumn("timestampCol", 1658484765000000) + .at(1658484769000000n); + expect(sender.toBufferView().toString()).toBe( + "tableName boolCol=t,timestampCol=1658484765000000t 1658484769000000000\n", + ); + await sender.close(); + }); + + it("supports setting designated ns timestamp as BigInt from client", async function () { + const sender = new Sender({ + protocol: "tcp", + host: "host", + init_buf_size: 1024, + }); + await sender + .table("tableName") + .booleanColumn("boolCol", true) + .timestampColumn("timestampCol", 1658484765000000) + .at(1658484769000000123n, "ns"); + expect(sender.toBufferView().toString()).toBe( + "tableName boolCol=t,timestampCol=1658484765000000t 1658484769000000123\n", + ); + await sender.close(); + }); + + it("supports setting designated us timestamp as BigInt from client", async function () { + const sender = new Sender({ + protocol: "tcp", + host: "host", + init_buf_size: 1024, + }); + await sender + .table("tableName") + .booleanColumn("boolCol", true) + .timestampColumn("timestampCol", 1658484765000000) + .at(1658484769000000n, "us"); + expect(sender.toBufferView().toString()).toBe( + "tableName boolCol=t,timestampCol=1658484765000000t 1658484769000000000\n", + ); + await sender.close(); + }); + + it("supports setting designated ms timestamp as BigInt from client", async function () { + const sender = new Sender({ + protocol: "tcp", + host: "host", + init_buf_size: 1024, + }); + await sender + .table("tableName") + .booleanColumn("boolCol", true) + .timestampColumn("timestampCol", 1658484765000000) + .at(1658484769000n, "ms"); + expect(sender.toBufferView().toString()).toBe( + "tableName boolCol=t,timestampCol=1658484765000000t 1658484769000000000\n", + ); + await sender.close(); + }); + + it("throws exception if table name is not a string", async function () { + const sender = new Sender({ + protocol: "tcp", + host: "host", + init_buf_size: 1024, + }); + // @ts-expect-error invalid options + expect(() => sender.table(23456)).toThrow( + "Table name must be a string, received number", + ); + await sender.close(); + }); + + it("throws exception if table name is too long", async function () { + const sender = new Sender({ + protocol: "tcp", + host: "host", + init_buf_size: 1024, + }); + expect(() => + sender.table( + "123456789012345678901234567890123456789012345678901234567890" + + "12345678901234567890123456789012345678901234567890123456789012345678", + ), + ).toThrow("Table name is too long, max length is 127"); + await sender.close(); + }); + + it("throws exception if table name is set more times", async function () { + const sender = new Sender({ + protocol: "tcp", + host: "host", + init_buf_size: 1024, + }); + expect(() => + sender.table("tableName").symbol("name", "value").table("newTableName"), + ).toThrow("Table name has already been set"); + await sender.close(); + }); + + it("throws exception if symbol name is not a string", async function () { + const sender = new Sender({ + protocol: "tcp", + host: "host", + init_buf_size: 1024, + }); + // @ts-expect-error invalid options + expect(() => sender.table("tableName").symbol(12345.5656, "value")).toThrow( + "Symbol name must be a string, received number", + ); + await sender.close(); + }); + + it("throws exception if symbol name is empty string", async function () { + const sender = new Sender({ + protocol: "tcp", + host: "host", + init_buf_size: 1024, + }); + expect(() => sender.table("tableName").symbol("", "value")).toThrow( + "Empty string is not allowed as column name", + ); + await sender.close(); + }); + + it("throws exception if column name is not a string", async function () { + const sender = new Sender({ + protocol: "tcp", + host: "host", + init_buf_size: 1024, + }); + expect(() => + // @ts-expect-error invalid options + sender.table("tableName").stringColumn(12345.5656, "value"), + ).toThrow("Column name must be a string, received number"); + await sender.close(); + }); + + it("throws exception if column name is empty string", async function () { + const sender = new Sender({ + protocol: "tcp", + host: "host", + init_buf_size: 1024, + }); + expect(() => sender.table("tableName").stringColumn("", "value")).toThrow( + "Empty string is not allowed as column name", + ); + await sender.close(); + }); + + it("throws exception if column name is too long", async function () { + const sender = new Sender({ + protocol: "tcp", + host: "host", + init_buf_size: 1024, + }); + expect(() => + sender + .table("tableName") + .stringColumn( + "123456789012345678901234567890123456789012345678901234567890" + + "12345678901234567890123456789012345678901234567890123456789012345678", + "value", + ), + ).toThrow("Column name is too long, max length is 127"); + await sender.close(); + }); + + it("throws exception if column value is not the right type", async function () { + const sender = new Sender({ + protocol: "tcp", + host: "host", + init_buf_size: 1024, + }); + expect(() => + // @ts-expect-error invalid options + sender.table("tableName").stringColumn("columnName", false), + ).toThrow("Column value must be of type string, received boolean"); + await sender.close(); + }); + + it("throws exception if adding column without setting table name", async function () { + const sender = new Sender({ + protocol: "tcp", + host: "host", + init_buf_size: 1024, + }); + expect(() => sender.floatColumn("name", 12.459)).toThrow( + "Column can be set only after table name is set", + ); + await sender.close(); + }); + + it("throws exception if adding symbol without setting table name", async function () { + const sender = new Sender({ + protocol: "tcp", + host: "host", + init_buf_size: 1024, + }); + expect(() => sender.symbol("name", "value")).toThrow( + "Symbol can be added only after table name is set and before any column added", + ); + await sender.close(); + }); + + it("throws exception if adding symbol after columns", async function () { + const sender = new Sender({ + protocol: "tcp", + host: "host", + init_buf_size: 1024, + }); + expect(() => + sender + .table("tableName") + .stringColumn("name", "value") + .symbol("symbolName", "symbolValue"), + ).toThrow( + "Symbol can be added only after table name is set and before any column added", + ); + await sender.close(); + }); + + it("returns null if preparing an empty buffer for send", async function () { + const sender = new Sender({ + protocol: "tcp", + host: "host", + init_buf_size: 1024, + }); + expect(sender.toBufferView()).toBe(null); + await sender.close(); + }); + + it("ignores unfinished rows when preparing a buffer for send", async function () { + const sender = new Sender({ + protocol: "tcp", + host: "host", + init_buf_size: 1024, + }); + sender.table("tableName").symbol("name", "value"); + await sender.at(1234567890n, "ns"); + sender.table("tableName").symbol("name", "value2"); + expect(sender.toBufferView(sender.endOfLastRow).toString()).toBe( + "tableName,name=value 1234567890\n", + ); + await sender.close(); + }); + + it("throws exception if a float is passed as integer field", async function () { + const sender = new Sender({ + protocol: "tcp", + host: "host", + init_buf_size: 1024, + }); + expect(() => + sender.table("tableName").intColumn("intField", 123.222), + ).toThrow("Value must be an integer, received 123.222"); + await sender.close(); + }); + + it("throws exception if a float is passed as timestamp field", async function () { + const sender = new Sender({ + protocol: "tcp", + host: "host", + init_buf_size: 1024, + }); + expect(() => + sender.table("tableName").timestampColumn("intField", 123.222), + ).toThrow("Value must be an integer or BigInt, received 123.222"); + await sender.close(); + }); + + it("throws exception if designated timestamp is not an integer or bigint", async function () { + const sender = new Sender({ + protocol: "tcp", + host: "host", + init_buf_size: 1024, + }); + try { + await sender + .table("tableName") + .symbol("name", "value") + .at(23232322323.05); + } catch (e) { + expect(e.message).toEqual( + "Designated timestamp must be an integer or BigInt, received 23232322323.05", + ); + } + await sender.close(); + }); + + it("throws exception if designated timestamp is invalid", async function () { + const sender = new Sender({ + protocol: "tcp", + host: "host", + init_buf_size: 1024, + }); + try { + // @ts-expect-error invalid options + await sender.table("tableName").symbol("name", "value").at("invalid_dts"); + } catch (e) { + expect(e.message).toEqual( + "Designated timestamp must be an integer or BigInt, received invalid_dts", + ); + } + await sender.close(); + }); + + it("throws exception if designated timestamp is set without any fields added", async function () { + const sender = new Sender({ + protocol: "tcp", + host: "host", + init_buf_size: 1024, + }); + try { + await sender.table("tableName").at(12345678n, "ns"); + } catch (e) { + expect(e.message).toEqual( + "The row must have a symbol or column set before it is closed", + ); + } + await sender.close(); + }); + + it("extends the size of the buffer if data does not fit", async function () { + const sender = new Sender({ + protocol: "tcp", + host: "host", + init_buf_size: 8, + }); + expect(sender.bufferSize).toBe(8); + expect(sender.position).toBe(0); + sender.table("tableName"); + expect(sender.bufferSize).toBe(16); + expect(sender.position).toBe("tableName".length); + sender.intColumn("intField", 123); + expect(sender.bufferSize).toBe(32); + expect(sender.position).toBe("tableName intField=123i".length); + await sender.atNow(); + expect(sender.bufferSize).toBe(32); + expect(sender.position).toBe("tableName intField=123i\n".length); + expect(sender.toBufferView().toString()).toBe("tableName intField=123i\n"); + + await sender + .table("table2") + .intColumn("intField", 125) + .stringColumn("strField", "test") + .atNow(); + expect(sender.bufferSize).toBe(64); + expect(sender.position).toBe( + 'tableName intField=123i\ntable2 intField=125i,strField="test"\n'.length, + ); + expect(sender.toBufferView().toString()).toBe( + 'tableName intField=123i\ntable2 intField=125i,strField="test"\n', + ); + await sender.close(); + }); + + it("throws exception if tries to extend the size of the buffer above max buffer size", async function () { + const sender = Sender.fromConfig( + "tcp::addr=host;init_buf_size=8;max_buf_size=48;", + ); + expect(sender.bufferSize).toBe(8); + expect(sender.position).toBe(0); + sender.table("tableName"); + expect(sender.bufferSize).toBe(16); + expect(sender.position).toBe("tableName".length); + sender.intColumn("intField", 123); + expect(sender.bufferSize).toBe(32); + expect(sender.position).toBe("tableName intField=123i".length); + await sender.atNow(); + expect(sender.bufferSize).toBe(32); + expect(sender.position).toBe("tableName intField=123i\n".length); + expect(sender.toBufferView().toString()).toBe("tableName intField=123i\n"); + + try { + await sender + .table("table2") + .intColumn("intField", 125) + .stringColumn("strField", "test") + .atNow(); + } catch (err) { + expect(err.message).toBe( + "Max buffer size is 48 bytes, requested buffer size: 64", + ); + } + await sender.close(); + }); + + it("is possible to clear the buffer by calling reset()", async function () { + const sender = new Sender({ + protocol: "tcp", + host: "host", + init_buf_size: 1024, + }); + await sender + .table("tableName") + .booleanColumn("boolCol", true) + .timestampColumn("timestampCol", 1658484765000000) + .atNow(); + await sender + .table("tableName") + .booleanColumn("boolCol", false) + .timestampColumn("timestampCol", 1658484766000000) + .atNow(); + expect(sender.toBufferView().toString()).toBe( + "tableName boolCol=t,timestampCol=1658484765000000t\n" + + "tableName boolCol=f,timestampCol=1658484766000000t\n", + ); + + sender.reset(); + await sender + .table("tableName") + .floatColumn("floatCol", 1234567890) + .timestampColumn("timestampCol", 1658484767000000) + .atNow(); + expect(sender.toBufferView().toString()).toBe( + "tableName floatCol=1234567890,timestampCol=1658484767000000t\n", + ); + await sender.close(); + }); +}); + +describe("Sender tests with containerized QuestDB instance", () => { + let container; + + async function query(container, query) { + const options = { + hostname: container.getHost(), + port: container.getMappedPort(QUESTDB_HTTP_PORT), + path: `/exec?query=${encodeURIComponent(query)}`, + method: "GET", + }; + + return new Promise((resolve, reject) => { + const req = http.request(options, (response) => { + if (response.statusCode === HTTP_OK) { + const body: Uint8Array[] = []; + response + .on("data", (data: Uint8Array) => { + body.push(data); + }) + .on("end", () => { + resolve(JSON.parse(Buffer.concat(body).toString())); + }); + } else { + reject( + new Error( + `HTTP request failed, statusCode=${response.statusCode}, query=${query}`, + ), + ); + } + }); + + req.on("error", (error) => { + reject(error); + }); + + req.end(); + }); + } + + async function runSelect(container, select, expectedCount, timeout = 60000) { + const interval = 500; + const num = timeout / interval; + let selectResult; + for (let i = 0; i < num; i++) { + selectResult = await query(container, select); + if (selectResult && selectResult.count >= expectedCount) { + return selectResult; + } + await sleep(interval); + } + throw new Error( + `Timed out while waiting for ${expectedCount} rows, select='${select}'`, + ); + } + + function getFieldsString(schema) { + let fields = ""; + for (const element of schema) { + fields += `${element.name} ${element.type}, `; + } + return fields.substring(0, fields.length - 2); + } + + beforeAll(async () => { + container = await new GenericContainer("questdb/questdb:nightly") + .withExposedPorts(QUESTDB_HTTP_PORT, QUESTDB_ILP_PORT) + .start(); + + const stream = await container.logs(); + stream + .on("data", (line) => console.log(line)) + .on("err", (line) => console.error(line)) + .on("end", () => console.log("Stream closed")); + }, 3000000); + + afterAll(async () => { + await container.stop(); + }); + + it("can ingest data via TCP and run queries", async () => { + const sender = new Sender({ + protocol: "tcp", + host: container.getHost(), + port: container.getMappedPort(QUESTDB_ILP_PORT), + }); + await sender.connect(); + + const tableName = "test_tcp"; + const schema = [ + { name: "location", type: "SYMBOL" }, + { name: "temperature", type: "DOUBLE" }, + { name: "timestamp", type: "TIMESTAMP" }, + ]; + + // create table + const createTableResult = (await query( + container, + `CREATE TABLE ${tableName}(${getFieldsString(schema)}) TIMESTAMP (timestamp) PARTITION BY DAY BYPASS WAL;`, + )) as { + ddl: string; + }; + expect(createTableResult.ddl).toBe("OK"); + + // alter table + const alterTableResult = (await query( + container, + `ALTER TABLE ${tableName} SET PARAM maxUncommittedRows = 1;`, + )) as { + ddl: string; + }; + expect(alterTableResult.ddl).toBe("OK"); + + // ingest via client + await sender + .table(tableName) + .symbol("location", "us") + .floatColumn("temperature", 17.1) + .at(1658484765000000000n, "ns"); + await sender.flush(); + + // query table + const select1Result = await runSelect(container, tableName, 1); + expect(select1Result.query).toBe(tableName); + expect(select1Result.count).toBe(1); + expect(select1Result.columns).toStrictEqual(schema); + expect(select1Result.dataset).toStrictEqual([ + ["us", 17.1, "2022-07-22T10:12:45.000000Z"], + ]); + + // ingest via client, add new column + await sender + .table(tableName) + .symbol("location", "us") + .floatColumn("temperature", 17.3) + .at(1658484765000666000n, "ns"); + await sender + .table(tableName) + .symbol("location", "emea") + .floatColumn("temperature", 17.4) + .at(1658484765000999000n, "ns"); + await sender + .table(tableName) + .symbol("location", "emea") + .symbol("city", "london") + .floatColumn("temperature", 18.8) + .at(1658484765001234000n, "ns"); + await sender.flush(); + + // query table + const select2Result = await runSelect(container, tableName, 4); + expect(select2Result.query).toBe(tableName); + expect(select2Result.count).toBe(4); + expect(select2Result.columns).toStrictEqual([ + { name: "location", type: "SYMBOL" }, + { name: "temperature", type: "DOUBLE" }, + { name: "timestamp", type: "TIMESTAMP" }, + { name: "city", type: "SYMBOL" }, + ]); + expect(select2Result.dataset).toStrictEqual([ + ["us", 17.1, "2022-07-22T10:12:45.000000Z", null], + ["us", 17.3, "2022-07-22T10:12:45.000666Z", null], + ["emea", 17.4, "2022-07-22T10:12:45.000999Z", null], + ["emea", 18.8, "2022-07-22T10:12:45.001234Z", "london"], + ]); + + await sender.close(); + }); + + it("can ingest data via HTTP with auto flush rows", async () => { + const sender = Sender.fromConfig( + `http::addr=${container.getHost()}:${container.getMappedPort(QUESTDB_HTTP_PORT)};auto_flush_interval=0;auto_flush_rows=1`, + ); + + const tableName = "test_http_rows"; + const schema = [ + { name: "location", type: "SYMBOL" }, + { name: "temperature", type: "DOUBLE" }, + { name: "timestamp", type: "TIMESTAMP" }, + ]; + + // ingest via client + await sender + .table(tableName) + .symbol("location", "us") + .floatColumn("temperature", 17.1) + .at(1658484765000000000n, "ns"); + + // query table + const select1Result = await runSelect(container, tableName, 1); + expect(select1Result.query).toBe(tableName); + expect(select1Result.count).toBe(1); + expect(select1Result.columns).toStrictEqual(schema); + expect(select1Result.dataset).toStrictEqual([ + ["us", 17.1, "2022-07-22T10:12:45.000000Z"], + ]); + + // ingest via client, add new column + await sender + .table(tableName) + .symbol("location", "us") + .floatColumn("temperature", 17.36) + .at(1658484765000666000n, "ns"); + await sender + .table(tableName) + .symbol("location", "emea") + .floatColumn("temperature", 17.41) + .at(1658484765000999000n, "ns"); + await sender + .table(tableName) + .symbol("location", "emea") + .symbol("city", "london") + .floatColumn("temperature", 18.81) + .at(1658484765001234000n, "ns"); + + // query table + const select2Result = await runSelect(container, tableName, 4); + expect(select2Result.query).toBe(tableName); + expect(select2Result.count).toBe(4); + expect(select2Result.columns).toStrictEqual([ + { name: "location", type: "SYMBOL" }, + { name: "temperature", type: "DOUBLE" }, + { name: "timestamp", type: "TIMESTAMP" }, + { name: "city", type: "SYMBOL" }, + ]); + expect(select2Result.dataset).toStrictEqual([ + ["us", 17.1, "2022-07-22T10:12:45.000000Z", null], + ["us", 17.36, "2022-07-22T10:12:45.000666Z", null], + ["emea", 17.41, "2022-07-22T10:12:45.000999Z", null], + ["emea", 18.81, "2022-07-22T10:12:45.001234Z", "london"], + ]); + + await sender.close(); + }); + + it("can ingest data via HTTP with auto flush interval", async () => { + const sender = Sender.fromConfig( + `http::addr=${container.getHost()}:${container.getMappedPort(QUESTDB_HTTP_PORT)};auto_flush_interval=1;auto_flush_rows=0`, + ); + + const tableName = "test_http_interval"; + const schema = [ + { name: "location", type: "SYMBOL" }, + { name: "temperature", type: "DOUBLE" }, + { name: "timestamp", type: "TIMESTAMP" }, + ]; + + // wait longer than the set auto flush interval to make sure there is a flush + await sleep(10); + + // ingest via client + await sender + .table(tableName) + .symbol("location", "us") + .floatColumn("temperature", 17.1) + .at(1658484765000000000n, "ns"); + + // query table + const select1Result = await runSelect(container, tableName, 1); + expect(select1Result.query).toBe(tableName); + expect(select1Result.count).toBe(1); + expect(select1Result.columns).toStrictEqual(schema); + expect(select1Result.dataset).toStrictEqual([ + ["us", 17.1, "2022-07-22T10:12:45.000000Z"], + ]); + + // ingest via client, add new column + await sleep(10); + await sender + .table(tableName) + .symbol("location", "us") + .floatColumn("temperature", 17.36) + .at(1658484765000666000n, "ns"); + await sleep(10); + await sender + .table(tableName) + .symbol("location", "emea") + .floatColumn("temperature", 17.41) + .at(1658484765000999000n, "ns"); + await sleep(10); + await sender + .table(tableName) + .symbol("location", "emea") + .symbol("city", "london") + .floatColumn("temperature", 18.81) + .at(1658484765001234000n, "ns"); + + // query table + const select2Result = await runSelect(container, tableName, 4); + expect(select2Result.query).toBe(tableName); + expect(select2Result.count).toBe(4); + expect(select2Result.columns).toStrictEqual([ + { name: "location", type: "SYMBOL" }, + { name: "temperature", type: "DOUBLE" }, + { name: "timestamp", type: "TIMESTAMP" }, + { name: "city", type: "SYMBOL" }, + ]); + expect(select2Result.dataset).toStrictEqual([ + ["us", 17.1, "2022-07-22T10:12:45.000000Z", null], + ["us", 17.36, "2022-07-22T10:12:45.000666Z", null], + ["emea", 17.41, "2022-07-22T10:12:45.000999Z", null], + ["emea", 18.81, "2022-07-22T10:12:45.001234Z", "london"], + ]); + + await sender.close(); + }); + + it("does not duplicate rows if await is missing when calling flush", async () => { + // setting copyBuffer to make sure promises send data from their own local buffer + const sender = new Sender({ + protocol: "tcp", + host: container.getHost(), + port: container.getMappedPort(QUESTDB_ILP_PORT), + copy_buffer: true, + }); + await sender.connect(); + + const tableName = "test2"; + const schema = [ + { name: "location", type: "SYMBOL" }, + { name: "temperature", type: "DOUBLE" }, + { name: "timestamp", type: "TIMESTAMP" }, + ]; + + // create table + const createTableResult = (await query( + container, + `CREATE TABLE ${tableName}(${getFieldsString(schema)}) TIMESTAMP (timestamp) PARTITION BY DAY BYPASS WAL;`, + )) as { + ddl: string; + }; + expect(createTableResult.ddl).toBe("OK"); + + // alter table + const alterTableResult = (await query( + container, + `ALTER TABLE ${tableName} SET PARAM maxUncommittedRows = 1;`, + )) as { + ddl: string; + }; + expect(alterTableResult.ddl).toBe("OK"); + + // ingest via client + const numOfRows = 100; + for (let i = 0; i < numOfRows; i++) { + await sender + .table(tableName) + .symbol("location", "us") + .floatColumn("temperature", i) + .at(1658484765000000000n, "ns"); + // missing await is intentional + await sender.flush(); + } + + // query table + const selectQuery = `${tableName} order by temperature`; + const selectResult = await runSelect(container, selectQuery, numOfRows); + expect(selectResult.query).toBe(selectQuery); + expect(selectResult.count).toBe(numOfRows); + expect(selectResult.columns).toStrictEqual(schema); + + const expectedData: (string | number)[][] = []; + for (let i = 0; i < numOfRows; i++) { + expectedData.push(["us", i, "2022-07-22T10:12:45.000000Z"]); + } + expect(selectResult.dataset).toStrictEqual(expectedData); + + await sender.close(); + }); +}); diff --git a/test/testapp.js b/test/testapp.js deleted file mode 100644 index dbe91c4..0000000 --- a/test/testapp.js +++ /dev/null @@ -1,64 +0,0 @@ -'use strict'; - -const { Proxy } = require('./proxy'); -const { Sender } = require('../index'); -const { readFileSync } = require('fs'); - -const PROXY_PORT = 9099; -const PORT = 9009; -const HOST = 'localhost'; - -const USER_NAME = 'testapp'; -const PRIVATE_KEY = '9b9x5WhJywDEuo1KGQWSPNxtX-6X6R2BRCKhYMMY6n8'; -const AUTH = { - kid: USER_NAME, - d: PRIVATE_KEY -}; - -const senderOptions = { - protocol: 'tcps', - host: HOST, - port: PROXY_PORT, - auth: AUTH, - ca: readFileSync('certs/ca/ca.crt') // necessary only if the server uses self-signed certificate -}; - -const proxyTLS = { - key: readFileSync('certs/server/server.key'), - cert: readFileSync('certs/server/server.crt'), - ca: readFileSync('certs/ca/ca.crt') // authority chain for the clients -}; - -async function run() { - const proxy = new Proxy(); - await proxy.start(PROXY_PORT, PORT, HOST, proxyTLS); - - const sender = new Sender(senderOptions); //with authentication - const connected = await sender.connect(); //connection through proxy with encryption - if (connected) { - await sender.table('test') - .symbol('location', 'emea').symbol('city', 'budapest') - .stringColumn('hoppa', 'hello').stringColumn('hippi', 'hello').stringColumn('hippo', 'haho') - .floatColumn('temperature', 14.1).intColumn('intcol', 56) - .timestampColumn('tscol', Date.now(), 'ms') - .atNow(); - await sender.table('test') - .symbol('location', 'asia').symbol('city', 'singapore') - .stringColumn('hoppa', 'hi').stringColumn('hopp', 'hello').stringColumn('hippo', 'huhu') - .floatColumn('temperature', 7.1) - .at(1658484765000555000n, 'ns'); - await sender.flush(); - - await sender.table('test') - .symbol('location', 'emea').symbol('city', 'miskolc') - .stringColumn('hoppa', 'hello').stringColumn('hippi', 'hello').stringColumn('hippo', 'lalalala') - .floatColumn('temperature', 13.1).intColumn('intcol', 333) - .atNow(); - await sender.flush(); - } - await sender.close(); - - await proxy.stop(); -} - -run().catch(console.error); diff --git a/test/testapp.ts b/test/testapp.ts new file mode 100644 index 0000000..2536b58 --- /dev/null +++ b/test/testapp.ts @@ -0,0 +1,78 @@ +import { readFileSync } from "node:fs"; + +import { Proxy } from "./_utils_/proxy"; +import { Sender } from "../src/index"; +import { SenderOptions } from "../src/options"; + +const PROXY_PORT = 9099; +const PORT = 9009; +const HOST = "localhost"; + +const USER_NAME = "testapp"; +const PRIVATE_KEY = "9b9x5WhJywDEuo1KGQWSPNxtX-6X6R2BRCKhYMMY6n8"; +const AUTH = { + kid: USER_NAME, + d: PRIVATE_KEY, +}; + +const senderOptions: SenderOptions = { + protocol: "tcps", + host: HOST, + port: PROXY_PORT, + addr: "localhost", + tls_ca: readFileSync("certs/ca/ca.crt"), // necessary only if the server uses self-signed certificate +}; + +const proxyTLS = { + key: readFileSync("certs/server/server.key"), + cert: readFileSync("certs/server/server.crt"), + ca: readFileSync("certs/ca/ca.crt"), // authority chain for the clients +}; + +async function run() { + const proxy = new Proxy(); + await proxy.start(PROXY_PORT, PORT, HOST, proxyTLS); + + const sender = new Sender(senderOptions); //with authentication + const connected = await sender.connect(); //connection through proxy with encryption + if (connected) { + await sender + .table("test") + .symbol("location", "emea") + .symbol("city", "budapest") + .stringColumn("hoppa", "hello") + .stringColumn("hippi", "hello") + .stringColumn("hippo", "haho") + .floatColumn("temperature", 14.1) + .intColumn("intcol", 56) + .timestampColumn("tscol", Date.now(), "ms") + .atNow(); + await sender + .table("test") + .symbol("location", "asia") + .symbol("city", "singapore") + .stringColumn("hoppa", "hi") + .stringColumn("hopp", "hello") + .stringColumn("hippo", "huhu") + .floatColumn("temperature", 7.1) + .at(1658484765000555000n, "ns"); + await sender.flush(); + + await sender + .table("test") + .symbol("location", "emea") + .symbol("city", "miskolc") + .stringColumn("hoppa", "hello") + .stringColumn("hippi", "hello") + .stringColumn("hippo", "lalalala") + .floatColumn("temperature", 13.1) + .intColumn("intcol", 333) + .atNow(); + await sender.flush(); + } + await sender.close(); + + await proxy.stop(); +} + +run().catch(console.error); diff --git a/tsconfig.json b/tsconfig.json index dd60453..10a49a8 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,15 +1,15 @@ { - "include": ["index.js"], + "include": ["src"], "compilerOptions": { - // Tells TypeScript to read .js files, normally they are ignored as source files - "allowJs": true, - // Generate .d.ts files + "moduleResolution": "bundler", + "module": "ESNext", "declaration": true, - // This compiler run should only output .d.ts files - "emitDeclarationOnly": true, + "target": "ESNext", + "lib": [ + "es2020", + "esnext" + ], // Types should go into this directory - // Removing this would place the .d.ts files next to the .js files - "outDir": "types", // Go to .js file when using IDE functions like "Go to Definition" in VSCode "declarationMap": true } diff --git a/types/index.d.ts b/types/index.d.ts deleted file mode 100644 index 2f4ce48..0000000 --- a/types/index.d.ts +++ /dev/null @@ -1,3 +0,0 @@ -export { Sender }; -import { Sender } from "./src/sender"; -//# sourceMappingURL=index.d.ts.map \ No newline at end of file diff --git a/types/index.d.ts.map b/types/index.d.ts.map deleted file mode 100644 index 653c749..0000000 --- a/types/index.d.ts.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../index.js"],"names":[],"mappings":""} \ No newline at end of file diff --git a/types/src/logging.d.ts b/types/src/logging.d.ts deleted file mode 100644 index ad05730..0000000 --- a/types/src/logging.d.ts +++ /dev/null @@ -1,10 +0,0 @@ -/** - * Simple logger to write log messages to the console.
- * Supported logging levels are `error`, `warn`, `info` and `debug`.
- * Throws an error if logging level is invalid. - * - * @param {'error'|'warn'|'info'|'debug'} level - The log level of the message. - * @param {string} message - The log message. - */ -export function log(level: 'error' | 'warn' | 'info' | 'debug', message: string): void; -//# sourceMappingURL=logging.d.ts.map \ No newline at end of file diff --git a/types/src/logging.d.ts.map b/types/src/logging.d.ts.map deleted file mode 100644 index 7edf218..0000000 --- a/types/src/logging.d.ts.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"logging.d.ts","sourceRoot":"","sources":["../../src/logging.js"],"names":[],"mappings":"AAWA;;;;;;;GAOG;AACH,2BAHW,OAAO,GAAC,MAAM,GAAC,MAAM,GAAC,OAAO,WAC7B,MAAM,QAUhB"} \ No newline at end of file diff --git a/types/src/options.d.ts b/types/src/options.d.ts deleted file mode 100644 index 75fabb2..0000000 --- a/types/src/options.d.ts +++ /dev/null @@ -1,167 +0,0 @@ -/** @classdesc - * Sender configuration options.
- *
- * Properties of the object are initialized through a configuration string.
- * The configuration string has the following format: <protocol>::<key>=<value><key>=<value>...;
- * The keys are case-sensitive, the trailing semicolon is optional.
- * The values are validated, and an error is thrown if the format is invalid.
- *
- * Connection and protocol options - *
    - *
  • protocol: enum, accepted values: http, https, tcp, tcps - The protocol used to communicate with the server.
    - * When https or tcps used, the connection is secured with TLS encryption. - *
  • - *
  • addr: string - Hostname and port, separated by colon. This key is mandatory, but the port part is optional.
    - * If no port is specified, a default will be used.
    - * When the protocol is HTTP/HTTPS, the port defaults to 9000. When the protocol is TCP/TCPS, the port defaults to 9009.
    - *
    - * Examples: http::addr=localhost:9000, https::addr=localhost:9000, http::addr=localhost, tcp::addr=localhost:9009 - *
  • - *
- *
- * Authentication options - *
    - *
  • username: string - Used for authentication.
    - * For HTTP, Basic Authentication requires the password option.
    - * For TCP with JWK token authentication, token option is required. - *
  • - *
  • password: string - Password for HTTP Basic authentication, should be accompanied by the username option. - *
  • - *
  • token: string - For HTTP with Bearer authentication, this is the bearer token.
    - * For TCP with JWK token authentication, this is the private key part of the JWK token, - * and must be accompanied by the username option. - *
  • - *
- *
- * TLS options - *
    - *
  • tls_verify: enum, accepted values: on, unsafe_off - When the HTTPS or TCPS protocols are selected, TLS encryption is used.
    - * By default, the Sender will verify the server's certificate, but this check can be disabled by setting this option to off. This is useful - * non-production environments where self-signed certificates might be used, but should be avoided in production if possible. - *
  • - *
  • tls_ca: string - Path to a file containing the root CA's certificate in PEM format.
    - * Can be useful when self-signed certificates are used, otherwise should not be set. - *
  • - *
- *
- * Auto flush options - *
    - *
  • auto_flush: enum, accepted values: on, off - The Sender automatically flushes the buffer by default. This can be switched off - * by setting this option to off.
    - * When disabled, the flush() method of the Sender has to be called explicitly to make sure data is sent to the server.
    - * Manual buffer flushing can be useful, especially when we want to use transactions. When the HTTP protocol is used, each flush results in a single HTTP - * request, which becomes a single transaction on the server side. The transaction either succeeds, and all rows sent in the request are - * inserted; or it fails, and none of the rows make it into the database. - *
  • - *
  • auto_flush_rows: integer - The number of rows that will trigger a flush. When set to 0, row-based flushing is disabled.
    - * The Sender will default this parameter to 75000 rows when HTTP protocol is used, and to 600 in case of TCP protocol. - *
  • - *
  • auto_flush_interval: integer - The number of milliseconds that will trigger a flush, default value is 1000. - * When set to 0, interval-based flushing is disabled.
    - * Note that the setting is checked only when a new row is added to the buffer. There is no timer registered to flush the buffer automatically. - *
  • - *
- *
- * Buffer sizing options - *
    - *
  • init_buf_size: integer - Initial buffer size, defaults to 64 KiB in the Sender. - *
  • - *
  • max_buf_size: integer - Maximum buffer size, defaults to 100 MiB in the Sender.
    - * If the buffer would need to be extended beyond the maximum size, an error is thrown. - *
  • - *
- *
- * HTTP request specific options - *
    - *
  • request_timeout: integer - The time in milliseconds to wait for a response from the server, set to 10 seconds by default.
    - * This is in addition to the calculation derived from the request_min_throughput parameter. - *
  • - *
  • request_min_throughput: integer - Minimum expected throughput in bytes per second for HTTP requests, set to 100 KiB/s seconds by default.
    - * If the throughput is lower than this value, the connection will time out. This is used to calculate an additional - * timeout on top of request_timeout. This is useful for large requests. You can set this value to 0 to disable this logic. - *
  • - *
  • retry_timeout: integer - The time in milliseconds to continue retrying after a failed HTTP request, set to 10 seconds by default.
    - * The interval between retries is an exponential backoff starting at 10ms and doubling after each failed attempt up to a maximum of 1 second. - *
  • - *
- *
- * Other options - *
    - *
  • max_name_len: integer - The maximum length of a table or column name, the Sender defaults this parameter to 127.
    - * Recommended to use the same setting as the server, which also uses 127 by default. - *
  • - *
  • copy_buffer: enum, accepted values: on, off - By default, the Sender creates a new buffer for every flush() call, - * and the data to be sent to the server is copied into this new buffer. - * Setting the flag to off results in reusing the same buffer instance for each flush() call.
    - * Use this flag only if calls to the client are serialised. - *
  • - *
- */ -export class SenderOptions { - /** - * Creates a Sender options object by parsing the provided configuration string. - * - * @param {string} configurationString - Configuration string.
- * @param {object} extraOptions - Optional extra configuration.
- * - 'log' is a logging function used by the Sender.
- * Prototype: (level: 'error'|'warn'|'info'|'debug', message: string) => void.
- * - 'agent' is a custom http/https agent used by the Sender when http/https transport is used.
- * A http.Agent or https.Agent object is expected. - * - * @return {SenderOptions} A Sender configuration object initialized from the provided configuration string. - */ - static fromConfig(configurationString: string, extraOptions?: object): SenderOptions; - /** - * Creates a Sender options object by parsing the configuration string set in the QDB_CLIENT_CONF environment variable. - * - * @param {object} extraOptions - Optional extra configuration.
- * - 'log' is a logging function used by the Sender.
- * Prototype: (level: 'error'|'warn'|'info'|'debug', message: string) => void.
- * - 'agent' is a custom http/https agent used by the Sender when http/https transport is used.
- * A http.Agent or https.Agent object is expected. - * - * @return {SenderOptions} A Sender configuration object initialized from the QDB_CLIENT_CONF environment variable. - */ - static fromEnv(extraOptions?: object): SenderOptions; - /** - * Creates a Sender options object by parsing the provided configuration string. - * - * @param {string} configurationString - Configuration string.
- * @param {object} extraOptions - Optional extra configuration.
- * - 'log' is a logging function used by the Sender.
- * Prototype: (level: 'error'|'warn'|'info'|'debug', message: string) => void.
- * - 'agent' is a custom http/https agent used by the Sender when http/https transport is used.
- * A http.Agent or https.Agent object is expected. - */ - constructor(configurationString: string, extraOptions?: object); - protocol: any; - addr: any; - host: any; - port: any; - username: any; - password: any; - token: any; - token_x: any; - token_y: any; - auto_flush: any; - auto_flush_rows: any; - auto_flush_interval: any; - copy_buffer: any; - request_min_throughput: any; - request_timeout: any; - retry_timeout: any; - init_buf_size: any; - max_buf_size: any; - tls_verify: any; - tls_ca: any; - tls_roots: any; - tls_roots_password: any; - max_name_len: any; - log: any; - agent: any; -} -export const HTTP: "http"; -export const HTTPS: "https"; -export const TCP: "tcp"; -export const TCPS: "tcps"; -//# sourceMappingURL=options.d.ts.map \ No newline at end of file diff --git a/types/src/options.d.ts.map b/types/src/options.d.ts.map deleted file mode 100644 index 633aaea..0000000 --- a/types/src/options.d.ts.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"options.d.ts","sourceRoot":"","sources":["../../src/options.js"],"names":[],"mappings":"AAiBA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkGG;AACH;IAiEI;;;;;;;;;;;OAWG;IACH,uCATW,MAAM,iBACN,MAAM,GAML,aAAa,CAIxB;IAED;;;;;;;;;;OAUG;IACH,8BARW,MAAM,GAML,aAAa,CAIxB;IAvDD;;;;;;;;;OASG;IACH,iCAPW,MAAM,iBACN,MAAM,EAoBhB;IA7DD,cAAS;IACT,UAAK;IACL,UAAK;IACL,UAAK;IAGL,cAAS;IACT,cAAS;IACT,WAAM;IACN,aAAQ;IACR,aAAQ;IAER,gBAAW;IACX,qBAAgB;IAChB,yBAAoB;IAGpB,iBAAY;IAEZ,4BAAuB;IACvB,qBAAgB;IAChB,mBAAc;IAGd,mBAAc;IACd,kBAAa;IAEb,gBAAW;IACX,YAAO;IACP,eAAU;IACV,wBAAmB;IAEnB,kBAAa;IAEb,SAAI;IACJ,WAAM;CA0DT;AA3MD,0BAAoB;AACpB,4BAAsB;AACtB,wBAAkB;AAClB,0BAAoB"} \ No newline at end of file diff --git a/types/src/sender.d.ts b/types/src/sender.d.ts deleted file mode 100644 index 0206bfa..0000000 --- a/types/src/sender.d.ts +++ /dev/null @@ -1,252 +0,0 @@ -/// -/// -/// -/// -/// -/** @classdesc - * The QuestDB client's API provides methods to connect to the database, ingest data, and close the connection. - * The supported protocols are HTTP and TCP. HTTP is preferred as it provides feedback in the HTTP response.
- * Based on benchmarks HTTP also provides higher throughput, if configured to ingest data in bigger batches. - *

- * The client supports authentication.
- * Authentication details can be passed to the Sender in its configuration options.
- * The client supports Basic username/password and Bearer token authentication methods when used with HTTP protocol, - * and JWK token authentication when ingesting data via TCP.
- * Please, note that authentication is enabled by default in QuestDB Enterprise only.
- * Details on how to configure authentication in the open source version of - * QuestDB: {@link https://questdb.io/docs/reference/api/ilp/authenticate} - *

- *

- * The client also supports TLS encryption for both, HTTP and TCP transports to provide a secure connection.
- * Please, note that the open source version of QuestDB does not support TLS, and requires an external reverse-proxy, - * such as Nginx to enable encryption. - *

- *

- * The client uses a buffer to store data. It automatically flushes the buffer by sending its content to the server. - * Auto flushing can be disabled via configuration options to gain control over transactions. Initial and maximum - * buffer sizes can also be set. - *

- *

- * It is recommended that the Sender is created by using one of the static factory methods, - * Sender.fromConfig(configString, extraOptions) or Sender.fromEnv(extraOptions)). - * If the Sender is created via its constructor, at least the SenderOptions configuration object should be - * initialized from a configuration string to make sure that the parameters are validated.
- * Detailed description of the Sender's configuration options can be found in - * the SenderOptions documentation. - *

- *

- * Extra options can be provided to the Sender in the extraOptions configuration object.
- * A custom logging function and a custom HTTP(S) agent can be passed to the Sender in this object.
- * The logger implementation provides the option to direct log messages to the same place where the host application's - * log is saved. The default logger writes to the console.
- * The custom HTTP(S) agent option becomes handy if there is a need to modify the default options set for the - * HTTP(S) connections. A popular setting would be disabling persistent connections, in this case an agent can be - * passed to the Sender with keepAlive set to false.
- * For example: Sender.fromConfig(`http::addr=host:port`, { agent: new http.Agent({ keepAlive: false })})
- * If no custom agent is configured, the Sender will use its own agent which overrides some default values - * of http.Agent/https.Agent. The Sender's own agent uses persistent connections with 1 minute idle - * timeout, and limits the number of open connections to the server, which is set to 256 for each host. - *

- */ -export class Sender { - /** @private */ private static DEFAULT_HTTP_AGENT; - /** @private */ private static DEFAULT_HTTPS_AGENT; - /** - * Creates a Sender options object by parsing the provided configuration string. - * - * @param {string} configurationString - Configuration string.
- * @param {object} extraOptions - Optional extra configuration.
- * - 'log' is a logging function used by the Sender.
- * Prototype: (level: 'error'|'warn'|'info'|'debug', message: string) => void.
- * - 'agent' is a custom http/https agent used by the Sender when http/https transport is used.
- * A http.Agent or https.Agent object is expected. - * - * @return {Sender} A Sender object initialized from the provided configuration string. - */ - static fromConfig(configurationString: string, extraOptions?: object): Sender; - /** - * Creates a Sender options object by parsing the configuration string set in the QDB_CLIENT_CONF environment variable. - * - * @param {object} extraOptions - Optional extra configuration.
- * - 'log' is a logging function used by the Sender.
- * Prototype: (level: 'error'|'warn'|'info'|'debug', message: string) => void.
- * - 'agent' is a custom http/https agent used by the Sender when http/https transport is used.
- * A http.Agent or https.Agent object is expected. - * - * @return {Sender} A Sender object initialized from the QDB_CLIENT_CONF environment variable. - */ - static fromEnv(extraOptions?: object): Sender; - /** - * Creates an instance of Sender. - * - * @param {SenderOptions} options - Sender configuration object.
- * See SenderOptions documentation for detailed description of configuration options.
- */ - constructor(options: SenderOptions); - /** @private */ private http; - /** @private */ private secure; - /** @private */ private host; - /** @private */ private port; - /** @private */ private socket; - /** @private */ private username; - /** @private */ private password; - /** @private */ private token; - /** @private */ private tlsVerify; - /** @private */ private tlsCA; - /** @private */ private bufferSize; - /** @private */ private maxBufferSize; - /** @private */ private buffer; - /** @private */ private toBuffer; - /** @private */ private doResolve; - /** @private */ private position; - /** @private */ private endOfLastRow; - /** @private */ private autoFlush; - /** @private */ private autoFlushRows; - /** @private */ private autoFlushInterval; - /** @private */ private lastFlushTime; - /** @private */ private pendingRowCount; - /** @private */ private requestMinThroughput; - /** @private */ private requestTimeout; - /** @private */ private retryTimeout; - /** @private */ private hasTable; - /** @private */ private hasSymbols; - /** @private */ private hasColumns; - /** @private */ private maxNameLength; - /** @private */ private log; - /** @private */ private agent; - jwk: any; - /** - * Extends the size of the sender's buffer.
- * Can be used to increase the size of buffer if overflown. - * The buffer's content is copied into the new buffer. - * - * @param {number} bufferSize - New size of the buffer used by the sender, provided in bytes. - */ - resize(bufferSize: number): void; - /** - * Resets the buffer, data added to the buffer will be lost.
- * In other words it clears the buffer and sets the writing position to the beginning of the buffer. - * - * @return {Sender} Returns with a reference to this sender. - */ - reset(): Sender; - /** - * Creates a TCP connection to the database. - * - * @param {net.NetConnectOpts | tls.ConnectionOptions} connectOptions - Connection options, host and port are required. - * - * @return {Promise} Resolves to true if the client is connected. - */ - connect(connectOptions?: net.NetConnectOpts | tls.ConnectionOptions): Promise; - /** - * @ignore - * @return {http.Agent} Returns the default http agent. - */ - getDefaultHttpAgent(): http.Agent; - /** - * @ignore - * @return {https.Agent} Returns the default https agent. - */ - getDefaultHttpsAgent(): https.Agent; - /** - * Closes the TCP connection to the database.
- * Data sitting in the Sender's buffer will be lost unless flush() is called before close(). - */ - close(): Promise; - /** - * Sends the buffer's content to the database and compacts the buffer. - * If the last row is not finished it stays in the sender's buffer. - * - * @return {Promise} Resolves to true when there was data in the buffer to send. - */ - flush(): Promise; - /** - * @ignore - * @return {Buffer} Returns a cropped buffer ready to send to the server or null if there is nothing to send. - * The returned buffer is backed by the sender's buffer. - */ - toBufferView(pos?: any): Buffer; - /** - * @ignore - * @return {Buffer} Returns a cropped buffer ready to send to the server or null if there is nothing to send. - * The returned buffer is a copy of the sender's buffer. - */ - toBufferNew(pos?: any): Buffer; - /** - * Write the table name into the buffer of the sender. - * - * @param {string} table - Table name. - * @return {Sender} Returns with a reference to this sender. - */ - table(table: string): Sender; - /** - * Write a symbol name and value into the buffer of the sender. - * - * @param {string} name - Symbol name. - * @param {any} value - Symbol value, toString() will be called to extract the actual symbol value from the parameter. - * @return {Sender} Returns with a reference to this sender. - */ - symbol(name: string, value: any): Sender; - /** - * Write a string column with its value into the buffer of the sender. - * - * @param {string} name - Column name. - * @param {string} value - Column value, accepts only string values. - * @return {Sender} Returns with a reference to this sender. - */ - stringColumn(name: string, value: string): Sender; - /** - * Write a boolean column with its value into the buffer of the sender. - * - * @param {string} name - Column name. - * @param {boolean} value - Column value, accepts only boolean values. - * @return {Sender} Returns with a reference to this sender. - */ - booleanColumn(name: string, value: boolean): Sender; - /** - * Write a float column with its value into the buffer of the sender. - * - * @param {string} name - Column name. - * @param {number} value - Column value, accepts only number values. - * @return {Sender} Returns with a reference to this sender. - */ - floatColumn(name: string, value: number): Sender; - /** - * Write an integer column with its value into the buffer of the sender. - * - * @param {string} name - Column name. - * @param {number} value - Column value, accepts only number values. - * @return {Sender} Returns with a reference to this sender. - */ - intColumn(name: string, value: number): Sender; - /** - * Write a timestamp column with its value into the buffer of the sender. - * - * @param {string} name - Column name. - * @param {number | bigint} value - Epoch timestamp, accepts numbers or BigInts. - * @param {string} [unit=us] - Timestamp unit. Supported values: 'ns' - nanoseconds, 'us' - microseconds, 'ms' - milliseconds. Defaults to 'us'. - * @return {Sender} Returns with a reference to this sender. - */ - timestampColumn(name: string, value: number | bigint, unit?: string): Sender; - /** - * Closing the row after writing the designated timestamp into the buffer of the sender. - * - * @param {number | bigint} timestamp - Designated epoch timestamp, accepts numbers or BigInts. - * @param {string} [unit=us] - Timestamp unit. Supported values: 'ns' - nanoseconds, 'us' - microseconds, 'ms' - milliseconds. Defaults to 'us'. - */ - at(timestamp: number | bigint, unit?: string): Promise; - /** - * Closing the row without writing designated timestamp into the buffer of the sender.
- * Designated timestamp will be populated by the server on this record. - */ - atNow(): Promise; -} -export const DEFAULT_BUFFER_SIZE: 65536; -export const DEFAULT_MAX_BUFFER_SIZE: 104857600; -import net = require("net"); -import tls = require("tls"); -import http = require("http"); -import https = require("https"); -import { Buffer } from "buffer"; -import { SenderOptions } from "./options"; -//# sourceMappingURL=sender.d.ts.map \ No newline at end of file diff --git a/types/src/sender.d.ts.map b/types/src/sender.d.ts.map deleted file mode 100644 index 7b10361..0000000 --- a/types/src/sender.d.ts.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"sender.d.ts","sourceRoot":"","sources":["../../src/sender.js"],"names":[],"mappings":";;;;;AA8CA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4CG;AACH;IACI,eAAe,CAAC,kCAA0B;IAC1C,eAAe,CAAC,mCAA2B;IAgI3C;;;;;;;;;;;OAWG;IACH,uCATW,MAAM,iBACN,MAAM,GAML,MAAM,CAIjB;IAED;;;;;;;;;;OAUG;IACH,8BARW,MAAM,GAML,MAAM,CAIjB;IAlHD;;;;;OAKG;IACH,qBAHW,aAAa,EAgFvB;IA5HD,eAAe,CAAC,aAAK;IACrB,eAAe,CAAC,eAAO;IACvB,eAAe,CAAC,aAAK;IACrB,eAAe,CAAC,aAAK;IAErB,eAAe,CAAC,eAAO;IAEvB,eAAe,CAAC,iBAAS;IACzB,eAAe,CAAC,iBAAS;IACzB,eAAe,CAAC,cAAM;IAEtB,eAAe,CAAC,kBAAU;IAC1B,eAAe,CAAC,cAAM;IAEtB,eAAe,CAAC,mBAAW;IAC3B,eAAe,CAAC,sBAAc;IAC9B,eAAe,CAAC,eAAO;IACvB,eAAe,CAAC,iBAAS;IACzB,eAAe,CAAC,kBAAU;IAC1B,eAAe,CAAC,iBAAS;IACzB,eAAe,CAAC,qBAAa;IAE7B,eAAe,CAAC,kBAAU;IAC1B,eAAe,CAAC,sBAAc;IAC9B,eAAe,CAAC,0BAAkB;IAClC,eAAe,CAAC,sBAAc;IAC9B,eAAe,CAAC,wBAAgB;IAEhC,eAAe,CAAC,6BAAqB;IACrC,eAAe,CAAC,uBAAe;IAC/B,eAAe,CAAC,qBAAa;IAE7B,eAAe,CAAC,iBAAS;IACzB,eAAe,CAAC,mBAAW;IAC3B,eAAe,CAAC,mBAAW;IAE3B,eAAe,CAAC,sBAAc;IAE9B,eAAe,CAAC,YAAI;IACpB,eAAe,CAAC,cAAM;IAkDd,SAAgC;IAoExC;;;;;;OAMG;IACH,mBAFW,MAAM,QAgBhB;IAED;;;;;OAKG;IACH,SAFY,MAAM,CAQjB;IAED;;;;;;OAMG;IACH,yBAJW,IAAI,cAAc,GAAG,IAAI,iBAAiB,GAEzC,QAAQ,OAAO,CAAC,CAyE3B;IAED;;;OAGG;IACH,uBAFY,KAAK,KAAK,CAOrB;IAED;;;OAGG;IACH,wBAFY,MAAM,KAAK,CAOtB;IAED;;;OAGG;IACH,uBAQC;IAED;;;;;OAKG;IACH,SAFY,QAAQ,OAAO,CAAC,CAkB3B;IAED;;;;OAIG;IACH,yBAHY,MAAM,CAOjB;IAED;;;;OAIG;IACH,wBAHY,MAAM,CAWjB;IAED;;;;;OAKG;IACH,aAHW,MAAM,GACL,MAAM,CAcjB;IAED;;;;;;OAMG;IACH,aAJW,MAAM,SACN,GAAG,GACF,MAAM,CAkBjB;IAED;;;;;;OAMG;IACH,mBAJW,MAAM,SACN,MAAM,GACL,MAAM,CAUjB;IAED;;;;;;OAMG;IACH,oBAJW,MAAM,SACN,OAAO,GACN,MAAM,CAQjB;IAED;;;;;;OAMG;IACH,kBAJW,MAAM,SACN,MAAM,GACL,MAAM,CASjB;IAED;;;;;;OAMG;IACH,gBAJW,MAAM,SACN,MAAM,GACL,MAAM,CAajB;IAED;;;;;;;OAOG;IACH,sBALW,MAAM,SACN,MAAM,GAAG,MAAM,SACf,MAAM,GACL,MAAM,CAcjB;IAED;;;;;OAKG;IACH,cAHW,MAAM,GAAG,MAAM,SACf,MAAM,iBAkBhB;IAED;;;OAGG;IACH,uBASC;CACJ;AAzlBD,wCAAkC;AAClC,gDAA0C"} \ No newline at end of file diff --git a/types/src/validation.d.ts b/types/src/validation.d.ts deleted file mode 100644 index 2b35910..0000000 --- a/types/src/validation.d.ts +++ /dev/null @@ -1,17 +0,0 @@ -/** - * Validates a table name.
- * Throws an error if table name is invalid. - * - * @param {string} name - The table name to validate. - * @param {number} maxNameLength - The maximum length of table names. - */ -export function validateTableName(name: string, maxNameLength: number): void; -/** - * Validates a column name.
- * Throws an error if column name is invalid. - * - * @param {string} name - The column name to validate. - * @param {number} maxNameLength - The maximum length of column names. - */ -export function validateColumnName(name: string, maxNameLength: number): void; -//# sourceMappingURL=validation.d.ts.map \ No newline at end of file diff --git a/types/src/validation.d.ts.map b/types/src/validation.d.ts.map deleted file mode 100644 index b9626f9..0000000 --- a/types/src/validation.d.ts.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"validation.d.ts","sourceRoot":"","sources":["../../src/validation.js"],"names":[],"mappings":"AAEA;;;;;;GAMG;AACH,wCAHW,MAAM,iBACN,MAAM,QAyDhB;AAED;;;;;;GAMG;AACH,yCAHW,MAAM,iBACN,MAAM,QAgDhB"} \ No newline at end of file