From 41156cf2d9dba9fca601edd2e65ce291f8c3c37a Mon Sep 17 00:00:00 2001
From: Serhiy Morenko
Date: Mon, 6 Jul 2026 19:30:16 +0200
Subject: [PATCH 01/20] feat: removed not reliable dev deps, reorganized tests
& moved to node native test structure
---
.github/workflows/build.yml | 20 +-
.gitignore | 1 +
README.md | 13 +-
benchmark/affinity.ts | 2 +-
benchmark/index.ts | 338 +-
benchmark/redis-test.ts | 207 +-
eslint.config.mjs | 98 -
index.ts | 6 +-
package-lock.json | 6013 +++--------------
package.json | 48 +-
src/ClusterManager.ts | 19 +-
src/ClusteredRedisQueue.ts | 225 +-
src/IMessageQueue.ts | 24 +-
src/RedisQueue.ts | 393 +-
src/UDPClusterManager.ts | 59 +-
src/UDPWorker.ts | 46 +-
src/copyEventEmitter.ts | 4 +-
src/index.ts | 1 -
src/profile.ts | 108 +-
src/promisify.ts | 113 -
src/uuid.ts | 40 +-
...edRedisQueue.addServer.defaultInit.spec.ts | 34 -
...usteredRedisQueue.addServer.noInit.spec.ts | 32 -
test/ClusteredRedisQueue.extra.spec.ts | 48 -
test/ClusteredRedisQueue.initialize.spec.ts | 37 -
test/ClusteredRedisQueue.matchServers.spec.ts | 23 -
test/ClusteredRedisQueue.ts | 344 -
test/RedisQueue.cleanup.catch.spec.ts | 41 -
test/RedisQueue.connect.fallbacks.spec.ts | 40 -
...Queue.processCleanup.clientsFilter.spec.ts | 53 -
test/RedisQueue.processCleanup.extra.spec.ts | 38 -
...edisQueue.processCleanup.multiscan.spec.ts | 42 -
...edisQueue.processCleanup.nullmatch.spec.ts | 49 -
test/RedisQueue.publish.spec.ts | 70 -
test/RedisQueue.send.extra.branches.spec.ts | 41 -
test/RedisQueue.send.worker.mode.spec.ts | 36 -
test/RedisQueue.unsubscribe.spec.ts | 53 -
...UDPClusterManager.missing.branches.spec.ts | 39 -
test/helpers/index.ts | 38 +
test/mocks/dgram.ts | 2 +-
test/mocks/logger.ts | 2 +-
test/mocks/os.ts | 2 +-
test/mocks/redis.ts | 58 +-
test/profile.decorator.branches.spec.ts | 34 -
test/profile.more.branches.spec.ts | 57 -
test/profile.rejection.spec.ts | 33 -
test/profile.ts | 219 -
test/promisify.ts | 94 -
test/{ => unit}/ClusterManager.spec.ts | 36 +-
.../ClusteredRedisQueue/AddServer.spec.ts | 71 +
.../ClusteredRedisQueue.spec.ts | 398 ++
.../ClusteredRedisQueue/Initialize.spec.ts | 46 +
.../ClusteredRedisQueue/MatchServers.spec.ts | 44 +
.../CopyEventEmitter.spec.ts} | 55 +-
test/{IMQ.ts => unit/IMQ.spec.ts} | 51 +-
.../IMessageQueue.spec.ts} | 15 +-
test/unit/Profile/Decorator.spec.ts | 72 +
test/unit/Profile/Profile.spec.ts | 297 +
test/unit/RedisQueue/Cleanup.spec.ts | 35 +
test/unit/RedisQueue/Connect.spec.ts | 35 +
test/unit/RedisQueue/ProcessCleanup.spec.ts | 165 +
.../RedisQueue/ProcessDelayed.spec.ts} | 40 +-
test/unit/RedisQueue/Publish.spec.ts | 72 +
.../RedisQueue/RedisQueue.spec.ts} | 201 +-
test/unit/RedisQueue/Send.spec.ts | 57 +
test/unit/RedisQueue/Unsubscribe.spec.ts | 40 +
.../UDPClusterManager/DestroySocket.spec.ts} | 20 +-
.../UDPClusterManager.spec.ts} | 72 +-
test/{uuid.ts => unit/Uuid.spec.ts} | 37 +-
tsconfig.json | 27 +-
70 files changed, 3569 insertions(+), 7654 deletions(-)
delete mode 100644 eslint.config.mjs
delete mode 100644 src/promisify.ts
delete mode 100644 test/ClusteredRedisQueue.addServer.defaultInit.spec.ts
delete mode 100644 test/ClusteredRedisQueue.addServer.noInit.spec.ts
delete mode 100644 test/ClusteredRedisQueue.extra.spec.ts
delete mode 100644 test/ClusteredRedisQueue.initialize.spec.ts
delete mode 100644 test/ClusteredRedisQueue.matchServers.spec.ts
delete mode 100644 test/ClusteredRedisQueue.ts
delete mode 100644 test/RedisQueue.cleanup.catch.spec.ts
delete mode 100644 test/RedisQueue.connect.fallbacks.spec.ts
delete mode 100644 test/RedisQueue.processCleanup.clientsFilter.spec.ts
delete mode 100644 test/RedisQueue.processCleanup.extra.spec.ts
delete mode 100644 test/RedisQueue.processCleanup.multiscan.spec.ts
delete mode 100644 test/RedisQueue.processCleanup.nullmatch.spec.ts
delete mode 100644 test/RedisQueue.publish.spec.ts
delete mode 100644 test/RedisQueue.send.extra.branches.spec.ts
delete mode 100644 test/RedisQueue.send.worker.mode.spec.ts
delete mode 100644 test/RedisQueue.unsubscribe.spec.ts
delete mode 100644 test/UDPClusterManager.missing.branches.spec.ts
create mode 100644 test/helpers/index.ts
delete mode 100644 test/profile.decorator.branches.spec.ts
delete mode 100644 test/profile.more.branches.spec.ts
delete mode 100644 test/profile.rejection.spec.ts
delete mode 100644 test/profile.ts
delete mode 100644 test/promisify.ts
rename test/{ => unit}/ClusterManager.spec.ts (55%)
create mode 100644 test/unit/ClusteredRedisQueue/AddServer.spec.ts
create mode 100644 test/unit/ClusteredRedisQueue/ClusteredRedisQueue.spec.ts
create mode 100644 test/unit/ClusteredRedisQueue/Initialize.spec.ts
create mode 100644 test/unit/ClusteredRedisQueue/MatchServers.spec.ts
rename test/{copyEventEmitter.ts => unit/CopyEventEmitter.spec.ts} (83%)
rename test/{IMQ.ts => unit/IMQ.spec.ts} (56%)
rename test/{IMessageQueue.EventEmitter.spec.ts => unit/IMessageQueue.spec.ts} (82%)
create mode 100644 test/unit/Profile/Decorator.spec.ts
create mode 100644 test/unit/Profile/Profile.spec.ts
create mode 100644 test/unit/RedisQueue/Cleanup.spec.ts
create mode 100644 test/unit/RedisQueue/Connect.spec.ts
create mode 100644 test/unit/RedisQueue/ProcessCleanup.spec.ts
rename test/{RedisQueue.processDelayed.catch.spec.ts => unit/RedisQueue/ProcessDelayed.spec.ts} (52%)
create mode 100644 test/unit/RedisQueue/Publish.spec.ts
rename test/{RedisQueue.ts => unit/RedisQueue/RedisQueue.spec.ts} (61%)
create mode 100644 test/unit/RedisQueue/Send.spec.ts
create mode 100644 test/unit/RedisQueue/Unsubscribe.spec.ts
rename test/{UDPClusterManager.destroySocket.spec.ts => unit/UDPClusterManager/DestroySocket.spec.ts} (72%)
rename test/{UDPClusterManager.ts => unit/UDPClusterManager/UDPClusterManager.spec.ts} (68%)
rename test/{uuid.ts => unit/Uuid.spec.ts} (73%)
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 67bcd75..06d757b 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -9,8 +9,9 @@ jobs:
runs-on: ubuntu-latest
strategy:
+ fail-fast: false
matrix:
- node-version: [lts/*]
+ node-version: [20.x, 22.x, 24.x]
steps:
- name: Checkout repository
@@ -20,9 +21,22 @@ jobs:
uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
+ cache: npm
- name: Install dependencies
- run: npm install
+ run: npm ci
+
+ - name: Check formatting
+ run: npm run format:check
+
+ - name: Lint
+ run: npm run lint
- name: Run tests
- run: npm test
+ run: npm run test-lcov
+
+ - name: Upload coverage to Coveralls
+ if: matrix.node-version == '24.x'
+ uses: coverallsapp/github-action@v2
+ with:
+ file: coverage/lcov.info
diff --git a/.gitignore b/.gitignore
index e04899e..229818a 100644
--- a/.gitignore
+++ b/.gitignore
@@ -4,6 +4,7 @@ tmp/
out-tsc/
build/
.nyc_output/
+.agent-out/
node_modules/
diff --git a/README.md b/README.md
index b11ca5f..b311c25 100644
--- a/README.md
+++ b/README.md
@@ -147,12 +147,23 @@ predictable.*
## Running Unit Tests
+Tests run on the native Node.js test runner (`node:test`) with `node:assert` and
+no external test framework, so a plain clone and install is all that is needed:
+
~~~bash
git clone git@github.com:imqueue/core.git
-cd imq
+cd core
+npm install
npm test
~~~
+To produce a coverage report use:
+
+~~~bash
+npm run test-coverage # prints coverage summary to the console
+npm run test-lcov # writes coverage/lcov.info
+~~~
+
## License
This project is licensed under the GNU General Public License v3.0.
diff --git a/benchmark/affinity.ts b/benchmark/affinity.ts
index 06cb7e3..322b500 100644
--- a/benchmark/affinity.ts
+++ b/benchmark/affinity.ts
@@ -28,4 +28,4 @@ export function setAffinity(cpu: number) {
if (os.platform() === 'linux') {
exec(`taskset -cp ${cpu} ${process.pid}`);
}
-}
\ No newline at end of file
+}
diff --git a/benchmark/index.ts b/benchmark/index.ts
index 3276452..f5f0cb5 100644
--- a/benchmark/index.ts
+++ b/benchmark/index.ts
@@ -24,81 +24,138 @@
import { execSync as exec } from 'child_process';
import * as os from 'os';
import * as fs from 'fs';
-import * as yargs from 'yargs';
+import { parseArgs } from 'node:util';
import { run } from './redis-test';
-import { resolve } from 'path';
+import { resolve } from 'path';
import { uuid, AnyJson } from '..';
import { setAffinity } from './affinity';
const cluster: any = require('cluster');
/**
- * Command line args
- * @type {yargs.Arguments}
+ * Command line options: canonical long name -> parseArgs config + description
+ * used to render `--help`.
*/
-const ARGV: any = yargs
-.help('h')
-.alias('h', 'help')
-
-.alias('c', 'children')
-.describe('c', 'Number of children test process to fork')
-
-.alias('d', 'delay')
-.describe('d', 'Number of milliseconds to delay message delivery for ' +
- 'delayed messages. By default delayed messages is of and this ' +
- 'argument is equal to 0.')
-
-.alias('m', 'messages')
-.describe('m', 'Number of messages to be sent by a child process ' +
- 'during test execution.')
-
-.alias('z', 'gzip')
-.describe('z', 'Use gzip for message encoding/decoding.')
-
-.alias('s', 'safe')
-.describe('s', 'Use safe (guaranteed) message delivery algorithm.')
-
-.alias('e', 'example-message')
-.describe('e', 'Path to a file containing JSON of example message to ' +
- 'use during the tests.')
-
-.alias('p', 'port')
-.describe('p', 'Redis server port to connect to.')
-
-.alias('t', 'message-multiply-times')
-.describe('t', 'Increase sample message data given number of times per ' +
- 'request.')
+const OPTIONS: {
+ [name: string]: {
+ type: 'string' | 'boolean';
+ short: string;
+ describe: string;
+ };
+} = {
+ help: {
+ type: 'boolean',
+ short: 'h',
+ describe: 'Show this help.',
+ },
+ children: {
+ type: 'string',
+ short: 'c',
+ describe: 'Number of children test process to fork.',
+ },
+ delay: {
+ type: 'string',
+ short: 'd',
+ describe:
+ 'Number of milliseconds to delay message delivery for ' +
+ 'delayed messages. By default delayed messages is off and this ' +
+ 'argument is equal to 0.',
+ },
+ messages: {
+ type: 'string',
+ short: 'm',
+ describe:
+ 'Number of messages to be sent by a child process ' +
+ 'during test execution.',
+ },
+ gzip: {
+ type: 'boolean',
+ short: 'z',
+ describe: 'Use gzip for message encoding/decoding.',
+ },
+ safe: {
+ type: 'boolean',
+ short: 's',
+ describe: 'Use safe (guaranteed) message delivery algorithm.',
+ },
+ 'example-message': {
+ type: 'string',
+ short: 'e',
+ describe:
+ 'Path to a file containing JSON of example message to ' +
+ 'use during the tests.',
+ },
+ port: {
+ type: 'string',
+ short: 'p',
+ describe: 'Redis server port to connect to.',
+ },
+ 'message-multiply-times': {
+ type: 'string',
+ short: 't',
+ describe:
+ 'Increase sample message data given number of times per ' +
+ 'request.',
+ },
+};
-.boolean(['h', 'z', 's'])
- .argv;
+/**
+ * Command line args
+ */
+const ARGV: any = parseArgs({
+ options: Object.fromEntries(
+ Object.entries(OPTIONS).map(([name, { type, short }]) => [
+ name,
+ { type, short },
+ ]),
+ ) as any,
+ allowPositionals: true,
+}).values;
+
+if (ARGV.help) {
+ const help = Object.entries(OPTIONS)
+ .map(([name, o]) => ` -${o.short}, --${name}\n ${o.describe}`)
+ .join('\n\n');
+
+ process.stdout.write(
+ `Usage: node benchmark [options]\n\nOptions:\n${help}\n`,
+ );
+ process.exit(0);
+}
-let maxChildren = Number(ARGV.c) || 1;
+let maxChildren = Number(ARGV.children) || 1;
const METRICS_DELAY = 100;
const CPUS = os.cpus();
const numCpus = CPUS.length;
const CPU_NAMES = ['Redis Process, CPU Used, %'];
-const STEPS = Number(ARGV.m) || 10000;
-const MSG_DELAY = Number(ARGV.d) || 0;
-const USE_GZIP: boolean = !!ARGV.z;
-const MSG_MULTIPLIER = Number(ARGV.t) || 0;
-const SAFE_DELIVERY: boolean = !!ARGV.s;
-const REDIS_PORT: number = Number(ARGV.p) || 6379;
+const STEPS = Number(ARGV.messages) || 10000;
+const MSG_DELAY = Number(ARGV.delay) || 0;
+const USE_GZIP: boolean = !!ARGV.gzip;
+const MSG_MULTIPLIER = Number(ARGV['message-multiply-times']) || 0;
+const SAFE_DELIVERY: boolean = !!ARGV.safe;
+const REDIS_PORT: number = Number(ARGV.port) || 6379;
let SAMPLE_MESSAGE: AnyJson;
-if (ARGV.e) {
+if (ARGV['example-message']) {
try {
- SAMPLE_MESSAGE = JSON.parse(fs.readFileSync(ARGV.e + '').toString());
+ SAMPLE_MESSAGE = JSON.parse(
+ fs.readFileSync(ARGV['example-message'] + '').toString(),
+ );
if (MSG_MULTIPLIER) {
- SAMPLE_MESSAGE = new Array(MSG_MULTIPLIER)
- .fill(SAMPLE_MESSAGE);
+ SAMPLE_MESSAGE = Array.from(
+ { length: MSG_MULTIPLIER },
+ () => SAMPLE_MESSAGE,
+ );
}
} catch (err) {
- console.warn('Given example message is invalid, ' +
- 'proceeding test execution with with standard ' +
- 'example message.');
+ console.warn(
+ 'Given example message is invalid, ' +
+ 'proceeding test execution with with standard ' +
+ 'example message.',
+ );
}
}
@@ -134,7 +191,7 @@ function cpuAvg(i: number) {
return {
idle: totalIdle / cpus.length,
- total: totalTick / cpus.length
+ total: totalTick / cpus.length,
};
}
@@ -150,9 +207,7 @@ interface MachineStats {
* @param {any} memusage
* @return {MachineStats}
*/
-function buildStats(
- { metrics, memusage }: any
-): MachineStats {
+function buildStats({ metrics, memusage }: any): MachineStats {
const stats: any[] = [];
const memStats: any[] = ['System Memory Used, %'];
@@ -165,12 +220,12 @@ function buildStats(
stats[cpu] = [CPU_NAMES[cpu]];
}
- stats[cpu].push(100 - ~~(100 * idle / total));
+ stats[cpu].push(100 - ~~((100 * idle) / total));
}
}
for (let i = 0, s = memusage.length; i < s; i++) {
- memStats.push(100 - ~~(100 * memusage[i].free / memusage[i].total));
+ memStats.push(100 - ~~((100 * memusage[i].free) / memusage[i].total));
}
return { stats, memStats };
@@ -187,30 +242,33 @@ function buildChartConfig(id: string, stats: any[]) {
return {
bindto: `#${id}`,
data: {
- columns: stats
+ columns: stats,
},
point: { show: false },
axis: {
x: {
type: 'category',
- categories: stats[0].slice(1).map((v: any, i: number) =>
- ((i * 100) / 1000).toFixed(1) + 's' as any
- ),
+ categories: stats[0]
+ .slice(1)
+ .map(
+ (v: any, i: number) =>
+ (((i * 100) / 1000).toFixed(1) + 's') as any,
+ ),
tick: {
centered: true,
fit: false,
culling: { max: 20 },
- outer: false
- }
+ outer: false,
+ },
},
y: {
max: 100,
- tick: { outer: false }
- }
+ tick: { outer: false },
+ },
},
zoom: {
- enabled: true
- }
+ enabled: true,
+ },
};
}
@@ -223,9 +281,11 @@ function buildChartConfig(id: string, stats: any[]) {
* @return {string}
*/
function bytesCount(data: any[], fmt: Intl.NumberFormat, key: string) {
- return fmt.format(Math.round(data.reduce((prev, next) =>
- prev + next[key], 0
- ) / data.length))
+ return fmt.format(
+ Math.round(
+ data.reduce((prev, next) => prev + next[key], 0) / data.length,
+ ),
+ );
}
/**
@@ -234,13 +294,11 @@ function bytesCount(data: any[], fmt: Intl.NumberFormat, key: string) {
* @param {{ metrics: any, memusage: any }} stats
* @param {any} data
*/
-function saveStats({ metrics, memusage }: any, data: any[]) {
+function saveStats({ metrics, memusage }: any, data: any[]) {
const { stats, memStats } = buildStats({ metrics, memusage });
const config = buildChartConfig('cpu-usage', stats);
const memConfig = buildChartConfig('memory-usage', [memStats]);
- const fmt = new Intl.NumberFormat(
- 'en-US', { maximumSignificantDigits: 3 }
- );
+ const fmt = new Intl.NumberFormat('en-US', { maximumSignificantDigits: 3 });
// language=HTML
let html = `
@@ -267,44 +325,55 @@ function saveStats({ metrics, memusage }: any, data: any[]) {
Test Execution Information
- - Execution Datetime: ${ new Date().toISOString() }
+ - Execution Datetime: ${new Date().toISOString()}
- System Info:
- - CPU: ${CPUS[0].model} × ${ numCpus } cores
- - CPU Clock Speed: ${ CPUS[0].speed }Mhz
- - RAM: ${ Math.ceil(os.totalmem() / Math.pow(1024, 3)) }GB
- - OS Architecture: ${ os.arch() }
- - OS Platform: ${ os.platform() }
- - Node Version: ${ process.versions.node }
+ - CPU: ${CPUS[0].model} × ${numCpus} cores
+ - CPU Clock Speed: ${CPUS[0].speed}Mhz
+ - RAM: ${Math.ceil(os.totalmem() / Math.pow(1024, 3))}GB
+ - OS Architecture: ${os.arch()}
+ - OS Platform: ${os.platform()}
+ - Node Version: ${process.versions.node}
- Number of workers: ${fmt.format(maxChildren)}
- Number of messages per worker: ${fmt.format(STEPS)}
- Total messages executed: ${fmt.format(STEPS * maxChildren)}
- - Round-trip ratio across all workers is: ${
- fmt.format(data.reduce((prev, next) =>
- prev + next.ratio, 0
- ))
- } msg/sec
- - Average message payload to redis is: ${
- bytesCount(data, fmt, 'bytesLen')
- } bytes
- - Average source message payload is: ${
- bytesCount(data, fmt, 'srcBytesLen')
- } bytes
- - Average time of all messages delivery is: ${
- fmt.format(Number((data.reduce((prev, next) =>
- prev + next.time, 0
- ) / 1000 / data.length).toFixed(2)))
- } sec ±10 ms
- - Max delivery time is: ${
- fmt.format(
- Number((Math.max.apply(null, data.map((item => item.time)))
- / 1000).toFixed(2)))
- } sec ±10 ms
+ - Round-trip ratio across all workers is: ${fmt.format(
+ data.reduce((prev, next) => prev + next.ratio, 0),
+ )} msg/sec
+ - Average message payload to redis is: ${bytesCount(
+ data,
+ fmt,
+ 'bytesLen',
+ )} bytes
+ - Average source message payload is: ${bytesCount(
+ data,
+ fmt,
+ 'srcBytesLen',
+ )} bytes
+ - Average time of all messages delivery is: ${fmt.format(
+ Number(
+ (
+ data.reduce((prev, next) => prev + next.time, 0) /
+ 1000 /
+ data.length
+ ).toFixed(2),
+ ),
+ )} sec ±10 ms
+ - Max delivery time is: ${fmt.format(
+ Number(
+ (
+ Math.max.apply(
+ null,
+ data.map(item => item.time),
+ ) / 1000
+ ).toFixed(2),
+ ),
+ )} sec ±10 ms
${MSG_DELAY ? '- Message delivery delay used: ' + MSG_DELAY + '
' : ''}
- - Gzip compression for messages is: ${ USE_GZIP ? 'On' : 'Off' }
- - Safe delivery is: ${ SAFE_DELIVERY ? 'On' : 'Off' }
+ - Gzip compression for messages is: ${USE_GZIP ? 'On' : 'Off'}
+ - Safe delivery is: ${SAFE_DELIVERY ? 'On' : 'Off'}
CPU Usage
@@ -350,7 +419,7 @@ if (cluster.isMaster) {
const data: any[] = [];
statsWorker.on('message', (msg: any) => {
- if (/^metrics:/.test(msg)) {
+ if (msg.startsWith('metrics:')) {
saveStats(JSON.parse(msg.split('metrics:').pop() || ''), data);
process.exit(0);
}
@@ -361,7 +430,7 @@ if (cluster.isMaster) {
worker.send(`imq ${i}`);
worker.on('message', (msg: string) => {
- if (/^data:/.test(msg)) {
+ if (msg.startsWith('data:')) {
done++;
data.push(JSON.parse(msg.split('data:').pop() || ''));
@@ -371,15 +440,13 @@ if (cluster.isMaster) {
}
});
}
-}
-
-else {
+} else {
const metrics: any[] = [];
const memusage: any[] = [];
let metricsInterval: any;
process.on('message', async (msg: string) => {
- if (/^imq/.test(msg)) {
+ if (msg.startsWith('imq')) {
const index = parseInt(String(msg.split(/\s+/).pop()), 10);
const core = numCpus <= 2 ? 1 : index + 2;
@@ -392,55 +459,58 @@ else {
MSG_DELAY,
USE_GZIP,
SAFE_DELIVERY,
- SAMPLE_MESSAGE
+ SAMPLE_MESSAGE,
);
(
process).send('data:' + JSON.stringify(data));
- }
-
- catch (err) {
+ } catch (err) {
(process).send('data:' + JSON.stringify(null));
- console.error(err.stack);
+ console.error(err instanceof Error ? err.stack : err);
process.exit(1);
}
- }
-
- else if (msg === 'stats') {
+ } else if (msg === 'stats') {
setAffinity(1);
const redisProcess = exec('ps ax|grep redis-server')
- .toString('utf8')
- .split(/\r?\n/)[0];
+ .toString('utf8')
+ .split(/\r?\n/)[0];
const core = numCpus > 1 ? 1 : 0;
- if (core && os.platform() === 'linux' &&
+ if (
+ core &&
+ os.platform() === 'linux' &&
/redis-server/.test(redisProcess) &&
!/grep/.test(redisProcess)
) {
const redisPid = parseInt(redisProcess.split(/\s+/)[0], 10);
- redisPid && exec(`taskset -cp ${core} ${redisPid}`);
+ if (redisPid) {
+ exec(`taskset -cp ${core} ${redisPid}`);
+ }
}
metricsInterval = setInterval(() => {
metrics.push(
- CPU_NAMES.map((name: string, i: number) => cpuAvg(i + 1))
+ CPU_NAMES.map((name: string, i: number) => cpuAvg(i + 1)),
);
memusage.push({
total: os.totalmem(),
- free: os.freemem()
+ free: os.freemem(),
});
}, METRICS_DELAY);
- }
-
- else if (msg === 'stop') {
- metricsInterval && clearInterval(metricsInterval);
+ } else if (msg === 'stop') {
+ if (metricsInterval) {
+ clearInterval(metricsInterval);
+ }
metricsInterval = null;
console.log('Finalizing...');
- (process).send('metrics:' + JSON.stringify({
- metrics,
- memusage
- }));
+ (process).send(
+ 'metrics:' +
+ JSON.stringify({
+ metrics,
+ memusage,
+ }),
+ );
process.exit(0);
}
});
-}
\ No newline at end of file
+}
diff --git a/benchmark/redis-test.ts b/benchmark/redis-test.ts
index 82e7c17..3877fc0 100644
--- a/benchmark/redis-test.ts
+++ b/benchmark/redis-test.ts
@@ -26,61 +26,66 @@ import IMQ, {
IJson,
uuid,
pack,
- JsonObject, AnyJson,
+ JsonObject,
+ AnyJson,
} from '../index';
/**
* Sample message used within tests
*/
const JSON_EXAMPLE: JsonObject = {
- "Glossary": {
- "Title": "Example Glossary",
- "GlossDiv": {
- "Title": "ß∆",
- "GlossList": [{
- "GlossEntry": {
- "ID": "SGML",
- "SortAs": "SGML",
- "GlossTerm": "Standard Generalized Markup Language",
- "Acronym": "SGML",
- "Abbrev": "ISO 8879:1986",
- "GlossDef": {
- "para": "A meta-markup language, used to create markup languages such as DocBook.",
- "GlossSeeAlso": ["GML", "XML"]
+ Glossary: {
+ Title: 'Example Glossary',
+ GlossDiv: {
+ Title: 'ß∆',
+ GlossList: [
+ {
+ GlossEntry: {
+ ID: 'SGML',
+ SortAs: 'SGML',
+ GlossTerm: 'Standard Generalized Markup Language',
+ Acronym: 'SGML',
+ Abbrev: 'ISO 8879:1986',
+ GlossDef: {
+ para: 'A meta-markup language, used to create markup languages such as DocBook.',
+ GlossSeeAlso: ['GML', 'XML'],
+ },
+ GlossSee: 'markup, non-markup, joke-cup',
},
- "GlossSee": "markup, non-markup, joke-cup"
- }
- }, {
- "GlossEntry": {
- "ID": "SGML",
- "SortAs": "SGML",
- "GlossTerm": "Standard Generalized Markup Language",
- "Acronym": "SGML",
- "Abbrev": "ISO 8879:1986",
- "MoreBytes": "",
- "GlossDef": {
- "para": "A meta-markup language, used to create markup languages such as DocBook.",
- "GlossSeeAlso": ["GML", "XML", "FML", "RML", "FCL"]
+ },
+ {
+ GlossEntry: {
+ ID: 'SGML',
+ SortAs: 'SGML',
+ GlossTerm: 'Standard Generalized Markup Language',
+ Acronym: 'SGML',
+ Abbrev: 'ISO 8879:1986',
+ MoreBytes: '',
+ GlossDef: {
+ para: 'A meta-markup language, used to create markup languages such as DocBook.',
+ GlossSeeAlso: ['GML', 'XML', 'FML', 'RML', 'FCL'],
+ },
+ GlossSee: 'markup',
},
- "GlossSee": "markup"
- }
- }, {
- "GlossEntry": {
- "ID": "SGML",
- "SortAs": "SGML",
- "GlossTerm": "Standard Generalized Markup Language",
- "Acronym": "SGML",
- "Abbrev": "ISO 8879:1986",
- "MoreBytes": [0, 1, 2, 3, 4, 5, 6],
- "GlossDef": {
- "para": "A meta-markup language, used to create markup languages such as DocBook.",
- "GlossSeeAlso": ["GML", "XML", "OGL", "PPL"]
+ },
+ {
+ GlossEntry: {
+ ID: 'SGML',
+ SortAs: 'SGML',
+ GlossTerm: 'Standard Generalized Markup Language',
+ Acronym: 'SGML',
+ Abbrev: 'ISO 8879:1986',
+ MoreBytes: [0, 1, 2, 3, 4, 5, 6],
+ GlossDef: {
+ para: 'A meta-markup language, used to create markup languages such as DocBook.',
+ GlossSeeAlso: ['GML', 'XML', 'OGL', 'PPL'],
+ },
+ GlossSee: 'markup',
},
- "GlossSee": "markup"
- }
- }]
- }
- }
+ },
+ ],
+ },
+ },
};
/**
@@ -111,74 +116,76 @@ export async function run(
msgDelay: number = 0,
useGzip: boolean = false,
safeDelivery: boolean = false,
- jsonExample: AnyJson = JSON_EXAMPLE
+ jsonExample: AnyJson = JSON_EXAMPLE,
) {
const bytesLen = bytes(
(useGzip ? pack : JSON.stringify)(jsonExample),
- useGzip
+ useGzip,
);
const srcBytesLen = bytes(JSON.stringify(jsonExample));
- return new Promise(async (resolve) => {
- const queueName = `imq-test:${uuid()}`;
- const options: Partial = {
- vendor: 'Redis',
- port,
- useGzip,
- safeDelivery
- };
- const mq = await IMQ.create(queueName, options).start();
-
- let count = 0;
- const fmt = new Intl.NumberFormat(
- 'en-US', { maximumSignificantDigits: 3 }
- );
+ return new Promise(resolve => {
+ void (async () => {
+ const queueName = `imq-test:${uuid()}`;
+ const options: Partial = {
+ vendor: 'Redis',
+ port,
+ useGzip,
+ safeDelivery,
+ };
+ const mq = await IMQ.create(queueName, options).start();
- mq.on('message', () => count++);
+ let count = 0;
+ const fmt = new Intl.NumberFormat('en-US', {
+ maximumSignificantDigits: 3,
+ });
- if (msgDelay) {
- console.log(
- 'Sending %s messages, using %s delay please, wait...',
- fmt.format(steps),
- fmt.format(msgDelay)
- );
- } else {
- console.log(
- 'Sending %s messages, please, wait...',
- fmt.format(steps)
- );
- }
+ mq.on('message', () => count++);
- const start = Date.now();
-
- for (let i = 0; i < steps; i++) {
- mq.send(queueName, jsonExample as JsonObject, msgDelay).catch();
- }
-
- const interval = setInterval(async () => {
- if (count >= steps) {
- const time = Date.now() - start;
- const ratio = count / (time / 1000);
-
- console.log(
- '%s is sent/received in %s ±10 ms',
- fmt.format(count),
- fmt.format(time)
- );
+ if (msgDelay) {
console.log(
- 'Round-trip ratio: %s messages/sec',
- fmt.format(ratio)
+ 'Sending %s messages, using %s delay please, wait...',
+ fmt.format(steps),
+ fmt.format(msgDelay),
);
+ } else {
console.log(
- 'Message payload is: %s bytes',
- fmt.format(bytesLen)
+ 'Sending %s messages, please, wait...',
+ fmt.format(steps),
);
+ }
- mq.destroy().catch();
+ const start = Date.now();
- clearInterval(interval);
- resolve({ count, time, ratio, bytesLen, srcBytesLen });
+ for (let i = 0; i < steps; i++) {
+ mq.send(queueName, jsonExample as JsonObject, msgDelay).catch();
}
- }, 10);
+
+ const interval = setInterval(async () => {
+ if (count >= steps) {
+ const time = Date.now() - start;
+ const ratio = count / (time / 1000);
+
+ console.log(
+ '%s is sent/received in %s ±10 ms',
+ fmt.format(count),
+ fmt.format(time),
+ );
+ console.log(
+ 'Round-trip ratio: %s messages/sec',
+ fmt.format(ratio),
+ );
+ console.log(
+ 'Message payload is: %s bytes',
+ fmt.format(bytesLen),
+ );
+
+ mq.destroy().catch();
+
+ clearInterval(interval);
+ resolve({ count, time, ratio, bytesLen, srcBytesLen });
+ }
+ }, 10);
+ })();
});
-}
\ No newline at end of file
+}
diff --git a/eslint.config.mjs b/eslint.config.mjs
deleted file mode 100644
index a0210a5..0000000
--- a/eslint.config.mjs
+++ /dev/null
@@ -1,98 +0,0 @@
-/*!
- * I'm Queue Software Project
- * Copyright (C) 2025 imqueue.com
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
- *
- * If you want to use this code in a closed source (commercial) project, you can
- * purchase a proprietary commercial license. Please contact us at
- * to get commercial licensing options.
- */
-import typescriptEslintEslintPlugin from "@typescript-eslint/eslint-plugin";
-import tsParser from "@typescript-eslint/parser";
-import path from "node:path";
-import { fileURLToPath } from "node:url";
-import js from "@eslint/js";
-import { FlatCompat } from "@eslint/eslintrc";
-
-const __filename = fileURLToPath(import.meta.url);
-const __dirname = path.dirname(__filename);
-const compat = new FlatCompat({
- baseDirectory: __dirname,
- recommendedConfig: js.configs.recommended,
- allConfig: js.configs.all
-});
-
-export default [...compat.extends(
- "plugin:@typescript-eslint/recommended",
- "plugin:@typescript-eslint/recommended-requiring-type-checking",
-), {
- plugins: {
- "@typescript-eslint": typescriptEslintEslintPlugin,
- },
-
- languageOptions: {
- parser: tsParser,
- ecmaVersion: 5,
- sourceType: "module",
-
- parserOptions: {
- project: "tsconfig.json",
- },
- },
-
- rules: {
- "@typescript-eslint/interface-name-prefix": "off",
- "@typescript-eslint/explicit-function-return-type": ["warn"],
- "@typescript-eslint/explicit-module-boundary-types": "off",
- "@typescript-eslint/no-unsafe-member-access": "off",
- "@typescript-eslint/no-explicit-any": "off",
- "@typescript-eslint/no-unsafe-assignment": "warn",
- "@typescript-eslint/no-unsafe-argument": "warn",
- "no-trailing-spaces": "warn",
- "no-unused-vars": "off",
- "@typescript-eslint/no-unused-vars": ["warn"],
- "semi": ["warn", "always"],
-
- "no-console": ["warn", {
- allow: ["warn", "error", "info"],
- }],
-
- "no-debugger": "error",
- "arrow-parens": ["warn", "as-needed"],
-
- "quotes": ["error", "single", {
- avoidEscape: true,
- allowTemplateLiterals: true,
- }],
-
- "comma-dangle": ["warn", {
- arrays: "always-multiline",
- objects: "always-multiline",
- imports: "always-multiline",
- exports: "always-multiline",
- functions: "always-multiline",
- }],
-
- "max-len": ["warn", {
- code: 80,
- ignoreComments: true,
- ignoreRegExpLiterals: true,
- ignoreTrailingComments: true,
- ignoreTemplateLiterals: true,
- ignoreStrings: true,
- ignoreUrls: true,
- }],
- },
-}];
diff --git a/index.ts b/index.ts
index a2f8af0..755ab2c 100644
--- a/index.ts
+++ b/index.ts
@@ -21,7 +21,6 @@
* purchase a proprietary commercial license. Please contact us at
* to get commercial licensing options.
*/
-import 'reflect-metadata';
import {
IMessageQueue,
IMessageQueueConstructor,
@@ -65,8 +64,9 @@ export default class IMQ {
ClassName = `Clustered${ClassName}`;
}
- const Adapter: IMessageQueueConstructor =
- require(`${__dirname}/src/${ClassName}.js`)[ClassName];
+ const Adapter: IMessageQueueConstructor = require(
+ `${__dirname}/src/${ClassName}.js`,
+ )[ClassName];
return new Adapter(name, options, mode);
}
diff --git a/package-lock.json b/package-lock.json
index c5a9f71..0f40730 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -9,5284 +9,1297 @@
"version": "2.0.26",
"license": "GPL-3.0-only",
"dependencies": {
- "ioredis": "^5.7.0"
+ "ioredis": "^5.11.1"
},
"devDependencies": {
- "@eslint/js": "^9.33.0",
- "@types/chai": "^5.2.2",
- "@types/eslint__eslintrc": "^2.1.2",
- "@types/mocha": "^10.0.0",
"@types/mock-require": "^3.0.0",
- "@types/node": "^24.2.1",
- "@types/sinon": "^17.0.4",
- "@types/yargs": "^17.0.33",
- "@typescript-eslint/eslint-plugin": "^8.39.0",
- "@typescript-eslint/parser": "^8.39.0",
- "@typescript-eslint/typescript-estree": "^8.39.0",
- "chai": "^5.2.1",
- "coveralls-next": "^5.0.0",
- "eslint": "^9.33.0",
- "eslint-plugin-jsdoc": "^52.0.4",
- "mocha": "^11.7.1",
- "mocha-lcov-reporter": "^1.3.0",
+ "@types/node": "^24.9.1",
"mock-require": "^3.0.3",
- "nyc": "^17.1.0",
- "open": "^10.2.0",
- "reflect-metadata": "^0.2.2",
- "sinon": "^21.0.0",
- "source-map-support": "^0.5.21",
- "ts-node": "^10.9.2",
- "typedoc": "^0.28.9",
- "typescript": "^5.9.2",
- "yargs": "^18.0.0"
+ "open": "^11.0.0",
+ "oxfmt": "0.57.0",
+ "oxlint": "1.72.0",
+ "typedoc": "^0.28.20",
+ "typescript": "^6.0.3"
}
},
- "node_modules/@babel/code-frame": {
- "version": "7.27.1",
- "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz",
- "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==",
+ "node_modules/@gerrit0/mini-shiki": {
+ "version": "3.23.0",
+ "resolved": "https://registry.npmjs.org/@gerrit0/mini-shiki/-/mini-shiki-3.23.0.tgz",
+ "integrity": "sha512-bEMORlG0cqdjVyCEuU0cDQbORWX+kYCeo0kV1lbxF5bt4r7SID2l9bqsxJEM0zndaxpOUT7riCyIVEuqq/Ynxg==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@babel/helper-validator-identifier": "^7.27.1",
- "js-tokens": "^4.0.0",
- "picocolors": "^1.1.1"
- },
- "engines": {
- "node": ">=6.9.0"
+ "@shikijs/engine-oniguruma": "^3.23.0",
+ "@shikijs/langs": "^3.23.0",
+ "@shikijs/themes": "^3.23.0",
+ "@shikijs/types": "^3.23.0",
+ "@shikijs/vscode-textmate": "^10.0.2"
}
},
- "node_modules/@babel/compat-data": {
- "version": "7.28.5",
- "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.28.5.tgz",
- "integrity": "sha512-6uFXyCayocRbqhZOB+6XcuZbkMNimwfVGFji8CTZnCzOHVGvDqzvitu1re2AU5LROliz7eQPhB8CpAMvnx9EjA==",
+ "node_modules/@ioredis/commands": {
+ "version": "1.10.0",
+ "resolved": "https://registry.npmjs.org/@ioredis/commands/-/commands-1.10.0.tgz",
+ "integrity": "sha512-UmeW7z4LfctwoQ5wkhVzgq8tXkreED2xZGpX+Bg+zA+WJFZCT6c062AfCK/Dfk81xZnnwdhJCUMkitihRaoC2Q==",
+ "license": "MIT"
+ },
+ "node_modules/@oxfmt/binding-android-arm-eabi": {
+ "version": "0.57.0",
+ "resolved": "https://registry.npmjs.org/@oxfmt/binding-android-arm-eabi/-/binding-android-arm-eabi-0.57.0.tgz",
+ "integrity": "sha512-qVBsEO+KugOsCmUHcO8iqNnqc65p7PCKpCs8M66mPZ+Ri+CWbcpoQOEJBg2OTu03+0qu++NK1jj6IzvQVs0Sig==",
+ "cpu": [
+ "arm"
+ ],
"dev": true,
"license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
"engines": {
- "node": ">=6.9.0"
+ "node": "^20.19.0 || >=22.12.0"
}
},
- "node_modules/@babel/core": {
- "version": "7.28.5",
- "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.5.tgz",
- "integrity": "sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==",
+ "node_modules/@oxfmt/binding-android-arm64": {
+ "version": "0.57.0",
+ "resolved": "https://registry.npmjs.org/@oxfmt/binding-android-arm64/-/binding-android-arm64-0.57.0.tgz",
+ "integrity": "sha512-mp6PibWbao3aizijcheOeHQaYEhcUAt8pwLniYbtLfHxL/psFF0BykAwCj+s3c6qIpa8yN8keZICWrqtZ70w8g==",
+ "cpu": [
+ "arm64"
+ ],
"dev": true,
"license": "MIT",
- "dependencies": {
- "@babel/code-frame": "^7.27.1",
- "@babel/generator": "^7.28.5",
- "@babel/helper-compilation-targets": "^7.27.2",
- "@babel/helper-module-transforms": "^7.28.3",
- "@babel/helpers": "^7.28.4",
- "@babel/parser": "^7.28.5",
- "@babel/template": "^7.27.2",
- "@babel/traverse": "^7.28.5",
- "@babel/types": "^7.28.5",
- "@jridgewell/remapping": "^2.3.5",
- "convert-source-map": "^2.0.0",
- "debug": "^4.1.0",
- "gensync": "^1.0.0-beta.2",
- "json5": "^2.2.3",
- "semver": "^6.3.1"
- },
+ "optional": true,
+ "os": [
+ "android"
+ ],
"engines": {
- "node": ">=6.9.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/babel"
+ "node": "^20.19.0 || >=22.12.0"
}
},
- "node_modules/@babel/core/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,
- "license": "MIT"
- },
- "node_modules/@babel/core/node_modules/semver": {
- "version": "6.3.1",
- "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
- "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
+ "node_modules/@oxfmt/binding-darwin-arm64": {
+ "version": "0.57.0",
+ "resolved": "https://registry.npmjs.org/@oxfmt/binding-darwin-arm64/-/binding-darwin-arm64-0.57.0.tgz",
+ "integrity": "sha512-T+0stuCBqmUVY+aMIvrgXhzGhHO3sD5tNiiEcYqgSdPsnukskQqn2u5qOVD0sv1l7RLdFS5Z/f5Wi9Ktyjr3Eg==",
+ "cpu": [
+ "arm64"
+ ],
"dev": true,
- "license": "ISC",
- "bin": {
- "semver": "bin/semver.js"
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
}
},
- "node_modules/@babel/generator": {
- "version": "7.28.5",
- "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.5.tgz",
- "integrity": "sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ==",
+ "node_modules/@oxfmt/binding-darwin-x64": {
+ "version": "0.57.0",
+ "resolved": "https://registry.npmjs.org/@oxfmt/binding-darwin-x64/-/binding-darwin-x64-0.57.0.tgz",
+ "integrity": "sha512-O+3JbqWs/mCI2oi4xfhRO2IVPFJNDDEBV8Odo+ZpmsUOeKJfjXoNH7nDmBEQcDgK7NfjDIyE7kRgYSZcTLDO0A==",
+ "cpu": [
+ "x64"
+ ],
"dev": true,
"license": "MIT",
- "dependencies": {
- "@babel/parser": "^7.28.5",
- "@babel/types": "^7.28.5",
- "@jridgewell/gen-mapping": "^0.3.12",
- "@jridgewell/trace-mapping": "^0.3.28",
- "jsesc": "^3.0.2"
- },
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
"engines": {
- "node": ">=6.9.0"
+ "node": "^20.19.0 || >=22.12.0"
}
},
- "node_modules/@babel/helper-compilation-targets": {
- "version": "7.27.2",
- "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.2.tgz",
- "integrity": "sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==",
+ "node_modules/@oxfmt/binding-freebsd-x64": {
+ "version": "0.57.0",
+ "resolved": "https://registry.npmjs.org/@oxfmt/binding-freebsd-x64/-/binding-freebsd-x64-0.57.0.tgz",
+ "integrity": "sha512-pxwhxVC+JkLX9twOQ/8C/vbuOQcMZyKIDmiRDZfO7yITuVcIdZCiLRqqf4QOxb2+8FWrRXzQpm+1DBKcMpHSSQ==",
+ "cpu": [
+ "x64"
+ ],
"dev": true,
"license": "MIT",
- "dependencies": {
- "@babel/compat-data": "^7.27.2",
- "@babel/helper-validator-option": "^7.27.1",
- "browserslist": "^4.24.0",
- "lru-cache": "^5.1.1",
- "semver": "^6.3.1"
- },
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
"engines": {
- "node": ">=6.9.0"
+ "node": "^20.19.0 || >=22.12.0"
}
},
- "node_modules/@babel/helper-compilation-targets/node_modules/semver": {
- "version": "6.3.1",
- "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
- "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
+ "node_modules/@oxfmt/binding-linux-arm-gnueabihf": {
+ "version": "0.57.0",
+ "resolved": "https://registry.npmjs.org/@oxfmt/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-0.57.0.tgz",
+ "integrity": "sha512-pxBU4zH2imB/MDBfth2rOMeVxXUMjRQLCazagwLARIFH3hVlxZJBlM4nSnHXaIHJK4/qezoFCIORN6AY8Mra4A==",
+ "cpu": [
+ "arm"
+ ],
"dev": true,
- "license": "ISC",
- "bin": {
- "semver": "bin/semver.js"
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
}
},
- "node_modules/@babel/helper-globals": {
- "version": "7.28.0",
- "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz",
- "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==",
+ "node_modules/@oxfmt/binding-linux-arm-musleabihf": {
+ "version": "0.57.0",
+ "resolved": "https://registry.npmjs.org/@oxfmt/binding-linux-arm-musleabihf/-/binding-linux-arm-musleabihf-0.57.0.tgz",
+ "integrity": "sha512-JAprOzt8tycYou36ZgEw14DlRHTiN8qdtKANdV3VZIRIvTI/lh/cX13c9pJ/EnDk2GT3FASH7KvCgQ2AufAifQ==",
+ "cpu": [
+ "arm"
+ ],
"dev": true,
"license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
"engines": {
- "node": ">=6.9.0"
+ "node": "^20.19.0 || >=22.12.0"
}
},
- "node_modules/@babel/helper-module-imports": {
- "version": "7.27.1",
- "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz",
- "integrity": "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==",
+ "node_modules/@oxfmt/binding-linux-arm64-gnu": {
+ "version": "0.57.0",
+ "resolved": "https://registry.npmjs.org/@oxfmt/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-0.57.0.tgz",
+ "integrity": "sha512-ajtjaxSaj9xl4BW7REt+Cef/ttzbAq00Bq4z7JUDZEfgFXdwSjH8K9bF+IcIJzZB9lKqMfQ4eHuSFOvvlvtqOg==",
+ "cpu": [
+ "arm64"
+ ],
"dev": true,
+ "libc": [
+ "glibc"
+ ],
"license": "MIT",
- "dependencies": {
- "@babel/traverse": "^7.27.1",
- "@babel/types": "^7.27.1"
- },
+ "optional": true,
+ "os": [
+ "linux"
+ ],
"engines": {
- "node": ">=6.9.0"
+ "node": "^20.19.0 || >=22.12.0"
}
},
- "node_modules/@babel/helper-module-transforms": {
- "version": "7.28.3",
- "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.3.tgz",
- "integrity": "sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw==",
+ "node_modules/@oxfmt/binding-linux-arm64-musl": {
+ "version": "0.57.0",
+ "resolved": "https://registry.npmjs.org/@oxfmt/binding-linux-arm64-musl/-/binding-linux-arm64-musl-0.57.0.tgz",
+ "integrity": "sha512-p4Y/+RYk9Bk5WO+zHSUXAClRmZ2fbJCejMuCAsU2HhyME4jqf6Ftt/mJYEwIah1wGCBDYOB7wEGV1x5bCEZ6hA==",
+ "cpu": [
+ "arm64"
+ ],
"dev": true,
+ "libc": [
+ "musl"
+ ],
"license": "MIT",
- "dependencies": {
- "@babel/helper-module-imports": "^7.27.1",
- "@babel/helper-validator-identifier": "^7.27.1",
- "@babel/traverse": "^7.28.3"
- },
+ "optional": true,
+ "os": [
+ "linux"
+ ],
"engines": {
- "node": ">=6.9.0"
- },
- "peerDependencies": {
- "@babel/core": "^7.0.0"
+ "node": "^20.19.0 || >=22.12.0"
}
},
- "node_modules/@babel/helper-string-parser": {
- "version": "7.27.1",
- "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz",
- "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==",
+ "node_modules/@oxfmt/binding-linux-ppc64-gnu": {
+ "version": "0.57.0",
+ "resolved": "https://registry.npmjs.org/@oxfmt/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-0.57.0.tgz",
+ "integrity": "sha512-By6tRALAZsno0F4zedmtG+wdMvJiJmJoXM4d3+A9zHE4HRXLqXITwRH8mgrlcXc5yJM2g2W3riRPwTYdgemZLQ==",
+ "cpu": [
+ "ppc64"
+ ],
"dev": true,
+ "libc": [
+ "glibc"
+ ],
"license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
"engines": {
- "node": ">=6.9.0"
+ "node": "^20.19.0 || >=22.12.0"
}
},
- "node_modules/@babel/helper-validator-identifier": {
- "version": "7.28.5",
- "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz",
- "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==",
+ "node_modules/@oxfmt/binding-linux-riscv64-gnu": {
+ "version": "0.57.0",
+ "resolved": "https://registry.npmjs.org/@oxfmt/binding-linux-riscv64-gnu/-/binding-linux-riscv64-gnu-0.57.0.tgz",
+ "integrity": "sha512-skYeG+RgvyzspqVEBsEprL90OYYZfoVNqB3HcCNR6QDJyXKOzfDRT3zncnHmUaFluIlBHuY23mU1b5WGgR98hA==",
+ "cpu": [
+ "riscv64"
+ ],
"dev": true,
+ "libc": [
+ "glibc"
+ ],
"license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
"engines": {
- "node": ">=6.9.0"
+ "node": "^20.19.0 || >=22.12.0"
}
},
- "node_modules/@babel/helper-validator-option": {
- "version": "7.27.1",
- "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz",
- "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==",
+ "node_modules/@oxfmt/binding-linux-riscv64-musl": {
+ "version": "0.57.0",
+ "resolved": "https://registry.npmjs.org/@oxfmt/binding-linux-riscv64-musl/-/binding-linux-riscv64-musl-0.57.0.tgz",
+ "integrity": "sha512-FFgACrZOXAXUh5KQh2mt1CDOVOZmn+QzHP71wM9QobNwyQvoFfyAeefVUltW83g3sm7LTiH3yfFqLLVUpA5ZFQ==",
+ "cpu": [
+ "riscv64"
+ ],
"dev": true,
+ "libc": [
+ "musl"
+ ],
"license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
"engines": {
- "node": ">=6.9.0"
+ "node": "^20.19.0 || >=22.12.0"
}
},
- "node_modules/@babel/helpers": {
- "version": "7.28.4",
- "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.4.tgz",
- "integrity": "sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w==",
+ "node_modules/@oxfmt/binding-linux-s390x-gnu": {
+ "version": "0.57.0",
+ "resolved": "https://registry.npmjs.org/@oxfmt/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-0.57.0.tgz",
+ "integrity": "sha512-Nm/BAOfQeFiiKd502mZn/GAVKJwtd0RdCg17G3Wz/WSOIQmDi3+7/SZH4BHn1Ye5KvTVH3ua8WvfwLLycNIuvA==",
+ "cpu": [
+ "s390x"
+ ],
"dev": true,
+ "libc": [
+ "glibc"
+ ],
"license": "MIT",
- "dependencies": {
- "@babel/template": "^7.27.2",
- "@babel/types": "^7.28.4"
- },
+ "optional": true,
+ "os": [
+ "linux"
+ ],
"engines": {
- "node": ">=6.9.0"
+ "node": "^20.19.0 || >=22.12.0"
}
},
- "node_modules/@babel/parser": {
- "version": "7.28.5",
- "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.5.tgz",
- "integrity": "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ==",
+ "node_modules/@oxfmt/binding-linux-x64-gnu": {
+ "version": "0.57.0",
+ "resolved": "https://registry.npmjs.org/@oxfmt/binding-linux-x64-gnu/-/binding-linux-x64-gnu-0.57.0.tgz",
+ "integrity": "sha512-BiSy5Ku3mQqyxS6YIqAJgd403wEUWvI7kerfzPxc2l/txZVmZM0pSj7oDM+4bGBExowxOi7o73jEam1W0EDTZg==",
+ "cpu": [
+ "x64"
+ ],
"dev": true,
+ "libc": [
+ "glibc"
+ ],
"license": "MIT",
- "dependencies": {
- "@babel/types": "^7.28.5"
- },
- "bin": {
- "parser": "bin/babel-parser.js"
- },
+ "optional": true,
+ "os": [
+ "linux"
+ ],
"engines": {
- "node": ">=6.0.0"
+ "node": "^20.19.0 || >=22.12.0"
}
},
- "node_modules/@babel/template": {
- "version": "7.27.2",
- "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz",
- "integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==",
+ "node_modules/@oxfmt/binding-linux-x64-musl": {
+ "version": "0.57.0",
+ "resolved": "https://registry.npmjs.org/@oxfmt/binding-linux-x64-musl/-/binding-linux-x64-musl-0.57.0.tgz",
+ "integrity": "sha512-BCRkJiotz5s9afLYD2LuMvzAoDYx9H17E/YbDyu4xK7l4zHDPeny9ErSXL//i/nJyaOwRk08x4b8cgJC00+JDg==",
+ "cpu": [
+ "x64"
+ ],
"dev": true,
+ "libc": [
+ "musl"
+ ],
"license": "MIT",
- "dependencies": {
- "@babel/code-frame": "^7.27.1",
- "@babel/parser": "^7.27.2",
- "@babel/types": "^7.27.1"
- },
+ "optional": true,
+ "os": [
+ "linux"
+ ],
"engines": {
- "node": ">=6.9.0"
+ "node": "^20.19.0 || >=22.12.0"
}
},
- "node_modules/@babel/traverse": {
- "version": "7.28.5",
- "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.5.tgz",
- "integrity": "sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ==",
+ "node_modules/@oxfmt/binding-openharmony-arm64": {
+ "version": "0.57.0",
+ "resolved": "https://registry.npmjs.org/@oxfmt/binding-openharmony-arm64/-/binding-openharmony-arm64-0.57.0.tgz",
+ "integrity": "sha512-4Oaxe1qrGgXfpCJ1C/ERJ2iCtV2rN1R79ga9fsfyVHfSQRu/hVW780u2KDqZWFZ/iGTHODJji0JemxqFZ63eIQ==",
+ "cpu": [
+ "arm64"
+ ],
"dev": true,
"license": "MIT",
- "dependencies": {
- "@babel/code-frame": "^7.27.1",
- "@babel/generator": "^7.28.5",
- "@babel/helper-globals": "^7.28.0",
- "@babel/parser": "^7.28.5",
- "@babel/template": "^7.27.2",
- "@babel/types": "^7.28.5",
- "debug": "^4.3.1"
- },
+ "optional": true,
+ "os": [
+ "openharmony"
+ ],
"engines": {
- "node": ">=6.9.0"
+ "node": "^20.19.0 || >=22.12.0"
}
},
- "node_modules/@babel/types": {
- "version": "7.28.5",
- "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.5.tgz",
- "integrity": "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==",
+ "node_modules/@oxfmt/binding-win32-arm64-msvc": {
+ "version": "0.57.0",
+ "resolved": "https://registry.npmjs.org/@oxfmt/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-0.57.0.tgz",
+ "integrity": "sha512-MYLAsDnhdNsSGheLYhWgbk0vfIrlS84iQYun/y21fX6u0jj8iBtYtbpZMdiqYeuf8U12eVPUjVY2xE2NrCfJ0g==",
+ "cpu": [
+ "arm64"
+ ],
"dev": true,
"license": "MIT",
- "dependencies": {
- "@babel/helper-string-parser": "^7.27.1",
- "@babel/helper-validator-identifier": "^7.28.5"
- },
+ "optional": true,
+ "os": [
+ "win32"
+ ],
"engines": {
- "node": ">=6.9.0"
+ "node": "^20.19.0 || >=22.12.0"
}
},
- "node_modules/@cspotcode/source-map-support": {
- "version": "0.8.1",
- "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz",
- "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==",
+ "node_modules/@oxfmt/binding-win32-ia32-msvc": {
+ "version": "0.57.0",
+ "resolved": "https://registry.npmjs.org/@oxfmt/binding-win32-ia32-msvc/-/binding-win32-ia32-msvc-0.57.0.tgz",
+ "integrity": "sha512-PBwdzZALJY/jcCx2E6is0yu+cuVXeySTDmwuseD+9j0mHqlRNxwlKgsyRTBed/woPeqfVfuXfWjoq4Cx2Zt3Eg==",
+ "cpu": [
+ "ia32"
+ ],
"dev": true,
"license": "MIT",
- "dependencies": {
- "@jridgewell/trace-mapping": "0.3.9"
- },
+ "optional": true,
+ "os": [
+ "win32"
+ ],
"engines": {
- "node": ">=12"
+ "node": "^20.19.0 || >=22.12.0"
}
},
- "node_modules/@cspotcode/source-map-support/node_modules/@jridgewell/trace-mapping": {
- "version": "0.3.9",
- "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz",
- "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==",
+ "node_modules/@oxfmt/binding-win32-x64-msvc": {
+ "version": "0.57.0",
+ "resolved": "https://registry.npmjs.org/@oxfmt/binding-win32-x64-msvc/-/binding-win32-x64-msvc-0.57.0.tgz",
+ "integrity": "sha512-bQJdH9i4RRfw55jm7+8/xS7GzHLLTbHx4huhrrDxQJaJtbSDbsyOnODvP1ftT7EG0KFKAYO2S+q6AcioXODx8w==",
+ "cpu": [
+ "x64"
+ ],
"dev": true,
"license": "MIT",
- "dependencies": {
- "@jridgewell/resolve-uri": "^3.0.3",
- "@jridgewell/sourcemap-codec": "^1.4.10"
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
}
},
- "node_modules/@es-joy/jsdoccomment": {
- "version": "0.52.0",
- "resolved": "https://registry.npmjs.org/@es-joy/jsdoccomment/-/jsdoccomment-0.52.0.tgz",
- "integrity": "sha512-BXuN7BII+8AyNtn57euU2Yxo9yA/KUDNzrpXyi3pfqKmBhhysR6ZWOebFh3vyPoqA3/j1SOvGgucElMGwlXing==",
+ "node_modules/@oxlint/binding-android-arm-eabi": {
+ "version": "1.72.0",
+ "resolved": "https://registry.npmjs.org/@oxlint/binding-android-arm-eabi/-/binding-android-arm-eabi-1.72.0.tgz",
+ "integrity": "sha512-zhCmvn+1Mj3UchAc/90i99S0t7jJUsHmFVSPg4UWrjO8b8eaSGwscgO6QAUtvHBstkjQwBttQNswEnAF1mIQdA==",
+ "cpu": [
+ "arm"
+ ],
"dev": true,
"license": "MIT",
- "dependencies": {
- "@types/estree": "^1.0.8",
- "@typescript-eslint/types": "^8.34.1",
- "comment-parser": "1.4.1",
- "esquery": "^1.6.0",
- "jsdoc-type-pratt-parser": "~4.1.0"
- },
+ "optional": true,
+ "os": [
+ "android"
+ ],
"engines": {
- "node": ">=20.11.0"
+ "node": "^20.19.0 || >=22.12.0"
}
},
- "node_modules/@eslint-community/eslint-utils": {
- "version": "4.9.0",
- "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.0.tgz",
- "integrity": "sha512-ayVFHdtZ+hsq1t2Dy24wCmGXGe4q9Gu3smhLYALJrr473ZH27MsnSL+LKUlimp4BWJqMDMLmPpx/Q9R3OAlL4g==",
+ "node_modules/@oxlint/binding-android-arm64": {
+ "version": "1.72.0",
+ "resolved": "https://registry.npmjs.org/@oxlint/binding-android-arm64/-/binding-android-arm64-1.72.0.tgz",
+ "integrity": "sha512-mtH+aY/ozv1eZoCUC2owjFAtyNBKHpJHygKeEu9zXXnQGW1Q2/qOpvx+I+Lf23+TvTz66F4iiXUbl2cGvoLPCQ==",
+ "cpu": [
+ "arm64"
+ ],
"dev": true,
"license": "MIT",
- "dependencies": {
- "eslint-visitor-keys": "^3.4.3"
- },
+ "optional": true,
+ "os": [
+ "android"
+ ],
"engines": {
- "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
- },
- "funding": {
- "url": "https://opencollective.com/eslint"
- },
- "peerDependencies": {
- "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0"
+ "node": "^20.19.0 || >=22.12.0"
}
},
- "node_modules/@eslint-community/regexpp": {
- "version": "4.12.2",
- "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz",
- "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==",
+ "node_modules/@oxlint/binding-darwin-arm64": {
+ "version": "1.72.0",
+ "resolved": "https://registry.npmjs.org/@oxlint/binding-darwin-arm64/-/binding-darwin-arm64-1.72.0.tgz",
+ "integrity": "sha512-EvnajNPDtfknB3ZieeOOyDTwJn9QXDiwfnF4ZDQqART6RG6hjY4WigQcZdGoK2dkB3e1vrmEzN9aYbQCUkh/gQ==",
+ "cpu": [
+ "arm64"
+ ],
"dev": true,
"license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
"engines": {
- "node": "^12.0.0 || ^14.0.0 || >=16.0.0"
+ "node": "^20.19.0 || >=22.12.0"
}
},
- "node_modules/@eslint/config-array": {
- "version": "0.21.1",
- "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.1.tgz",
- "integrity": "sha512-aw1gNayWpdI/jSYVgzN5pL0cfzU02GT3NBpeT/DXbx1/1x7ZKxFPd9bwrzygx/qiwIQiJ1sw/zD8qY/kRvlGHA==",
+ "node_modules/@oxlint/binding-darwin-x64": {
+ "version": "1.72.0",
+ "resolved": "https://registry.npmjs.org/@oxlint/binding-darwin-x64/-/binding-darwin-x64-1.72.0.tgz",
+ "integrity": "sha512-ZkCdEa/G80A7vEHfeCDz/+L3m33DE73v32mDKhgOIgz8Uwf0DFcK7+uu6qC+7LEhmz5fpOe1osWKyjSNMydFIQ==",
+ "cpu": [
+ "x64"
+ ],
"dev": true,
- "license": "Apache-2.0",
- "dependencies": {
- "@eslint/object-schema": "^2.1.7",
- "debug": "^4.3.1",
- "minimatch": "^3.1.2"
- },
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
"engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ "node": "^20.19.0 || >=22.12.0"
}
},
- "node_modules/@eslint/config-array/node_modules/brace-expansion": {
- "version": "1.1.12",
- "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
- "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
+ "node_modules/@oxlint/binding-freebsd-x64": {
+ "version": "1.72.0",
+ "resolved": "https://registry.npmjs.org/@oxlint/binding-freebsd-x64/-/binding-freebsd-x64-1.72.0.tgz",
+ "integrity": "sha512-NroXv2vh+sxVY1uya/rM5pjhx1hm8BzlYpx9q67QP0Xhw5MH2bf5GJylpvLEC+781p1Xli/317EoV9AlGwViag==",
+ "cpu": [
+ "x64"
+ ],
"dev": true,
"license": "MIT",
- "dependencies": {
- "balanced-match": "^1.0.0",
- "concat-map": "0.0.1"
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
}
},
- "node_modules/@eslint/config-array/node_modules/minimatch": {
- "version": "3.1.2",
- "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
- "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
+ "node_modules/@oxlint/binding-linux-arm-gnueabihf": {
+ "version": "1.72.0",
+ "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.72.0.tgz",
+ "integrity": "sha512-0NDywYgfj279Ou/BcQuCYSj7NJwBfmWn5qc5uGO/Ny7fUWmXyIpvawqX/8acQlWG6IXelJsJhj+JAy6sjsKj0A==",
+ "cpu": [
+ "arm"
+ ],
"dev": true,
- "license": "ISC",
- "dependencies": {
- "brace-expansion": "^1.1.7"
- },
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
"engines": {
- "node": "*"
+ "node": "^20.19.0 || >=22.12.0"
}
},
- "node_modules/@eslint/config-helpers": {
- "version": "0.4.1",
- "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.4.1.tgz",
- "integrity": "sha512-csZAzkNhsgwb0I/UAV6/RGFTbiakPCf0ZrGmrIxQpYvGZ00PhTkSnyKNolphgIvmnJeGw6rcGVEXfTzUnFuEvw==",
+ "node_modules/@oxlint/binding-linux-arm-musleabihf": {
+ "version": "1.72.0",
+ "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-arm-musleabihf/-/binding-linux-arm-musleabihf-1.72.0.tgz",
+ "integrity": "sha512-4vpXB06h65Ezsy4hRyrGjGrfa1SkVPii09yaajiYhmVpgsFiLD+KNxIx/BNAY+XiO+i1yqp9HHdwqM8VTqa5XQ==",
+ "cpu": [
+ "arm"
+ ],
"dev": true,
- "license": "Apache-2.0",
- "dependencies": {
- "@eslint/core": "^0.16.0"
- },
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
"engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ "node": "^20.19.0 || >=22.12.0"
}
},
- "node_modules/@eslint/core": {
- "version": "0.16.0",
- "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.16.0.tgz",
- "integrity": "sha512-nmC8/totwobIiFcGkDza3GIKfAw1+hLiYVrh3I1nIomQ8PEr5cxg34jnkmGawul/ep52wGRAcyeDCNtWKSOj4Q==",
+ "node_modules/@oxlint/binding-linux-arm64-gnu": {
+ "version": "1.72.0",
+ "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.72.0.tgz",
+ "integrity": "sha512-immaN4g2ZGFiOkKrvRX9LvzZdd2GkQM5wR+UyzYyUuyhUTXGQ4HKUJH18xp4G8OfhCVaVAJfKZxwE1r8+4hhaQ==",
+ "cpu": [
+ "arm64"
+ ],
"dev": true,
- "license": "Apache-2.0",
- "dependencies": {
- "@types/json-schema": "^7.0.15"
- },
+ "libc": [
+ "glibc"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
"engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ "node": "^20.19.0 || >=22.12.0"
}
},
- "node_modules/@eslint/eslintrc": {
- "version": "3.3.1",
- "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.1.tgz",
- "integrity": "sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ==",
+ "node_modules/@oxlint/binding-linux-arm64-musl": {
+ "version": "1.72.0",
+ "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.72.0.tgz",
+ "integrity": "sha512-JGHS9Mnr7iWyyLDxgCv1MhzVpAckgptg00F2gnxt/GD7lQ2SW1BRcxHqhSTaSdDpjWRrBkBxMMh4+Hn3aVtExg==",
+ "cpu": [
+ "arm64"
+ ],
"dev": true,
+ "libc": [
+ "musl"
+ ],
"license": "MIT",
- "dependencies": {
- "ajv": "^6.12.4",
- "debug": "^4.3.2",
- "espree": "^10.0.1",
- "globals": "^14.0.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"
- },
+ "optional": true,
+ "os": [
+ "linux"
+ ],
"engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
- },
- "funding": {
- "url": "https://opencollective.com/eslint"
+ "node": "^20.19.0 || >=22.12.0"
}
},
- "node_modules/@eslint/eslintrc/node_modules/brace-expansion": {
- "version": "1.1.12",
- "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
- "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
+ "node_modules/@oxlint/binding-linux-ppc64-gnu": {
+ "version": "1.72.0",
+ "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.72.0.tgz",
+ "integrity": "sha512-AOYgBZqxNshrg83P9v0RYv+m8s10Cqkj4/PxXFDhcS3k7FqsIG5+CxErshZCIN7G8iy4Y+VGfAsuEdar8AcbBg==",
+ "cpu": [
+ "ppc64"
+ ],
"dev": true,
+ "libc": [
+ "glibc"
+ ],
"license": "MIT",
- "dependencies": {
- "balanced-match": "^1.0.0",
- "concat-map": "0.0.1"
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
}
},
- "node_modules/@eslint/eslintrc/node_modules/ignore": {
- "version": "5.3.2",
- "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz",
- "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==",
+ "node_modules/@oxlint/binding-linux-riscv64-gnu": {
+ "version": "1.72.0",
+ "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-riscv64-gnu/-/binding-linux-riscv64-gnu-1.72.0.tgz",
+ "integrity": "sha512-QMybPS5ij3/vrKG67mqzHwW++91sYxK/PPUVi6SBtNCEzW4niS52fVBdXbQ6nou0wWbUPEpx8Sl/ZjtgE3clXA==",
+ "cpu": [
+ "riscv64"
+ ],
"dev": true,
+ "libc": [
+ "glibc"
+ ],
"license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
"engines": {
- "node": ">= 4"
+ "node": "^20.19.0 || >=22.12.0"
}
},
- "node_modules/@eslint/eslintrc/node_modules/minimatch": {
- "version": "3.1.2",
- "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
- "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
+ "node_modules/@oxlint/binding-linux-riscv64-musl": {
+ "version": "1.72.0",
+ "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-riscv64-musl/-/binding-linux-riscv64-musl-1.72.0.tgz",
+ "integrity": "sha512-gOc3W7JV0PXRpIL7stUlLe3Wa9Gp0Kdlup87IT3gHDvPKck2xNgMIl/Gs2lldYY2lyXZDC4rWi3hmoLUobkgbQ==",
+ "cpu": [
+ "riscv64"
+ ],
"dev": true,
- "license": "ISC",
- "dependencies": {
- "brace-expansion": "^1.1.7"
- },
+ "libc": [
+ "musl"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
"engines": {
- "node": "*"
+ "node": "^20.19.0 || >=22.12.0"
}
},
- "node_modules/@eslint/js": {
- "version": "9.38.0",
- "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.38.0.tgz",
- "integrity": "sha512-UZ1VpFvXf9J06YG9xQBdnzU+kthors6KjhMAl6f4gH4usHyh31rUf2DLGInT8RFYIReYXNSydgPY0V2LuWgl7A==",
+ "node_modules/@oxlint/binding-linux-s390x-gnu": {
+ "version": "1.72.0",
+ "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.72.0.tgz",
+ "integrity": "sha512-rpGxph+FjjHcYI5q6uxB3Az+tnfmEnDbSA8+PK9ZE/VzyUAkvBOMeuY7ZQMhu5mpZH7YQDsTdW6Cx4kV/msc6w==",
+ "cpu": [
+ "s390x"
+ ],
"dev": true,
+ "libc": [
+ "glibc"
+ ],
"license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
"engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
- },
- "funding": {
- "url": "https://eslint.org/donate"
+ "node": "^20.19.0 || >=22.12.0"
}
},
- "node_modules/@eslint/object-schema": {
- "version": "2.1.7",
- "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.7.tgz",
- "integrity": "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==",
+ "node_modules/@oxlint/binding-linux-x64-gnu": {
+ "version": "1.72.0",
+ "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.72.0.tgz",
+ "integrity": "sha512-WND+uhf/Ko13SLqQMWQUgsZuLvYYEvL0ZKgg0tgGYfLqxG7l8Ju123fHDMJyYSDl5E3bUbpFUuii/OvMreFQzw==",
+ "cpu": [
+ "x64"
+ ],
"dev": true,
- "license": "Apache-2.0",
+ "libc": [
+ "glibc"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
"engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ "node": "^20.19.0 || >=22.12.0"
}
},
- "node_modules/@eslint/plugin-kit": {
- "version": "0.4.0",
- "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.4.0.tgz",
- "integrity": "sha512-sB5uyeq+dwCWyPi31B2gQlVlo+j5brPlWx4yZBrEaRo/nhdDE8Xke1gsGgtiBdaBTxuTkceLVuVt/pclrasb0A==",
+ "node_modules/@oxlint/binding-linux-x64-musl": {
+ "version": "1.72.0",
+ "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-x64-musl/-/binding-linux-x64-musl-1.72.0.tgz",
+ "integrity": "sha512-SrpbrUL70nG9vh6zP4/oKHWgLuHquwsr7MW9XOn0olBVgh10Uqr8qscKhQoBGEn6olK/IUpn5GSKcdQ5AjUhGA==",
+ "cpu": [
+ "x64"
+ ],
"dev": true,
- "license": "Apache-2.0",
- "dependencies": {
- "@eslint/core": "^0.16.0",
- "levn": "^0.4.1"
- },
+ "libc": [
+ "musl"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
"engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ "node": "^20.19.0 || >=22.12.0"
}
},
- "node_modules/@gerrit0/mini-shiki": {
- "version": "3.14.0",
- "resolved": "https://registry.npmjs.org/@gerrit0/mini-shiki/-/mini-shiki-3.14.0.tgz",
- "integrity": "sha512-c5X8fwPLOtUS8TVdqhynz9iV0GlOtFUT1ppXYzUUlEXe4kbZ/mvMT8wXoT8kCwUka+zsiloq7sD3pZ3+QVTuNQ==",
+ "node_modules/@oxlint/binding-openharmony-arm64": {
+ "version": "1.72.0",
+ "resolved": "https://registry.npmjs.org/@oxlint/binding-openharmony-arm64/-/binding-openharmony-arm64-1.72.0.tgz",
+ "integrity": "sha512-qkrsEn6NmgFKr7U/QnezQMb+q/vzAy0Dd9Y95gQGQTyjzDLN+HRZMuM5u70iyH4nBLCfKBzhjMsYCehKay2jyg==",
+ "cpu": [
+ "arm64"
+ ],
"dev": true,
"license": "MIT",
- "dependencies": {
- "@shikijs/engine-oniguruma": "^3.14.0",
- "@shikijs/langs": "^3.14.0",
- "@shikijs/themes": "^3.14.0",
- "@shikijs/types": "^3.14.0",
- "@shikijs/vscode-textmate": "^10.0.2"
+ "optional": true,
+ "os": [
+ "openharmony"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
}
},
- "node_modules/@humanfs/core": {
- "version": "0.19.1",
- "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz",
- "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==",
+ "node_modules/@oxlint/binding-win32-arm64-msvc": {
+ "version": "1.72.0",
+ "resolved": "https://registry.npmjs.org/@oxlint/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.72.0.tgz",
+ "integrity": "sha512-LWR6ZlFZph+KPjXv8opgZsXRDCdrdQe8VL8Cg9zxCoBS73h6znzZpydVgmdnwj8mB9AuSM5jxEgDJDpQkjboeg==",
+ "cpu": [
+ "arm64"
+ ],
"dev": true,
- "license": "Apache-2.0",
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
"engines": {
- "node": ">=18.18.0"
+ "node": "^20.19.0 || >=22.12.0"
}
},
- "node_modules/@humanfs/node": {
- "version": "0.16.7",
- "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.7.tgz",
- "integrity": "sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==",
+ "node_modules/@oxlint/binding-win32-ia32-msvc": {
+ "version": "1.72.0",
+ "resolved": "https://registry.npmjs.org/@oxlint/binding-win32-ia32-msvc/-/binding-win32-ia32-msvc-1.72.0.tgz",
+ "integrity": "sha512-yt6HEh7IsHvtjRWtmeZRX134eaXKHq5Gnqlf1xBJdJl1JtdoRUEJw3nAxpZoUDS860cX/foKbztO441anVBtVQ==",
+ "cpu": [
+ "ia32"
+ ],
"dev": true,
- "license": "Apache-2.0",
- "dependencies": {
- "@humanfs/core": "^0.19.1",
- "@humanwhocodes/retry": "^0.4.0"
- },
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
"engines": {
- "node": ">=18.18.0"
+ "node": "^20.19.0 || >=22.12.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==",
+ "node_modules/@oxlint/binding-win32-x64-msvc": {
+ "version": "1.72.0",
+ "resolved": "https://registry.npmjs.org/@oxlint/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.72.0.tgz",
+ "integrity": "sha512-b2eKFD2hX7tIwmo/cyH6TDq8vzWRZ2qNHrzoGntUTmq0h3zQh/uX3eTSHCwI8OB/ADQfJCRelLItK8BsxuucDA==",
+ "cpu": [
+ "x64"
+ ],
"dev": true,
- "license": "Apache-2.0",
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
"engines": {
- "node": ">=12.22"
- },
- "funding": {
- "type": "github",
- "url": "https://github.com/sponsors/nzakas"
+ "node": "^20.19.0 || >=22.12.0"
}
},
- "node_modules/@humanwhocodes/retry": {
- "version": "0.4.3",
- "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz",
- "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==",
+ "node_modules/@shikijs/engine-oniguruma": {
+ "version": "3.23.0",
+ "resolved": "https://registry.npmjs.org/@shikijs/engine-oniguruma/-/engine-oniguruma-3.23.0.tgz",
+ "integrity": "sha512-1nWINwKXxKKLqPibT5f4pAFLej9oZzQTsby8942OTlsJzOBZ0MWKiwzMsd+jhzu8YPCHAswGnnN1YtQfirL35g==",
"dev": true,
- "license": "Apache-2.0",
- "engines": {
- "node": ">=18.18"
- },
- "funding": {
- "type": "github",
- "url": "https://github.com/sponsors/nzakas"
+ "license": "MIT",
+ "dependencies": {
+ "@shikijs/types": "3.23.0",
+ "@shikijs/vscode-textmate": "^10.0.2"
}
},
- "node_modules/@ioredis/commands": {
- "version": "1.4.0",
- "resolved": "https://registry.npmjs.org/@ioredis/commands/-/commands-1.4.0.tgz",
- "integrity": "sha512-aFT2yemJJo+TZCmieA7qnYGQooOS7QfNmYrzGtsYd3g9j5iDP8AimYYAesf79ohjbLG12XxC4nG5DyEnC88AsQ==",
- "license": "MIT"
- },
- "node_modules/@isaacs/cliui": {
- "version": "8.0.2",
- "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz",
- "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==",
+ "node_modules/@shikijs/langs": {
+ "version": "3.23.0",
+ "resolved": "https://registry.npmjs.org/@shikijs/langs/-/langs-3.23.0.tgz",
+ "integrity": "sha512-2Ep4W3Re5aB1/62RSYQInK9mM3HsLeB91cHqznAJMuylqjzNVAVCMnNWRHFtcNHXsoNRayP9z1qj4Sq3nMqYXg==",
"dev": true,
- "license": "ISC",
+ "license": "MIT",
"dependencies": {
- "string-width": "^5.1.2",
- "string-width-cjs": "npm:string-width@^4.2.0",
- "strip-ansi": "^7.0.1",
- "strip-ansi-cjs": "npm:strip-ansi@^6.0.1",
- "wrap-ansi": "^8.1.0",
- "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0"
- },
- "engines": {
- "node": ">=12"
- }
- },
- "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,
- "license": "ISC",
- "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,
- "license": "MIT",
- "dependencies": {
- "sprintf-js": "~1.0.2"
- }
- },
- "node_modules/@istanbuljs/load-nyc-config/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,
- "license": "MIT",
- "dependencies": {
- "locate-path": "^5.0.0",
- "path-exists": "^4.0.0"
- },
- "engines": {
- "node": ">=8"
- }
- },
- "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,
- "license": "MIT",
- "dependencies": {
- "argparse": "^1.0.7",
- "esprima": "^4.0.0"
- },
- "bin": {
- "js-yaml": "bin/js-yaml.js"
- }
- },
- "node_modules/@istanbuljs/load-nyc-config/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,
- "license": "MIT",
- "dependencies": {
- "p-locate": "^4.1.0"
- },
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/@istanbuljs/load-nyc-config/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,
- "license": "MIT",
- "dependencies": {
- "p-try": "^2.0.0"
- },
- "engines": {
- "node": ">=6"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/@istanbuljs/load-nyc-config/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,
- "license": "MIT",
- "dependencies": {
- "p-limit": "^2.2.0"
- },
- "engines": {
- "node": ">=8"
- }
- },
- "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,
- "license": "MIT",
- "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,
- "license": "MIT",
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/@jridgewell/gen-mapping": {
- "version": "0.3.13",
- "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz",
- "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@jridgewell/sourcemap-codec": "^1.5.0",
- "@jridgewell/trace-mapping": "^0.3.24"
- }
- },
- "node_modules/@jridgewell/remapping": {
- "version": "2.3.5",
- "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz",
- "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@jridgewell/gen-mapping": "^0.3.5",
- "@jridgewell/trace-mapping": "^0.3.24"
- }
- },
- "node_modules/@jridgewell/resolve-uri": {
- "version": "3.1.2",
- "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
- "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=6.0.0"
- }
- },
- "node_modules/@jridgewell/sourcemap-codec": {
- "version": "1.5.5",
- "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz",
- "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/@jridgewell/trace-mapping": {
- "version": "0.3.31",
- "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz",
- "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==",
- "dev": true,
- "license": "MIT",
- "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,
- "license": "MIT",
- "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,
- "license": "MIT",
- "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,
- "license": "MIT",
- "dependencies": {
- "@nodelib/fs.scandir": "2.1.5",
- "fastq": "^1.6.0"
- },
- "engines": {
- "node": ">= 8"
- }
- },
- "node_modules/@pkgjs/parseargs": {
- "version": "0.11.0",
- "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz",
- "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==",
- "dev": true,
- "license": "MIT",
- "optional": true,
- "engines": {
- "node": ">=14"
- }
- },
- "node_modules/@shikijs/engine-oniguruma": {
- "version": "3.14.0",
- "resolved": "https://registry.npmjs.org/@shikijs/engine-oniguruma/-/engine-oniguruma-3.14.0.tgz",
- "integrity": "sha512-TNcYTYMbJyy+ZjzWtt0bG5y4YyMIWC2nyePz+CFMWqm+HnZZyy9SWMgo8Z6KBJVIZnx8XUXS8U2afO6Y0g1Oug==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@shikijs/types": "3.14.0",
- "@shikijs/vscode-textmate": "^10.0.2"
- }
- },
- "node_modules/@shikijs/langs": {
- "version": "3.14.0",
- "resolved": "https://registry.npmjs.org/@shikijs/langs/-/langs-3.14.0.tgz",
- "integrity": "sha512-DIB2EQY7yPX1/ZH7lMcwrK5pl+ZkP/xoSpUzg9YC8R+evRCCiSQ7yyrvEyBsMnfZq4eBzLzBlugMyTAf13+pzg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@shikijs/types": "3.14.0"
+ "@shikijs/types": "3.23.0"
}
},
"node_modules/@shikijs/themes": {
- "version": "3.14.0",
- "resolved": "https://registry.npmjs.org/@shikijs/themes/-/themes-3.14.0.tgz",
- "integrity": "sha512-fAo/OnfWckNmv4uBoUu6dSlkcBc+SA1xzj5oUSaz5z3KqHtEbUypg/9xxgJARtM6+7RVm0Q6Xnty41xA1ma1IA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@shikijs/types": "3.14.0"
- }
- },
- "node_modules/@shikijs/types": {
- "version": "3.14.0",
- "resolved": "https://registry.npmjs.org/@shikijs/types/-/types-3.14.0.tgz",
- "integrity": "sha512-bQGgC6vrY8U/9ObG1Z/vTro+uclbjjD/uG58RvfxKZVD5p9Yc1ka3tVyEFy7BNJLzxuWyHH5NWynP9zZZS59eQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@shikijs/vscode-textmate": "^10.0.2",
- "@types/hast": "^3.0.4"
- }
- },
- "node_modules/@shikijs/vscode-textmate": {
- "version": "10.0.2",
- "resolved": "https://registry.npmjs.org/@shikijs/vscode-textmate/-/vscode-textmate-10.0.2.tgz",
- "integrity": "sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/@sinonjs/commons": {
- "version": "3.0.1",
- "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.1.tgz",
- "integrity": "sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==",
- "dev": true,
- "license": "BSD-3-Clause",
- "dependencies": {
- "type-detect": "4.0.8"
- }
- },
- "node_modules/@sinonjs/fake-timers": {
- "version": "13.0.5",
- "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-13.0.5.tgz",
- "integrity": "sha512-36/hTbH2uaWuGVERyC6da9YwGWnzUZXuPro/F2LfsdOsLnCojz/iSH8MxUt/FD2S5XBSVPhmArFUXcpCQ2Hkiw==",
- "dev": true,
- "license": "BSD-3-Clause",
- "dependencies": {
- "@sinonjs/commons": "^3.0.1"
- }
- },
- "node_modules/@sinonjs/samsam": {
- "version": "8.0.3",
- "resolved": "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-8.0.3.tgz",
- "integrity": "sha512-hw6HbX+GyVZzmaYNh82Ecj1vdGZrqVIn/keDTg63IgAwiQPO+xCz99uG6Woqgb4tM0mUiFENKZ4cqd7IX94AXQ==",
- "dev": true,
- "license": "BSD-3-Clause",
- "dependencies": {
- "@sinonjs/commons": "^3.0.1",
- "type-detect": "^4.1.0"
- }
- },
- "node_modules/@sinonjs/samsam/node_modules/type-detect": {
- "version": "4.1.0",
- "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.1.0.tgz",
- "integrity": "sha512-Acylog8/luQ8L7il+geoSxhEkazvkslg7PSNKOX59mbB9cOveP5aq9h74Y7YU8yDpJwetzQQrfIwtf4Wp4LKcw==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=4"
- }
- },
- "node_modules/@tsconfig/node10": {
- "version": "1.0.11",
- "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.11.tgz",
- "integrity": "sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/@tsconfig/node12": {
- "version": "1.0.11",
- "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz",
- "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/@tsconfig/node14": {
- "version": "1.0.3",
- "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz",
- "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/@tsconfig/node16": {
- "version": "1.0.4",
- "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz",
- "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/@types/chai": {
- "version": "5.2.3",
- "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz",
- "integrity": "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@types/deep-eql": "*",
- "assertion-error": "^2.0.1"
- }
- },
- "node_modules/@types/deep-eql": {
- "version": "4.0.2",
- "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz",
- "integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/@types/eslint": {
- "version": "9.6.1",
- "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-9.6.1.tgz",
- "integrity": "sha512-FXx2pKgId/WyYo2jXw63kk7/+TY7u7AziEJxJAnSFzHlqTAS3Ync6SvgYAN/k4/PQpnnVuzoMuVnByKK2qp0ag==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@types/estree": "*",
- "@types/json-schema": "*"
- }
- },
- "node_modules/@types/eslint__eslintrc": {
- "version": "2.1.2",
- "resolved": "https://registry.npmjs.org/@types/eslint__eslintrc/-/eslint__eslintrc-2.1.2.tgz",
- "integrity": "sha512-qXvzPFY7Rz05xD8ZApXJ3S8xStQD2Ibzu3EFIF0UMNOAfLY5xUu3H61q0JrHo2OXD6rcFG75yUxNQbkKtFKBSw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@types/eslint": "*"
- }
- },
- "node_modules/@types/estree": {
- "version": "1.0.8",
- "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz",
- "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/@types/hast": {
- "version": "3.0.4",
- "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz",
- "integrity": "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@types/unist": "*"
- }
- },
- "node_modules/@types/json-schema": {
- "version": "7.0.15",
- "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz",
- "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/@types/mocha": {
- "version": "10.0.10",
- "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-10.0.10.tgz",
- "integrity": "sha512-xPyYSz1cMPnJQhl0CLMH68j3gprKZaTjG3s5Vi+fDgx+uhG9NOXwbVt52eFS8ECyXhyKcjDLCBEqBExKuiZb7Q==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/@types/mock-require": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/@types/mock-require/-/mock-require-3.0.0.tgz",
- "integrity": "sha512-TZ/s3ufaF+kEFPx9PqXKNd7OsPJoVsgOd59EA8XQLTRjJCsOdx5sEAOGhyrZ3Os78iaDKyGJWdQWyYWRg1Iyvg==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/@types/node": {
- "version": "24.9.1",
- "resolved": "https://registry.npmjs.org/@types/node/-/node-24.9.1.tgz",
- "integrity": "sha512-QoiaXANRkSXK6p0Duvt56W208du4P9Uye9hWLWgGMDTEoKPhuenzNcC4vGUmrNkiOKTlIrBoyNQYNpSwfEZXSg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "undici-types": "~7.16.0"
- }
- },
- "node_modules/@types/sinon": {
- "version": "17.0.4",
- "resolved": "https://registry.npmjs.org/@types/sinon/-/sinon-17.0.4.tgz",
- "integrity": "sha512-RHnIrhfPO3+tJT0s7cFaXGZvsL4bbR3/k7z3P312qMS4JaS2Tk+KiwiLx1S0rQ56ERj00u1/BtdyVd0FY+Pdew==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@types/sinonjs__fake-timers": "*"
- }
- },
- "node_modules/@types/sinonjs__fake-timers": {
- "version": "15.0.1",
- "resolved": "https://registry.npmjs.org/@types/sinonjs__fake-timers/-/sinonjs__fake-timers-15.0.1.tgz",
- "integrity": "sha512-Ko2tjWJq8oozHzHV+reuvS5KYIRAokHnGbDwGh/J64LntgpbuylF74ipEL24HCyRjf9FOlBiBHWBR1RlVKsI1w==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/@types/unist": {
- "version": "3.0.3",
- "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz",
- "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/@types/yargs": {
- "version": "17.0.34",
- "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.34.tgz",
- "integrity": "sha512-KExbHVa92aJpw9WDQvzBaGVE2/Pz+pLZQloT2hjL8IqsZnV62rlPOYvNnLmf/L2dyllfVUOVBj64M0z/46eR2A==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@types/yargs-parser": "*"
- }
- },
- "node_modules/@types/yargs-parser": {
- "version": "21.0.3",
- "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.3.tgz",
- "integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/@typescript-eslint/eslint-plugin": {
- "version": "8.46.2",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.46.2.tgz",
- "integrity": "sha512-ZGBMToy857/NIPaaCucIUQgqueOiq7HeAKkhlvqVV4lm089zUFW6ikRySx2v+cAhKeUCPuWVHeimyk6Dw1iY3w==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@eslint-community/regexpp": "^4.10.0",
- "@typescript-eslint/scope-manager": "8.46.2",
- "@typescript-eslint/type-utils": "8.46.2",
- "@typescript-eslint/utils": "8.46.2",
- "@typescript-eslint/visitor-keys": "8.46.2",
- "graphemer": "^1.4.0",
- "ignore": "^7.0.0",
- "natural-compare": "^1.4.0",
- "ts-api-utils": "^2.1.0"
- },
- "engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/typescript-eslint"
- },
- "peerDependencies": {
- "@typescript-eslint/parser": "^8.46.2",
- "eslint": "^8.57.0 || ^9.0.0",
- "typescript": ">=4.8.4 <6.0.0"
- }
- },
- "node_modules/@typescript-eslint/parser": {
- "version": "8.46.2",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.46.2.tgz",
- "integrity": "sha512-BnOroVl1SgrPLywqxyqdJ4l3S2MsKVLDVxZvjI1Eoe8ev2r3kGDo+PcMihNmDE+6/KjkTubSJnmqGZZjQSBq/g==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@typescript-eslint/scope-manager": "8.46.2",
- "@typescript-eslint/types": "8.46.2",
- "@typescript-eslint/typescript-estree": "8.46.2",
- "@typescript-eslint/visitor-keys": "8.46.2",
- "debug": "^4.3.4"
- },
- "engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/typescript-eslint"
- },
- "peerDependencies": {
- "eslint": "^8.57.0 || ^9.0.0",
- "typescript": ">=4.8.4 <6.0.0"
- }
- },
- "node_modules/@typescript-eslint/project-service": {
- "version": "8.46.2",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.46.2.tgz",
- "integrity": "sha512-PULOLZ9iqwI7hXcmL4fVfIsBi6AN9YxRc0frbvmg8f+4hQAjQ5GYNKK0DIArNo+rOKmR/iBYwkpBmnIwin4wBg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@typescript-eslint/tsconfig-utils": "^8.46.2",
- "@typescript-eslint/types": "^8.46.2",
- "debug": "^4.3.4"
- },
- "engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/typescript-eslint"
- },
- "peerDependencies": {
- "typescript": ">=4.8.4 <6.0.0"
- }
- },
- "node_modules/@typescript-eslint/scope-manager": {
- "version": "8.46.2",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.46.2.tgz",
- "integrity": "sha512-LF4b/NmGvdWEHD2H4MsHD8ny6JpiVNDzrSZr3CsckEgCbAGZbYM4Cqxvi9L+WqDMT+51Ozy7lt2M+d0JLEuBqA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@typescript-eslint/types": "8.46.2",
- "@typescript-eslint/visitor-keys": "8.46.2"
- },
- "engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/typescript-eslint"
- }
- },
- "node_modules/@typescript-eslint/tsconfig-utils": {
- "version": "8.46.2",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.46.2.tgz",
- "integrity": "sha512-a7QH6fw4S57+F5y2FIxxSDyi5M4UfGF+Jl1bCGd7+L4KsaUY80GsiF/t0UoRFDHAguKlBaACWJRmdrc6Xfkkag==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/typescript-eslint"
- },
- "peerDependencies": {
- "typescript": ">=4.8.4 <6.0.0"
- }
- },
- "node_modules/@typescript-eslint/type-utils": {
- "version": "8.46.2",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.46.2.tgz",
- "integrity": "sha512-HbPM4LbaAAt/DjxXaG9yiS9brOOz6fabal4uvUmaUYe6l3K1phQDMQKBRUrr06BQkxkvIZVVHttqiybM9nJsLA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@typescript-eslint/types": "8.46.2",
- "@typescript-eslint/typescript-estree": "8.46.2",
- "@typescript-eslint/utils": "8.46.2",
- "debug": "^4.3.4",
- "ts-api-utils": "^2.1.0"
- },
- "engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/typescript-eslint"
- },
- "peerDependencies": {
- "eslint": "^8.57.0 || ^9.0.0",
- "typescript": ">=4.8.4 <6.0.0"
- }
- },
- "node_modules/@typescript-eslint/types": {
- "version": "8.46.2",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.46.2.tgz",
- "integrity": "sha512-lNCWCbq7rpg7qDsQrd3D6NyWYu+gkTENkG5IKYhUIcxSb59SQC/hEQ+MrG4sTgBVghTonNWq42bA/d4yYumldQ==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/typescript-eslint"
- }
- },
- "node_modules/@typescript-eslint/typescript-estree": {
- "version": "8.46.2",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.46.2.tgz",
- "integrity": "sha512-f7rW7LJ2b7Uh2EiQ+7sza6RDZnajbNbemn54Ob6fRwQbgcIn+GWfyuHDHRYgRoZu1P4AayVScrRW+YfbTvPQoQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@typescript-eslint/project-service": "8.46.2",
- "@typescript-eslint/tsconfig-utils": "8.46.2",
- "@typescript-eslint/types": "8.46.2",
- "@typescript-eslint/visitor-keys": "8.46.2",
- "debug": "^4.3.4",
- "fast-glob": "^3.3.2",
- "is-glob": "^4.0.3",
- "minimatch": "^9.0.4",
- "semver": "^7.6.0",
- "ts-api-utils": "^2.1.0"
- },
- "engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/typescript-eslint"
- },
- "peerDependencies": {
- "typescript": ">=4.8.4 <6.0.0"
- }
- },
- "node_modules/@typescript-eslint/utils": {
- "version": "8.46.2",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.46.2.tgz",
- "integrity": "sha512-sExxzucx0Tud5tE0XqR0lT0psBQvEpnpiul9XbGUB1QwpWJJAps1O/Z7hJxLGiZLBKMCutjTzDgmd1muEhBnVg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@eslint-community/eslint-utils": "^4.7.0",
- "@typescript-eslint/scope-manager": "8.46.2",
- "@typescript-eslint/types": "8.46.2",
- "@typescript-eslint/typescript-estree": "8.46.2"
- },
- "engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/typescript-eslint"
- },
- "peerDependencies": {
- "eslint": "^8.57.0 || ^9.0.0",
- "typescript": ">=4.8.4 <6.0.0"
- }
- },
- "node_modules/@typescript-eslint/visitor-keys": {
- "version": "8.46.2",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.46.2.tgz",
- "integrity": "sha512-tUFMXI4gxzzMXt4xpGJEsBsTox0XbNQ1y94EwlD/CuZwFcQP79xfQqMhau9HsRc/J0cAPA/HZt1dZPtGn9V/7w==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@typescript-eslint/types": "8.46.2",
- "eslint-visitor-keys": "^4.2.1"
- },
- "engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/typescript-eslint"
- }
- },
- "node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": {
- "version": "4.2.1",
- "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz",
- "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==",
- "dev": true,
- "license": "Apache-2.0",
- "engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
- },
- "funding": {
- "url": "https://opencollective.com/eslint"
- }
- },
- "node_modules/acorn": {
- "version": "8.15.0",
- "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz",
- "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
- "dev": true,
- "license": "MIT",
- "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,
- "license": "MIT",
- "peerDependencies": {
- "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0"
- }
- },
- "node_modules/acorn-walk": {
- "version": "8.3.4",
- "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.4.tgz",
- "integrity": "sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "acorn": "^8.11.0"
- },
- "engines": {
- "node": ">=0.4.0"
- }
- },
- "node_modules/aggregate-error": {
- "version": "3.1.0",
- "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz",
- "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "clean-stack": "^2.0.0",
- "indent-string": "^4.0.0"
- },
- "engines": {
- "node": ">=8"
- }
- },
- "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,
- "license": "MIT",
- "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-regex": {
- "version": "6.2.2",
- "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz",
- "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=12"
- },
- "funding": {
- "url": "https://github.com/chalk/ansi-regex?sponsor=1"
- }
- },
- "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,
- "license": "MIT",
- "dependencies": {
- "color-convert": "^2.0.1"
- },
- "engines": {
- "node": ">=8"
- },
- "funding": {
- "url": "https://github.com/chalk/ansi-styles?sponsor=1"
- }
- },
- "node_modules/append-transform": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/append-transform/-/append-transform-2.0.0.tgz",
- "integrity": "sha512-7yeyCEurROLQJFv5Xj4lEGTy0borxepjFv1g22oAdqFu//SrAlDl1O1Nxx15SH1RoliUml6p8dwJW9jvZughhg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "default-require-extensions": "^3.0.0"
- },
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/archy": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/archy/-/archy-1.0.0.tgz",
- "integrity": "sha512-Xg+9RwCg/0p32teKdGMPTPnVXKD0w3DfHnFTficozsAgsvq2XenPJq/MYpzzQ/v8zrOyJn6Ds39VA4JIDwFfqw==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/are-docs-informative": {
- "version": "0.0.2",
- "resolved": "https://registry.npmjs.org/are-docs-informative/-/are-docs-informative-0.0.2.tgz",
- "integrity": "sha512-ixiS0nLNNG5jNQzgZJNoUpBKdo9yTYZMGJ+QgT2jmjR7G7+QHRCc4v6LQ3NgE7EBJq+o0ams3waJwkrlBom8Ig==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=14"
- }
- },
- "node_modules/arg": {
- "version": "4.1.3",
- "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz",
- "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==",
- "dev": true,
- "license": "MIT"
- },
- "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,
- "license": "Python-2.0"
- },
- "node_modules/assertion-error": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz",
- "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=12"
- }
- },
- "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,
- "license": "MIT"
- },
- "node_modules/baseline-browser-mapping": {
- "version": "2.8.20",
- "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.8.20.tgz",
- "integrity": "sha512-JMWsdF+O8Orq3EMukbUN1QfbLK9mX2CkUmQBcW2T0s8OmdAUL5LLM/6wFwSrqXzlXB13yhyK9gTKS1rIizOduQ==",
- "dev": true,
- "license": "Apache-2.0",
- "bin": {
- "baseline-browser-mapping": "dist/cli.js"
- }
- },
- "node_modules/brace-expansion": {
- "version": "2.0.2",
- "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz",
- "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "balanced-match": "^1.0.0"
- }
- },
- "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/browser-stdout": {
- "version": "1.3.1",
- "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz",
- "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==",
- "dev": true,
- "license": "ISC"
- },
- "node_modules/browserslist": {
- "version": "4.27.0",
- "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.27.0.tgz",
- "integrity": "sha512-AXVQwdhot1eqLihwasPElhX2tAZiBjWdJ9i/Zcj2S6QYIjkx62OKSfnobkriB81C3l4w0rVy3Nt4jaTBltYEpw==",
- "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"
- }
- ],
- "license": "MIT",
- "dependencies": {
- "baseline-browser-mapping": "^2.8.19",
- "caniuse-lite": "^1.0.30001751",
- "electron-to-chromium": "^1.5.238",
- "node-releases": "^2.0.26",
- "update-browserslist-db": "^1.1.4"
- },
- "bin": {
- "browserslist": "cli.js"
- },
- "engines": {
- "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
- }
- },
- "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,
- "license": "MIT"
- },
- "node_modules/bundle-name": {
- "version": "4.1.0",
- "resolved": "https://registry.npmjs.org/bundle-name/-/bundle-name-4.1.0.tgz",
- "integrity": "sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "run-applescript": "^7.0.0"
- },
- "engines": {
- "node": ">=18"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/caching-transform": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/caching-transform/-/caching-transform-4.0.0.tgz",
- "integrity": "sha512-kpqOvwXnjjN44D89K5ccQC+RUrsy7jB/XLlRrx0D7/2HNcTPqzsb6XgYoErwko6QsV184CA2YgS1fxDiiDZMWA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "hasha": "^5.0.0",
- "make-dir": "^3.0.0",
- "package-hash": "^4.0.0",
- "write-file-atomic": "^3.0.0"
- },
- "engines": {
- "node": ">=8"
- }
- },
- "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,
- "license": "MIT",
- "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,
- "license": "MIT",
- "engines": {
- "node": ">=6"
- }
- },
- "node_modules/caniuse-lite": {
- "version": "1.0.30001751",
- "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001751.tgz",
- "integrity": "sha512-A0QJhug0Ly64Ii3eIqHu5X51ebln3k4yTUkY1j8drqpWHVreg/VLijN48cZ1bYPiqOQuqpkIKnzr/Ul8V+p6Cw==",
- "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"
- }
- ],
- "license": "CC-BY-4.0"
- },
- "node_modules/chai": {
- "version": "5.3.3",
- "resolved": "https://registry.npmjs.org/chai/-/chai-5.3.3.tgz",
- "integrity": "sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "assertion-error": "^2.0.1",
- "check-error": "^2.1.1",
- "deep-eql": "^5.0.1",
- "loupe": "^3.1.0",
- "pathval": "^2.0.0"
- },
- "engines": {
- "node": ">=18"
- }
- },
- "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,
- "license": "MIT",
- "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/check-error": {
- "version": "2.1.1",
- "resolved": "https://registry.npmjs.org/check-error/-/check-error-2.1.1.tgz",
- "integrity": "sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">= 16"
- }
- },
- "node_modules/chokidar": {
- "version": "4.0.3",
- "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz",
- "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "readdirp": "^4.0.1"
- },
- "engines": {
- "node": ">= 14.16.0"
- },
- "funding": {
- "url": "https://paulmillr.com/funding/"
- }
- },
- "node_modules/clean-stack": {
- "version": "2.2.0",
- "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz",
- "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=6"
- }
- },
- "node_modules/cliui": {
- "version": "9.0.1",
- "resolved": "https://registry.npmjs.org/cliui/-/cliui-9.0.1.tgz",
- "integrity": "sha512-k7ndgKhwoQveBL+/1tqGJYNz097I7WOvwbmmU2AR5+magtbjPWQTS1C5vzGkBC8Ym8UWRzfKUzUUqFLypY4Q+w==",
- "dev": true,
- "license": "ISC",
- "dependencies": {
- "string-width": "^7.2.0",
- "strip-ansi": "^7.1.0",
- "wrap-ansi": "^9.0.0"
- },
- "engines": {
- "node": ">=20"
- }
- },
- "node_modules/cliui/node_modules/ansi-styles": {
- "version": "6.2.3",
- "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz",
- "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=12"
- },
- "funding": {
- "url": "https://github.com/chalk/ansi-styles?sponsor=1"
- }
- },
- "node_modules/cliui/node_modules/emoji-regex": {
- "version": "10.6.0",
- "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz",
- "integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/cliui/node_modules/string-width": {
- "version": "7.2.0",
- "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz",
- "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "emoji-regex": "^10.3.0",
- "get-east-asian-width": "^1.0.0",
- "strip-ansi": "^7.1.0"
- },
- "engines": {
- "node": ">=18"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/cliui/node_modules/wrap-ansi": {
- "version": "9.0.2",
- "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.2.tgz",
- "integrity": "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "ansi-styles": "^6.2.1",
- "string-width": "^7.0.0",
- "strip-ansi": "^7.1.0"
- },
- "engines": {
- "node": ">=18"
- },
- "funding": {
- "url": "https://github.com/chalk/wrap-ansi?sponsor=1"
- }
- },
- "node_modules/cluster-key-slot": {
- "version": "1.1.2",
- "resolved": "https://registry.npmjs.org/cluster-key-slot/-/cluster-key-slot-1.1.2.tgz",
- "integrity": "sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA==",
- "license": "Apache-2.0",
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "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,
- "license": "MIT",
- "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,
- "license": "MIT"
- },
- "node_modules/comment-parser": {
- "version": "1.4.1",
- "resolved": "https://registry.npmjs.org/comment-parser/-/comment-parser-1.4.1.tgz",
- "integrity": "sha512-buhp5kePrmda3vhc5B9t7pUQXAb2Tnd0qgpkIhPhkHXxJpiPJ11H0ZEU0oBpJ2QztSbzG/ZxMj/CHsYJqRHmyg==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">= 12.0.0"
- }
- },
- "node_modules/commondir": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz",
- "integrity": "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==",
- "dev": true,
- "license": "MIT"
- },
- "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,
- "license": "MIT"
- },
- "node_modules/convert-source-map": {
- "version": "1.9.0",
- "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz",
- "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/coveralls-next": {
- "version": "5.0.0",
- "resolved": "https://registry.npmjs.org/coveralls-next/-/coveralls-next-5.0.0.tgz",
- "integrity": "sha512-RCj6Oflf6iQtN3Q5b0SSemEbQBzeBjQlLUrc3bfNECTy83hMJA9krdNZ5GTRm7Jpbyo92yKUbQDP5FYlWcL5sA==",
- "dev": true,
- "license": "BSD-2-Clause",
- "dependencies": {
- "js-yaml": "4.1.0",
- "lcov-parse": "1.0.0",
- "log-driver": "1.2.7",
- "minimist": "1.2.8"
- },
- "bin": {
- "coveralls": "bin/coveralls.js"
- },
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/create-require": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz",
- "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/cross-spawn": {
- "version": "7.0.6",
- "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
- "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "path-key": "^3.1.0",
- "shebang-command": "^2.0.0",
- "which": "^2.0.1"
- },
- "engines": {
- "node": ">= 8"
- }
- },
- "node_modules/debug": {
- "version": "4.4.3",
- "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
- "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
- "license": "MIT",
- "dependencies": {
- "ms": "^2.1.3"
- },
- "engines": {
- "node": ">=6.0"
- },
- "peerDependenciesMeta": {
- "supports-color": {
- "optional": true
- }
- }
- },
- "node_modules/decamelize": {
- "version": "1.2.0",
- "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz",
- "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/deep-eql": {
- "version": "5.0.2",
- "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-5.0.2.tgz",
- "integrity": "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=6"
- }
- },
- "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,
- "license": "MIT"
- },
- "node_modules/default-browser": {
- "version": "5.2.1",
- "resolved": "https://registry.npmjs.org/default-browser/-/default-browser-5.2.1.tgz",
- "integrity": "sha512-WY/3TUME0x3KPYdRRxEJJvXRHV4PyPoUsxtZa78lwItwRQRHhd2U9xOscaT/YTf8uCXIAjeJOFBVEh/7FtD8Xg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "bundle-name": "^4.1.0",
- "default-browser-id": "^5.0.0"
- },
- "engines": {
- "node": ">=18"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/default-browser-id": {
- "version": "5.0.0",
- "resolved": "https://registry.npmjs.org/default-browser-id/-/default-browser-id-5.0.0.tgz",
- "integrity": "sha512-A6p/pu/6fyBcA1TRz/GqWYPViplrftcW2gZC9q79ngNCKAeR/X3gcEdXQHl4KNXV+3wgIJ1CPkJQ3IHM6lcsyA==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=18"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/default-require-extensions": {
- "version": "3.0.1",
- "resolved": "https://registry.npmjs.org/default-require-extensions/-/default-require-extensions-3.0.1.tgz",
- "integrity": "sha512-eXTJmRbm2TIt9MgWTsOH1wEuhew6XGZcMeGKCtLedIg/NCsg1iBePXkceTdK4Fii7pzmN9tGsZhKzZ4h7O/fxw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "strip-bom": "^4.0.0"
- },
- "engines": {
- "node": ">=8"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/define-lazy-prop": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-3.0.0.tgz",
- "integrity": "sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=12"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/denque": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/denque/-/denque-2.1.0.tgz",
- "integrity": "sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==",
- "license": "Apache-2.0",
- "engines": {
- "node": ">=0.10"
- }
- },
- "node_modules/diff": {
- "version": "7.0.0",
- "resolved": "https://registry.npmjs.org/diff/-/diff-7.0.0.tgz",
- "integrity": "sha512-PJWHUb1RFevKCwaFA9RlG5tCd+FO5iRh9A8HEtkmBH2Li03iJriB6m6JIN4rGz3K3JLawI7/veA1xzRKP6ISBw==",
- "dev": true,
- "license": "BSD-3-Clause",
- "engines": {
- "node": ">=0.3.1"
- }
- },
- "node_modules/eastasianwidth": {
- "version": "0.2.0",
- "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz",
- "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/electron-to-chromium": {
- "version": "1.5.241",
- "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.241.tgz",
- "integrity": "sha512-ILMvKX/ZV5WIJzzdtuHg8xquk2y0BOGlFOxBVwTpbiXqWIH0hamG45ddU4R3PQ0gYu+xgo0vdHXHli9sHIGb4w==",
- "dev": true,
- "license": "ISC"
- },
- "node_modules/emoji-regex": {
- "version": "9.2.2",
- "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz",
- "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/entities": {
- "version": "4.5.0",
- "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz",
- "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==",
- "dev": true,
- "license": "BSD-2-Clause",
- "engines": {
- "node": ">=0.12"
- },
- "funding": {
- "url": "https://github.com/fb55/entities?sponsor=1"
- }
- },
- "node_modules/es6-error": {
- "version": "4.1.1",
- "resolved": "https://registry.npmjs.org/es6-error/-/es6-error-4.1.1.tgz",
- "integrity": "sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/escalade": {
- "version": "3.2.0",
- "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz",
- "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=6"
- }
- },
- "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,
- "license": "MIT",
- "engines": {
- "node": ">=10"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/eslint": {
- "version": "9.38.0",
- "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.38.0.tgz",
- "integrity": "sha512-t5aPOpmtJcZcz5UJyY2GbvpDlsK5E8JqRqoKtfiKE3cNh437KIqfJr3A3AKf5k64NPx6d0G3dno6XDY05PqPtw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@eslint-community/eslint-utils": "^4.8.0",
- "@eslint-community/regexpp": "^4.12.1",
- "@eslint/config-array": "^0.21.1",
- "@eslint/config-helpers": "^0.4.1",
- "@eslint/core": "^0.16.0",
- "@eslint/eslintrc": "^3.3.1",
- "@eslint/js": "9.38.0",
- "@eslint/plugin-kit": "^0.4.0",
- "@humanfs/node": "^0.16.6",
- "@humanwhocodes/module-importer": "^1.0.1",
- "@humanwhocodes/retry": "^0.4.2",
- "@types/estree": "^1.0.6",
- "ajv": "^6.12.4",
- "chalk": "^4.0.0",
- "cross-spawn": "^7.0.6",
- "debug": "^4.3.2",
- "escape-string-regexp": "^4.0.0",
- "eslint-scope": "^8.4.0",
- "eslint-visitor-keys": "^4.2.1",
- "espree": "^10.4.0",
- "esquery": "^1.5.0",
- "esutils": "^2.0.2",
- "fast-deep-equal": "^3.1.3",
- "file-entry-cache": "^8.0.0",
- "find-up": "^5.0.0",
- "glob-parent": "^6.0.2",
- "ignore": "^5.2.0",
- "imurmurhash": "^0.1.4",
- "is-glob": "^4.0.0",
- "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.3"
- },
- "bin": {
- "eslint": "bin/eslint.js"
- },
- "engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
- },
- "funding": {
- "url": "https://eslint.org/donate"
- },
- "peerDependencies": {
- "jiti": "*"
- },
- "peerDependenciesMeta": {
- "jiti": {
- "optional": true
- }
- }
- },
- "node_modules/eslint-plugin-jsdoc": {
- "version": "52.0.4",
- "resolved": "https://registry.npmjs.org/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-52.0.4.tgz",
- "integrity": "sha512-be5OzGlLExvcK13Il3noU7/v7WmAQGenTmCaBKf1pwVtPOb6X+PGFVnJad0QhMj4KKf45XjE4hbsBxv25q1fTg==",
- "dev": true,
- "license": "BSD-3-Clause",
- "dependencies": {
- "@es-joy/jsdoccomment": "~0.52.0",
- "are-docs-informative": "^0.0.2",
- "comment-parser": "1.4.1",
- "debug": "^4.4.1",
- "escape-string-regexp": "^4.0.0",
- "espree": "^10.4.0",
- "esquery": "^1.6.0",
- "parse-imports-exports": "^0.2.4",
- "semver": "^7.7.2",
- "spdx-expression-parse": "^4.0.0"
- },
- "engines": {
- "node": ">=20.11.0"
- },
- "peerDependencies": {
- "eslint": "^7.0.0 || ^8.0.0 || ^9.0.0"
- }
- },
- "node_modules/eslint-scope": {
- "version": "8.4.0",
- "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz",
- "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==",
- "dev": true,
- "license": "BSD-2-Clause",
- "dependencies": {
- "esrecurse": "^4.3.0",
- "estraverse": "^5.2.0"
- },
- "engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.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,
- "license": "Apache-2.0",
- "engines": {
- "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
- },
- "funding": {
- "url": "https://opencollective.com/eslint"
- }
- },
- "node_modules/eslint/node_modules/brace-expansion": {
- "version": "1.1.12",
- "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
- "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "balanced-match": "^1.0.0",
- "concat-map": "0.0.1"
- }
- },
- "node_modules/eslint/node_modules/eslint-visitor-keys": {
- "version": "4.2.1",
- "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz",
- "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==",
- "dev": true,
- "license": "Apache-2.0",
- "engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
- },
- "funding": {
- "url": "https://opencollective.com/eslint"
- }
- },
- "node_modules/eslint/node_modules/ignore": {
- "version": "5.3.2",
- "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz",
- "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">= 4"
- }
- },
- "node_modules/eslint/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,
- "license": "ISC",
- "dependencies": {
- "brace-expansion": "^1.1.7"
- },
- "engines": {
- "node": "*"
- }
- },
- "node_modules/espree": {
- "version": "10.4.0",
- "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz",
- "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==",
- "dev": true,
- "license": "BSD-2-Clause",
- "dependencies": {
- "acorn": "^8.15.0",
- "acorn-jsx": "^5.3.2",
- "eslint-visitor-keys": "^4.2.1"
- },
- "engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
- },
- "funding": {
- "url": "https://opencollective.com/eslint"
- }
- },
- "node_modules/espree/node_modules/eslint-visitor-keys": {
- "version": "4.2.1",
- "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz",
- "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==",
- "dev": true,
- "license": "Apache-2.0",
- "engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.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,
- "license": "BSD-2-Clause",
- "bin": {
- "esparse": "bin/esparse.js",
- "esvalidate": "bin/esvalidate.js"
- },
- "engines": {
- "node": ">=4"
- }
- },
- "node_modules/esquery": {
- "version": "1.6.0",
- "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz",
- "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==",
- "dev": true,
- "license": "BSD-3-Clause",
- "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,
- "license": "BSD-2-Clause",
- "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,
- "license": "BSD-2-Clause",
- "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,
- "license": "BSD-2-Clause",
- "engines": {
- "node": ">=0.10.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,
- "license": "MIT"
- },
- "node_modules/fast-glob": {
- "version": "3.3.3",
- "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz",
- "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@nodelib/fs.stat": "^2.0.2",
- "@nodelib/fs.walk": "^1.2.3",
- "glob-parent": "^5.1.2",
- "merge2": "^1.3.0",
- "micromatch": "^4.0.8"
- },
- "engines": {
- "node": ">=8.6.0"
- }
- },
- "node_modules/fast-glob/node_modules/glob-parent": {
- "version": "5.1.2",
- "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
- "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
- "dev": true,
- "license": "ISC",
- "dependencies": {
- "is-glob": "^4.0.1"
- },
- "engines": {
- "node": ">= 6"
- }
- },
- "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,
- "license": "MIT"
- },
- "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,
- "license": "MIT"
- },
- "node_modules/fastq": {
- "version": "1.19.1",
- "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz",
- "integrity": "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==",
- "dev": true,
- "license": "ISC",
- "dependencies": {
- "reusify": "^1.0.4"
- }
- },
- "node_modules/file-entry-cache": {
- "version": "8.0.0",
- "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz",
- "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "flat-cache": "^4.0.0"
- },
- "engines": {
- "node": ">=16.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-cache-dir": {
- "version": "3.3.2",
- "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.3.2.tgz",
- "integrity": "sha512-wXZV5emFEjrridIgED11OoUKLxiYjAcqot/NJdAkOhlJ+vGzwhOAfcG5OX1jP+S0PcjEn8bdMJv+g2jwQ3Onig==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "commondir": "^1.0.1",
- "make-dir": "^3.0.2",
- "pkg-dir": "^4.1.0"
- },
- "engines": {
- "node": ">=8"
- },
- "funding": {
- "url": "https://github.com/avajs/find-cache-dir?sponsor=1"
- }
- },
- "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,
- "license": "MIT",
- "dependencies": {
- "locate-path": "^6.0.0",
- "path-exists": "^4.0.0"
- },
- "engines": {
- "node": ">=10"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/flat": {
- "version": "5.0.2",
- "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz",
- "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==",
- "dev": true,
- "license": "BSD-3-Clause",
- "bin": {
- "flat": "cli.js"
- }
- },
- "node_modules/flat-cache": {
- "version": "4.0.1",
- "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz",
- "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "flatted": "^3.2.9",
- "keyv": "^4.5.4"
- },
- "engines": {
- "node": ">=16"
- }
- },
- "node_modules/flatted": {
- "version": "3.3.3",
- "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz",
- "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==",
- "dev": true,
- "license": "ISC"
- },
- "node_modules/foreground-child": {
- "version": "3.3.1",
- "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz",
- "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==",
- "dev": true,
- "license": "ISC",
- "dependencies": {
- "cross-spawn": "^7.0.6",
- "signal-exit": "^4.0.1"
- },
- "engines": {
- "node": ">=14"
- },
- "funding": {
- "url": "https://github.com/sponsors/isaacs"
- }
- },
- "node_modules/fromentries": {
- "version": "1.3.2",
- "resolved": "https://registry.npmjs.org/fromentries/-/fromentries-1.3.2.tgz",
- "integrity": "sha512-cHEpEQHUg0f8XdtZCc2ZAhrHzKzT0MrFUTcvx+hfxYu7rGMDc5SKoXFh+n4YigxsHXRzc6OrCshdR1bWH6HHyg==",
- "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"
- }
- ],
- "license": "MIT"
- },
- "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,
- "license": "ISC"
- },
- "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,
- "license": "MIT",
- "engines": {
- "node": ">=6.9.0"
- }
- },
- "node_modules/get-caller-file": {
- "version": "1.0.3",
- "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-1.0.3.tgz",
- "integrity": "sha512-3t6rVToeoZfYSGd8YoLFR2DJkiQrIiUrGcjvFX2mDw3bn6k2OtwHN0TNCLbBO+w8qTvimhDkv+LSscbJY1vE6w==",
- "dev": true,
- "license": "ISC"
- },
- "node_modules/get-east-asian-width": {
- "version": "1.4.0",
- "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.4.0.tgz",
- "integrity": "sha512-QZjmEOC+IT1uk6Rx0sX22V6uHWVwbdbxf1faPqJ1QhLdGgsRGCZoyaQBm/piRdJy/D2um6hM1UP7ZEeQ4EkP+Q==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=18"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "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,
- "license": "MIT",
- "engines": {
- "node": ">=8.0.0"
- }
- },
- "node_modules/glob": {
- "version": "10.4.5",
- "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz",
- "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==",
- "dev": true,
- "license": "ISC",
- "dependencies": {
- "foreground-child": "^3.1.0",
- "jackspeak": "^3.1.2",
- "minimatch": "^9.0.4",
- "minipass": "^7.1.2",
- "package-json-from-dist": "^1.0.0",
- "path-scurry": "^1.11.1"
- },
- "bin": {
- "glob": "dist/esm/bin.mjs"
- },
- "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,
- "license": "ISC",
- "dependencies": {
- "is-glob": "^4.0.3"
- },
- "engines": {
- "node": ">=10.13.0"
- }
- },
- "node_modules/globals": {
- "version": "14.0.0",
- "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz",
- "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=18"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/graceful-fs": {
- "version": "4.2.11",
- "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
- "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==",
- "dev": true,
- "license": "ISC"
- },
- "node_modules/graphemer": {
- "version": "1.4.0",
- "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz",
- "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==",
- "dev": true,
- "license": "MIT"
- },
- "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,
- "license": "MIT",
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/hasha": {
- "version": "5.2.2",
- "resolved": "https://registry.npmjs.org/hasha/-/hasha-5.2.2.tgz",
- "integrity": "sha512-Hrp5vIK/xr5SkeN2onO32H0MgNZ0f17HRNH39WfL0SYUNOTZ5Lz1TJ8Pajo/87dYGEFlLMm7mIc/k/s6Bvz9HQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "is-stream": "^2.0.0",
- "type-fest": "^0.8.0"
- },
- "engines": {
- "node": ">=8"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/he": {
- "version": "1.2.0",
- "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz",
- "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==",
- "dev": true,
- "license": "MIT",
- "bin": {
- "he": "bin/he"
- }
- },
- "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,
- "license": "MIT"
- },
- "node_modules/ignore": {
- "version": "7.0.5",
- "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz",
- "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">= 4"
- }
- },
- "node_modules/import-fresh": {
- "version": "3.3.1",
- "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz",
- "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "parent-module": "^1.0.0",
- "resolve-from": "^4.0.0"
- },
- "engines": {
- "node": ">=6"
- },
- "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,
- "license": "MIT",
- "engines": {
- "node": ">=0.8.19"
- }
- },
- "node_modules/indent-string": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz",
- "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=8"
- }
- },
- "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==",
- "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.",
- "dev": true,
- "license": "ISC",
- "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,
- "license": "ISC"
- },
- "node_modules/ioredis": {
- "version": "5.8.2",
- "resolved": "https://registry.npmjs.org/ioredis/-/ioredis-5.8.2.tgz",
- "integrity": "sha512-C6uC+kleiIMmjViJINWk80sOQw5lEzse1ZmvD+S/s8p8CWapftSaC+kocGTx6xrbrJ4WmYQGC08ffHLr6ToR6Q==",
- "license": "MIT",
- "dependencies": {
- "@ioredis/commands": "1.4.0",
- "cluster-key-slot": "^1.1.0",
- "debug": "^4.3.4",
- "denque": "^2.1.0",
- "lodash.defaults": "^4.2.0",
- "lodash.isarguments": "^3.1.0",
- "redis-errors": "^1.2.0",
- "redis-parser": "^3.0.0",
- "standard-as-callback": "^2.1.0"
- },
- "engines": {
- "node": ">=12.22.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/ioredis"
- }
- },
- "node_modules/is-docker": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-3.0.0.tgz",
- "integrity": "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==",
- "dev": true,
- "license": "MIT",
- "bin": {
- "is-docker": "cli.js"
- },
- "engines": {
- "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "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,
- "license": "MIT",
- "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,
- "license": "MIT",
- "engines": {
- "node": ">=8"
- }
- },
- "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,
- "license": "MIT",
- "dependencies": {
- "is-extglob": "^2.1.1"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/is-inside-container": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/is-inside-container/-/is-inside-container-1.0.0.tgz",
- "integrity": "sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "is-docker": "^3.0.0"
- },
- "bin": {
- "is-inside-container": "cli.js"
- },
- "engines": {
- "node": ">=14.16"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "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,
- "license": "MIT",
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/is-plain-obj": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz",
- "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==",
- "dev": true,
- "license": "MIT",
- "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,
- "license": "MIT",
- "engines": {
- "node": ">=8"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/is-typedarray": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz",
- "integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/is-unicode-supported": {
- "version": "0.1.0",
- "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz",
- "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=10"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/is-windows": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz",
- "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/is-wsl": {
- "version": "3.1.0",
- "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-3.1.0.tgz",
- "integrity": "sha512-UcVfVfaK4Sc4m7X3dUSoHoozQGBEFeDC+zVo06t98xe8CzHSZZBekNXH+tu0NalHolcJ/QAGqS46Hef7QXBIMw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "is-inside-container": "^1.0.0"
- },
- "engines": {
- "node": ">=16"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/isexe": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
- "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
- "dev": true,
- "license": "ISC"
- },
- "node_modules/istanbul-lib-coverage": {
- "version": "3.2.2",
- "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz",
- "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==",
- "dev": true,
- "license": "BSD-3-Clause",
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/istanbul-lib-hook": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/istanbul-lib-hook/-/istanbul-lib-hook-3.0.0.tgz",
- "integrity": "sha512-Pt/uge1Q9s+5VAZ+pCo16TYMWPBIl+oaNIjgLQxcX0itS6ueeaA+pEfThZpH8WxhFgCiEb8sAJY6MdUKgiIWaQ==",
- "dev": true,
- "license": "BSD-3-Clause",
- "dependencies": {
- "append-transform": "^2.0.0"
- },
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/istanbul-lib-instrument": {
- "version": "6.0.3",
- "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.3.tgz",
- "integrity": "sha512-Vtgk7L/R2JHyyGW07spoFlB8/lpjiOLTjMdms6AFMraYt3BaJauod/NGrfnVG/y4Ix1JEuMRPDPEj2ua+zz1/Q==",
- "dev": true,
- "license": "BSD-3-Clause",
- "dependencies": {
- "@babel/core": "^7.23.9",
- "@babel/parser": "^7.23.9",
- "@istanbuljs/schema": "^0.1.3",
- "istanbul-lib-coverage": "^3.2.0",
- "semver": "^7.5.4"
- },
- "engines": {
- "node": ">=10"
- }
- },
- "node_modules/istanbul-lib-processinfo": {
- "version": "2.0.3",
- "resolved": "https://registry.npmjs.org/istanbul-lib-processinfo/-/istanbul-lib-processinfo-2.0.3.tgz",
- "integrity": "sha512-NkwHbo3E00oybX6NGJi6ar0B29vxyvNwoC7eJ4G4Yq28UfY758Hgn/heV8VRFhevPED4LXfFz0DQ8z/0kw9zMg==",
- "dev": true,
- "license": "ISC",
- "dependencies": {
- "archy": "^1.0.0",
- "cross-spawn": "^7.0.3",
- "istanbul-lib-coverage": "^3.2.0",
- "p-map": "^3.0.0",
- "rimraf": "^3.0.0",
- "uuid": "^8.3.2"
- },
- "engines": {
- "node": ">=8"
- }
- },
- "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,
- "license": "BSD-3-Clause",
- "dependencies": {
- "istanbul-lib-coverage": "^3.0.0",
- "make-dir": "^4.0.0",
- "supports-color": "^7.1.0"
- },
- "engines": {
- "node": ">=10"
- }
- },
- "node_modules/istanbul-lib-report/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,
- "license": "MIT",
- "dependencies": {
- "semver": "^7.5.3"
- },
- "engines": {
- "node": ">=10"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "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,
- "license": "BSD-3-Clause",
- "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.2.0",
- "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.2.0.tgz",
- "integrity": "sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==",
- "dev": true,
- "license": "BSD-3-Clause",
- "dependencies": {
- "html-escaper": "^2.0.0",
- "istanbul-lib-report": "^3.0.0"
- },
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/jackspeak": {
- "version": "3.4.3",
- "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz",
- "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==",
- "dev": true,
- "license": "BlueOak-1.0.0",
- "dependencies": {
- "@isaacs/cliui": "^8.0.2"
- },
- "funding": {
- "url": "https://github.com/sponsors/isaacs"
- },
- "optionalDependencies": {
- "@pkgjs/parseargs": "^0.11.0"
- }
- },
- "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,
- "license": "MIT"
- },
- "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,
- "license": "MIT",
- "dependencies": {
- "argparse": "^2.0.1"
- },
- "bin": {
- "js-yaml": "bin/js-yaml.js"
- }
- },
- "node_modules/jsdoc-type-pratt-parser": {
- "version": "4.1.0",
- "resolved": "https://registry.npmjs.org/jsdoc-type-pratt-parser/-/jsdoc-type-pratt-parser-4.1.0.tgz",
- "integrity": "sha512-Hicd6JK5Njt2QB6XYFS7ok9e37O8AYk3jTcppG4YVQnYjOemymvTcmc7OWsmq/Qqj5TdRFO5/x/tIPmBeRtGHg==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=12.0.0"
- }
- },
- "node_modules/jsesc": {
- "version": "3.1.0",
- "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz",
- "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==",
- "dev": true,
- "license": "MIT",
- "bin": {
- "jsesc": "bin/jsesc"
- },
- "engines": {
- "node": ">=6"
- }
- },
- "node_modules/json-buffer": {
- "version": "3.0.1",
- "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz",
- "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==",
- "dev": true,
- "license": "MIT"
- },
- "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,
- "license": "MIT"
- },
- "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,
- "license": "MIT"
- },
- "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,
- "license": "MIT",
- "bin": {
- "json5": "lib/cli.js"
- },
- "engines": {
- "node": ">=6"
- }
- },
- "node_modules/keyv": {
- "version": "4.5.4",
- "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz",
- "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "json-buffer": "3.0.1"
- }
- },
- "node_modules/lcov-parse": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/lcov-parse/-/lcov-parse-1.0.0.tgz",
- "integrity": "sha512-aprLII/vPzuQvYZnDRU78Fns9I2Ag3gi4Ipga/hxnVMCZC8DnR2nI7XBqrPoywGfxqIx/DgarGvDJZAD3YBTgQ==",
- "dev": true,
- "license": "BSD-3-Clause",
- "bin": {
- "lcov-parse": "bin/cli.js"
- }
- },
- "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,
- "license": "MIT",
- "dependencies": {
- "prelude-ls": "^1.2.1",
- "type-check": "~0.4.0"
- },
- "engines": {
- "node": ">= 0.8.0"
- }
- },
- "node_modules/linkify-it": {
- "version": "5.0.0",
- "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-5.0.0.tgz",
- "integrity": "sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "uc.micro": "^2.0.0"
- }
- },
- "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,
- "license": "MIT",
- "dependencies": {
- "p-locate": "^5.0.0"
- },
- "engines": {
- "node": ">=10"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "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==",
- "license": "MIT"
- },
- "node_modules/lodash.flattendeep": {
- "version": "4.4.0",
- "resolved": "https://registry.npmjs.org/lodash.flattendeep/-/lodash.flattendeep-4.4.0.tgz",
- "integrity": "sha512-uHaJFihxmJcEX3kT4I23ABqKKalJ/zDrDg0lsFtc1h+3uw49SIJ5beyhx5ExVRti3AvKoOJngIj7xz3oylPdWQ==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/lodash.isarguments": {
- "version": "3.1.0",
- "resolved": "https://registry.npmjs.org/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz",
- "integrity": "sha512-chi4NHZlZqZD18a0imDHnZPrDeBbTtVN7GXMwuGdRH9qotxAjYs3aVLKc7zNOG9eddR5Ksd8rvFEBc9SsggPpg==",
- "license": "MIT"
- },
- "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,
- "license": "MIT"
- },
- "node_modules/log-driver": {
- "version": "1.2.7",
- "resolved": "https://registry.npmjs.org/log-driver/-/log-driver-1.2.7.tgz",
- "integrity": "sha512-U7KCmLdqsGHBLeWqYlFA0V0Sl6P08EE1ZrmA9cxjUE0WVqT9qnyVDPz1kzpFEP0jdJuFnasWIfSd7fsaNXkpbg==",
- "dev": true,
- "license": "ISC",
- "engines": {
- "node": ">=0.8.6"
- }
- },
- "node_modules/log-symbols": {
- "version": "4.1.0",
- "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz",
- "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "chalk": "^4.1.0",
- "is-unicode-supported": "^0.1.0"
- },
- "engines": {
- "node": ">=10"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/loupe": {
- "version": "3.2.1",
- "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.2.1.tgz",
- "integrity": "sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ==",
- "dev": true,
- "license": "MIT"
- },
- "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,
- "license": "ISC",
- "dependencies": {
- "yallist": "^3.0.2"
- }
- },
- "node_modules/lunr": {
- "version": "2.3.9",
- "resolved": "https://registry.npmjs.org/lunr/-/lunr-2.3.9.tgz",
- "integrity": "sha512-zTU3DaZaF3Rt9rhN3uBMGQD3dD2/vFQqnvZCDv4dl5iOzq2IZQqTxu90r4E5J+nP70J3ilqVCrbho2eWaeW8Ow==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/make-dir": {
- "version": "3.1.0",
- "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz",
- "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "semver": "^6.0.0"
- },
- "engines": {
- "node": ">=8"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/make-dir/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,
- "license": "ISC",
- "bin": {
- "semver": "bin/semver.js"
- }
- },
- "node_modules/make-error": {
- "version": "1.3.6",
- "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz",
- "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==",
- "dev": true,
- "license": "ISC"
- },
- "node_modules/markdown-it": {
- "version": "14.1.0",
- "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-14.1.0.tgz",
- "integrity": "sha512-a54IwgWPaeBCAAsv13YgmALOF1elABB08FxO9i+r4VFk5Vl4pKokRPeX8u5TCgSsPi6ec1otfLjdOpVcgbpshg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "argparse": "^2.0.1",
- "entities": "^4.4.0",
- "linkify-it": "^5.0.0",
- "mdurl": "^2.0.0",
- "punycode.js": "^2.3.1",
- "uc.micro": "^2.1.0"
- },
- "bin": {
- "markdown-it": "bin/markdown-it.mjs"
- }
- },
- "node_modules/mdurl": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-2.0.0.tgz",
- "integrity": "sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/merge2": {
- "version": "1.4.1",
- "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz",
- "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">= 8"
- }
- },
- "node_modules/micromatch": {
- "version": "4.0.8",
- "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz",
- "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "braces": "^3.0.3",
- "picomatch": "^2.3.1"
- },
- "engines": {
- "node": ">=8.6"
- }
- },
- "node_modules/minimatch": {
- "version": "9.0.5",
- "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz",
- "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==",
- "dev": true,
- "license": "ISC",
- "dependencies": {
- "brace-expansion": "^2.0.1"
- },
- "engines": {
- "node": ">=16 || 14 >=14.17"
- },
- "funding": {
- "url": "https://github.com/sponsors/isaacs"
- }
- },
- "node_modules/minimist": {
- "version": "1.2.8",
- "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz",
- "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==",
- "dev": true,
- "license": "MIT",
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/minipass": {
- "version": "7.1.2",
- "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz",
- "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==",
- "dev": true,
- "license": "ISC",
- "engines": {
- "node": ">=16 || 14 >=14.17"
- }
- },
- "node_modules/mocha": {
- "version": "11.7.4",
- "resolved": "https://registry.npmjs.org/mocha/-/mocha-11.7.4.tgz",
- "integrity": "sha512-1jYAaY8x0kAZ0XszLWu14pzsf4KV740Gld4HXkhNTXwcHx4AUEDkPzgEHg9CM5dVcW+zv036tjpsEbLraPJj4w==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "browser-stdout": "^1.3.1",
- "chokidar": "^4.0.1",
- "debug": "^4.3.5",
- "diff": "^7.0.0",
- "escape-string-regexp": "^4.0.0",
- "find-up": "^5.0.0",
- "glob": "^10.4.5",
- "he": "^1.2.0",
- "is-path-inside": "^3.0.3",
- "js-yaml": "^4.1.0",
- "log-symbols": "^4.1.0",
- "minimatch": "^9.0.5",
- "ms": "^2.1.3",
- "picocolors": "^1.1.1",
- "serialize-javascript": "^6.0.2",
- "strip-json-comments": "^3.1.1",
- "supports-color": "^8.1.1",
- "workerpool": "^9.2.0",
- "yargs": "^17.7.2",
- "yargs-parser": "^21.1.1",
- "yargs-unparser": "^2.0.0"
- },
- "bin": {
- "_mocha": "bin/_mocha",
- "mocha": "bin/mocha.js"
- },
- "engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
- }
- },
- "node_modules/mocha-lcov-reporter": {
- "version": "1.3.0",
- "resolved": "https://registry.npmjs.org/mocha-lcov-reporter/-/mocha-lcov-reporter-1.3.0.tgz",
- "integrity": "sha512-/5zI2tW4lq/ft8MGpYQ1nIH6yePPtIzdGeUEwFMKfMRdLfAQ1QW2c68eEJop32tNdN5srHa/E2TzB+erm3YMYA==",
- "dev": true,
- "license": "BSD-2-Clause",
- "engines": {
- "node": ">= 0.6.0"
- }
- },
- "node_modules/mocha/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,
- "license": "MIT",
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/mocha/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,
- "license": "ISC",
- "dependencies": {
- "string-width": "^4.2.0",
- "strip-ansi": "^6.0.1",
- "wrap-ansi": "^7.0.0"
- },
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/mocha/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,
- "license": "MIT"
- },
- "node_modules/mocha/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,
- "license": "ISC",
- "engines": {
- "node": "6.* || 8.* || >= 10.*"
- }
- },
- "node_modules/mocha/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,
- "license": "MIT",
- "dependencies": {
- "emoji-regex": "^8.0.0",
- "is-fullwidth-code-point": "^3.0.0",
- "strip-ansi": "^6.0.1"
- },
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/mocha/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,
- "license": "MIT",
- "dependencies": {
- "ansi-regex": "^5.0.1"
- },
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/mocha/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,
- "license": "MIT",
- "dependencies": {
- "has-flag": "^4.0.0"
- },
- "engines": {
- "node": ">=10"
- },
- "funding": {
- "url": "https://github.com/chalk/supports-color?sponsor=1"
- }
- },
- "node_modules/mocha/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,
- "license": "MIT",
- "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/mocha/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,
- "license": "MIT",
- "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/mock-require": {
- "version": "3.0.3",
- "resolved": "https://registry.npmjs.org/mock-require/-/mock-require-3.0.3.tgz",
- "integrity": "sha512-lLzfLHcyc10MKQnNUCv7dMcoY/2Qxd6wJfbqCcVk3LDb8An4hF6ohk5AztrvgKhJCqj36uyzi/p5se+tvyD+Wg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "get-caller-file": "^1.0.2",
- "normalize-path": "^2.1.1"
- },
- "engines": {
- "node": ">=4.3.0"
- }
- },
- "node_modules/ms": {
- "version": "2.1.3",
- "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
- "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
- "license": "MIT"
- },
- "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,
- "license": "MIT"
- },
- "node_modules/node-preload": {
- "version": "0.2.1",
- "resolved": "https://registry.npmjs.org/node-preload/-/node-preload-0.2.1.tgz",
- "integrity": "sha512-RM5oyBy45cLEoHqCeh+MNuFAxO0vTFBLskvQbOKnEE7YTTSN4tbN8QWDIPQ6L+WvKsB/qLEGpYe2ZZ9d4W9OIQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "process-on-spawn": "^1.0.0"
- },
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/node-releases": {
- "version": "2.0.26",
- "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.26.tgz",
- "integrity": "sha512-S2M9YimhSjBSvYnlr5/+umAnPHE++ODwt5e2Ij6FoX45HA/s4vHdkDx1eax2pAPeAOqu4s9b7ppahsyEFdVqQA==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/normalize-path": {
- "version": "2.1.1",
- "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz",
- "integrity": "sha512-3pKJwH184Xo/lnH6oyP1q2pMd7HcypqqmRs91/6/i2CGtWwIKGCkOOMTm/zXbgTEWHw1uNpNi/igc3ePOYHb6w==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "remove-trailing-separator": "^1.0.1"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/nyc": {
- "version": "17.1.0",
- "resolved": "https://registry.npmjs.org/nyc/-/nyc-17.1.0.tgz",
- "integrity": "sha512-U42vQ4czpKa0QdI1hu950XuNhYqgoM+ZF1HT+VuUHL9hPfDPVvNQyltmMqdE9bUHMVa+8yNbc3QKTj8zQhlVxQ==",
- "dev": true,
- "license": "ISC",
- "dependencies": {
- "@istanbuljs/load-nyc-config": "^1.0.0",
- "@istanbuljs/schema": "^0.1.2",
- "caching-transform": "^4.0.0",
- "convert-source-map": "^1.7.0",
- "decamelize": "^1.2.0",
- "find-cache-dir": "^3.2.0",
- "find-up": "^4.1.0",
- "foreground-child": "^3.3.0",
- "get-package-type": "^0.1.0",
- "glob": "^7.1.6",
- "istanbul-lib-coverage": "^3.0.0",
- "istanbul-lib-hook": "^3.0.0",
- "istanbul-lib-instrument": "^6.0.2",
- "istanbul-lib-processinfo": "^2.0.2",
- "istanbul-lib-report": "^3.0.0",
- "istanbul-lib-source-maps": "^4.0.0",
- "istanbul-reports": "^3.0.2",
- "make-dir": "^3.0.0",
- "node-preload": "^0.2.1",
- "p-map": "^3.0.0",
- "process-on-spawn": "^1.0.0",
- "resolve-from": "^5.0.0",
- "rimraf": "^3.0.0",
- "signal-exit": "^3.0.2",
- "spawn-wrap": "^2.0.0",
- "test-exclude": "^6.0.0",
- "yargs": "^15.0.2"
- },
- "bin": {
- "nyc": "bin/nyc.js"
- },
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/nyc/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,
- "license": "MIT",
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/nyc/node_modules/brace-expansion": {
- "version": "1.1.12",
- "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
- "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "balanced-match": "^1.0.0",
- "concat-map": "0.0.1"
- }
- },
- "node_modules/nyc/node_modules/cliui": {
- "version": "6.0.0",
- "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz",
- "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==",
- "dev": true,
- "license": "ISC",
- "dependencies": {
- "string-width": "^4.2.0",
- "strip-ansi": "^6.0.0",
- "wrap-ansi": "^6.2.0"
- }
- },
- "node_modules/nyc/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,
- "license": "MIT"
- },
- "node_modules/nyc/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,
- "license": "MIT",
- "dependencies": {
- "locate-path": "^5.0.0",
- "path-exists": "^4.0.0"
- },
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/nyc/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,
- "license": "ISC",
- "engines": {
- "node": "6.* || 8.* || >= 10.*"
- }
- },
- "node_modules/nyc/node_modules/glob": {
- "version": "7.2.3",
- "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
- "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==",
- "deprecated": "Glob versions prior to v9 are no longer supported",
- "dev": true,
- "license": "ISC",
- "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/nyc/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,
- "license": "MIT",
- "dependencies": {
- "p-locate": "^4.1.0"
- },
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/nyc/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,
- "license": "ISC",
- "dependencies": {
- "brace-expansion": "^1.1.7"
- },
- "engines": {
- "node": "*"
- }
- },
- "node_modules/nyc/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,
- "license": "MIT",
- "dependencies": {
- "p-try": "^2.0.0"
- },
- "engines": {
- "node": ">=6"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/nyc/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,
- "license": "MIT",
- "dependencies": {
- "p-limit": "^2.2.0"
- },
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/nyc/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,
- "license": "MIT",
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/nyc/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,
- "license": "ISC"
- },
- "node_modules/nyc/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,
- "license": "MIT",
- "dependencies": {
- "emoji-regex": "^8.0.0",
- "is-fullwidth-code-point": "^3.0.0",
- "strip-ansi": "^6.0.1"
- },
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/nyc/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,
- "license": "MIT",
- "dependencies": {
- "ansi-regex": "^5.0.1"
- },
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/nyc/node_modules/wrap-ansi": {
- "version": "6.2.0",
- "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz",
- "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "ansi-styles": "^4.0.0",
- "string-width": "^4.1.0",
- "strip-ansi": "^6.0.0"
- },
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/nyc/node_modules/y18n": {
- "version": "4.0.3",
- "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz",
- "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==",
- "dev": true,
- "license": "ISC"
- },
- "node_modules/nyc/node_modules/yargs": {
- "version": "15.4.1",
- "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz",
- "integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "cliui": "^6.0.0",
- "decamelize": "^1.2.0",
- "find-up": "^4.1.0",
- "get-caller-file": "^2.0.1",
- "require-directory": "^2.1.1",
- "require-main-filename": "^2.0.0",
- "set-blocking": "^2.0.0",
- "string-width": "^4.2.0",
- "which-module": "^2.0.0",
- "y18n": "^4.0.0",
- "yargs-parser": "^18.1.2"
- },
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/nyc/node_modules/yargs-parser": {
- "version": "18.1.3",
- "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz",
- "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==",
- "dev": true,
- "license": "ISC",
- "dependencies": {
- "camelcase": "^5.0.0",
- "decamelize": "^1.2.0"
- },
- "engines": {
- "node": ">=6"
- }
- },
- "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,
- "license": "ISC",
- "dependencies": {
- "wrappy": "1"
- }
- },
- "node_modules/open": {
- "version": "10.2.0",
- "resolved": "https://registry.npmjs.org/open/-/open-10.2.0.tgz",
- "integrity": "sha512-YgBpdJHPyQ2UE5x+hlSXcnejzAvD0b22U2OuAP+8OnlJT+PjWPxtgmGqKKc+RgTM63U9gN0YzrYc71R2WT/hTA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "default-browser": "^5.2.1",
- "define-lazy-prop": "^3.0.0",
- "is-inside-container": "^1.0.0",
- "wsl-utils": "^0.1.0"
- },
- "engines": {
- "node": ">=18"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/optionator": {
- "version": "0.9.4",
- "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz",
- "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "deep-is": "^0.1.3",
- "fast-levenshtein": "^2.0.6",
- "levn": "^0.4.1",
- "prelude-ls": "^1.2.1",
- "type-check": "^0.4.0",
- "word-wrap": "^1.2.5"
- },
- "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,
- "license": "MIT",
- "dependencies": {
- "yocto-queue": "^0.1.0"
- },
- "engines": {
- "node": ">=10"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "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,
- "license": "MIT",
- "dependencies": {
- "p-limit": "^3.0.2"
- },
- "engines": {
- "node": ">=10"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/p-map": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/p-map/-/p-map-3.0.0.tgz",
- "integrity": "sha512-d3qXVTF/s+W+CdJ5A29wywV2n8CQQYahlgz2bFiA+4eVNJbHJodPZ+/gXwPGh0bOqA+j8S+6+ckmvLGPk1QpxQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "aggregate-error": "^3.0.0"
- },
- "engines": {
- "node": ">=8"
- }
- },
- "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,
- "license": "MIT",
- "engines": {
- "node": ">=6"
- }
- },
- "node_modules/package-hash": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/package-hash/-/package-hash-4.0.0.tgz",
- "integrity": "sha512-whdkPIooSu/bASggZ96BWVvZTRMOFxnyUG5PnTSGKoJE2gd5mbVNmR2Nj20QFzxYYgAXpoqC+AiXzl+UMRh7zQ==",
- "dev": true,
- "license": "ISC",
- "dependencies": {
- "graceful-fs": "^4.1.15",
- "hasha": "^5.0.0",
- "lodash.flattendeep": "^4.4.0",
- "release-zalgo": "^1.0.0"
- },
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/package-json-from-dist": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz",
- "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==",
- "dev": true,
- "license": "BlueOak-1.0.0"
- },
- "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,
- "license": "MIT",
- "dependencies": {
- "callsites": "^3.0.0"
- },
- "engines": {
- "node": ">=6"
- }
- },
- "node_modules/parse-imports-exports": {
- "version": "0.2.4",
- "resolved": "https://registry.npmjs.org/parse-imports-exports/-/parse-imports-exports-0.2.4.tgz",
- "integrity": "sha512-4s6vd6dx1AotCx/RCI2m7t7GCh5bDRUtGNvRfHSP2wbBQdMi67pPe7mtzmgwcaQ8VKK/6IB7Glfyu3qdZJPybQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "parse-statements": "1.0.11"
- }
- },
- "node_modules/parse-statements": {
- "version": "1.0.11",
- "resolved": "https://registry.npmjs.org/parse-statements/-/parse-statements-1.0.11.tgz",
- "integrity": "sha512-HlsyYdMBnbPQ9Jr/VgJ1YF4scnldvJpJxCVx6KgqPL4dxppsWrJHCIIxQXMJrqGnsRkNPATbeMJ8Yxu7JMsYcA==",
- "dev": true,
- "license": "MIT"
- },
- "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,
- "license": "MIT",
- "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,
- "license": "MIT",
- "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,
- "license": "MIT",
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/path-scurry": {
- "version": "1.11.1",
- "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz",
- "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==",
- "dev": true,
- "license": "BlueOak-1.0.0",
- "dependencies": {
- "lru-cache": "^10.2.0",
- "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0"
- },
- "engines": {
- "node": ">=16 || 14 >=14.18"
- },
- "funding": {
- "url": "https://github.com/sponsors/isaacs"
- }
- },
- "node_modules/path-scurry/node_modules/lru-cache": {
- "version": "10.4.3",
- "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz",
- "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==",
- "dev": true,
- "license": "ISC"
- },
- "node_modules/pathval": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/pathval/-/pathval-2.0.1.tgz",
- "integrity": "sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">= 14.16"
- }
- },
- "node_modules/picocolors": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
- "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
- "dev": true,
- "license": "ISC"
- },
- "node_modules/picomatch": {
- "version": "2.3.1",
- "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
- "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=8.6"
- },
- "funding": {
- "url": "https://github.com/sponsors/jonschlinkert"
- }
- },
- "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,
- "license": "MIT",
- "dependencies": {
- "find-up": "^4.0.0"
- },
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/pkg-dir/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,
- "license": "MIT",
- "dependencies": {
- "locate-path": "^5.0.0",
- "path-exists": "^4.0.0"
- },
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/pkg-dir/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,
- "license": "MIT",
- "dependencies": {
- "p-locate": "^4.1.0"
- },
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/pkg-dir/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,
- "license": "MIT",
- "dependencies": {
- "p-try": "^2.0.0"
- },
- "engines": {
- "node": ">=6"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/pkg-dir/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,
- "license": "MIT",
- "dependencies": {
- "p-limit": "^2.2.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,
- "license": "MIT",
- "engines": {
- "node": ">= 0.8.0"
- }
- },
- "node_modules/process-on-spawn": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/process-on-spawn/-/process-on-spawn-1.1.0.tgz",
- "integrity": "sha512-JOnOPQ/8TZgjs1JIH/m9ni7FfimjNa/PRx7y/Wb5qdItsnhO0jE4AT7fC0HjC28DUQWDr50dwSYZLdRMlqDq3Q==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "fromentries": "^1.2.0"
- },
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/punycode": {
- "version": "2.3.1",
- "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
- "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=6"
- }
- },
- "node_modules/punycode.js": {
- "version": "2.3.1",
- "resolved": "https://registry.npmjs.org/punycode.js/-/punycode.js-2.3.1.tgz",
- "integrity": "sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=6"
- }
- },
- "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"
- }
- ],
- "license": "MIT"
- },
- "node_modules/randombytes": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz",
- "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "safe-buffer": "^5.1.0"
- }
- },
- "node_modules/readdirp": {
- "version": "4.1.2",
- "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz",
- "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">= 14.18.0"
- },
- "funding": {
- "type": "individual",
- "url": "https://paulmillr.com/funding/"
- }
- },
- "node_modules/redis-errors": {
- "version": "1.2.0",
- "resolved": "https://registry.npmjs.org/redis-errors/-/redis-errors-1.2.0.tgz",
- "integrity": "sha512-1qny3OExCf0UvUV/5wpYKf2YwPcOqXzkwKKSmKHiE6ZMQs5heeE/c8eXK+PNllPvmjgAbfnsbpkGZWy8cBpn9w==",
- "license": "MIT",
- "engines": {
- "node": ">=4"
- }
- },
- "node_modules/redis-parser": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/redis-parser/-/redis-parser-3.0.0.tgz",
- "integrity": "sha512-DJnGAeenTdpMEH6uAJRK/uiyEIH9WVsUmoLwzudwGJUwZPp80PDBWPHXSAGNPwNvIXAbe7MSUB1zQFugFml66A==",
- "license": "MIT",
- "dependencies": {
- "redis-errors": "^1.0.0"
- },
- "engines": {
- "node": ">=4"
- }
- },
- "node_modules/reflect-metadata": {
- "version": "0.2.2",
- "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.2.2.tgz",
- "integrity": "sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q==",
- "dev": true,
- "license": "Apache-2.0"
- },
- "node_modules/release-zalgo": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/release-zalgo/-/release-zalgo-1.0.0.tgz",
- "integrity": "sha512-gUAyHVHPPC5wdqX/LG4LWtRYtgjxyX78oanFNTMMyFEfOqdC54s3eE82imuWKbOeqYht2CrNf64Qb8vgmmtZGA==",
- "dev": true,
- "license": "ISC",
- "dependencies": {
- "es6-error": "^4.0.1"
- },
- "engines": {
- "node": ">=4"
- }
- },
- "node_modules/remove-trailing-separator": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz",
- "integrity": "sha512-/hS+Y0u3aOfIETiaiirUFwDBDzmXPvO+jAfKTitUngIPzdKc6Z0LoFjM/CK5PL4C+eKwHohlHAb6H0VFfmmUsw==",
- "dev": true,
- "license": "ISC"
- },
- "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,
- "license": "MIT",
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/require-main-filename": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz",
- "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==",
- "dev": true,
- "license": "ISC"
- },
- "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,
- "license": "MIT",
- "engines": {
- "node": ">=4"
- }
- },
- "node_modules/reusify": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz",
- "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==",
- "dev": true,
- "license": "MIT",
- "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==",
- "deprecated": "Rimraf versions prior to v4 are no longer supported",
- "dev": true,
- "license": "ISC",
- "dependencies": {
- "glob": "^7.1.3"
- },
- "bin": {
- "rimraf": "bin.js"
- },
- "funding": {
- "url": "https://github.com/sponsors/isaacs"
- }
- },
- "node_modules/rimraf/node_modules/brace-expansion": {
- "version": "1.1.12",
- "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
- "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "balanced-match": "^1.0.0",
- "concat-map": "0.0.1"
- }
- },
- "node_modules/rimraf/node_modules/glob": {
- "version": "7.2.3",
- "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
- "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==",
- "deprecated": "Glob versions prior to v9 are no longer supported",
- "dev": true,
- "license": "ISC",
- "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/rimraf/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,
- "license": "ISC",
- "dependencies": {
- "brace-expansion": "^1.1.7"
- },
- "engines": {
- "node": "*"
- }
- },
- "node_modules/run-applescript": {
- "version": "7.1.0",
- "resolved": "https://registry.npmjs.org/run-applescript/-/run-applescript-7.1.0.tgz",
- "integrity": "sha512-DPe5pVFaAsinSaV6QjQ6gdiedWDcRCbUuiQfQa2wmWV7+xC9bGulGI8+TdRmoFkAPaBXk8CrAbnlY2ISniJ47Q==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=18"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "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"
- }
- ],
- "license": "MIT",
- "dependencies": {
- "queue-microtask": "^1.2.2"
- }
- },
- "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"
- }
- ],
- "license": "MIT"
- },
- "node_modules/semver": {
- "version": "7.7.3",
- "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz",
- "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==",
- "dev": true,
- "license": "ISC",
- "bin": {
- "semver": "bin/semver.js"
- },
- "engines": {
- "node": ">=10"
- }
- },
- "node_modules/serialize-javascript": {
- "version": "6.0.2",
- "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz",
- "integrity": "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==",
- "dev": true,
- "license": "BSD-3-Clause",
- "dependencies": {
- "randombytes": "^2.1.0"
- }
- },
- "node_modules/set-blocking": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz",
- "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==",
- "dev": true,
- "license": "ISC"
- },
- "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,
- "license": "MIT",
- "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,
- "license": "MIT",
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/signal-exit": {
- "version": "4.1.0",
- "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz",
- "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==",
- "dev": true,
- "license": "ISC",
- "engines": {
- "node": ">=14"
- },
- "funding": {
- "url": "https://github.com/sponsors/isaacs"
- }
- },
- "node_modules/sinon": {
- "version": "21.0.0",
- "resolved": "https://registry.npmjs.org/sinon/-/sinon-21.0.0.tgz",
- "integrity": "sha512-TOgRcwFPbfGtpqvZw+hyqJDvqfapr1qUlOizROIk4bBLjlsjlB00Pg6wMFXNtJRpu+eCZuVOaLatG7M8105kAw==",
- "dev": true,
- "license": "BSD-3-Clause",
- "dependencies": {
- "@sinonjs/commons": "^3.0.1",
- "@sinonjs/fake-timers": "^13.0.5",
- "@sinonjs/samsam": "^8.0.1",
- "diff": "^7.0.0",
- "supports-color": "^7.2.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/sinon"
- }
- },
- "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,
- "license": "BSD-3-Clause",
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/source-map-support": {
- "version": "0.5.21",
- "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz",
- "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==",
+ "version": "3.23.0",
+ "resolved": "https://registry.npmjs.org/@shikijs/themes/-/themes-3.23.0.tgz",
+ "integrity": "sha512-5qySYa1ZgAT18HR/ypENL9cUSGOeI2x+4IvYJu4JgVJdizn6kG4ia5Q1jDEOi7gTbN4RbuYtmHh0W3eccOrjMA==",
"dev": true,
"license": "MIT",
"dependencies": {
- "buffer-from": "^1.0.0",
- "source-map": "^0.6.0"
+ "@shikijs/types": "3.23.0"
}
},
- "node_modules/spawn-wrap": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/spawn-wrap/-/spawn-wrap-2.0.0.tgz",
- "integrity": "sha512-EeajNjfN9zMnULLwhZZQU3GWBoFNkbngTUPfaawT4RkMiviTxcX0qfhVbGey39mfctfDHkWtuecgQ8NJcyQWHg==",
+ "node_modules/@shikijs/types": {
+ "version": "3.23.0",
+ "resolved": "https://registry.npmjs.org/@shikijs/types/-/types-3.23.0.tgz",
+ "integrity": "sha512-3JZ5HXOZfYjsYSk0yPwBrkupyYSLpAE26Qc0HLghhZNGTZg/SKxXIIgoxOpmmeQP0RRSDJTk1/vPfw9tbw+jSQ==",
"dev": true,
- "license": "ISC",
+ "license": "MIT",
"dependencies": {
- "foreground-child": "^2.0.0",
- "is-windows": "^1.0.2",
- "make-dir": "^3.0.0",
- "rimraf": "^3.0.0",
- "signal-exit": "^3.0.2",
- "which": "^2.0.1"
- },
- "engines": {
- "node": ">=8"
+ "@shikijs/vscode-textmate": "^10.0.2",
+ "@types/hast": "^3.0.4"
}
},
- "node_modules/spawn-wrap/node_modules/foreground-child": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-2.0.0.tgz",
- "integrity": "sha512-dCIq9FpEcyQyXKCkyzmlPTFNgrCzPudOe+mhvJU5zAtlBnGVy2yKxtfsxK2tQBThwq225jcvBjpw1Gr40uzZCA==",
+ "node_modules/@shikijs/vscode-textmate": {
+ "version": "10.0.2",
+ "resolved": "https://registry.npmjs.org/@shikijs/vscode-textmate/-/vscode-textmate-10.0.2.tgz",
+ "integrity": "sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg==",
"dev": true,
- "license": "ISC",
- "dependencies": {
- "cross-spawn": "^7.0.0",
- "signal-exit": "^3.0.2"
- },
- "engines": {
- "node": ">=8.0.0"
- }
+ "license": "MIT"
},
- "node_modules/spawn-wrap/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==",
+ "node_modules/@types/hast": {
+ "version": "3.0.4",
+ "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz",
+ "integrity": "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==",
"dev": true,
- "license": "ISC"
+ "license": "MIT",
+ "dependencies": {
+ "@types/unist": "*"
+ }
},
- "node_modules/spdx-exceptions": {
- "version": "2.5.0",
- "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.5.0.tgz",
- "integrity": "sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w==",
+ "node_modules/@types/mock-require": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/@types/mock-require/-/mock-require-3.0.0.tgz",
+ "integrity": "sha512-TZ/s3ufaF+kEFPx9PqXKNd7OsPJoVsgOd59EA8XQLTRjJCsOdx5sEAOGhyrZ3Os78iaDKyGJWdQWyYWRg1Iyvg==",
"dev": true,
- "license": "CC-BY-3.0"
+ "license": "MIT"
},
- "node_modules/spdx-expression-parse": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-4.0.0.tgz",
- "integrity": "sha512-Clya5JIij/7C6bRR22+tnGXbc4VKlibKSVj2iHvVeX5iMW7s1SIQlqu699JkODJJIhh/pUu8L0/VLh8xflD+LQ==",
+ "node_modules/@types/node": {
+ "version": "24.9.1",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-24.9.1.tgz",
+ "integrity": "sha512-QoiaXANRkSXK6p0Duvt56W208du4P9Uye9hWLWgGMDTEoKPhuenzNcC4vGUmrNkiOKTlIrBoyNQYNpSwfEZXSg==",
"dev": true,
"license": "MIT",
"dependencies": {
- "spdx-exceptions": "^2.1.0",
- "spdx-license-ids": "^3.0.0"
+ "undici-types": "~7.16.0"
}
},
- "node_modules/spdx-license-ids": {
- "version": "3.0.22",
- "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.22.tgz",
- "integrity": "sha512-4PRT4nh1EImPbt2jASOKHX7PB7I+e4IWNLvkKFDxNhJlfjbYlleYQh285Z/3mPTHSAK/AvdMmw5BNNuYH8ShgQ==",
+ "node_modules/@types/unist": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz",
+ "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==",
"dev": true,
- "license": "CC0-1.0"
+ "license": "MIT"
},
- "node_modules/sprintf-js": {
- "version": "1.0.3",
- "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
- "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==",
+ "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,
- "license": "BSD-3-Clause"
- },
- "node_modules/standard-as-callback": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/standard-as-callback/-/standard-as-callback-2.1.0.tgz",
- "integrity": "sha512-qoRRSyROncaz1z0mvYqIE4lCd9p2R90i6GxW3uZv5ucSu8tU7B5HXUP1gG8pVZsYNVaXjk8ClXHPttLyxAL48A==",
- "license": "MIT"
+ "license": "Python-2.0"
},
- "node_modules/string-width": {
- "version": "5.1.2",
- "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz",
- "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==",
+ "node_modules/bundle-name": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/bundle-name/-/bundle-name-4.1.0.tgz",
+ "integrity": "sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q==",
"dev": true,
"license": "MIT",
"dependencies": {
- "eastasianwidth": "^0.2.0",
- "emoji-regex": "^9.2.2",
- "strip-ansi": "^7.0.1"
+ "run-applescript": "^7.0.0"
},
"engines": {
- "node": ">=12"
+ "node": ">=18"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
- "node_modules/string-width-cjs": {
- "name": "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,
- "license": "MIT",
- "dependencies": {
- "emoji-regex": "^8.0.0",
- "is-fullwidth-code-point": "^3.0.0",
- "strip-ansi": "^6.0.1"
- },
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/string-width-cjs/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,
- "license": "MIT",
+ "node_modules/cluster-key-slot": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/cluster-key-slot/-/cluster-key-slot-1.1.1.tgz",
+ "integrity": "sha512-rwHwUfXL40Chm1r08yrhU3qpUvdVlgkKNeyeGPOxnW8/SyVDvgRaed/Uz54AqWNaTCAThlj6QAs3TZcKI0xDEw==",
+ "license": "Apache-2.0",
"engines": {
- "node": ">=8"
+ "node": ">=0.10.0"
}
},
- "node_modules/string-width-cjs/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,
- "license": "MIT"
- },
- "node_modules/string-width-cjs/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,
+ "node_modules/debug": {
+ "version": "4.4.3",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
+ "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
"license": "MIT",
"dependencies": {
- "ansi-regex": "^5.0.1"
+ "ms": "^2.1.3"
},
"engines": {
- "node": ">=8"
+ "node": ">=6.0"
+ },
+ "peerDependenciesMeta": {
+ "supports-color": {
+ "optional": true
+ }
}
},
- "node_modules/strip-ansi": {
- "version": "7.1.2",
- "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz",
- "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==",
+ "node_modules/default-browser": {
+ "version": "5.5.0",
+ "resolved": "https://registry.npmjs.org/default-browser/-/default-browser-5.5.0.tgz",
+ "integrity": "sha512-H9LMLr5zwIbSxrmvikGuI/5KGhZ8E2zH3stkMgM5LpOWDutGM2JZaj460Udnf1a+946zc7YBgrqEWwbk7zHvGw==",
"dev": true,
"license": "MIT",
"dependencies": {
- "ansi-regex": "^6.0.1"
+ "bundle-name": "^4.1.0",
+ "default-browser-id": "^5.0.0"
},
"engines": {
- "node": ">=12"
+ "node": ">=18"
},
"funding": {
- "url": "https://github.com/chalk/strip-ansi?sponsor=1"
- }
- },
- "node_modules/strip-ansi-cjs": {
- "name": "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,
- "license": "MIT",
- "dependencies": {
- "ansi-regex": "^5.0.1"
- },
- "engines": {
- "node": ">=8"
+ "url": "https://github.com/sponsors/sindresorhus"
}
},
- "node_modules/strip-ansi-cjs/node_modules/ansi-regex": {
+ "node_modules/default-browser-id": {
"version": "5.0.1",
- "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
- "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
- "dev": true,
- "license": "MIT",
- "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,
- "license": "MIT",
- "engines": {
- "node": ">=8"
- }
- },
- "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==",
+ "resolved": "https://registry.npmjs.org/default-browser-id/-/default-browser-id-5.0.1.tgz",
+ "integrity": "sha512-x1VCxdX4t+8wVfd1so/9w+vQ4vx7lKd2Qp5tDRutErwmR85OgmfX7RlLRMWafRMY7hbEiXIbudNrjOAPa/hL8Q==",
"dev": true,
"license": "MIT",
"engines": {
- "node": ">=8"
+ "node": ">=18"
},
"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==",
+ "node_modules/define-lazy-prop": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-3.0.0.tgz",
+ "integrity": "sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==",
"dev": true,
"license": "MIT",
- "dependencies": {
- "has-flag": "^4.0.0"
- },
"engines": {
- "node": ">=8"
- }
- },
- "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,
- "license": "ISC",
- "dependencies": {
- "@istanbuljs/schema": "^0.1.2",
- "glob": "^7.1.4",
- "minimatch": "^3.0.4"
+ "node": ">=12"
},
- "engines": {
- "node": ">=8"
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
}
},
- "node_modules/test-exclude/node_modules/brace-expansion": {
- "version": "1.1.12",
- "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
- "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "balanced-match": "^1.0.0",
- "concat-map": "0.0.1"
+ "node_modules/denque": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/denque/-/denque-2.1.0.tgz",
+ "integrity": "sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==",
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=0.10"
}
},
- "node_modules/test-exclude/node_modules/glob": {
- "version": "7.2.3",
- "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
- "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==",
- "deprecated": "Glob versions prior to v9 are no longer supported",
+ "node_modules/entities": {
+ "version": "4.5.0",
+ "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz",
+ "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==",
"dev": true,
- "license": "ISC",
- "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"
- },
+ "license": "BSD-2-Clause",
"engines": {
- "node": "*"
+ "node": ">=0.12"
},
"funding": {
- "url": "https://github.com/sponsors/isaacs"
+ "url": "https://github.com/fb55/entities?sponsor=1"
}
},
- "node_modules/test-exclude/node_modules/minimatch": {
- "version": "3.1.2",
- "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
- "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
+ "node_modules/get-caller-file": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-1.0.3.tgz",
+ "integrity": "sha512-3t6rVToeoZfYSGd8YoLFR2DJkiQrIiUrGcjvFX2mDw3bn6k2OtwHN0TNCLbBO+w8qTvimhDkv+LSscbJY1vE6w==",
"dev": true,
- "license": "ISC",
- "dependencies": {
- "brace-expansion": "^1.1.7"
- },
- "engines": {
- "node": "*"
- }
+ "license": "ISC"
},
- "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,
+ "node_modules/ioredis": {
+ "version": "5.11.1",
+ "resolved": "https://registry.npmjs.org/ioredis/-/ioredis-5.11.1.tgz",
+ "integrity": "sha512-ehuGcf94bQXhfagULNXrJdfnWO38v070jxSx/qE87Kjzmu2fU7ro5EFAb+OPituLqgfyuQaym5DlrNydW2sJ9A==",
"license": "MIT",
"dependencies": {
- "is-number": "^7.0.0"
+ "@ioredis/commands": "1.10.0",
+ "cluster-key-slot": "1.1.1",
+ "debug": "4.4.3",
+ "denque": "2.1.0",
+ "redis-errors": "1.2.0",
+ "redis-parser": "3.0.0",
+ "standard-as-callback": "2.1.0"
},
"engines": {
- "node": ">=8.0"
- }
- },
- "node_modules/ts-api-utils": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.1.0.tgz",
- "integrity": "sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=18.12"
+ "node": ">=12.22.0"
},
- "peerDependencies": {
- "typescript": ">=4.8.4"
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/ioredis"
}
},
- "node_modules/ts-node": {
- "version": "10.9.2",
- "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz",
- "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==",
+ "node_modules/is-docker": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-3.0.0.tgz",
+ "integrity": "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==",
"dev": true,
"license": "MIT",
- "dependencies": {
- "@cspotcode/source-map-support": "^0.8.0",
- "@tsconfig/node10": "^1.0.7",
- "@tsconfig/node12": "^1.0.7",
- "@tsconfig/node14": "^1.0.0",
- "@tsconfig/node16": "^1.0.2",
- "acorn": "^8.4.1",
- "acorn-walk": "^8.1.1",
- "arg": "^4.1.0",
- "create-require": "^1.1.0",
- "diff": "^4.0.1",
- "make-error": "^1.1.1",
- "v8-compile-cache-lib": "^3.0.1",
- "yn": "3.1.1"
- },
"bin": {
- "ts-node": "dist/bin.js",
- "ts-node-cwd": "dist/bin-cwd.js",
- "ts-node-esm": "dist/bin-esm.js",
- "ts-node-script": "dist/bin-script.js",
- "ts-node-transpile-only": "dist/bin-transpile.js",
- "ts-script": "dist/bin-script-deprecated.js"
- },
- "peerDependencies": {
- "@swc/core": ">=1.2.50",
- "@swc/wasm": ">=1.2.50",
- "@types/node": "*",
- "typescript": ">=2.7"
+ "is-docker": "cli.js"
},
- "peerDependenciesMeta": {
- "@swc/core": {
- "optional": true
- },
- "@swc/wasm": {
- "optional": true
- }
- }
- },
- "node_modules/ts-node/node_modules/diff": {
- "version": "4.0.2",
- "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz",
- "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==",
- "dev": true,
- "license": "BSD-3-Clause",
"engines": {
- "node": ">=0.3.1"
- }
- },
- "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,
- "license": "MIT",
- "dependencies": {
- "prelude-ls": "^1.2.1"
+ "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
},
- "engines": {
- "node": ">= 0.8.0"
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
}
},
- "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==",
+ "node_modules/is-in-ssh": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/is-in-ssh/-/is-in-ssh-1.0.0.tgz",
+ "integrity": "sha512-jYa6Q9rH90kR1vKB6NM7qqd1mge3Fx4Dhw5TVlK1MUBqhEOuCagrEHMevNuCcbECmXZ0ThXkRm+Ymr51HwEPAw==",
"dev": true,
"license": "MIT",
"engines": {
- "node": ">=4"
- }
- },
- "node_modules/type-fest": {
- "version": "0.8.1",
- "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz",
- "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==",
- "dev": true,
- "license": "(MIT OR CC0-1.0)",
- "engines": {
- "node": ">=8"
+ "node": ">=20"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
}
},
- "node_modules/typedarray-to-buffer": {
- "version": "3.1.5",
- "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz",
- "integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==",
+ "node_modules/is-inside-container": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/is-inside-container/-/is-inside-container-1.0.0.tgz",
+ "integrity": "sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==",
"dev": true,
"license": "MIT",
"dependencies": {
- "is-typedarray": "^1.0.0"
- }
- },
- "node_modules/typedoc": {
- "version": "0.28.14",
- "resolved": "https://registry.npmjs.org/typedoc/-/typedoc-0.28.14.tgz",
- "integrity": "sha512-ftJYPvpVfQvFzpkoSfHLkJybdA/geDJ8BGQt/ZnkkhnBYoYW6lBgPQXu6vqLxO4X75dA55hX8Af847H5KXlEFA==",
- "dev": true,
- "license": "Apache-2.0",
- "dependencies": {
- "@gerrit0/mini-shiki": "^3.12.0",
- "lunr": "^2.3.9",
- "markdown-it": "^14.1.0",
- "minimatch": "^9.0.5",
- "yaml": "^2.8.1"
+ "is-docker": "^3.0.0"
},
"bin": {
- "typedoc": "bin/typedoc"
+ "is-inside-container": "cli.js"
},
"engines": {
- "node": ">= 18",
- "pnpm": ">= 10"
- },
- "peerDependencies": {
- "typescript": "5.0.x || 5.1.x || 5.2.x || 5.3.x || 5.4.x || 5.5.x || 5.6.x || 5.7.x || 5.8.x || 5.9.x"
- }
- },
- "node_modules/typescript": {
- "version": "5.9.3",
- "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
- "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
- "dev": true,
- "license": "Apache-2.0",
- "bin": {
- "tsc": "bin/tsc",
- "tsserver": "bin/tsserver"
+ "node": ">=14.16"
},
- "engines": {
- "node": ">=14.17"
- }
- },
- "node_modules/uc.micro": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-2.1.0.tgz",
- "integrity": "sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/undici-types": {
- "version": "7.16.0",
- "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz",
- "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==",
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/is-wsl": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-3.1.1.tgz",
+ "integrity": "sha512-e6rvdUCiQCAuumZslxRJWR/Doq4VpPR82kqclvcS0efgt430SlGIk05vdCN58+VrzgtIcfNODjozVielycD4Sw==",
"dev": true,
- "license": "MIT"
+ "license": "MIT",
+ "dependencies": {
+ "is-inside-container": "^1.0.0"
+ },
+ "engines": {
+ "node": ">=16"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
},
- "node_modules/update-browserslist-db": {
- "version": "1.1.4",
- "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.4.tgz",
- "integrity": "sha512-q0SPT4xyU84saUX+tomz1WLkxUbuaJnR1xWt17M7fJtEJigJeWUNGUqrauFXsHnqev9y9JTRGwk13tFBuKby4A==",
+ "node_modules/linkify-it": {
+ "version": "5.0.2",
+ "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-5.0.2.tgz",
+ "integrity": "sha512-ONTm2jCMAVZjgQa/Fy1kScXsuOoF5NPTsoFBdE1KVIZ2vAh/r9+Bqo+0jINCBYnavTPQZz38QzFTme79ENoN3Q==",
"dev": true,
"funding": [
{
- "type": "opencollective",
- "url": "https://opencollective.com/browserslist"
- },
- {
- "type": "tidelift",
- "url": "https://tidelift.com/funding/github/npm/browserslist"
+ "type": "github",
+ "url": "https://github.com/sponsors/puzrin"
},
{
"type": "github",
- "url": "https://github.com/sponsors/ai"
+ "url": "https://github.com/sponsors/markdown-it"
}
],
"license": "MIT",
"dependencies": {
- "escalade": "^3.2.0",
- "picocolors": "^1.1.1"
- },
- "bin": {
- "update-browserslist-db": "cli.js"
- },
- "peerDependencies": {
- "browserslist": ">= 4.21.0"
+ "uc.micro": "^2.0.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==",
+ "node_modules/lunr": {
+ "version": "2.3.9",
+ "resolved": "https://registry.npmjs.org/lunr/-/lunr-2.3.9.tgz",
+ "integrity": "sha512-zTU3DaZaF3Rt9rhN3uBMGQD3dD2/vFQqnvZCDv4dl5iOzq2IZQqTxu90r4E5J+nP70J3ilqVCrbho2eWaeW8Ow==",
"dev": true,
- "license": "BSD-2-Clause",
- "dependencies": {
- "punycode": "^2.1.0"
- }
+ "license": "MIT"
},
- "node_modules/uuid": {
- "version": "8.3.2",
- "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
- "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==",
+ "node_modules/markdown-it": {
+ "version": "14.3.0",
+ "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-14.3.0.tgz",
+ "integrity": "sha512-RCEsPjR+sr0x+AuYp601tKTkgFG4YEPLCzHST3cQ/fhlJkqAkz1L2/Qbp1j9qw5SBwQHFBoW8+hoN5xssOF0Tw==",
"dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/puzrin"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/markdown-it"
+ }
+ ],
"license": "MIT",
+ "dependencies": {
+ "argparse": "^2.0.1",
+ "entities": "^4.5.0",
+ "linkify-it": "^5.0.2",
+ "mdurl": "^2.0.0",
+ "punycode.js": "^2.3.1",
+ "uc.micro": "^2.1.0"
+ },
"bin": {
- "uuid": "dist/bin/uuid"
+ "markdown-it": "bin/markdown-it.mjs"
}
},
- "node_modules/v8-compile-cache-lib": {
- "version": "3.0.1",
- "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz",
- "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==",
+ "node_modules/mdurl": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-2.0.0.tgz",
+ "integrity": "sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==",
"dev": true,
"license": "MIT"
},
- "node_modules/which": {
- "version": "2.0.2",
- "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
- "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
+ "node_modules/mock-require": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/mock-require/-/mock-require-3.0.3.tgz",
+ "integrity": "sha512-lLzfLHcyc10MKQnNUCv7dMcoY/2Qxd6wJfbqCcVk3LDb8An4hF6ohk5AztrvgKhJCqj36uyzi/p5se+tvyD+Wg==",
"dev": true,
- "license": "ISC",
+ "license": "MIT",
"dependencies": {
- "isexe": "^2.0.0"
- },
- "bin": {
- "node-which": "bin/node-which"
+ "get-caller-file": "^1.0.2",
+ "normalize-path": "^2.1.1"
},
"engines": {
- "node": ">= 8"
+ "node": ">=4.3.0"
}
},
- "node_modules/which-module": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.1.tgz",
- "integrity": "sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==",
- "dev": true,
- "license": "ISC"
+ "node_modules/ms": {
+ "version": "2.1.3",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
+ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
+ "license": "MIT"
},
- "node_modules/word-wrap": {
- "version": "1.2.5",
- "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz",
- "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==",
+ "node_modules/normalize-path": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz",
+ "integrity": "sha512-3pKJwH184Xo/lnH6oyP1q2pMd7HcypqqmRs91/6/i2CGtWwIKGCkOOMTm/zXbgTEWHw1uNpNi/igc3ePOYHb6w==",
"dev": true,
"license": "MIT",
+ "dependencies": {
+ "remove-trailing-separator": "^1.0.1"
+ },
"engines": {
"node": ">=0.10.0"
}
},
- "node_modules/workerpool": {
- "version": "9.3.4",
- "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-9.3.4.tgz",
- "integrity": "sha512-TmPRQYYSAnnDiEB0P/Ytip7bFGvqnSU6I2BcuSw7Hx+JSg/DsUi5ebYfc8GYaSdpuvOcEs6dXxPurOYpe9QFwg==",
- "dev": true,
- "license": "Apache-2.0"
- },
- "node_modules/wrap-ansi": {
- "version": "8.1.0",
- "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz",
- "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==",
+ "node_modules/open": {
+ "version": "11.0.0",
+ "resolved": "https://registry.npmjs.org/open/-/open-11.0.0.tgz",
+ "integrity": "sha512-smsWv2LzFjP03xmvFoJ331ss6h+jixfA4UUV/Bsiyuu4YJPfN+FIQGOIiv4w9/+MoHkfkJ22UIaQWRVFRfH6Vw==",
"dev": true,
"license": "MIT",
"dependencies": {
- "ansi-styles": "^6.1.0",
- "string-width": "^5.0.1",
- "strip-ansi": "^7.0.1"
+ "default-browser": "^5.4.0",
+ "define-lazy-prop": "^3.0.0",
+ "is-in-ssh": "^1.0.0",
+ "is-inside-container": "^1.0.0",
+ "powershell-utils": "^0.1.0",
+ "wsl-utils": "^0.3.0"
},
"engines": {
- "node": ">=12"
+ "node": ">=20"
},
"funding": {
- "url": "https://github.com/chalk/wrap-ansi?sponsor=1"
+ "url": "https://github.com/sponsors/sindresorhus"
}
},
- "node_modules/wrap-ansi-cjs": {
- "name": "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==",
+ "node_modules/oxfmt": {
+ "version": "0.57.0",
+ "resolved": "https://registry.npmjs.org/oxfmt/-/oxfmt-0.57.0.tgz",
+ "integrity": "sha512-ZB7Bi+rGDSqmVIo9jwcLyFgjxXvQhDdU+jx+ZrVy6VRiVXK2+CHc4hO3J4dUQjHe7V0ymHB+MDuv5z+NhK07HA==",
"dev": true,
"license": "MIT",
"dependencies": {
- "ansi-styles": "^4.0.0",
- "string-width": "^4.1.0",
- "strip-ansi": "^6.0.0"
+ "tinypool": "2.1.0"
+ },
+ "bin": {
+ "oxfmt": "bin/oxfmt"
},
"engines": {
- "node": ">=10"
+ "node": "^20.19.0 || >=22.12.0"
},
"funding": {
- "url": "https://github.com/chalk/wrap-ansi?sponsor=1"
+ "url": "https://github.com/sponsors/Boshen"
+ },
+ "optionalDependencies": {
+ "@oxfmt/binding-android-arm-eabi": "0.57.0",
+ "@oxfmt/binding-android-arm64": "0.57.0",
+ "@oxfmt/binding-darwin-arm64": "0.57.0",
+ "@oxfmt/binding-darwin-x64": "0.57.0",
+ "@oxfmt/binding-freebsd-x64": "0.57.0",
+ "@oxfmt/binding-linux-arm-gnueabihf": "0.57.0",
+ "@oxfmt/binding-linux-arm-musleabihf": "0.57.0",
+ "@oxfmt/binding-linux-arm64-gnu": "0.57.0",
+ "@oxfmt/binding-linux-arm64-musl": "0.57.0",
+ "@oxfmt/binding-linux-ppc64-gnu": "0.57.0",
+ "@oxfmt/binding-linux-riscv64-gnu": "0.57.0",
+ "@oxfmt/binding-linux-riscv64-musl": "0.57.0",
+ "@oxfmt/binding-linux-s390x-gnu": "0.57.0",
+ "@oxfmt/binding-linux-x64-gnu": "0.57.0",
+ "@oxfmt/binding-linux-x64-musl": "0.57.0",
+ "@oxfmt/binding-openharmony-arm64": "0.57.0",
+ "@oxfmt/binding-win32-arm64-msvc": "0.57.0",
+ "@oxfmt/binding-win32-ia32-msvc": "0.57.0",
+ "@oxfmt/binding-win32-x64-msvc": "0.57.0"
+ },
+ "peerDependencies": {
+ "svelte": "^5.0.0",
+ "vite-plus": "*"
+ },
+ "peerDependenciesMeta": {
+ "svelte": {
+ "optional": true
+ },
+ "vite-plus": {
+ "optional": true
+ }
}
},
- "node_modules/wrap-ansi-cjs/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==",
+ "node_modules/oxlint": {
+ "version": "1.72.0",
+ "resolved": "https://registry.npmjs.org/oxlint/-/oxlint-1.72.0.tgz",
+ "integrity": "sha512-1rhdZIP/EvoI91ABIwNU5Q8+bWf8mjrS5UzIOZld4d4bXxJvtlUhlQvaoTogIGin/qdErMOrwaIJvCSIAKTLhA==",
"dev": true,
"license": "MIT",
+ "bin": {
+ "oxlint": "bin/oxlint"
+ },
"engines": {
- "node": ">=8"
+ "node": "^20.19.0 || >=22.12.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/Boshen"
+ },
+ "optionalDependencies": {
+ "@oxlint/binding-android-arm-eabi": "1.72.0",
+ "@oxlint/binding-android-arm64": "1.72.0",
+ "@oxlint/binding-darwin-arm64": "1.72.0",
+ "@oxlint/binding-darwin-x64": "1.72.0",
+ "@oxlint/binding-freebsd-x64": "1.72.0",
+ "@oxlint/binding-linux-arm-gnueabihf": "1.72.0",
+ "@oxlint/binding-linux-arm-musleabihf": "1.72.0",
+ "@oxlint/binding-linux-arm64-gnu": "1.72.0",
+ "@oxlint/binding-linux-arm64-musl": "1.72.0",
+ "@oxlint/binding-linux-ppc64-gnu": "1.72.0",
+ "@oxlint/binding-linux-riscv64-gnu": "1.72.0",
+ "@oxlint/binding-linux-riscv64-musl": "1.72.0",
+ "@oxlint/binding-linux-s390x-gnu": "1.72.0",
+ "@oxlint/binding-linux-x64-gnu": "1.72.0",
+ "@oxlint/binding-linux-x64-musl": "1.72.0",
+ "@oxlint/binding-openharmony-arm64": "1.72.0",
+ "@oxlint/binding-win32-arm64-msvc": "1.72.0",
+ "@oxlint/binding-win32-ia32-msvc": "1.72.0",
+ "@oxlint/binding-win32-x64-msvc": "1.72.0"
+ },
+ "peerDependencies": {
+ "oxlint-tsgolint": ">=0.22.1",
+ "vite-plus": "*"
+ },
+ "peerDependenciesMeta": {
+ "oxlint-tsgolint": {
+ "optional": true
+ },
+ "vite-plus": {
+ "optional": true
+ }
}
},
- "node_modules/wrap-ansi-cjs/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,
- "license": "MIT"
- },
- "node_modules/wrap-ansi-cjs/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==",
+ "node_modules/powershell-utils": {
+ "version": "0.1.0",
+ "resolved": "https://registry.npmjs.org/powershell-utils/-/powershell-utils-0.1.0.tgz",
+ "integrity": "sha512-dM0jVuXJPsDN6DvRpea484tCUaMiXWjuCn++HGTqUWzGDjv5tZkEZldAJ/UMlqRYGFrD/etByo4/xOuC/snX2A==",
"dev": true,
"license": "MIT",
- "dependencies": {
- "emoji-regex": "^8.0.0",
- "is-fullwidth-code-point": "^3.0.0",
- "strip-ansi": "^6.0.1"
- },
"engines": {
- "node": ">=8"
+ "node": ">=20"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
}
},
- "node_modules/wrap-ansi-cjs/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==",
+ "node_modules/punycode.js": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/punycode.js/-/punycode.js-2.3.1.tgz",
+ "integrity": "sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==",
"dev": true,
"license": "MIT",
- "dependencies": {
- "ansi-regex": "^5.0.1"
- },
"engines": {
- "node": ">=8"
+ "node": ">=6"
}
},
- "node_modules/wrap-ansi/node_modules/ansi-styles": {
- "version": "6.2.3",
- "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz",
- "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==",
- "dev": true,
+ "node_modules/redis-errors": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/redis-errors/-/redis-errors-1.2.0.tgz",
+ "integrity": "sha512-1qny3OExCf0UvUV/5wpYKf2YwPcOqXzkwKKSmKHiE6ZMQs5heeE/c8eXK+PNllPvmjgAbfnsbpkGZWy8cBpn9w==",
"license": "MIT",
"engines": {
- "node": ">=12"
- },
- "funding": {
- "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ "node": ">=4"
}
},
- "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,
- "license": "ISC"
- },
- "node_modules/write-file-atomic": {
- "version": "3.0.3",
- "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-3.0.3.tgz",
- "integrity": "sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==",
- "dev": true,
- "license": "ISC",
+ "node_modules/redis-parser": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/redis-parser/-/redis-parser-3.0.0.tgz",
+ "integrity": "sha512-DJnGAeenTdpMEH6uAJRK/uiyEIH9WVsUmoLwzudwGJUwZPp80PDBWPHXSAGNPwNvIXAbe7MSUB1zQFugFml66A==",
+ "license": "MIT",
"dependencies": {
- "imurmurhash": "^0.1.4",
- "is-typedarray": "^1.0.0",
- "signal-exit": "^3.0.2",
- "typedarray-to-buffer": "^3.1.5"
+ "redis-errors": "^1.0.0"
+ },
+ "engines": {
+ "node": ">=4"
}
},
- "node_modules/write-file-atomic/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==",
+ "node_modules/remove-trailing-separator": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz",
+ "integrity": "sha512-/hS+Y0u3aOfIETiaiirUFwDBDzmXPvO+jAfKTitUngIPzdKc6Z0LoFjM/CK5PL4C+eKwHohlHAb6H0VFfmmUsw==",
"dev": true,
"license": "ISC"
},
- "node_modules/wsl-utils": {
- "version": "0.1.0",
- "resolved": "https://registry.npmjs.org/wsl-utils/-/wsl-utils-0.1.0.tgz",
- "integrity": "sha512-h3Fbisa2nKGPxCpm89Hk33lBLsnaGBvctQopaBSOW/uIs6FTe1ATyAnKFJrzVs9vpGdsTe73WF3V4lIsk4Gacw==",
+ "node_modules/run-applescript": {
+ "version": "7.1.0",
+ "resolved": "https://registry.npmjs.org/run-applescript/-/run-applescript-7.1.0.tgz",
+ "integrity": "sha512-DPe5pVFaAsinSaV6QjQ6gdiedWDcRCbUuiQfQa2wmWV7+xC9bGulGI8+TdRmoFkAPaBXk8CrAbnlY2ISniJ47Q==",
"dev": true,
"license": "MIT",
- "dependencies": {
- "is-wsl": "^3.1.0"
- },
"engines": {
"node": ">=18"
},
@@ -5294,172 +1307,144 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
- "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,
- "license": "ISC",
- "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,
- "license": "ISC"
+ "node_modules/standard-as-callback": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/standard-as-callback/-/standard-as-callback-2.1.0.tgz",
+ "integrity": "sha512-qoRRSyROncaz1z0mvYqIE4lCd9p2R90i6GxW3uZv5ucSu8tU7B5HXUP1gG8pVZsYNVaXjk8ClXHPttLyxAL48A==",
+ "license": "MIT"
},
- "node_modules/yaml": {
- "version": "2.8.1",
- "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.1.tgz",
- "integrity": "sha512-lcYcMxX2PO9XMGvAJkJ3OsNMw+/7FKes7/hgerGUYWIoWu5j/+YQqcZr5JnPZWzOsEBgMbSbiSTn/dv/69Mkpw==",
+ "node_modules/tinypool": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-2.1.0.tgz",
+ "integrity": "sha512-Pugqs6M0m7Lv1I7FtxN4aoyToKg1C4tu+/381vH35y8oENM/Ai7f7C4StcoK4/+BSw9ebcS8jRiVrORFKCALLw==",
"dev": true,
- "license": "ISC",
- "bin": {
- "yaml": "bin.mjs"
- },
+ "license": "MIT",
"engines": {
- "node": ">= 14.6"
+ "node": "^20.0.0 || >=22.0.0"
}
},
- "node_modules/yargs": {
- "version": "18.0.0",
- "resolved": "https://registry.npmjs.org/yargs/-/yargs-18.0.0.tgz",
- "integrity": "sha512-4UEqdc2RYGHZc7Doyqkrqiln3p9X2DZVxaGbwhn2pi7MrRagKaOcIKe8L3OxYcbhXLgLFUS3zAYuQjKBQgmuNg==",
+ "node_modules/typedoc": {
+ "version": "0.28.20",
+ "resolved": "https://registry.npmjs.org/typedoc/-/typedoc-0.28.20.tgz",
+ "integrity": "sha512-uSKqkh8Cr48vllnEy+jdaAgOeR6Y+QCBW7usgUsKj7gJEfR7stw9U/fE49LBnj2tPRKPY0c0EBJSWe9Appmplg==",
"dev": true,
- "license": "MIT",
+ "license": "Apache-2.0",
"dependencies": {
- "cliui": "^9.0.1",
- "escalade": "^3.1.1",
- "get-caller-file": "^2.0.5",
- "string-width": "^7.2.0",
- "y18n": "^5.0.5",
- "yargs-parser": "^22.0.0"
+ "@gerrit0/mini-shiki": "^3.23.0",
+ "lunr": "^2.3.9",
+ "markdown-it": "^14.3.0",
+ "minimatch": "^10.2.5",
+ "yaml": "^2.9.0"
+ },
+ "bin": {
+ "typedoc": "bin/typedoc"
},
"engines": {
- "node": "^20.19.0 || ^22.12.0 || >=23"
+ "node": ">= 18",
+ "pnpm": ">= 10"
+ },
+ "peerDependencies": {
+ "typescript": "5.0.x || 5.1.x || 5.2.x || 5.3.x || 5.4.x || 5.5.x || 5.6.x || 5.7.x || 5.8.x || 5.9.x || 6.0.x"
}
},
- "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==",
+ "node_modules/typedoc/node_modules/balanced-match": {
+ "version": "4.0.4",
+ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz",
+ "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==",
"dev": true,
- "license": "ISC",
+ "license": "MIT",
"engines": {
- "node": ">=12"
+ "node": "18 || 20 || >=22"
}
},
- "node_modules/yargs-unparser": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-2.0.0.tgz",
- "integrity": "sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==",
+ "node_modules/typedoc/node_modules/brace-expansion": {
+ "version": "5.0.7",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.7.tgz",
+ "integrity": "sha512-7oFy703dxfY3/NLxC1fh2SUCQ0H9rmAY+5EpDVfXjUTTs+HEwR2nYaqLv+GWcTsumwxPfiz6CzCNkwXwBUwqCA==",
"dev": true,
"license": "MIT",
"dependencies": {
- "camelcase": "^6.0.0",
- "decamelize": "^4.0.0",
- "flat": "^5.0.2",
- "is-plain-obj": "^2.1.0"
+ "balanced-match": "^4.0.2"
},
"engines": {
- "node": ">=10"
+ "node": "18 || 20 || >=22"
}
},
- "node_modules/yargs-unparser/node_modules/camelcase": {
- "version": "6.3.0",
- "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz",
- "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==",
+ "node_modules/typedoc/node_modules/minimatch": {
+ "version": "10.2.5",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz",
+ "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==",
"dev": true,
- "license": "MIT",
+ "license": "BlueOak-1.0.0",
+ "dependencies": {
+ "brace-expansion": "^5.0.5"
+ },
"engines": {
- "node": ">=10"
+ "node": "18 || 20 || >=22"
},
"funding": {
- "url": "https://github.com/sponsors/sindresorhus"
+ "url": "https://github.com/sponsors/isaacs"
}
},
- "node_modules/yargs-unparser/node_modules/decamelize": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-4.0.0.tgz",
- "integrity": "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==",
+ "node_modules/typescript": {
+ "version": "6.0.3",
+ "resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.3.tgz",
+ "integrity": "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==",
"dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=10"
+ "license": "Apache-2.0",
+ "bin": {
+ "tsc": "bin/tsc",
+ "tsserver": "bin/tsserver"
},
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
+ "engines": {
+ "node": ">=14.17"
}
},
- "node_modules/yargs/node_modules/emoji-regex": {
- "version": "10.6.0",
- "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz",
- "integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==",
+ "node_modules/uc.micro": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-2.1.0.tgz",
+ "integrity": "sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==",
"dev": true,
"license": "MIT"
},
- "node_modules/yargs/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==",
+ "node_modules/undici-types": {
+ "version": "7.16.0",
+ "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz",
+ "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==",
"dev": true,
- "license": "ISC",
- "engines": {
- "node": "6.* || 8.* || >= 10.*"
- }
+ "license": "MIT"
},
- "node_modules/yargs/node_modules/string-width": {
- "version": "7.2.0",
- "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz",
- "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==",
+ "node_modules/wsl-utils": {
+ "version": "0.3.1",
+ "resolved": "https://registry.npmjs.org/wsl-utils/-/wsl-utils-0.3.1.tgz",
+ "integrity": "sha512-g/eziiSUNBSsdDJtCLB8bdYEUMj4jR7AGeUo96p/3dTafgjHhpF4RiCFPiRILwjQoDXx5MqkBr4fwWtR3Ky4Wg==",
"dev": true,
"license": "MIT",
"dependencies": {
- "emoji-regex": "^10.3.0",
- "get-east-asian-width": "^1.0.0",
- "strip-ansi": "^7.1.0"
+ "is-wsl": "^3.1.0",
+ "powershell-utils": "^0.1.0"
},
"engines": {
- "node": ">=18"
+ "node": ">=20"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
- "node_modules/yargs/node_modules/yargs-parser": {
- "version": "22.0.0",
- "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-22.0.0.tgz",
- "integrity": "sha512-rwu/ClNdSMpkSrUb+d6BRsSkLUq1fmfsY6TOpYzTwvwkg1/NRG85KBy3kq++A8LKQwX6lsu+aWad+2khvuXrqw==",
+ "node_modules/yaml": {
+ "version": "2.9.0",
+ "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.9.0.tgz",
+ "integrity": "sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA==",
"dev": true,
"license": "ISC",
+ "bin": {
+ "yaml": "bin.mjs"
+ },
"engines": {
- "node": "^20.19.0 || ^22.12.0 || >=23"
- }
- },
- "node_modules/yn": {
- "version": "3.1.1",
- "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz",
- "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=6"
- }
- },
- "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,
- "license": "MIT",
- "engines": {
- "node": ">=10"
+ "node": ">= 14.6"
},
"funding": {
- "url": "https://github.com/sponsors/sindresorhus"
+ "url": "https://github.com/sponsors/eemeli"
}
}
}
diff --git a/package.json b/package.json
index d41118a..558b4f4 100644
--- a/package.json
+++ b/package.json
@@ -12,16 +12,19 @@
],
"scripts": {
"benchmark": "node benchmark -c $(( $(nproc) - 2 )) -m 100000",
- "prepare": "./node_modules/.bin/tsc",
- "test": "./node_modules/.bin/tsc && ./node_modules/.bin/nyc mocha --exit --timeout 10000 && ./node_modules/.bin/nyc report --reporter=text-lcov",
- "test-fast": "./node_modules/.bin/tsc && ./node_modules/.bin/nyc mocha --exit --timeout 10000 && /usr/bin/env node -e \"import('open').then(open => open.default('file://`pwd`/coverage/index.html', { wait: false }))\"",
- "test-local": "export COVERALLS_REPO_TOKEN=$IMQ_COVERALLS_TOKEN && npm test && /usr/bin/env node -e \"import('open').then(open => open.default('https://coveralls.io/github/imqueue/imq', { wait: false }))\"",
+ "build": "tsc",
+ "prepare": "tsc",
+ "lint": "oxlint",
+ "format": "oxfmt \"index.ts\" \"src/**/*.ts\" \"test/**/*.ts\" \"benchmark/**/*.ts\"",
+ "format:check": "oxfmt --check \"index.ts\" \"src/**/*.ts\" \"test/**/*.ts\" \"benchmark/**/*.ts\"",
+ "test": "tsc && node --test --test-timeout=15000 \"test/**/*.spec.js\"",
+ "test-coverage": "tsc && node --test --experimental-test-coverage --test-timeout=15000 \"test/**/*.spec.js\"",
+ "test-lcov": "tsc && node --test --experimental-test-coverage --test-reporter=lcov --test-reporter-destination=coverage/lcov.info --test-timeout=15000 \"test/**/*.spec.js\"",
"test-dev": "npm run test && npm run clean-js && npm run clean-typedefs && npm run clean-maps",
- "test-coverage": "cat ./coverage/lcov.info | CODECLIMATE_API_HOST=https://codebeat.co/webhooks/code_coverage CODECLIMATE_REPO_TOKEN=85bb2a18-4ebb-4e48-a2ce-92b7bf438b1a ./node_modules/.bin/codeclimate-test-reporter",
"clean-typedefs": "find . -name '*.d.ts' -not -wholename '*node_modules*' -not -wholename '*generator*' -type f -delete",
"clean-maps": "find . -name '*.js.map' -not -wholename '*node_modules*' -not -wholename '*generator*' -type f -delete",
"clean-js": "find . -name '*.js' -not -wholename '*node_modules*' -not -wholename '*generator*' -type f -delete",
- "clean-tests": "rm -rf .nyc_output coverage",
+ "clean-tests": "rm -rf .nyc_output coverage .agent-out",
"clean-doc": "rm -rf docs",
"clean-benchmark": "rm -rf benchmark-result",
"clean": "npm run clean-tests && npm run clean-typedefs && npm run clean-maps && npm run clean-js && npm run clean-doc && npm run clean-benchmark",
@@ -38,36 +41,17 @@
"author": "imqueue.com (https://imqueue.com)",
"license": "GPL-3.0-only",
"dependencies": {
- "ioredis": "^5.8.2"
+ "ioredis": "^5.11.1"
},
"devDependencies": {
- "@eslint/js": "^9.33.0",
- "@types/chai": "^5.2.2",
- "@types/eslint__eslintrc": "^2.1.2",
- "@types/mocha": "^10.0.0",
"@types/mock-require": "^3.0.0",
- "@types/node": "^24.2.1",
- "@types/sinon": "^17.0.4",
- "@types/yargs": "^17.0.33",
- "@typescript-eslint/eslint-plugin": "^8.39.0",
- "@typescript-eslint/parser": "^8.39.0",
- "@typescript-eslint/typescript-estree": "^8.39.0",
- "chai": "^5.2.1",
- "coveralls-next": "^5.0.0",
- "eslint": "^9.33.0",
- "eslint-plugin-jsdoc": "^52.0.4",
- "mocha": "^11.7.1",
- "mocha-lcov-reporter": "^1.3.0",
+ "@types/node": "^24.9.1",
"mock-require": "^3.0.3",
- "nyc": "^17.1.0",
- "open": "^10.2.0",
- "reflect-metadata": "^0.2.2",
- "sinon": "^21.0.0",
- "source-map-support": "^0.5.21",
- "ts-node": "^10.9.2",
- "typedoc": "^0.28.9",
- "typescript": "^5.9.2",
- "yargs": "^18.0.0"
+ "open": "^11.0.0",
+ "oxfmt": "0.57.0",
+ "oxlint": "1.72.0",
+ "typedoc": "^0.28.20",
+ "typescript": "^6.0.3"
},
"main": "index.js",
"typescript": {
diff --git a/src/ClusterManager.ts b/src/ClusterManager.ts
index 86bbab5..00de6de 100644
--- a/src/ClusterManager.ts
+++ b/src/ClusterManager.ts
@@ -25,11 +25,9 @@ import { IMessageQueueConnection, IServerInput } from './IMessageQueue';
import { uuid } from './uuid';
export interface ICluster {
- add: (server: IServerInput) => T;
+ add: (server: IServerInput) => IMessageQueueConnection;
remove: (server: IServerInput) => void;
- find: (
- server: IServerInput,
- ) => T | undefined;
+ find: (server: IServerInput) => IMessageQueueConnection | undefined;
}
export interface InitializedCluster extends ICluster {
@@ -42,10 +40,9 @@ export abstract class ClusterManager {
protected constructor() {}
public init(cluster: ICluster): InitializedCluster {
- const initializedCluster = Object.assign(
- cluster,
- { id: uuid() },
- ) as InitializedCluster;
+ const initializedCluster = Object.assign(cluster, {
+ id: uuid(),
+ }) as InitializedCluster;
this.clusters.push(initializedCluster);
@@ -67,9 +64,9 @@ export abstract class ClusterManager {
this.clusters = this.clusters.filter(cluster => cluster.id !== id);
if (
- this.clusters.length === 0
- && destroy
- && typeof this.destroy === 'function'
+ this.clusters.length === 0 &&
+ destroy &&
+ typeof this.destroy === 'function'
) {
await this.destroy();
}
diff --git a/src/ClusteredRedisQueue.ts b/src/ClusteredRedisQueue.ts
index 739535e..6240a2f 100644
--- a/src/ClusteredRedisQueue.ts
+++ b/src/ClusteredRedisQueue.ts
@@ -55,9 +55,9 @@ interface ClusterState {
* Implements the possibility to scale queues horizontally between several
* redis instances.
*/
-export class ClusteredRedisQueue implements IMessageQueue,
- EventEmitter {
-
+export class ClusteredRedisQueue
+ implements IMessageQueue, EventEmitter
+{
/**
* Logger instance associated with this queue instance
*
@@ -154,13 +154,14 @@ export class ClusteredRedisQueue implements IMessageQueue,
this.logger = this.options.logger || console;
if (!this.options.cluster && !this.options.clusterManagers?.length) {
- throw new TypeError('ClusteredRedisQueue: cluster ' +
- 'configuration is missing!');
+ throw new TypeError(
+ 'ClusteredRedisQueue: cluster ' + 'configuration is missing!',
+ );
}
this.mqOptions = { ...this.options };
- const cluster = [...this.mqOptions.cluster || []];
+ const cluster = [...(this.mqOptions.cluster || [])];
delete this.mqOptions.cluster;
@@ -172,11 +173,13 @@ export class ClusteredRedisQueue implements IMessageQueue,
this.verbose('Initializing cluster managers...');
for (const manager of this.options.clusterManagers) {
- this.initializedClusters.push(manager.init({
- add: this.addServer.bind(this),
- remove: this.removeServer.bind(this),
- find: this.findServer.bind(this),
- }));
+ this.initializedClusters.push(
+ manager.init({
+ add: this.addServer.bind(this),
+ remove: this.removeServer.bind(this),
+ find: this.findServer.bind(this),
+ }),
+ );
}
}
}
@@ -190,8 +193,10 @@ export class ClusteredRedisQueue implements IMessageQueue,
public async start(): Promise {
this.state.started = true;
- return await this.batch('start',
- 'Starting clustered redis message queue...');
+ return await this.batch(
+ 'start',
+ 'Starting clustered redis message queue...',
+ );
}
/**
@@ -203,8 +208,10 @@ export class ClusteredRedisQueue implements IMessageQueue,
public async stop(): Promise {
this.state.started = false;
- return await this.batch('stop',
- 'Stopping clustered redis message queue...');
+ return await this.batch(
+ 'stop',
+ 'Stopping clustered redis message queue...',
+ );
}
/**
@@ -226,17 +233,13 @@ export class ClusteredRedisQueue implements IMessageQueue,
errorHandler?: (err: Error) => void,
): Promise {
if (!this.imqLength) {
- return await new Promise(resolve => this.clusterEmitter.once(
- 'initialized',
- async ({ imq }) => {
- resolve(await imq.send(
- toQueue,
- message,
- delay,
- errorHandler,
- ));
- },
- ));
+ return await new Promise(resolve =>
+ this.clusterEmitter.once('initialized', async ({ imq }) => {
+ resolve(
+ await imq.send(toQueue, message, delay, errorHandler),
+ );
+ }),
+ );
}
if (this.currentQueue >= this.imqLength) {
@@ -261,8 +264,10 @@ export class ClusteredRedisQueue implements IMessageQueue,
public async destroy(): Promise {
this.state.started = false;
- await this.batch('destroy',
- 'Destroying clustered redis message queue...');
+ await this.batch(
+ 'destroy',
+ 'Destroying clustered redis message queue...',
+ );
if (!this.options.clusterManagers?.length) {
return;
@@ -283,8 +288,10 @@ export class ClusteredRedisQueue implements IMessageQueue,
* @returns {Promise}
*/
public async clear(): Promise {
- return await this.batch('clear',
- 'Clearing clustered redis message queue...');
+ return await this.batch(
+ 'clear',
+ 'Clearing clustered redis message queue...',
+ );
}
public async queueLength(): Promise {
@@ -301,9 +308,9 @@ export class ClusteredRedisQueue implements IMessageQueue,
private verbose(message: string): void {
if (this.options.verbose) {
- this.logger.info(`[IMQ-CORE][ClusteredRedisQueue][${
- this.name
- }]: ${ message }`);
+ this.logger.info(
+ `[IMQ-CORE][ClusteredRedisQueue][${this.name}]: ${message}`,
+ );
}
}
@@ -329,136 +336,134 @@ export class ClusteredRedisQueue implements IMessageQueue,
return this;
}
- /* tslint:disable */
// EventEmitter interface
// istanbul ignore next
- public on(...args: any[]) {
+ /**
+ * Applies the named EventEmitter method to every underlying emitter,
+ * forwarding the call across the whole cluster. Dispatch is reflective
+ * (method chosen by name), so a single contained cast bridges the dynamic
+ * call while the public method signatures below stay fully typed.
+ *
+ * @param {K} method
+ * @param {any[]} args
+ * @return {unknown[]}
+ */
+ private applyToEmitters(
+ method: K,
+ args: any[],
+ ): unknown[] {
+ const results: unknown[] = [];
+
for (const imq of this.eventEmitters()) {
- imq.on.apply(imq, args);
+ const fn = imq[method] as unknown as (...a: any[]) => unknown;
+
+ results.push(fn.apply(imq, args));
}
- return this;
+ return results;
}
// istanbul ignore next
- // noinspection JSUnusedGlobalSymbols
- public off(...args: any[]) {
- for (const imq of this.eventEmitters()) {
- imq.off.apply(imq, args);
- }
+ public on(...args: any[]): this {
+ this.applyToEmitters('on', args);
return this;
}
// istanbul ignore next
- public once(...args: any[]) {
- for (const imq of this.eventEmitters()) {
- imq.once.apply(imq, args);
- }
+ // noinspection JSUnusedGlobalSymbols
+ public off(...args: any[]): this {
+ this.applyToEmitters('off', args);
return this;
}
// istanbul ignore next
- public addListener(...args: any[]) {
- for (const imq of this.eventEmitters()) {
- imq.addListener.apply(imq, args);
- }
+ public once(...args: any[]): this {
+ this.applyToEmitters('once', args);
return this;
}
// istanbul ignore next
- public removeListener(...args: any[]) {
- for (const imq of this.eventEmitters()) {
- imq.removeListener.apply(imq, args);
- }
+ public addListener(...args: any[]): this {
+ this.applyToEmitters('addListener', args);
return this;
}
// istanbul ignore next
- public removeAllListeners(...args: any[]) {
- for (const imq of this.eventEmitters()) {
- imq.removeAllListeners.apply(imq, args);
- }
+ public removeListener(...args: any[]): this {
+ this.applyToEmitters('removeListener', args);
return this;
}
// istanbul ignore next
- public prependListener(...args: any[]) {
- for (const imq of this.eventEmitters()) {
- imq.prependListener.apply(imq, args);
- }
+ public removeAllListeners(...args: any[]): this {
+ this.applyToEmitters('removeAllListeners', args);
return this;
}
// istanbul ignore next
- public prependOnceListener(...args: any[]) {
- for (const imq of this.eventEmitters()) {
- imq.prependOnceListener.apply(imq, args);
- }
+ public prependListener(...args: any[]): this {
+ this.applyToEmitters('prependListener', args);
return this;
}
// istanbul ignore next
- public setMaxListeners(...args: any[]) {
- for (const imq of this.eventEmitters()) {
- imq.setMaxListeners.apply(imq, args);
- }
+ public prependOnceListener(...args: any[]): this {
+ this.applyToEmitters('prependOnceListener', args);
return this;
}
// istanbul ignore next
- public listeners(...args: any[]) {
- let listeners: any[] = [];
-
- for (const imq of this.eventEmitters()) {
- listeners = listeners.concat(imq.listeners.apply(imq, args));
- }
+ public setMaxListeners(...args: any[]): this {
+ this.applyToEmitters('setMaxListeners', args);
- return listeners;
+ return this;
}
// istanbul ignore next
- public rawListeners(...args: any[]) {
- let rawListeners: any[] = [];
-
- for (const imq of this.eventEmitters()) {
- rawListeners = rawListeners.concat(
- imq.rawListeners.apply(imq, args),
- );
- }
+ // Aggregates listeners across every underlying emitter, so the result is
+ // an untyped union rather than Node's per-emitter conditional listener type.
+ public listeners(...args: any[]): any[] {
+ return this.applyToEmitters('listeners', args).flat();
+ }
- return rawListeners;
+ // istanbul ignore next
+ public rawListeners(...args: any[]): any[] {
+ return this.applyToEmitters('rawListeners', args).flat();
}
// istanbul ignore next
- public getMaxListeners() {
+ public getMaxListeners(): number {
return this.templateEmitter.getMaxListeners();
}
// istanbul ignore next
- public emit(...args: any[]) {
- for (const imq of this.eventEmitters()) {
- imq.emit.apply(imq, args);
- }
+ public emit(...args: any[]): boolean {
+ this.applyToEmitters('emit', args);
return true;
}
// istanbul ignore next
- public eventNames(...args: any[]) {
- return this.templateEmitter.eventNames.apply(this.imqs[0], args);
+ public eventNames(): (keyof EventMap)[] {
+ const source = this.imqs[0] || this.templateEmitter;
+
+ return source.eventNames() as (keyof EventMap)[];
}
// istanbul ignore next
- public listenerCount(...args: any[]) {
- return this.templateEmitter.listenerCount.apply(this.imqs[0], args);
+ public listenerCount(...args: any[]): number {
+ const source = this.imqs[0] || this.templateEmitter;
+ const fn = source.listenerCount as (...a: any[]) => number;
+
+ return fn.apply(source, args);
}
// istanbul ignore next
@@ -508,7 +513,7 @@ export class ClusteredRedisQueue implements IMessageQueue,
* @returns {void}
*/
protected addServer(server: IServerInput): ClusterServer {
- this.verbose(`Adding new server: ${ JSON.stringify(server) }`);
+ this.verbose(`Adding new server: ${JSON.stringify(server)}`);
return this.addServerWithQueueInitializing(server, true);
}
@@ -520,7 +525,7 @@ export class ClusteredRedisQueue implements IMessageQueue,
* @returns {void}
*/
protected removeServer(server: IServerInput): void {
- this.verbose(`Removing the server: ${ JSON.stringify(server) }`);
+ this.verbose(`Removing the server: ${JSON.stringify(server)}`);
const remove = this.findServer(server);
@@ -532,17 +537,14 @@ export class ClusteredRedisQueue implements IMessageQueue,
if (imqToRemove) {
this.imqs = this.imqs.filter(
- imq => imqToRemove.redisKey !== imq.redisKey
+ imq => imqToRemove.redisKey !== imq.redisKey,
);
imqToRemove.destroy().catch();
}
this.imqLength = this.imqs.length;
this.servers = this.servers.filter(
- existing => !ClusteredRedisQueue.matchServers(
- existing,
- server,
- ),
+ existing => !ClusteredRedisQueue.matchServers(existing, server),
);
this.clusterEmitter.emit('remove', {
server: remove,
@@ -594,12 +596,12 @@ export class ClusteredRedisQueue implements IMessageQueue,
private async initializeQueue(imq: RedisQueue): Promise {
copyEventEmitter(this.templateEmitter, imq);
- this.verbose(`Initializing queue with state: ${
- JSON.stringify(this.state)
- }`);
+ this.verbose(
+ `Initializing queue with state: ${JSON.stringify(this.state)}`,
+ );
if (this.state.started) {
- await imq.start();
+ await imq.start();
}
if (this.state.subscription) {
@@ -611,11 +613,8 @@ export class ClusteredRedisQueue implements IMessageQueue,
}
private findServer(server: IServerInput): ClusterServer | undefined {
- return this.servers.find(
- existing => ClusteredRedisQueue.matchServers(
- existing,
- server,
- ),
+ return this.servers.find(existing =>
+ ClusteredRedisQueue.matchServers(existing, server),
);
}
@@ -623,8 +622,8 @@ export class ClusteredRedisQueue implements IMessageQueue,
source: IServerInput,
target: IServerInput,
): boolean {
- const sameAddress = target.host === source.host
- && target.port === source.port;
+ const sameAddress =
+ target.host === source.host && target.port === source.port;
if (!target.id && !source.id) {
return sameAddress;
diff --git a/src/IMessageQueue.ts b/src/IMessageQueue.ts
index 51d714b..032136f 100644
--- a/src/IMessageQueue.ts
+++ b/src/IMessageQueue.ts
@@ -30,8 +30,14 @@ export { EventEmitter } from 'events';
/**
* Represents any JSON-serializable value
*/
-export type AnyJson = boolean | number | string | null | undefined |
- JsonArray | JsonObject;
+export type AnyJson =
+ | boolean
+ | number
+ | string
+ | null
+ | undefined
+ | JsonArray
+ | JsonObject;
/**
* Represents JSON serializable object
@@ -200,7 +206,7 @@ export interface IMQOptions extends Partial {
*
* @type {string}
*/
- cleanupFilter: string,
+ cleanupFilter: string;
/**
* Message queue vendor
@@ -296,8 +302,8 @@ export interface IMQOptions extends Partial {
}
export interface EventMap {
- message: [data: any, id: string, from: string],
- error: [error: Error, eventName: string],
+ message: [data: any, id: string, from: string];
+ error: [error: Error, eventName: string];
}
export type IMessageQueueConstructor = new (
@@ -387,8 +393,12 @@ export interface IMessageQueue extends EventEmitter {
* internal error occurs during message send execution.
* @returns {Promise} - message identifier
*/
- send(toQueue: string, message: JsonObject, delay?: number,
- errorHandler?: (err: Error) => void): Promise;
+ send(
+ toQueue: string,
+ message: JsonObject,
+ delay?: number,
+ errorHandler?: (err: Error) => void,
+ ): Promise;
/**
* Creates or uses subscription channel with the given name and sets
diff --git a/src/RedisQueue.ts b/src/RedisQueue.ts
index 7b54c27..a3da54d 100644
--- a/src/RedisQueue.ts
+++ b/src/RedisQueue.ts
@@ -116,9 +116,10 @@ const IMQ_REDIS_MAX_LISTENERS_LIMIT = +(
* Class RedisQueue
* Implements simple messaging queue over redis.
*/
-export class RedisQueue extends EventEmitter
- implements IMessageQueue {
-
+export class RedisQueue
+ extends EventEmitter
+ implements IMessageQueue
+{
[name: string]: any;
/**
@@ -205,8 +206,8 @@ export class RedisQueue extends EventEmitter
* Internal per-channel reconnection state
*/
private reconnectTimers: Partial> = {};
- private reconnectAttempts: Partial>
- = {};
+ private reconnectAttempts: Partial> =
+ {};
private reconnecting: Partial> = {};
/**
@@ -220,21 +221,21 @@ export class RedisQueue extends EventEmitter
* @type {{moveDelayed: {code: string}}}
*/
// tslint:disable-next-line:completed-docs
- private scripts: { [name: string]: { code: string, checksum?: string } } = {
+ private scripts: { [name: string]: { code: string; checksum?: string } } = {
moveDelayed: {
code:
'local messages = redis.call(' +
- '"zrangebyscore", KEYS[1], "-inf", ARGV[1]) ' +
+ '"zrangebyscore", KEYS[1], "-inf", ARGV[1]) ' +
'local count = table.getn(messages) ' +
'local message ' +
'local i = 1 ' +
'if count > 0 then ' +
- 'while messages[i] do ' +
- 'redis.call("lpush", KEYS[2], messages[i]) ' +
- 'i = i + 1 ' +
- 'end ' +
- 'redis.call("zremrangebyscore", KEYS[1], ' +
- '"-inf", ARGV[1]) ' +
+ 'while messages[i] do ' +
+ 'redis.call("lpush", KEYS[2], messages[i]) ' +
+ 'i = i + 1 ' +
+ 'end ' +
+ 'redis.call("zremrangebyscore", KEYS[1], ' +
+ '"-inf", ARGV[1]) ' +
'end ' +
'return count',
},
@@ -269,10 +270,7 @@ export class RedisQueue extends EventEmitter
) {
super();
- this.options = buildOptions(
- DEFAULT_IMQ_OPTIONS,
- options,
- );
+ this.options = buildOptions(DEFAULT_IMQ_OPTIONS, options);
/* tslint:disable */
this.pack = this.options.useGzip ? pack : JSON.stringify;
@@ -280,19 +278,22 @@ export class RedisQueue extends EventEmitter
/* tslint:enable */
this.redisKey = `${this.options.host}:${this.options.port}`;
- this.verbose(`Initializing queue on ${
- this.options.host }:${
- this.options.port} with prefix ${
- this.options.prefix } and safeDelivery = ${
- this.options.safeDelivery }, and safeDeliveryTtl = ${
- this.options.safeDeliveryTtl }, and watcherCheckDelay = ${
- this.options.watcherCheckDelay }, and useGzip = ${
- this.options.useGzip }`);
+ this.verbose(
+ `Initializing queue on ${this.options.host}:${
+ this.options.port
+ } with prefix ${this.options.prefix} and safeDelivery = ${
+ this.options.safeDelivery
+ }, and safeDeliveryTtl = ${
+ this.options.safeDeliveryTtl
+ }, and watcherCheckDelay = ${
+ this.options.watcherCheckDelay
+ }, and useGzip = ${this.options.useGzip}`,
+ );
}
private verbose(message: string): void {
if (this.options.verbose) {
- this.logger.info(`[IMQ-CORE][${ this.name }]: ${ message }`);
+ this.logger.info(`[IMQ-CORE][${this.name}]: ${message}`);
}
}
@@ -311,7 +312,7 @@ export class RedisQueue extends EventEmitter
// istanbul ignore next
if (!channel) {
throw new TypeError(
- `${ channel }: No subscription channel name provided!`,
+ `${channel}: No subscription channel name provided!`,
);
}
@@ -319,7 +320,8 @@ export class RedisQueue extends EventEmitter
if (this.subscriptionName && this.subscriptionName !== channel) {
throw new TypeError(
`Invalid channel name provided: expected "${
- this.subscriptionName}", but "${ channel }" given instead!`,
+ this.subscriptionName
+ }", but "${channel}" given instead!`,
);
} else if (!this.subscriptionName) {
this.subscriptionName = channel;
@@ -336,13 +338,14 @@ export class RedisQueue extends EventEmitter
handler(JSON.parse(message) as unknown as JsonObject);
}
- this.verbose(`Received message from ${
- ch } channel, data: ${
- JSON.stringify(message) }`,
+ this.verbose(
+ `Received message from ${ch} channel, data: ${JSON.stringify(
+ message,
+ )}`,
);
});
- this.verbose(`Subscribed to ${ channel } channel`);
+ this.verbose(`Subscribed to ${channel} channel`);
}
/**
@@ -360,15 +363,16 @@ export class RedisQueue extends EventEmitter
`${this.options.prefix}:${this.subscriptionName}`,
);
- this.verbose(`Unsubscribed from ${
- this.subscriptionName } channel`);
+ this.verbose(
+ `Unsubscribed from ${this.subscriptionName} channel`,
+ );
}
this.subscription.removeAllListeners();
this.subscription.quit();
this.subscription.disconnect(false);
} catch (error) {
- this.verbose(`Unsubscribe error: ${ error }`);
+ this.verbose(`Unsubscribe error: ${error}`);
}
}
@@ -395,14 +399,9 @@ export class RedisQueue extends EventEmitter
const jsonData = JSON.stringify(data);
const name = toName || this.name;
- await this.writer.publish(
- `${this.options.prefix}:${ name }`,
- jsonData,
- );
+ await this.writer.publish(`${this.options.prefix}:${name}`, jsonData);
- this.verbose(`Published message to ${
- name } channel, data: ${
- jsonData }
+ this.verbose(`Published message to ${name} channel, data: ${jsonData}
`);
}
@@ -413,7 +412,7 @@ export class RedisQueue extends EventEmitter
*/
public async start(): Promise {
if (!this.name) {
- throw new TypeError(`${ this.name }: No queue name provided!`);
+ throw new TypeError(`${this.name}: No queue name provided!`);
}
if (this.initialized) {
@@ -446,8 +445,8 @@ export class RedisQueue extends EventEmitter
let exitCode = 0;
setTimeout(() => {
- this.verbose(`Shutting down after ${
- IMQ_SHUTDOWN_TIMEOUT } timeout`,
+ this.verbose(
+ `Shutting down after ${IMQ_SHUTDOWN_TIMEOUT} timeout`,
);
process.exit(exitCode);
}, IMQ_SHUTDOWN_TIMEOUT);
@@ -512,7 +511,7 @@ export class RedisQueue extends EventEmitter
const cb = (error: any, op: string) => {
// istanbul ignore next
if (error) {
- this.verbose(`Writer ${ op } error: ${ error }`);
+ this.verbose(`Writer ${op} error: ${error}`);
if (errorHandler) {
errorHandler(error as unknown as Error);
@@ -521,7 +520,10 @@ export class RedisQueue extends EventEmitter
};
if (delay) {
- this.writer.zadd(`${key}:delayed`, Date.now() + delay, packet,
+ this.writer.zadd(
+ `${key}:delayed`,
+ Date.now() + delay,
+ packet,
(err: any) => {
// istanbul ignore next
if (err) {
@@ -530,17 +532,25 @@ export class RedisQueue extends EventEmitter
return;
}
- this.writer.set(`${key}:${id}:ttl`, '', 'PX', delay, 'NX',
- (err: any) => {
- // istanbul ignore next
- if (err) {
- cb(err, 'SET');
-
- return;
- }
- },
- ).catch((err: any) => cb(err, 'SET'));
- });
+ this.writer
+ .set(
+ `${key}:${id}:ttl`,
+ '',
+ 'PX',
+ delay,
+ 'NX',
+ (err: any) => {
+ // istanbul ignore next
+ if (err) {
+ cb(err, 'SET');
+
+ return;
+ }
+ },
+ )
+ .catch((err: any) => cb(err, 'SET'));
+ },
+ );
} else {
this.writer.lpush(key, packet, (err: any) => {
// istanbul ignore next
@@ -613,7 +623,7 @@ export class RedisQueue extends EventEmitter
await Promise.all([
this.writer.del(this.key),
- this.writer.del(`${ this.key }:delayed`),
+ this.writer.del(`${this.key}:delayed`),
]);
this.verbose('Expired queue keys cleared!');
@@ -621,8 +631,9 @@ export class RedisQueue extends EventEmitter
// istanbul ignore next
if (this.initialized) {
this.logger.error(
- `${ context.name }: error clearing the redis queue host ${
- this.redisKey } on writer, pid ${ process.pid }:`,
+ `${this.name}: error clearing the redis queue host ${
+ this.redisKey
+ } on writer, pid ${process.pid}:`,
err,
);
}
@@ -721,7 +732,7 @@ export class RedisQueue extends EventEmitter
* @returns {string}
*/
private get lockKey(): string {
- return `${ this.options.prefix }:watch:lock`;
+ return `${this.options.prefix}:watch:lock`;
}
/**
@@ -779,13 +790,16 @@ export class RedisQueue extends EventEmitter
if (client) {
try {
client.removeAllListeners();
- client.quit().then(() => {
- client.disconnect(false);
- }).catch(e => {
- this.verbose(`Error quitting ${ channel }: ${ e }`);
- });
+ client
+ .quit()
+ .then(() => {
+ client.disconnect(false);
+ })
+ .catch(e => {
+ this.verbose(`Error quitting ${channel}: ${e}`);
+ });
} catch (error) {
- this.verbose(`Error destroying ${ channel }: ${ error }`);
+ this.verbose(`Error destroying ${channel}: ${error}`);
}
}
}
@@ -804,7 +818,7 @@ export class RedisQueue extends EventEmitter
options: IMQOptions,
context: RedisQueue = this,
): Promise {
- this.verbose(`Connecting to ${ channel } channel...`);
+ this.verbose(`Connecting to ${channel} channel...`);
// istanbul ignore next
if (context[channel]) {
@@ -846,7 +860,7 @@ export class RedisQueue extends EventEmitter
'close',
]) {
redis.on(event, () => {
- context.verbose(`Redis Event fired: ${ event }`);
+ context.verbose(`Redis Event fired: ${event}`);
});
}
@@ -858,13 +872,22 @@ export class RedisQueue extends EventEmitter
this.logger.info(
'%s: %s channel connected, host %s, pid %s',
- context.name, channel, this.redisKey, process.pid,
+ context.name,
+ channel,
+ this.redisKey,
+ process.pid,
);
switch (channel) {
- case 'reader': this.read(); break;
- case 'writer': await this.processDelayed(this.key); break;
- case 'watcher': await this.initWatcher(); break;
+ case 'reader':
+ this.read();
+ break;
+ case 'writer':
+ await this.processDelayed(this.key);
+ break;
+ case 'watcher':
+ await this.initWatcher();
+ break;
}
return context[channel];
@@ -905,8 +928,11 @@ export class RedisQueue extends EventEmitter
this.reconnecting[channel] = true;
this.reconnectAttempts[channel] = attempts;
- this.verbose(`Scheduling ${ channel } reconnect in ${
- delay } ms (attempt ${ attempts })`);
+ this.verbose(
+ `Scheduling ${channel} reconnect in ${
+ delay
+ } ms (attempt ${attempts})`,
+ );
if (this.reconnectTimers[channel]) {
clearTimeout(this.reconnectTimers[channel] as any);
@@ -948,10 +974,10 @@ export class RedisQueue extends EventEmitter
this.reconnectTimers[channel] = undefined;
}
- this.verbose(`Reconnected ${ channel } channel`);
+ this.verbose(`Reconnected ${channel} channel`);
} catch (err) {
this.reconnecting[channel] = false;
- this.verbose(`Reconnect ${ channel } failed: ${ err }`);
+ this.verbose(`Reconnect ${channel} failed: ${err}`);
this.scheduleReconnect(channel);
}
}, delay);
@@ -971,9 +997,9 @@ export class RedisQueue extends EventEmitter
prefix: string,
name: RedisConnectionChannel,
): string {
- const uniqueSuffix = `pid:${ process.pid }:host:${ os.hostname() }`;
+ const uniqueSuffix = `pid:${process.pid}:host:${os.hostname()}`;
- return`${ prefix }:${ contextName }:${ name }:${ uniqueSuffix }`;
+ return `${prefix}:${contextName}:${name}:${uniqueSuffix}`;
}
/**
@@ -989,8 +1015,8 @@ export class RedisQueue extends EventEmitter
channel: RedisConnectionChannel,
): (error: Error) => void {
// istanbul ignore next
- return ((error: Error & { code: string }) => {
- this.verbose(`Redis Error: ${ error }`);
+ return (error: Error & { code?: string }) => {
+ this.verbose(`Redis Error: ${error}`);
if (this.destroyed) {
return;
@@ -998,8 +1024,8 @@ export class RedisQueue extends EventEmitter
this.logger.error(
`${context.name}: error connecting redis host ${
- this.redisKey} on ${
- channel}, pid ${process.pid}:`,
+ this.redisKey
+ } on ${channel}, pid ${process.pid}:`,
error,
);
@@ -1010,7 +1036,7 @@ export class RedisQueue extends EventEmitter
) {
this.scheduleReconnect(channel);
}
- });
+ };
}
/**
@@ -1025,21 +1051,24 @@ export class RedisQueue extends EventEmitter
context: RedisQueue,
channel: RedisConnectionChannel,
): (...args: any[]) => any {
- this.verbose(`Redis ${ channel } is closing...`);
+ this.verbose(`Redis ${channel} is closing...`);
// istanbul ignore next
- return (() => {
+ return () => {
this.initialized = false;
this.logger.warn(
'%s: redis connection %s closed on host %s, pid %s!',
- context.name, channel, this.redisKey, process.pid,
+ context.name,
+ channel,
+ this.redisKey,
+ process.pid,
);
if (!this.destroyed) {
this.scheduleReconnect(channel);
}
- });
+ };
}
/**
@@ -1111,7 +1140,10 @@ export class RedisQueue extends EventEmitter
if (this.scripts.moveDelayed.checksum) {
await this.writer.evalsha(
this.scripts.moveDelayed.checksum,
- 2, `${ key }:delayed`, key, Date.now(),
+ 2,
+ `${key}:delayed`,
+ key,
+ Date.now(),
);
}
} catch (err) {
@@ -1139,14 +1171,14 @@ export class RedisQueue extends EventEmitter
const data = await this.writer.scan(
cursor,
'MATCH',
- `${ this.options.prefix }:*:worker:*`,
+ `${this.options.prefix}:*:worker:*`,
'COUNT',
'1000',
);
cursor = data.shift() as string;
- const keys = data.shift() as string[] || [];
+ const keys = (data.shift() as string[]) || [];
await this.processKeys(keys, now);
@@ -1180,9 +1212,11 @@ export class RedisQueue extends EventEmitter
return;
}
- this.verbose(`Watching ${ keys.length } keys: ${
- keys.map(key => `"${ key }"`).join(', ')
- }`);
+ this.verbose(
+ `Watching ${keys.length} keys: ${keys
+ .map(key => `"${key}"`)
+ .join(', ')}`,
+ );
for (const key of keys) {
const kp: string[] = key.split(':');
@@ -1191,7 +1225,7 @@ export class RedisQueue extends EventEmitter
continue;
}
- await this.writer.rpoplpush(key, `${ kp.shift() }:${ kp.shift() }`);
+ await this.writer.rpoplpush(key, `${kp.shift()}:${kp.shift()}`);
}
}
@@ -1215,11 +1249,7 @@ export class RedisQueue extends EventEmitter
await this.processDelayed(key.join(':'));
} catch (err) {
- this.emitError(
- 'OnWatch',
- 'watch error',
- err as unknown as Error,
- );
+ this.emitError('OnWatch', 'watch error', err as unknown as Error);
}
}
@@ -1249,17 +1279,15 @@ export class RedisQueue extends EventEmitter
}
try {
- this.writer.config(
- 'SET',
- 'notify-keyspace-events',
- 'Ex',
- ).catch(err => {
- this.emitError(
- 'OnConfig',
- 'events config error',
- err as unknown as Error,
- );
- });
+ this.writer
+ .config('SET', 'notify-keyspace-events', 'Ex')
+ .catch(err => {
+ this.emitError(
+ 'OnConfig',
+ 'events config error',
+ err as unknown as Error,
+ );
+ });
} catch (err) {
this.emitError(
'OnConfig',
@@ -1272,12 +1300,14 @@ export class RedisQueue extends EventEmitter
'pmessage',
this.onWatchMessage.bind(this) as unknown as () => void,
);
- this.watcher.psubscribe(
- '__keyevent@0__:expired',
- `${ this.options.prefix }:delayed:*`,
- ).catch(err => {
- this.verbose(`Error subscribing to watcher channel: ${ err }`);
- });
+ this.watcher
+ .psubscribe(
+ '__keyevent@0__:expired',
+ `${this.options.prefix}:delayed:*`,
+ )
+ .catch(err => {
+ this.verbose(`Error subscribing to watcher channel: ${err}`);
+ });
// watch for expired unhandled safe queues
if (!this.safeCheckInterval) {
@@ -1287,7 +1317,7 @@ export class RedisQueue extends EventEmitter
if (!this.writer) {
this.cleanSafeCheckInterval();
- return ;
+ return;
}
if (this.options.safeDelivery) {
@@ -1295,7 +1325,9 @@ export class RedisQueue extends EventEmitter
}
await this.processCleanup();
- }) as unknown as () => void, this.options.safeDeliveryTtl);
+ }) as unknown as () => void,
+ this.options.safeDeliveryTtl,
+ );
}
}
@@ -1319,40 +1351,42 @@ export class RedisQueue extends EventEmitter
}
const filter: RegExp = new RegExp(
- this.options.prefix + ':' +
- (this.options.cleanupFilter || '*').replace(/\*/g, '.*'),
+ this.options.prefix +
+ ':' +
+ (this.options.cleanupFilter || '*').replace(/\*/g, '.*'),
'i',
);
- this.verbose(`Cleaning up keys matching ${ filter }`);
+ this.verbose(`Cleaning up keys matching ${filter}`);
- const clients: string = (await this.writer.client(
- 'LIST',
- ) as string).toString() || '';
+ const clients: string =
+ ((await this.writer.client('LIST')) as string).toString() || '';
const connectedKeys = (clients.match(RX_CLIENT_NAME) || [])
- .filter((name: string) =>
- RX_CLIENT_TEST.test(name) && filter.test(name),
+ .filter(
+ (name: string) =>
+ RX_CLIENT_TEST.test(name) && filter.test(name),
)
- .map((name: string) => name
- .replace(/^name=/, '')
- .replace(RX_CLIENT_CLEAN, ''),
+ .map((name: string) =>
+ name.replace(/^name=/, '').replace(RX_CLIENT_CLEAN, ''),
)
- .filter((name: string, i: number, a: string[]) =>
- a.indexOf(name) === i,
+ .filter(
+ (name: string, i: number, a: string[]) =>
+ a.indexOf(name) === i,
);
const keysToRemove: string[] = [];
let cursor = '0';
- this.verbose(`Found connected keys: ${
- connectedKeys.map(k => `"${ k }"`).join(', ')
- }`);
+ this.verbose(
+ `Found connected keys: ${connectedKeys
+ .map(k => `"${k}"`)
+ .join(', ')}`,
+ );
while (true) {
const data = await this.writer.scan(
cursor,
'MATCH',
- `${
- this.options.prefix}:${
+ `${this.options.prefix}:${
this.options.cleanupFilter || '*'
}`,
'COUNT',
@@ -1361,11 +1395,12 @@ export class RedisQueue extends EventEmitter
cursor = data.shift() as string;
- const keys = data.shift() as string[] || [];
+ const keys = (data.shift() as string[]) || [];
keysToRemove.push(
...keys.filter(
- key => key !== this.lockKey &&
+ key =>
+ key !== this.lockKey &&
connectedKeys.every(
connectedKey =>
key.indexOf(connectedKey) === -1,
@@ -1374,16 +1409,18 @@ export class RedisQueue extends EventEmitter
);
if (cursor === '0') {
- break ;
+ break;
}
}
if (keysToRemove.length) {
await this.writer.del(...keysToRemove);
- this.verbose(`Keys ${
- keysToRemove.map(k => `"${ k }"`).join(', ')
- } were successfully removed!`);
+ this.verbose(
+ `Keys ${keysToRemove
+ .map(k => `"${k}"`)
+ .join(', ')} were successfully removed!`,
+ );
}
} catch (err) {
this.logger.warn('Clean-up error occurred:', err);
@@ -1413,8 +1450,10 @@ export class RedisQueue extends EventEmitter
}
} catch (err) {
// istanbul ignore next
- // eslint-disable-next-line @typescript-eslint/no-unsafe-call
- if (err.message.match(/Stream connection ended/)) {
+ if (
+ err instanceof Error &&
+ err.message.match(/Stream connection ended/)
+ ) {
break;
}
@@ -1442,8 +1481,8 @@ export class RedisQueue extends EventEmitter
const key = this.key;
while (true) {
- const expire: number = Date.now() +
- Number(this.options.safeDeliveryTtl);
+ const expire: number =
+ Date.now() + Number(this.options.safeDeliveryTtl);
const workerKey = `${key}:worker:${uuid()}:${expire}`;
if (!this.reader || !this.writer) {
@@ -1458,9 +1497,7 @@ export class RedisQueue extends EventEmitter
break;
}
- const msgArr: any = await this.writer.lrange(
- workerKey, -1, 1,
- );
+ const msgArr: any = await this.writer.lrange(workerKey, -1, 1);
if (!msgArr || msgArr?.length !== 1) {
// noinspection ExceptionCaughtLocallyJS
@@ -1471,8 +1508,9 @@ export class RedisQueue extends EventEmitter
const [msg] = msgArr as any[];
this.process([key, msg]);
- this.writer.del(workerKey).catch(e =>
- this.logger.warn('OnReadSafe: del error', e));
+ this.writer
+ .del(workerKey)
+ .catch(e => this.logger.warn('OnReadSafe: del error', e));
}
} catch (err) {
// istanbul ignore next
@@ -1494,7 +1532,8 @@ export class RedisQueue extends EventEmitter
if (!this.reader) {
this.logger.error(
`${this.name}: reader connection is not initialized, pid ${
- process.pid} on redis host ${this.redisKey}!`,
+ process.pid
+ } on redis host ${this.redisKey}!`,
);
return this;
@@ -1564,13 +1603,16 @@ export class RedisQueue extends EventEmitter
private emitError(eventName: string, message: string, err: Error): void {
this.emit('error', err, eventName);
this.logger.error(
- `${this.name}: ${ message }, pid ${
- process.pid } on redis host ${ this.redisKey }:`,
+ `${this.name}: ${message}, pid ${
+ process.pid
+ } on redis host ${this.redisKey}:`,
err,
);
- this.verbose(`Error in event ${
- eventName }: ${ message }, pid ${
- process.pid } on redis host ${ this.redisKey }: ${ err }`);
+ this.verbose(
+ `Error in event ${eventName}: ${message}, pid ${
+ process.pid
+ } on redis host ${this.redisKey}: ${err}`,
+ );
}
/**
@@ -1591,10 +1633,10 @@ export class RedisQueue extends EventEmitter
this.scripts[script].checksum = checksum;
- const scriptExists = await this.writer.script(
+ const scriptExists = (await this.writer.script(
'EXISTS',
checksum,
- ) as number[];
+ )) as number[];
const loaded = (scriptExists || []).shift();
if (!loaded) {
@@ -1631,11 +1673,11 @@ export class RedisQueue extends EventEmitter
resolve: (...args: any[]) => void,
reject: (...args: any[]) => void,
): () => Promise {
- return (async () => {
+ return async () => {
try {
- const noWatcher = !await this.watcherCount();
+ const noWatcher = !(await this.watcherCount());
- if (await this.isLocked() && noWatcher) {
+ if ((await this.isLocked()) && noWatcher) {
await this.unlock();
await this.ownWatch();
}
@@ -1644,7 +1686,7 @@ export class RedisQueue extends EventEmitter
} catch (err) {
reject(err);
}
- });
+ };
}
/**
@@ -1655,13 +1697,10 @@ export class RedisQueue extends EventEmitter
*/
// istanbul ignore next
private async initWatcher(): Promise {
- return new Promise(
- (async (
- resolve: (...args: any[]) => void,
- reject: (...args: any[]) => void,
- ): Promise => {
+ return new Promise((resolve, reject) => {
+ void (async (): Promise => {
try {
- if (!await this.watcherCount()) {
+ if (!(await this.watcherCount())) {
this.verbose('Initializing watcher...');
await this.ownWatch();
@@ -1683,17 +1722,15 @@ export class RedisQueue extends EventEmitter
}
} catch (err) {
this.logger.error(
- `${ this.name }: error initializing watcher, pid ${
- process.pid } on redis host ${ this.redisKey }`,
+ `${this.name}: error initializing watcher, pid ${
+ process.pid
+ } on redis host ${this.redisKey}`,
err,
);
reject(err);
}
- }) as unknown as (
- resolve: (...args: any[]) => void,
- reject: (...args: any[]) => void,
- ) => void,
- );
+ })();
+ });
}
}
diff --git a/src/UDPClusterManager.ts b/src/UDPClusterManager.ts
index 73bb159..8379e5a 100644
--- a/src/UDPClusterManager.ts
+++ b/src/UDPClusterManager.ts
@@ -87,15 +87,15 @@ export class UDPClusterManager extends ClusterManager {
private static workers: Record = {};
public static sockets: Record = {};
private readonly options: UDPClusterManagerOptions;
- private workerKey: string;
- private worker: Worker;
+ private workerKey!: string;
+ private worker!: Worker;
constructor(options?: Partial) {
super();
this.options = {
...DEFAULT_UDP_CLUSTER_MANAGER_OPTIONS,
- ...options || {},
+ ...options,
};
this.startWorkerListener();
@@ -108,16 +108,18 @@ export class UDPClusterManager extends ClusterManager {
private static async free(): Promise {
const workerKeys = Object.keys(UDPClusterManager.workers);
- await Promise.all(workerKeys.map(
- workerKey => UDPClusterManager.destroyWorker(
- workerKey,
- UDPClusterManager.workers[workerKey],
- )),
+ await Promise.all(
+ workerKeys.map(workerKey =>
+ UDPClusterManager.destroyWorker(
+ workerKey,
+ UDPClusterManager.workers[workerKey],
+ ),
+ ),
);
}
private startWorkerListener(): void {
- this.workerKey = `${ this.options.address }:${ this.options.port }`;
+ this.workerKey = `${this.options.address}:${this.options.port}`;
if (UDPClusterManager.workers[this.workerKey]) {
this.worker = UDPClusterManager.workers[this.workerKey];
@@ -129,7 +131,7 @@ export class UDPClusterManager extends ClusterManager {
workerData: this.options,
});
this.worker.on('message', message => {
- const [className, method] = message.type?.split(':');
+ const [className, method] = (message.type ?? '').split(':');
if (className !== 'cluster') {
return;
@@ -138,16 +140,21 @@ export class UDPClusterManager extends ClusterManager {
return this.anyCluster(cluster => {
if (method === 'add') {
try {
- const existing = typeof (cluster as any).find === 'function'
- ? (cluster as any).find(message.server, true)
- : undefined;
+ const existing =
+ typeof (cluster as any).find === 'function'
+ ? (cluster as any).find(message.server, true)
+ : undefined;
if (existing) {
return;
}
- } catch {/* ignore */}
+ } catch {
+ /* ignore */
+ }
}
- const clusterMethod = (cluster as any)[method as keyof ICluster];
+ const clusterMethod = (cluster as any)[
+ method as keyof ICluster
+ ];
if (!clusterMethod) {
return;
@@ -164,29 +171,31 @@ export class UDPClusterManager extends ClusterManager {
await UDPClusterManager.destroyWorker(this.workerKey, this.worker);
}
- public static async destroySocket(key: string, socket?: any): Promise {
+ public static async destroySocket(
+ key: string,
+ socket?: any,
+ ): Promise {
if (!socket) {
return;
}
- try {
- if (typeof socket.removeAllListeners === 'function') {
- socket.removeAllListeners();
- }
- } catch (e) {
- throw e;
+ if (typeof socket.removeAllListeners === 'function') {
+ socket.removeAllListeners();
}
if (typeof socket.close !== 'function') {
return;
}
- await new Promise((resolve) => {
+ await new Promise(resolve => {
socket.close(() => {
if (typeof socket.unref === 'function') {
socket.unref();
}
- if (UDPClusterManager.sockets && key in UDPClusterManager.sockets) {
+ if (
+ UDPClusterManager.sockets &&
+ key in UDPClusterManager.sockets
+ ) {
delete UDPClusterManager.sockets[key];
}
resolve();
@@ -209,7 +218,7 @@ export class UDPClusterManager extends ClusterManager {
}, 5000);
worker.postMessage({ type: 'stop' });
- worker.once('message', (message) => {
+ worker.once('message', message => {
if (message.type === 'stopped') {
clearTimeout(timeout);
worker.terminate();
diff --git a/src/UDPWorker.ts b/src/UDPWorker.ts
index da83b06..399abb8 100644
--- a/src/UDPWorker.ts
+++ b/src/UDPWorker.ts
@@ -64,9 +64,8 @@ class UDPWorker {
reuseAddr: true,
reusePort: true,
}).bind(this.options.port, this.selectNetworkInterface());
- this.socket.on(
- 'message',
- message => this.processMessage(this.parseMessage(message)),
+ this.socket.on('message', message =>
+ this.processMessage(this.parseMessage(message)),
);
}
@@ -126,17 +125,23 @@ class UDPWorker {
this.servers.set(key, stamp);
- const t: any = setTimeout(() => setImmediate(() => {
- if (this.servers.get(key) === stamp) {
- this.removeServer(message);
- }
- }), effectiveTimeout);
+ const t: any = setTimeout(
+ () =>
+ setImmediate(() => {
+ if (this.servers.get(key) === stamp) {
+ this.removeServer(message);
+ }
+ }),
+ effectiveTimeout,
+ );
// Avoid keeping the event loop alive due to pending timers
try {
if (t && typeof t.unref === 'function') {
t.unref();
}
- } catch {/* ignore */}
+ } catch {
+ /* ignore */
+ }
}
private processMessage(message: Message): void {
@@ -151,13 +156,13 @@ class UDPWorker {
private selectNetworkInterface(): string {
const interfaces = networkInterfaces();
- const broadcastAddress = this.options.address
- || this.options.limitedAddress;
+ const broadcastAddress =
+ this.options.address || this.options.limitedAddress;
const defaultAddress = '0.0.0.0';
if (
- !broadcastAddress
- || broadcastAddress === this.options.limitedAddress
+ !broadcastAddress ||
+ broadcastAddress === this.options.limitedAddress
) {
return defaultAddress;
}
@@ -168,8 +173,9 @@ class UDPWorker {
}
for (const net of interfaces[key]) {
- const shouldBeSelected = net.family === 'IPv4'
- && net.address.startsWith(
+ const shouldBeSelected =
+ net.family === 'IPv4' &&
+ net.address.startsWith(
broadcastAddress.replace(/\.255/g, ''),
);
@@ -183,13 +189,9 @@ class UDPWorker {
}
private parseMessage(input: Buffer): Message {
- const [
- name,
- id,
- type,
- address = '',
- timeout = '0',
- ] = input.toString().split('\t');
+ const [name, id, type, address = '', timeout = '0'] = input
+ .toString()
+ .split('\t');
const [host, port] = address.split(':');
return {
diff --git a/src/copyEventEmitter.ts b/src/copyEventEmitter.ts
index 2d4559f..20721b7 100644
--- a/src/copyEventEmitter.ts
+++ b/src/copyEventEmitter.ts
@@ -40,8 +40,8 @@ export function copyEventEmitter(
for (const originalListener of listeners) {
if (util.inspect(originalListener).includes('onceWrapper')) {
- const realListener = originalListener?.listener
- || originalListener;
+ const realListener =
+ originalListener?.listener || originalListener;
target.once(event, realListener);
} else {
diff --git a/src/index.ts b/src/index.ts
index c5c1b5f..2a271bc 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -40,7 +40,6 @@ export function buildOptions(
export * from './IMQMode';
export * from './profile';
export * from './uuid';
-export * from './promisify';
export * from './redis';
export * from './IMessageQueue';
export * from './RedisQueue';
diff --git a/src/profile.ts b/src/profile.ts
index 2a6c783..5b08e54 100644
--- a/src/profile.ts
+++ b/src/profile.ts
@@ -21,7 +21,6 @@
* purchase a proprietary commercial license. Please contact us at
* to get commercial licensing options.
*/
-import 'reflect-metadata';
import { ILogger } from '.';
export enum LogLevel {
@@ -100,7 +99,7 @@ export const IMQ_LOG_ARGS = !!+(process.env.IMQ_LOG_ARGS || 0);
* @type {AllowedTimeFormat | string}
*/
export const IMQ_LOG_TIME_FORMAT: AllowedTimeFormat =
- process.env.IMQ_LOG_TIME_FORMAT as AllowedTimeFormat || 'microseconds';
+ (process.env.IMQ_LOG_TIME_FORMAT as AllowedTimeFormat) || 'microseconds';
export interface DebugInfoOptions {
/**
@@ -159,15 +158,13 @@ export function logDebugInfo({
logger,
logLevel,
}: DebugInfoOptions) {
- const log = logger && typeof logger[logLevel] === 'function'
- ? logger[logLevel].bind(logger) : undefined;
+ const log =
+ logger && typeof logger[logLevel] === 'function'
+ ? logger[logLevel].bind(logger)
+ : undefined;
if (debugTime) {
- // noinspection TypeScriptUnresolvedFunction
- const time = parseInt(
- ((process.hrtime as any).bigint() - BigInt(start)) as any,
- 10,
- ) / 1000;
+ const time = Number(process.hrtime.bigint() - BigInt(start)) / 1000;
let timeStr: string;
// istanbul ignore next
@@ -193,21 +190,25 @@ export function logDebugInfo({
const cache: any[] = [];
try {
- argStr = JSON.stringify(args, (key: string, value: any) => {
- if (typeof value === 'object' && value !== null) {
- if (~cache.indexOf(value)) {
- try {
- return JSON.parse(JSON.stringify(value));
- } catch (error) {
- return;
+ argStr = JSON.stringify(
+ args,
+ (key: string, value: any) => {
+ if (typeof value === 'object' && value !== null) {
+ if (~cache.indexOf(value)) {
+ try {
+ return JSON.parse(JSON.stringify(value));
+ } catch (error) {
+ return;
+ }
}
- }
- cache.push(value);
- }
+ cache.push(value);
+ }
- return value;
- }, 2);
+ return value;
+ },
+ 2,
+ );
} catch (err) {
logger.error(err);
}
@@ -245,11 +246,15 @@ export function logDebugInfo({
* descriptor: TypedPropertyDescriptor<(...args: any[]) => any>
* ) => void}
*/
-export function profile(options?: ProfileDecoratorOptions): (
- target: any,
- methodName: string,
- descriptor: TypedPropertyDescriptor<(...args: any[]) => any>,
-) => void {
+export function profile(
+ options?: ProfileDecoratorOptions,
+): (
+ original: (this: This, ...args: Args) => Return,
+ context: ClassMethodDecoratorContext<
+ This,
+ (this: This, ...args: Args) => Return
+ >,
+) => (this: This, ...args: Args) => Return {
options = Object.assign({}, DEFAULT_OPTIONS, options);
const { enableDebugTime, enableDebugArgs, logLevel } = options;
@@ -264,47 +269,52 @@ export function profile(options?: ProfileDecoratorOptions): (
debugArgs = enableDebugArgs;
}
- return function wrapper(
- target: any,
- methodName: string,
- descriptor: TypedPropertyDescriptor<(...args: any[]) => any>,
- ) {
- /* istanbul ignore next */
- const original = descriptor.value || target[methodName];
+ return function decorator(
+ original: (this: This, ...args: Args) => Return,
+ context: ClassMethodDecoratorContext<
+ This,
+ (this: This, ...args: Args) => Return
+ >,
+ ): (this: This, ...args: Args) => Return {
+ const methodName = String(context.name);
- descriptor.value = function(...args: any[]) {
+ return function wrapper(this: This, ...args: Args): Return {
if (!(debugTime || debugArgs)) {
- return original.apply(this || target, args);
+ return original.apply(this, args);
}
+ const self = this as any;
/* istanbul ignore next */
- const className = typeof target === 'function' && target.name
- ? target.name // static
- : target.constructor.name; // dynamic
- // noinspection TypeScriptUnresolvedFunction
- const start = (process.hrtime as any).bigint();
- const result = original.apply(this || target, args);
+ const className = self
+ ? typeof self === 'function'
+ ? self.name // static
+ : self.constructor && self.constructor.name // dynamic
+ : '';
+ const start = process.hrtime.bigint();
+ const result = original.apply(this, args);
const debugOptions: DebugInfoOptions = {
args,
className,
debugArgs,
debugTime,
logLevel: logLevel ? verifyLogLevel(logLevel) : IMQ_LOG_LEVEL,
- logger: (this || target).logger,
+ logger: self && self.logger,
methodName,
start,
};
/* istanbul ignore next */
- if (result && typeof result.then === 'function') {
+ if (result && typeof (result as any).then === 'function') {
// async call detected
- result.then((res: any) => {
- logDebugInfo(debugOptions);
+ (result as any)
+ .then((res: any) => {
+ logDebugInfo(debugOptions);
- return res;
- }).catch(() => {
- logDebugInfo(debugOptions);
- });
+ return res;
+ })
+ .catch(() => {
+ logDebugInfo(debugOptions);
+ });
return result;
}
diff --git a/src/promisify.ts b/src/promisify.ts
deleted file mode 100644
index 437fe1f..0000000
--- a/src/promisify.ts
+++ /dev/null
@@ -1,113 +0,0 @@
-/*!
- * Makes callback handling function promise-like
- *
- * I'm Queue Software Project
- * Copyright (C) 2025 imqueue.com
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
- *
- * If you want to use this code in a closed source (commercial) project, you can
- * purchase a proprietary commercial license. Please contact us at
- * to get commercial licensing options.
- */
-/**
- * Returns entire list of the given object properties including
- * entire prototype chain
- *
- * @param {any} obj
- * @returns {string[]}
- */
-export function propertiesOf(obj: any): string[] {
- const props: string[] = [];
-
- // noinspection JSAssignmentUsedAsCondition
- do {
- Object.getOwnPropertyNames(obj).forEach((prop) => {
- if (!~props.indexOf(prop)) {
- props.push(prop);
- }
- });
- } while (obj = Object.getPrototypeOf(obj));
-
- return props;
-}
-
-// istanbul ignore next
-/**
- * Makes a callback function able to resolve or reject with given
- * resolve and reject functions
- *
- * @param {(...args: any[]) => any} resolve
- * @param {(...args: any[]) => any} reject
- * @return {(...args: any[]) => any}
- */
-function makeCallback(
- resolve: (...args: any[]) => any,
- reject: (...args: any[]) => any,
-): (...args: any[]) => any {
- return function callback(err: Error, ...args: any[]) {
- if (err) {
- return reject(err);
- }
-
- resolve(args.length === 1 ? args[0] : args);
- };
-}
-
-// istanbul ignore next
-/**
- * Makes given method promised
- *
- * @access private
- * @param {(...args: any[]) => any} method
- * @return {(...args: any[]) => Promise}
- */
-function makePromised(method: (...args: any[]) => any) {
- return function asyncMethod(...args: any[]): Promise {
- const callback = args[args.length - 1];
-
- if (typeof callback === 'function') {
- return method.apply(this, args);
- }
-
- return new Promise((resolve, reject) => {
- method.call(this, ...args, makeCallback(resolve, reject));
- });
- };
-}
-
-/**
- * Makes given object methods promise-like
- *
- * @param {any} obj - source object to modify
- * @param {string[]} restrict - stick promise-like behavior to a given
- * restricted list of methods
- * @return {void}
- */
-export function promisify(obj: any, restrict?: string[]): void {
- for (const prop of propertiesOf(obj)) {
- try {
- if (typeof obj[prop] !== 'function' ||
- (restrict && !~restrict.indexOf(prop.toLowerCase()))
- ) {
- continue;
- }
- } catch (err) {
- /* istanbul ignore next */
- continue;
- }
-
- obj[prop] = makePromised(obj[prop]);
- }
-}
diff --git a/src/uuid.ts b/src/uuid.ts
index 18fe85f..0006e14 100644
--- a/src/uuid.ts
+++ b/src/uuid.ts
@@ -23,43 +23,17 @@
* purchase a proprietary commercial license. Please contact us at
* to get commercial licensing options.
*/
-const lookupTable: string[] = [];
-
-for (let i = 0; i < 256; i++) {
- lookupTable[i] = (i < 16 ? '0' : '') + (i).toString(16);
-}
-
-const rand = Math.random.bind(Math);
+import { randomUUID } from 'crypto';
/**
- * Generates and returns Unified Unique Identifier
+ * Generates and returns Unified Unique Identifier (RFC 4122 v4).
+ *
+ * Uses the cryptographically strong native generator, which is both faster
+ * and collision-safe under high load compared to a Math.random() based
+ * implementation.
*
* @returns {string}
*/
export function uuid(): string {
- const d0 = rand() * 0x100000000 >>> 0;
- const d1 = rand() * 0x100000000 >>> 0;
- const d2 = rand() * 0x100000000 >>> 0;
- const d3 = rand() * 0x100000000 >>> 0;
-
- return lookupTable[d0 & 0xff] +
- lookupTable[d0 >> 8 & 0xff] +
- lookupTable[d0 >> 16 & 0xff] +
- lookupTable[d0 >> 24 & 0xff] +
- '-' +
- lookupTable[d1 & 0xff] +
- lookupTable[d1 >> 8 & 0xff] +
- '-' +
- lookupTable[d1 >> 16 & 0x0f | 0x40] +
- lookupTable[d1 >> 24 & 0xff] +
- '-' +
- lookupTable[d2 & 0x3f | 0x80] +
- lookupTable[d2 >> 8 & 0xff] +
- '-' +
- lookupTable[d2 >> 16 & 0xff] +
- lookupTable[d2 >> 24 & 0xff] +
- lookupTable[d3 & 0xff] +
- lookupTable[d3 >> 8 & 0xff] +
- lookupTable[d3 >> 16 & 0xff] +
- lookupTable[d3 >> 24 & 0xff];
+ return randomUUID();
}
diff --git a/test/ClusteredRedisQueue.addServer.defaultInit.spec.ts b/test/ClusteredRedisQueue.addServer.defaultInit.spec.ts
deleted file mode 100644
index e6e229c..0000000
--- a/test/ClusteredRedisQueue.addServer.defaultInit.spec.ts
+++ /dev/null
@@ -1,34 +0,0 @@
-/*!
- * ClusteredRedisQueue.addServerWithQueueInitializing default param branch
- */
-import './mocks';
-import { expect } from 'chai';
-import { ClusteredRedisQueue } from '../src';
-
-describe('ClusteredRedisQueue.addServerWithQueueInitializing() default param', () => {
- it('should use default initializeQueue=true when second param omitted', async () => {
- const cq: any = new ClusteredRedisQueue('CQ-Default', {
- logger: console,
- cluster: [{ host: '127.0.0.1', port: 6379 }],
- });
- // prevent any actual start/subscription side-effects
- (cq as any).state.started = false;
- (cq as any).state.subscription = null;
-
- const server = { host: '192.168.0.1', port: 6380 };
- const initializedSpy = new Promise((resolve) => {
- cq['clusterEmitter'].once('initialized', () => resolve());
- });
-
- // Call without the second argument to hit default "true" branch
- (cq as any).addServerWithQueueInitializing(server);
-
- await initializedSpy; // should emit initialized when default is true
-
- // Ensure the server added and queue length updated
- expect((cq as any).servers.some((s: any) => s.host === server.host && s.port === server.port)).to.equal(true);
- expect((cq as any).imqLength).to.equal((cq as any).imqs.length);
-
- await cq.destroy();
- });
-});
diff --git a/test/ClusteredRedisQueue.addServer.noInit.spec.ts b/test/ClusteredRedisQueue.addServer.noInit.spec.ts
deleted file mode 100644
index df4d6c7..0000000
--- a/test/ClusteredRedisQueue.addServer.noInit.spec.ts
+++ /dev/null
@@ -1,32 +0,0 @@
-/*!
- * Cover ClusteredRedisQueue.addServerWithQueueInitializing with initializeQueue=false
- */
-import './mocks';
-import { expect } from 'chai';
-import { ClusteredRedisQueue } from '../src';
-import { ClusterManager } from '../src/ClusterManager';
-
-const server = { host: '127.0.0.1', port: 6380 };
-
-describe('ClusteredRedisQueue.addServerWithQueueInitializing(false)', () => {
- it('should add server without initializing queue and not emit initialized', async () => {
- const manager = new (ClusterManager as any)();
- const cq: any = new ClusteredRedisQueue('NoInit', { clusterManagers: [manager] });
-
- let initializedCalled = false;
- (cq as any).clusterEmitter.on('initialized', () => { initializedCalled = true; });
-
- // call private method via any to cover branch
- (cq as any).addServerWithQueueInitializing(server, false);
-
- // should have server and imq added
- expect(cq.servers.length).to.be.greaterThan(0);
- expect(cq.imqs.length).to.be.greaterThan(0);
- // queueLength updated
- expect(cq.imqLength).to.equal(cq.imqs.length);
- // initialized not emitted
- expect(initializedCalled).to.equal(false);
-
- await cq.destroy();
- });
-});
diff --git a/test/ClusteredRedisQueue.extra.spec.ts b/test/ClusteredRedisQueue.extra.spec.ts
deleted file mode 100644
index 358d319..0000000
--- a/test/ClusteredRedisQueue.extra.spec.ts
+++ /dev/null
@@ -1,48 +0,0 @@
-/*!
- * Additional tests for ClusteredRedisQueue event emitter proxy methods
- */
-import './mocks';
-import { expect } from 'chai';
-import * as sinon from 'sinon';
-import { ClusteredRedisQueue } from '../src';
-import { ClusterManager } from '../src/ClusterManager';
-
-const clusterConfig = {
- cluster: [
- { host: '127.0.0.1', port: 6379 },
- ],
-};
-
-describe('ClusteredRedisQueue - EventEmitter proxy methods', () => {
- it('should cover rawListeners/getMaxListeners/eventNames/listenerCount/emit', async () => {
- const clusterManager = new (ClusterManager as any)();
- const cq: any = new ClusteredRedisQueue('ProxyQueue', {
- clusterManagers: [clusterManager],
- });
-
- // add underlying server and listener
- cq.addServer(clusterConfig.cluster[0]);
- const handler = sinon.spy();
- cq.imqs[0].on('test', handler);
-
- // set max listeners across emitters and verify getMaxListeners uses templateEmitter
- cq.setMaxListeners(20);
- expect(cq.getMaxListeners()).to.equal(20);
-
- // collect raw listeners
- const raw = cq.rawListeners('test');
- expect(raw.length).to.be.greaterThan(0);
-
- // event names come from underlying imq
- const names = cq.eventNames();
- expect(names).to.be.an('array');
- expect(names.map(String)).to.include('test');
-
- // listener count is aggregated via templateEmitter method applied on imq[0]
- expect(cq.listenerCount('test')).to.equal(1);
-
- // emit should return true
- expect(cq.emit('test', 1, 2, 3)).to.equal(true);
- expect(handler.calledOnce).to.be.true;
- });
-});
diff --git a/test/ClusteredRedisQueue.initialize.spec.ts b/test/ClusteredRedisQueue.initialize.spec.ts
deleted file mode 100644
index 90520e8..0000000
--- a/test/ClusteredRedisQueue.initialize.spec.ts
+++ /dev/null
@@ -1,37 +0,0 @@
-/*!
- * Additional tests for ClusteredRedisQueue.initializeQueue branches
- */
-import './mocks';
-import { expect } from 'chai';
-import * as sinon from 'sinon';
-import { ClusteredRedisQueue, RedisQueue } from '../src';
-import { ClusterManager } from '../src/ClusterManager';
-
-describe('ClusteredRedisQueue.initializeQueue()', () => {
- it('should call imq.start when started and imq.subscribe when subscription is set', async () => {
- const startStub = sinon.stub(RedisQueue.prototype as any, 'start').resolves(undefined);
- const subscribeStub = sinon.stub(RedisQueue.prototype as any, 'subscribe').resolves();
-
- const clusterManager = new (ClusterManager as any)();
- const cq: any = new ClusteredRedisQueue('InitCover', { clusterManagers: [clusterManager] });
-
- // mark started and set subscription using public APIs
- await cq.start();
- const channel = 'X';
- const handler = () => undefined;
- await cq.subscribe(channel, handler);
-
- // adding a server triggers initializeQueue which should call start and subscribe
- cq.addServer({ host: '127.0.0.1', port: 6453 });
-
- // allow promises to resolve
- await new Promise(res => setTimeout(res, 0));
-
- expect(startStub.called).to.be.true;
- expect(subscribeStub.called).to.be.true;
-
- startStub.restore();
- subscribeStub.restore();
- await cq.destroy();
- });
-});
diff --git a/test/ClusteredRedisQueue.matchServers.spec.ts b/test/ClusteredRedisQueue.matchServers.spec.ts
deleted file mode 100644
index 47e9287..0000000
--- a/test/ClusteredRedisQueue.matchServers.spec.ts
+++ /dev/null
@@ -1,23 +0,0 @@
-/*!
- * Tests for ClusteredRedisQueue.matchServers combinations
- */
-import './mocks';
-import { expect } from 'chai';
-import { ClusteredRedisQueue } from '../src';
-
-// Access private static via casting
-const match = (ClusteredRedisQueue as any).matchServers as (
- source: any, target: any, strict?: boolean
-) => boolean;
-
-describe('ClusteredRedisQueue.matchServers()', () => {
- it('should return sameAddress when no ids provided', () => {
- expect(match({ host: 'h', port: 1 }, { host: 'h', port: 1 })).to.be.true;
- expect(match({ host: 'h', port: 1 }, { host: 'h', port: 2 })).to.be.false;
- });
-
- it('should match servers if id provided', () => {
- expect(match({ id: 'a', host: 'h', port: 1 }, { id: 'a', host: 'h', port: 2 })).to.be.true;
- expect(match({ id: 'a', host: 'h', port: 1 }, { id: 'b', host: 'h', port: 1 })).to.be.true;
- });
-});
diff --git a/test/ClusteredRedisQueue.ts b/test/ClusteredRedisQueue.ts
deleted file mode 100644
index 3d37e8e..0000000
--- a/test/ClusteredRedisQueue.ts
+++ /dev/null
@@ -1,344 +0,0 @@
-/*!
- * RedisQueue Unit Tests
- *
- * I'm Queue Software Project
- * Copyright (C) 2025 imqueue.com
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
- *
- * If you want to use this code in a closed source (commercial) project, you can
- * purchase a proprietary commercial license. Please contact us at
- * to get commercial licensing options.
- */
-import { logger } from './mocks';
-import { expect } from 'chai';
-import * as sinon from 'sinon';
-import { ClusteredRedisQueue } from '../src';
-import { ClusterManager } from '../src/ClusterManager';
-
-process.setMaxListeners(100);
-
-const clusterConfig = {
- logger,
- cluster: [{
- host: '127.0.0.1',
- port: 7777
- }, {
- host: '127.0.0.1',
- port: 8888
- }]
-};
-
-describe('ClusteredRedisQueue', function() {
- this.timeout(30000);
-
- it('should be a class', () => {
- expect(typeof ClusteredRedisQueue).to.equal('function');
- });
-
- it('should implement IMessageQueue interface', () => {
- expect(typeof ClusteredRedisQueue.prototype.start)
- .to.equal('function');
- expect(typeof ClusteredRedisQueue.prototype.stop)
- .to.equal('function');
- expect(typeof ClusteredRedisQueue.prototype.send)
- .to.equal('function');
- expect(typeof ClusteredRedisQueue.prototype.destroy)
- .to.equal('function');
- });
-
- describe('constructor()', () => {
- it('should throw with improper options passed', () => {
- expect(() => new ClusteredRedisQueue('TestClusteredQueue'))
- .to.throw(TypeError);
- });
-
- it('should not throw if proper options passed', () => {
- expect(() => new ClusteredRedisQueue(
- 'TestClusteredQueue',
- clusterConfig
- )).not.to.throw(TypeError);
- });
-
- it('should initialize cluster manager', () => {
- const clusterManager = new (ClusterManager as any)();
-
- sinon.spy(clusterManager, 'init');
-
- new ClusteredRedisQueue(
- 'TestClusteredQueue',
- { clusterManagers: [clusterManager] },
- );
-
- expect(clusterManager.init.called).to.be.true;
- });
- });
-
- describe('start()', () => {
- it('should start each nested imq', async () => {
- const cq: any = new ClusteredRedisQueue(
- 'TestClusteredQueue',
- clusterConfig
- );
-
- cq.imqs.forEach((imq: any) => {
- sinon.spy(imq, 'start');
- });
-
- await cq.start();
-
- cq.imqs.forEach((imq: any) => {
- expect(imq.start.called).to.be.true;
- });
-
- await cq.destroy();
- });
- });
-
- describe('stop()', () => {
- it('should stop each nested imq', async () => {
- const cq: any = new ClusteredRedisQueue(
- 'TestClusteredQueue',
- clusterConfig
- );
-
- cq.imqs.forEach((imq: any) => {
- sinon.spy(imq, 'stop');
- });
-
- await cq.stop();
-
- cq.imqs.forEach((imq: any) => {
- expect(imq.stop.called).to.be.true;
- });
-
- await cq.destroy();
- });
- });
-
- describe('send()', () => {
- it('should balance send requests round-robin manner across nested ' +
- 'queues', async () => {
- const cq: any = new ClusteredRedisQueue(
- 'TestClusteredQueue',
- clusterConfig
- );
-
- cq.imqs.forEach((imq: any) => {
- sinon.spy(imq, 'send');
- });
-
- await cq.send('TestClusteredQueue', { 'hello': 'world' });
-
- expect(cq.imqs[0].send.calledOnce).to.be.true;
- expect(cq.imqs[1].send.called).to.be.false;
-
- await cq.send('TestClusteredQueue', { 'hello': 'world' });
-
- expect(cq.imqs[0].send.calledOnce).to.be.true;
- expect(cq.imqs[1].send.calledOnce).to.be.true;
-
- await cq.send('TestClusteredQueue', { 'hello': 'world' });
-
- expect(cq.imqs[0].send.calledTwice).to.be.true;
- expect(cq.imqs[1].send.calledOnce).to.be.true;
-
- await cq.destroy();
- });
-
- it('should send message after queue was initialized', done => {
- const clusterManager = new (ClusterManager as any)();
- const cqOne: any = new ClusteredRedisQueue(
- 'TestClusteredQueueOne',
- {
- clusterManagers: [clusterManager],
- logger,
- },
- );
- const cqTwo: any = new ClusteredRedisQueue(
- 'TestClusteredQueueTwo',
- {
- clusterManagers: [clusterManager],
- logger,
- },
- );
- const message = { 'hello': 'world' };
-
- cqOne.start();
- cqTwo.start();
-
- cqTwo.on('message', () => {
- cqOne.destroy();
- cqTwo.destroy();
-
- done();
- });
-
- cqOne.send('TestClusteredQueueTwo', message);
- cqTwo.addServer(clusterConfig.cluster[0]);
- cqOne.addServer(clusterConfig.cluster[0]);
- });
- });
-
- describe('destroy()', () => {
- it('should destroy each nested imq', async () => {
- const cq: any = new ClusteredRedisQueue(
- 'TestClusteredQueue',
- clusterConfig
- );
-
- cq.imqs.forEach((imq: any) => {
- sinon.spy(imq, 'destroy');
- });
-
- await cq.destroy();
-
- cq.imqs.forEach((imq: any) => {
- expect(imq.destroy.called).to.be.true;
- });
- });
- });
-
- describe('clear()', () => {
- it('should clear each nested imq', async () => {
- const cq: any = new ClusteredRedisQueue(
- 'TestClusteredQueue',
- clusterConfig
- );
-
- cq.imqs.forEach((imq: any) => {
- sinon.spy(imq, 'clear');
- });
-
- await cq.clear();
-
- cq.imqs.forEach((imq: any) => {
- expect(imq.clear.called).to.be.true;
- });
-
- await cq.destroy();
- });
- });
-
- describe('subscribe()', () => {
- it('should subscribe after queue initialization', () => {
- const clusterManager = new (ClusterManager as any)();
- const cq: any = new ClusteredRedisQueue(
- 'TestClusteredQueue',
- {
- clusterManagers: [clusterManager],
- logger,
- },
- );
- const channel = 'TestChannel';
-
- cq.subscribe(channel, () => {});
- cq.addServer(clusterConfig.cluster[0]);
-
- expect(cq.imqs[0].subscriptionName).to.be.equal(channel);
- });
- });
-
- describe('addServer()', () => {
- it('should add cluster server', () => {
- const clusterManager = new (ClusterManager as any)();
- const cq: any = new ClusteredRedisQueue(
- 'TestClusteredQueue',
- { clusterManagers: [clusterManager] },
- );
-
- cq.addServer(clusterConfig.cluster[0]);
-
- expect(cq.servers.length).to.be.equal(1);
- });
-
- it('should call adding cluster server method through the'
- + ' Cluster Manager', () => {
- const clusterManager = new (ClusterManager as any)();
- const cq: any = new ClusteredRedisQueue(
- 'TestClusteredQueue',
- { clusterManagers: [clusterManager] },
- );
-
- for (const server of clusterManager.clusters) {
- server.add(clusterConfig.cluster[0]);
- }
-
- expect(cq.servers.length).to.be.equal(1);
- });
- });
-
- describe('removeServer()', () => {
- it('should remove cluster server', () => {
- const clusterManager = new (ClusterManager as any)();
- const cq: any = new ClusteredRedisQueue(
- 'TestClusteredQueue',
- { clusterManagers: [clusterManager] },
- );
-
- cq.addServer(clusterConfig.cluster[0]);
- cq.removeServer(clusterConfig.cluster[0]);
-
- expect(cq.servers.length).to.be.equal(0);
- });
-
- it('should call removing cluster server method through the'
- + ' Cluster Manager', () => {
- const clusterManager = new (ClusterManager as any)();
- const cq: any = new ClusteredRedisQueue(
- 'TestClusteredQueue',
- { clusterManagers: [clusterManager] },
- );
-
- for (const server of clusterManager.clusters) {
- server.remove(clusterConfig.cluster[0]);
- }
-
- expect(cq.servers.length).to.be.equal(0);
- });
- });
-
- describe('findServer()', () => {
- it('should find cluster server', () => {
- const clusterManager = new (ClusterManager as any)();
- const cq: any = new ClusteredRedisQueue(
- 'TestClusteredQueue',
- { clusterManagers: [clusterManager] },
- );
-
- cq.addServer(clusterConfig.cluster[0]);
-
- const server = cq.findServer(clusterConfig.cluster[0]);
-
- expect(server).to.deep.include(clusterConfig.cluster[0]);
- });
-
- it('should call find cluster server method through the'
- + ' Cluster Manager', () => {
- const clusterManager = new (ClusterManager as any)();
- const cq: any = new ClusteredRedisQueue(
- 'TestClusteredQueue',
- { clusterManagers: [clusterManager] },
- );
-
- cq.addServer(clusterConfig.cluster[0]);
-
- for (const cluster of clusterManager.clusters) {
- const server = cluster.find(clusterConfig.cluster[0]);
-
- expect(server).to.deep.include(clusterConfig.cluster[0]);
- }
- });
- });
-});
diff --git a/test/RedisQueue.cleanup.catch.spec.ts b/test/RedisQueue.cleanup.catch.spec.ts
deleted file mode 100644
index 8066318..0000000
--- a/test/RedisQueue.cleanup.catch.spec.ts
+++ /dev/null
@@ -1,41 +0,0 @@
-/*!
- * Additional RedisQueue tests: processCleanup catch branch
- */
-import './mocks';
-import { expect } from 'chai';
-import * as sinon from 'sinon';
-import { RedisQueue } from '../src';
-import { logger as testLogger } from './mocks';
-
-function makeLogger() {
- return {
- log: (..._args: any[]) => undefined,
- info: (..._args: any[]) => undefined,
- warn: (..._args: any[]) => undefined,
- error: (..._args: any[]) => undefined,
- } as any;
-}
-
-describe('RedisQueue.processCleanup catch path', function() {
- this.timeout(10000);
-
- it('should log a warning when processCleanup throws', async () => {
- const logger = makeLogger();
- const warnSpy = sinon.spy(logger, 'warn');
- const rq: any = new RedisQueue('CleanupCatch', {
- logger,
- cleanup: true,
- });
-
- await rq.start();
- // Stub writer.client to throw to hit the catch branch
- const stub = sinon.stub(rq.writer, 'client').throws(new Error('LIST failed'));
-
- await rq.processCleanup();
-
- expect(warnSpy.called).to.be.true;
-
- stub.restore();
- await rq.destroy();
- });
-});
diff --git a/test/RedisQueue.connect.fallbacks.spec.ts b/test/RedisQueue.connect.fallbacks.spec.ts
deleted file mode 100644
index 60cb217..0000000
--- a/test/RedisQueue.connect.fallbacks.spec.ts
+++ /dev/null
@@ -1,40 +0,0 @@
-/*!
- * Additional RedisQueue tests: connect() option fallbacks branches
- */
-import './mocks';
-import { expect } from 'chai';
-import { RedisQueue, IMQMode } from '../src';
-
-function makeLogger() {
- return {
- log: (..._args: any[]) => undefined,
- info: (..._args: any[]) => undefined,
- warn: (..._args: any[]) => undefined,
- error: (..._args: any[]) => undefined,
- } as any;
-}
-
-describe('RedisQueue.connect() option fallbacks', function() {
- this.timeout(10000);
-
- it('should use fallback values when falsy options are provided', async () => {
- const logger = makeLogger();
- // Intentionally provide falsy values to trigger `||` fallbacks in connect()
- const rq: any = new RedisQueue('ConnFallbacks', {
- logger,
- port: 0 as unknown as number, // falsy to trigger 6379 fallback
- host: '' as unknown as string, // falsy to trigger 'localhost' fallback
- prefix: '' as unknown as string, // falsy to trigger '' fallback in connectionName
- cleanup: false,
- }, IMQMode.BOTH);
-
- await rq.start();
-
- // Basic sanity: writer/reader/watcher are created
- expect(Boolean(rq.writer)).to.equal(true);
- expect(Boolean(rq.reader)).to.equal(true);
- expect(Boolean(rq.watcher)).to.equal(true);
-
- await rq.destroy();
- });
-});
diff --git a/test/RedisQueue.processCleanup.clientsFilter.spec.ts b/test/RedisQueue.processCleanup.clientsFilter.spec.ts
deleted file mode 100644
index 98bde70..0000000
--- a/test/RedisQueue.processCleanup.clientsFilter.spec.ts
+++ /dev/null
@@ -1,53 +0,0 @@
-/*!
- * Additional RedisQueue tests: processCleanup connectedKeys filter branches
- */
-import './mocks';
-import { expect } from 'chai';
-import * as sinon from 'sinon';
-import { RedisQueue, uuid } from '../src';
-
-describe('RedisQueue.processCleanup connectedKeys RX/filter combinations', function() {
- this.timeout(5000);
-
- it('should handle RX_CLIENT_TEST true but filter false case (exclude unmatched prefix)', async () => {
- const name = `PCleanRX_${uuid()}`;
- const rq: any = new RedisQueue(name, {
- logger: console,
- cleanup: true,
- prefix: 'imqA',
- cleanupFilter: '*',
- });
-
- await rq.start();
-
- const writer: any = rq.writer;
-
- // Stub client('LIST') to include a writer channel with a different prefix,
- // so RX_CLIENT_TEST.test(name) is true but filter.test(name) is false.
- const clientStub = sinon.stub(writer, 'client');
- clientStub.callsFake(async (cmd: string) => {
- if (cmd === 'LIST') {
- return [
- 'id=1 name=imqZ:Other:writer:pid:1:host:x', // RX true, filter false
- 'id=2 name=imqA:Other:subscription:pid:1:host:x', // RX false, filter true
- ].join('\n');
- }
- return true as any;
- });
-
- // Return no keys on SCAN to avoid deletions and just walk the branch
- const scanStub = sinon.stub(writer, 'scan');
- scanStub.resolves(['0', []] as any);
-
- const delSpy = sinon.spy(writer, 'del');
-
- await rq.processCleanup();
-
- expect(delSpy.called).to.equal(false);
-
- clientStub.restore();
- scanStub.restore();
- delSpy.restore();
- await rq.destroy();
- });
-});
diff --git a/test/RedisQueue.processCleanup.extra.spec.ts b/test/RedisQueue.processCleanup.extra.spec.ts
deleted file mode 100644
index 761a27f..0000000
--- a/test/RedisQueue.processCleanup.extra.spec.ts
+++ /dev/null
@@ -1,38 +0,0 @@
-/*!
- * Additional RedisQueue tests for processCleanup branches
- */
-import './mocks';
-import { expect } from 'chai';
-import { RedisQueue, uuid } from '../src';
-import { RedisClientMock } from './mocks';
-
-describe('RedisQueue.processCleanup extra branches', function() {
- this.timeout(5000);
-
- it('should remove scanned keys that do not match any connectedKey (different prefix)', async () => {
- const name = uuid();
- const rq: any = new RedisQueue(name, {
- logger: console,
- cleanup: true,
- prefix: 'imqX',
- cleanupFilter: '*',
- });
-
- // start to create reader/writer/watcher with connection names
- await rq.start();
-
- // Create an orphan worker key with a different prefix so it won't include any connectedKey
- const orphanKey = 'imqY:orphan:worker:someuuid:123456';
- (RedisClientMock as any).__queues__[orphanKey] = ['payload'];
-
- // Sanity: ensure the key is present before cleanup
- expect((RedisClientMock as any).__queues__[orphanKey]).to.be.ok;
-
- await rq.processCleanup();
-
- // The orphan key should be deleted by cleanup (true branch of keysToRemove filter)
- expect((RedisClientMock as any).__queues__[orphanKey]).to.be.undefined;
-
- await rq.destroy();
- });
-});
diff --git a/test/RedisQueue.processCleanup.multiscan.spec.ts b/test/RedisQueue.processCleanup.multiscan.spec.ts
deleted file mode 100644
index db7825c..0000000
--- a/test/RedisQueue.processCleanup.multiscan.spec.ts
+++ /dev/null
@@ -1,42 +0,0 @@
-/*!
- * Additional RedisQueue tests for processCleanup branches: multi-scan and no-deletion path
- */
-import './mocks';
-import { expect } from 'chai';
-import * as sinon from 'sinon';
-import { RedisQueue, uuid } from '../src';
-
-describe('RedisQueue.processCleanup multi-scan/no-delete branches', function() {
- this.timeout(5000);
-
- it('should handle multi-page SCAN (cursor != "0" first) and avoid deletion when keys belong to connected clients', async () => {
- const name = `PClean_${uuid()}`;
- const rq: any = new RedisQueue(name, {
- logger: console,
- cleanup: true,
- prefix: 'imq',
- cleanupFilter: '*',
- });
-
- await rq.start();
-
- const writer: any = rq.writer;
-
- // Stub scan to first return non-zero cursor with undefined keys (to exercise `|| []`),
- // then return zero cursor with keys that include connectedKey (so no removal happens).
- const scanStub = sinon.stub(writer, 'scan');
- scanStub.onCall(0).resolves(['1', undefined] as any);
- scanStub.onCall(1).resolves(['0', [`imq:${name}:reader:pid:123`]] as any);
-
- const delSpy = sinon.spy(writer, 'del');
-
- await rq.processCleanup();
-
- // del should not be called because keysToRemove.length === 0
- expect(delSpy.called).to.equal(false);
-
- scanStub.restore();
- delSpy.restore();
- await rq.destroy();
- });
-});
diff --git a/test/RedisQueue.processCleanup.nullmatch.spec.ts b/test/RedisQueue.processCleanup.nullmatch.spec.ts
deleted file mode 100644
index e90b32e..0000000
--- a/test/RedisQueue.processCleanup.nullmatch.spec.ts
+++ /dev/null
@@ -1,49 +0,0 @@
-/*!
- * Additional RedisQueue tests for processCleanup branches: clients.match null and cleanupFilter falsy
- */
-import './mocks';
-import { expect } from 'chai';
-import * as sinon from 'sinon';
-import { RedisQueue, uuid } from '../src';
-
-describe('RedisQueue.processCleanup null-match and falsy cleanupFilter', function() {
- this.timeout(5000);
-
- it('should handle clients.match returning null and cleanupFilter as falsy (\'\')', async () => {
- const name = `PCleanNull_${uuid()}`;
- const rq: any = new RedisQueue(name, {
- logger: console,
- cleanup: true,
- prefix: 'imq',
- cleanupFilter: '', // falsy to exercise "|| '*'" in both RegExp and SCAN MATCH
- });
-
- await rq.start();
-
- const writer: any = rq.writer;
-
- // Force clients.match(...) to return null by stubbing client('LIST') to return a string without 'name='
- const clientStub = sinon.stub(writer, 'client');
- clientStub.callsFake(async (cmd: string) => {
- if (cmd === 'LIST') {
- return 'id=1 flags=x'; // no 'name='
- }
- return true as any;
- });
-
- // Ensure SCAN returns no keys, to avoid deletions and just cover the branch paths
- const scanStub = sinon.stub(writer, 'scan');
- scanStub.resolves(['0', []] as any);
-
- const delSpy = sinon.spy(writer, 'del');
-
- await rq.processCleanup();
-
- expect(delSpy.called).to.equal(false);
-
- clientStub.restore();
- scanStub.restore();
- delSpy.restore();
- await rq.destroy();
- });
-});
diff --git a/test/RedisQueue.publish.spec.ts b/test/RedisQueue.publish.spec.ts
deleted file mode 100644
index b4f48d7..0000000
--- a/test/RedisQueue.publish.spec.ts
+++ /dev/null
@@ -1,70 +0,0 @@
-/*!
- * Additional RedisQueue tests: publish() branches
- */
-import './mocks';
-import { expect } from 'chai';
-import * as sinon from 'sinon';
-import { RedisQueue, IMQMode } from '../src';
-
-function makeLogger() {
- return {
- log: (..._args: any[]) => undefined,
- info: (..._args: any[]) => undefined,
- warn: (..._args: any[]) => undefined,
- error: (..._args: any[]) => undefined,
- } as any;
-}
-
-describe('RedisQueue.publish()', function() {
- this.timeout(10000);
-
- it('should throw when writer is not connected', async () => {
- const logger = makeLogger();
- const rq: any = new RedisQueue('PubNoWriter', { logger }, IMQMode.PUBLISHER);
-
- let thrown: any;
- try {
- await rq.publish({ a: 1 });
- } catch (err) {
- thrown = err;
- }
-
- expect(thrown).to.be.instanceof(TypeError);
- expect(`${thrown}`).to.include('Writer is not connected');
-
- await rq.destroy().catch(() => undefined);
- });
-
- it('should publish to default channel when writer is connected', async () => {
- const logger = makeLogger();
- const rq: any = new RedisQueue('PubDefault', { logger }, IMQMode.PUBLISHER);
- await rq.start();
-
- const pubSpy = sinon.spy((rq as any).writer, 'publish');
- await rq.publish({ hello: 'world' });
-
- expect(pubSpy.called).to.equal(true);
- const [channel, msg] = pubSpy.getCall(0).args;
- expect(channel).to.equal('imq:PubDefault');
- expect(() => JSON.parse(msg)).not.to.throw();
-
- pubSpy.restore();
- await rq.destroy().catch(() => undefined);
- });
-
- it('should publish to provided toName channel when given', async () => {
- const logger = makeLogger();
- const rq: any = new RedisQueue('PubOther', { logger }, IMQMode.PUBLISHER);
- await rq.start();
-
- const pubSpy = sinon.spy((rq as any).writer, 'publish');
- await rq.publish({ t: true }, 'OtherChannel');
-
- expect(pubSpy.called).to.equal(true);
- const [channel] = pubSpy.getCall(0).args;
- expect(channel).to.equal('imq:OtherChannel');
-
- pubSpy.restore();
- await rq.destroy().catch(() => undefined);
- });
-});
diff --git a/test/RedisQueue.send.extra.branches.spec.ts b/test/RedisQueue.send.extra.branches.spec.ts
deleted file mode 100644
index 36da896..0000000
--- a/test/RedisQueue.send.extra.branches.spec.ts
+++ /dev/null
@@ -1,41 +0,0 @@
-/*!
- * Additional RedisQueue tests: send() extra branches
- */
-import './mocks';
-import { expect } from 'chai';
-import * as sinon from 'sinon';
-import { RedisQueue, IMQMode } from '../src';
-import { logger as testLogger } from './mocks';
-
-function makeLogger() {
- return {
- log: (..._args: any[]) => undefined,
- info: (..._args: any[]) => undefined,
- warn: (..._args: any[]) => undefined,
- error: (..._args: any[]) => undefined,
- } as any;
-}
-
-describe('RedisQueue.send() extra branches', function() {
- this.timeout(10000);
-
- it('should throw when writer is still uninitialized after start()', async () => {
- const logger = makeLogger();
- const rq: any = new RedisQueue('SendNoWriter', { logger }, IMQMode.PUBLISHER);
- // Force start to be a no-op so writer remains undefined
- const startStub = sinon.stub(rq, 'start').resolves(rq);
-
- let thrown: any;
- try {
- await rq.send('AnyQueue', { test: true });
- } catch (err) {
- thrown = err;
- }
-
- expect(thrown).to.be.instanceof(TypeError);
- expect(`${thrown}`).to.include('unable to initialize queue');
-
- startStub.restore();
- await rq.destroy().catch(() => undefined);
- });
-});
diff --git a/test/RedisQueue.send.worker.mode.spec.ts b/test/RedisQueue.send.worker.mode.spec.ts
deleted file mode 100644
index 213e987..0000000
--- a/test/RedisQueue.send.worker.mode.spec.ts
+++ /dev/null
@@ -1,36 +0,0 @@
-/*!
- * Additional RedisQueue tests: send() worker-only mode error
- */
-import './mocks';
-import { expect } from 'chai';
-import { RedisQueue, IMQMode } from '../src';
-
-function makeLogger() {
- return {
- log: (..._args: any[]) => undefined,
- info: (..._args: any[]) => undefined,
- warn: (..._args: any[]) => undefined,
- error: (..._args: any[]) => undefined,
- } as any;
-}
-
-describe('RedisQueue.send() worker-only mode', function() {
- this.timeout(10000);
-
- it('should throw when called in WORKER only mode', async () => {
- const logger = makeLogger();
- const rq: any = new RedisQueue('WorkerOnly', { logger }, IMQMode.WORKER);
-
- let thrown: any;
- try {
- await rq.send('AnyQueue', { test: true });
- } catch (err) {
- thrown = err;
- }
-
- expect(thrown).to.be.instanceof(TypeError);
- expect(`${thrown}`).to.include('WORKER only mode');
-
- await rq.destroy().catch(() => undefined);
- });
-});
diff --git a/test/RedisQueue.unsubscribe.spec.ts b/test/RedisQueue.unsubscribe.spec.ts
deleted file mode 100644
index a5af0e8..0000000
--- a/test/RedisQueue.unsubscribe.spec.ts
+++ /dev/null
@@ -1,53 +0,0 @@
-/*!
- * Additional RedisQueue tests: unsubscribe() cleanup path
- */
-import './mocks';
-import { expect } from 'chai';
-import * as sinon from 'sinon';
-import { RedisQueue } from '../src';
-
-function makeLogger() {
- return {
- log: (..._args: any[]) => undefined,
- info: (..._args: any[]) => undefined,
- warn: (..._args: any[]) => undefined,
- error: (..._args: any[]) => undefined,
- } as any;
-}
-
-describe('RedisQueue.unsubscribe()', function() {
- this.timeout(10000);
-
- it('should cleanup subscription channel when present', async () => {
- const logger = makeLogger();
- const rq: any = new RedisQueue('SubUnsub', { logger });
- await rq.start();
-
- const handler = sinon.spy();
- await rq.subscribe('SubUnsub', handler);
-
- expect(rq.subscription).to.be.ok;
- expect(rq.subscriptionName).to.equal('SubUnsub');
-
- const unsubSpy = sinon.spy(rq.subscription, 'unsubscribe');
- const ralSpy = sinon.spy(rq.subscription, 'removeAllListeners');
- const disconnectSpy = sinon.spy(rq.subscription, 'disconnect');
- const quitSpy = sinon.spy(rq.subscription, 'quit');
-
- await rq.unsubscribe();
-
- expect(unsubSpy.calledOnce).to.equal(true);
- expect(ralSpy.calledOnce).to.equal(true);
- expect(disconnectSpy.calledOnce).to.equal(true);
- expect(quitSpy.calledOnce).to.equal(true);
- expect(rq.subscription).to.equal(undefined);
- expect(rq.subscriptionName).to.equal(undefined);
-
- unsubSpy.restore();
- ralSpy.restore();
- disconnectSpy.restore();
- quitSpy.restore();
-
- await rq.destroy().catch(() => undefined);
- });
-});
diff --git a/test/UDPClusterManager.missing.branches.spec.ts b/test/UDPClusterManager.missing.branches.spec.ts
deleted file mode 100644
index 5d4e892..0000000
--- a/test/UDPClusterManager.missing.branches.spec.ts
+++ /dev/null
@@ -1,39 +0,0 @@
-/*!
- * UDPClusterManager missing branches coverage
- */
-import './mocks';
-import { expect } from 'chai';
-import * as sinon from 'sinon';
-import { UDPClusterManager } from '../src';
-
-describe('UDPClusterManager - cover remaining branches', () => {
- it('destroySocket should call socket.unref() when socket is present', async () => {
- // Prepare fake socket with unref
- const unref = sinon.spy();
- const removeAll = sinon.spy();
- const sock: any = {
- removeAllListeners: removeAll,
- close: (cb: (err?: any) => void) => cb(),
- unref,
- };
- const key = 'test-key';
- (UDPClusterManager as any).sockets[key] = sock;
- await (UDPClusterManager as any).destroySocket(key, sock);
- expect(unref.called).to.equal(true);
- expect((UDPClusterManager as any).sockets[key]).to.equal(undefined);
- });
-
- it('destroySocket should work when socket.unref() is absent (optional chaining negative branch)', async () => {
- const removeAll = sinon.spy();
- const sock: any = {
- removeAllListeners: removeAll,
- close: (cb: (err?: any) => void) => cb(),
- // no unref method
- };
- const key = 'test-key-2';
- (UDPClusterManager as any).sockets[key] = sock;
- await (UDPClusterManager as any).destroySocket(key, sock);
- // should not throw, sockets map cleaned
- expect((UDPClusterManager as any).sockets[key]).to.equal(undefined);
- });
-});
diff --git a/test/helpers/index.ts b/test/helpers/index.ts
new file mode 100644
index 0000000..38af796
--- /dev/null
+++ b/test/helpers/index.ts
@@ -0,0 +1,38 @@
+/*!
+ * IMQ Unit Test Helpers
+ *
+ * I'm Queue Software Project
+ * Copyright (C) 2025 imqueue.com
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ * If you want to use this code in a closed source (commercial) project, you can
+ * purchase a proprietary commercial license. Please contact us at
+ * to get commercial licensing options.
+ */
+import { ILogger } from '../..';
+
+/**
+ * Builds a fresh no-op logger suitable for spying on in unit tests.
+ *
+ * @return {ILogger}
+ */
+export function makeLogger(): ILogger {
+ return {
+ log: (..._args: any[]) => undefined,
+ info: (..._args: any[]) => undefined,
+ warn: (..._args: any[]) => undefined,
+ error: (..._args: any[]) => undefined,
+ } as unknown as ILogger;
+}
diff --git a/test/mocks/dgram.ts b/test/mocks/dgram.ts
index d238f01..4a01c3e 100644
--- a/test/mocks/dgram.ts
+++ b/test/mocks/dgram.ts
@@ -25,7 +25,7 @@ import mock = require('mock-require');
import { EventEmitter } from 'events';
class Socket extends EventEmitter {
- public bindArgs: any[];
+ public bindArgs: any[] = [];
constructor() {
super();
diff --git a/test/mocks/logger.ts b/test/mocks/logger.ts
index 2d7e4d2..df5afa6 100644
--- a/test/mocks/logger.ts
+++ b/test/mocks/logger.ts
@@ -25,5 +25,5 @@ export const logger: any = {
log() {},
info() {},
warn() {},
- error() {}
+ error() {},
};
diff --git a/test/mocks/os.ts b/test/mocks/os.ts
index be7ee59..d62155c 100644
--- a/test/mocks/os.ts
+++ b/test/mocks/os.ts
@@ -37,4 +37,4 @@ export const networkInterfaces = () => {
};
};
-mock('os', Object.assign(os, { networkInterfaces }));
+mock('os', { ...os, networkInterfaces });
diff --git a/test/mocks/redis.ts b/test/mocks/redis.ts
index d652392..44fee50 100644
--- a/test/mocks/redis.ts
+++ b/test/mocks/redis.ts
@@ -21,7 +21,7 @@
* purchase a proprietary commercial license. Please contact us at
* to get commercial licensing options.
*/
-import * as mock from 'mock-require';
+import mock from 'mock-require';
import { EventEmitter } from 'events';
import * as crypto from 'crypto';
@@ -81,7 +81,7 @@ export class RedisClientMock extends EventEmitter {
const self = RedisClientMock;
const key = args.shift();
let result = 0;
- if (/:watch:lock$/.test(key)) {
+ if (String(key).endsWith(':watch:lock')) {
if (typeof self.__keys[key] === 'undefined') {
self.__keys[key] = args.shift();
result = 1;
@@ -108,12 +108,15 @@ export class RedisClientMock extends EventEmitter {
const [key, timeout, cb] = args;
const q = RedisClientMock.__queues__[key] || [];
if (!q.length) {
- this.__rt && clearTimeout(this.__rt);
+ if (this.__rt) {
+ clearTimeout(this.__rt);
+ }
return new Promise(resolve => {
- this.__rt = setTimeout(() => resolve(this.brpop(
- key, timeout, cb,
- )), timeout || 100);
+ this.__rt = setTimeout(
+ () => resolve(this.brpop(key, timeout, cb)),
+ timeout || 100,
+ );
});
} else {
const result = [key, q.shift()];
@@ -128,23 +131,28 @@ export class RedisClientMock extends EventEmitter {
from: string,
to: string,
timeout: number,
- cb?: Function
+ cb?: Function,
): Promise {
- const fromQ = RedisClientMock.__queues__[from] =
- RedisClientMock.__queues__[from] || [];
- const toQ = RedisClientMock.__queues__[to] =
- RedisClientMock.__queues__[to] || [];
+ const fromQ = (RedisClientMock.__queues__[from] =
+ RedisClientMock.__queues__[from] || []);
+ const toQ = (RedisClientMock.__queues__[to] =
+ RedisClientMock.__queues__[to] || []);
if (!fromQ.length) {
- this.__rt && clearTimeout(this.__rt);
+ if (this.__rt) {
+ clearTimeout(this.__rt);
+ }
return new Promise(resolve => {
- this.__rt = setTimeout(() => resolve(this.brpoplpush(
- from, to, timeout, cb,
- )), timeout || 100);
+ this.__rt = setTimeout(
+ () => resolve(this.brpoplpush(from, to, timeout, cb)),
+ timeout || 100,
+ );
});
} else {
toQ.push(fromQ.shift());
- cb && cb(null, '1');
+ if (cb) {
+ cb(null, '1');
+ }
return '1';
}
@@ -157,8 +165,8 @@ export class RedisClientMock extends EventEmitter {
stop: number,
cb?: Function,
): boolean {
- const q = RedisClientMock.__queues__[key] =
- RedisClientMock.__queues__[key] || [];
+ const q = (RedisClientMock.__queues__[key] =
+ RedisClientMock.__queues__[key] || []);
const result = q.splice(start, stop);
this.cbExecute(cb, null, result);
return result;
@@ -189,13 +197,17 @@ export class RedisClientMock extends EventEmitter {
if (cmd === 'LOAD') {
const hash = sha1(scriptOrHash);
RedisClientMock.__scripts[hash] = scriptOrHash;
- isCb && cb(null, hash);
+ if (isCb) {
+ cb(null, hash);
+ }
return hash;
}
if (cmd === 'EXISTS') {
const hash = RedisClientMock.__scripts[scriptOrHash] !== undefined;
- isCb && cb(null, hash);
+ if (isCb) {
+ cb(null, hash);
+ }
return [Number(hash)];
}
@@ -217,8 +229,7 @@ export class RedisClientMock extends EventEmitter {
this.cbExecute(cb, null, result);
return result;
- }
- else if (cmd === 'SETNAME') {
+ } else if (cmd === 'SETNAME') {
this.__name = name;
self.__clientList[name] = true;
}
@@ -275,7 +286,7 @@ export class RedisClientMock extends EventEmitter {
const [key, score, value, cb] = args;
const timeout = score - Date.now();
setTimeout(() => {
- const toKey = key.split(/:/).slice(0,2).join(':');
+ const toKey = key.split(/:/).slice(0, 2).join(':');
this.lpush(toKey, value);
}, timeout);
this.cbExecute(cb);
@@ -321,6 +332,7 @@ export class RedisClientMock extends EventEmitter {
}
mock('ioredis', {
+ __esModule: true,
default: RedisClientMock,
Redis: RedisClientMock,
});
diff --git a/test/profile.decorator.branches.spec.ts b/test/profile.decorator.branches.spec.ts
deleted file mode 100644
index aa9c08a..0000000
--- a/test/profile.decorator.branches.spec.ts
+++ /dev/null
@@ -1,34 +0,0 @@
-/*!
- * Additional tests to cover remaining branches in profile decorator
- */
-import './mocks';
-import { expect } from 'chai';
-import { profile, LogLevel } from '..';
-
-// Note: We intentionally call decorated methods without a "this" context
-// to exercise the (this || target) branches inside the decorator wrapper.
-
-describe('profile decorator extra branches', () => {
- it('should return early via original.apply(target, ...) when both debug flags are false and this is undefined', () => {
- class T1 {
- @profile({ enableDebugTime: false, enableDebugArgs: false, logLevel: LogLevel.LOG })
- public m(...args: any[]) { return args; }
- }
- const o = new T1();
- const fn = Object.getPrototypeOf(o).m as Function; // wrapper
- const res = fn.call(undefined, 1, 2, 3);
- expect(res).to.deep.equal([1, 2, 3]);
- });
-
- it('should execute debug path with (this || target) picking target and logLevel fallback to IMQ_LOG_LEVEL', () => {
- class T2 {
- // no logger on prototype; calling with undefined this picks target
- @profile({ enableDebugTime: true, enableDebugArgs: true, logLevel: undefined as any })
- public m(..._args: any[]) { /* noop */ }
- }
- const o = new T2();
- const fn = Object.getPrototypeOf(o).m as Function; // wrapper
- // provide serializable args to avoid logger.error path when logger is undefined
- expect(() => fn.call(undefined, 1, { a: 2 }, 'x')).to.not.throw();
- });
-});
diff --git a/test/profile.more.branches.spec.ts b/test/profile.more.branches.spec.ts
deleted file mode 100644
index 80f1672..0000000
--- a/test/profile.more.branches.spec.ts
+++ /dev/null
@@ -1,57 +0,0 @@
-/*!
- * Additional profile.ts branch coverage tests
- */
-import './mocks';
-import { expect } from 'chai';
-import * as sinon from 'sinon';
-import * as mock from 'mock-require';
-
-// We re-require the module inside tests to pick up env changes when needed
-
-describe('profile.ts additional branches', () => {
- afterEach(() => {
- mock.stopAll();
- delete (process as any).env.IMQ_LOG_TIME_FORMAT;
- });
-
- it('logDebugInfo: should not attempt to call missing log method (no-op path)', () => {
- const { logDebugInfo, LogLevel } = mock.reRequire('../src/profile');
- const fakeLogger: any = {
- // intentionally no 'log' or 'info' method for selected level
- error: sinon.spy(),
- };
- const options = {
- debugTime: true,
- debugArgs: true,
- className: 'X',
- args: [1, { a: 2 }],
- methodName: 'm',
- start: (process.hrtime as any).bigint(),
- logger: fakeLogger,
- logLevel: LogLevel.LOG,
- };
- expect(() => logDebugInfo(options)).to.not.throw();
- // ensures error not called due to serialization success
- expect(fakeLogger.error.called).to.equal(false);
- });
-
- it('logDebugInfo: should call logger.error on JSON.stringify error (BigInt arg)', () => {
- const { logDebugInfo, LogLevel } = mock.reRequire('../src/profile');
- const fakeLogger: any = {
- error: sinon.spy(),
- };
- const args = [BigInt(1)]; // JSON.stringify throws on BigInt
- const options = {
- debugTime: false,
- debugArgs: true,
- className: 'Y',
- args,
- methodName: 'n',
- start: (process.hrtime as any).bigint(),
- logger: fakeLogger,
- logLevel: LogLevel.INFO,
- };
- logDebugInfo(options);
- expect(fakeLogger.error.calledOnce).to.equal(true);
- });
-});
diff --git a/test/profile.rejection.spec.ts b/test/profile.rejection.spec.ts
deleted file mode 100644
index bc4b7f0..0000000
--- a/test/profile.rejection.spec.ts
+++ /dev/null
@@ -1,33 +0,0 @@
-/*!
- * Additional profile tests for async rejection catch path
- */
-import './mocks';
-import { expect } from 'chai';
-import * as sinon from 'sinon';
-import { profile } from '..';
-import * as core from '..';
-
-class RejectingClass {
- public logger: any = { info: () => undefined, error: () => undefined };
-
- @profile({ enableDebugTime: true, enableDebugArgs: true })
- public async willReject(): Promise {
- return Promise.reject(new Error('boom'));
- }
-}
-
-describe('profile() async rejection path', () => {
- it('should log via logger when async method rejects', async () => {
- const logger = { info: sinon.spy(), error: () => undefined } as any;
- const obj = new RejectingClass();
- obj.logger = logger;
- try {
- await obj.willReject();
- } catch (e) {
- // expected
- }
- // allow microtask queue
- await new Promise(res => setTimeout(res, 0));
- expect(logger.info.called).to.be.true;
- });
-});
diff --git a/test/profile.ts b/test/profile.ts
deleted file mode 100644
index 8ad7433..0000000
--- a/test/profile.ts
+++ /dev/null
@@ -1,219 +0,0 @@
-/*!
- * profile() Function Unit Tests
- *
- * I'm Queue Software Project
- * Copyright (C) 2025 imqueue.com
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
- *
- * If you want to use this code in a closed source (commercial) project, you can
- * purchase a proprietary commercial license. Please contact us at
- * to get commercial licensing options.
- */
-import './mocks';
-import { expect } from 'chai';
-import * as sinon from 'sinon';
-import * as mock from 'mock-require';
-import {
- profile,
- ILogger,
- verifyLogLevel,
- LogLevel,
- DebugInfoOptions,
-} from '..';
-import { logger } from './mocks';
-
-const BIG_INT_SUPPORT = (() => {
- try {
- return !!BigInt(0);
- } catch (err) {
- return false;
- }
-})();
-
-class ProfiledClass {
- private logger: ILogger = logger;
-
- @profile()
- public decoratedMethod(...args: any[]) {
- return args;
- }
-
- @profile({
- enableDebugTime: true,
- enableDebugArgs: true,
- logLevel: LogLevel.LOG,
- })
- public async decoratedAsyncMethod() {
- return new Promise(resolve => setTimeout(resolve, 10));
- }
-}
-
-class ProfiledClassTimed {
- private logger: ILogger = logger;
-
- @profile({
- enableDebugTime: true,
- logLevel: LogLevel.LOG,
- })
- public decoratedMethod() {}
-}
-
-class ProfiledClassArgued {
- private logger: ILogger = logger;
-
- @profile({
- enableDebugTime: false,
- enableDebugArgs: true,
- logLevel: LogLevel.LOG,
- })
- public decoratedMethod() {}
-}
-
-class ProfiledClassTimedAndArgued {
- private logger: ILogger = logger;
-
- @profile({
- enableDebugTime: true,
- enableDebugArgs: true,
- logLevel: LogLevel.LOG,
- })
- public decoratedMethod() {}
-}
-
-describe('profile()', function() {
- let log: any;
- let error: any;
- let warn: any;
- let info: any;
-
- beforeEach(() => {
- log = sinon.spy(logger, 'log');
- error = sinon.spy(logger, 'error');
- warn = sinon.spy(logger, 'warn');
- info = sinon.spy(logger, 'info');
- });
-
- afterEach(() => {
- log.restore();
- error.restore();
- warn.restore();
- info.restore();
- mock.stopAll();
- delete process.env.IMQ_LOG_TIME_FORMAT;
- });
-
- it('should be a function', () => {
- expect(typeof profile).to.equal('function');
- });
-
- it('should be decorator factory', () => {
- expect(typeof profile()).to.equal('function');
- });
-
- it('should pass decorated method args with no change', () => {
- expect(new ProfiledClass().decoratedMethod(1, 2, 3))
- .to.deep.equal([1, 2, 3]);
- });
-
- it('should log time if enabled', () => {
- new ProfiledClassTimed().decoratedMethod();
- expect(log.calledOnce).to.be.true;
- });
-
- it('should log args if enabled', () => {
- new ProfiledClassArgued().decoratedMethod();
- expect(log.calledOnce).to.be.true;
- });
-
- it('should log time and args if both enabled', () => {
- new ProfiledClassTimedAndArgued().decoratedMethod();
- expect(log.calledTwice).to.be.true;
- });
-
- it('should handle async methods correctly', async () => {
- await new ProfiledClass().decoratedAsyncMethod();
- expect(log.calledTwice).to.be.true;
- });
-
- describe('verifyLogLevel()', () => {
- it('should return valid log levels as is', () => {
- expect(verifyLogLevel(LogLevel.LOG)).to.equal(LogLevel.LOG);
- expect(verifyLogLevel(LogLevel.INFO)).to.equal(LogLevel.INFO);
- expect(verifyLogLevel(LogLevel.WARN)).to.equal(LogLevel.WARN);
- expect(verifyLogLevel(LogLevel.ERROR)).to.equal(LogLevel.ERROR);
- });
-
- it('should return default log level on invalid value', () => {
- expect(verifyLogLevel('invalid')).to.equal(LogLevel.INFO);
- });
- });
-
- describe('logDebugInfo()', () => {
- const start = BIG_INT_SUPPORT ? BigInt(1) : 1;
- const baseOptions: DebugInfoOptions = {
- debugTime: true,
- debugArgs: true,
- className: 'TestClass',
- args: [1, 'a', { b: 2 }],
- methodName: 'testMethod',
- start,
- logger,
- logLevel: LogLevel.LOG,
- };
-
- it('should log time in microseconds by default', () => {
- const { logDebugInfo } = mock.reRequire('../src/profile');
- logDebugInfo(baseOptions);
- expect(log.calledWithMatch(/μs/)).to.be.true;
- });
-
- it('should log time in milliseconds', () => {
- process.env.IMQ_LOG_TIME_FORMAT = 'milliseconds';
- const { logDebugInfo } = mock.reRequire('../src/profile');
- logDebugInfo(baseOptions);
- expect(log.calledWithMatch(/ms/)).to.be.true;
- });
-
- it('should log time in seconds', () => {
- process.env.IMQ_LOG_TIME_FORMAT = 'seconds';
- const { logDebugInfo } = mock.reRequire('../src/profile');
- logDebugInfo(baseOptions);
- expect(log.calledWithMatch(/sec/)).to.be.true;
- });
-
- it('should handle circular references in args', () => {
- const { logDebugInfo } = mock.reRequire('../src/profile');
- const a: any = { b: 1 };
- const b = { a };
- a.b = b;
- logDebugInfo({ ...baseOptions, args: [a] });
- expect(error.notCalled).to.be.true;
- });
-
- it('should not log when logger method is missing', () => {
- const { logDebugInfo } = mock.reRequire('../src/profile');
- const dummyLogger: any = { error: logger.error.bind(logger) };
- logDebugInfo({ ...baseOptions, logger: dummyLogger, logLevel: 'nonexistent' as any });
- expect(log.notCalled).to.be.true;
- });
-
- it('should handle JSON.stringify errors', () => {
- const { logDebugInfo } = mock.reRequire('../src/profile');
- const badJson = { toJSON: () => { throw new Error('bad json'); } };
- logDebugInfo({ ...baseOptions, args: [badJson] });
- expect(error.calledOnce).to.be.true;
- });
- });
-});
\ No newline at end of file
diff --git a/test/promisify.ts b/test/promisify.ts
deleted file mode 100644
index 86eea56..0000000
--- a/test/promisify.ts
+++ /dev/null
@@ -1,94 +0,0 @@
-/*!
- * promisify() Function Unit Tests
- *
- * I'm Queue Software Project
- * Copyright (C) 2025 imqueue.com
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
- *
- * If you want to use this code in a closed source (commercial) project, you can
- * purchase a proprietary commercial license. Please contact us at
- * to get commercial licensing options.
- */
-import './mocks';
-import { expect } from 'chai';
-import * as sinon from 'sinon';
-import { promisify } from '..';
-
-class FirstTestClass {
- public one(callback?: (...args: any[]) => void) {
- setTimeout(() => callback && callback());
- }
-}
-
-class SecondTestClass {
- public one(callback?: (...args: any[]) => void) {
- setTimeout(() => callback && callback());
- }
- public two(callback?: (...args: any[]) => void) {
- setTimeout(() => callback && callback());
- }
- public three(callback?: (...args: any[]) => void) {
- setTimeout(() => callback && callback());
- }
-}
-
-class ThirdTestClass {
- // noinspection JSMethodCanBeStatic
- public one(callback?: (...args: any[]) => void) {
- callback && callback();
- }
-}
-
-class TestError extends Error {}
-class FourthTestClass {
- public one(callback?: (...args: any[]) => void) {
- setTimeout(() => callback && callback(new TestError()));
- }
-}
-
-describe('promisify()', function() {
-
- it('should convert callback-based method to promise-like', async () => {
- promisify(FirstTestClass.prototype);
- const o = new FirstTestClass();
- expect(o.one()).to.be.instanceof(Promise);
- });
-
- it('should restrict conversion to a given list of methods only', async () => {
- promisify(SecondTestClass.prototype, ['one', 'three']);
- const o = new SecondTestClass();
- expect(o.one()).to.be.instanceof(Promise);
- expect(o.two()).to.be.undefined;
- expect(o.three()).to.be.instanceof(Promise);
- });
-
- it('should act as callback-based if callback bypassed', () => {
- promisify(ThirdTestClass.prototype);
- const o = new ThirdTestClass();
- const spy = sinon.spy(() => {});
- expect(o.one(spy)).to.be.undefined;
- expect(spy.called).to.be.true;
- });
-
- it('should throw when promise-like', async () => {
- promisify(FourthTestClass.prototype);
- const o = new FourthTestClass();
- try {
- await o.one();
- } catch (err) {
- expect(err).to.be.instanceof(TestError);
- }
- });
-});
diff --git a/test/ClusterManager.spec.ts b/test/unit/ClusterManager.spec.ts
similarity index 55%
rename from test/ClusterManager.spec.ts
rename to test/unit/ClusterManager.spec.ts
index 42b86df..b86ffa7 100644
--- a/test/ClusterManager.spec.ts
+++ b/test/unit/ClusterManager.spec.ts
@@ -4,51 +4,57 @@
* I'm Queue Software Project
* Copyright (C) 2025 imqueue.com
*/
-import './mocks';
-import { expect } from 'chai';
-import * as sinon from 'sinon';
-import { ClusterManager, InitializedCluster } from '../src/ClusterManager';
+import '../mocks';
+import { describe, it, afterEach, mock } from 'node:test';
+import * as assert from 'node:assert/strict';
+import { ClusterManager, InitializedCluster } from '../../src/ClusterManager';
class TestClusterManager extends ClusterManager {
public destroyed = false;
- public constructor() { super(); }
+ public constructor() {
+ super();
+ }
public async destroy(): Promise {
this.destroyed = true;
}
}
describe('ClusterManager.remove()', () => {
+ afterEach(() => {
+ mock.restoreAll();
+ });
+
it('should call destroy when the last cluster is removed and destroy=true', async () => {
const cm = new TestClusterManager();
const cluster: InitializedCluster = cm.init({
- add: () => ({} as any),
+ add: () => ({}) as any,
remove: () => undefined,
find: () => undefined,
});
// sanity: one cluster registered
- expect((cm as any).clusters.length).to.equal(1);
- const spy = sinon.spy(cm, 'destroy');
+ assert.equal((cm as any).clusters.length, 1);
+ const spy = mock.method(cm, 'destroy');
await cm.remove(cluster, true);
- expect(spy.calledOnce).to.be.true;
- expect((cm as any).clusters.length).to.equal(0);
- expect(cm.destroyed).to.be.true;
+ assert.equal(spy.mock.callCount(), 1);
+ assert.equal((cm as any).clusters.length, 0);
+ assert.equal(cm.destroyed, true);
});
it('should not call destroy when destroy=false', async () => {
const cm = new TestClusterManager();
const cluster: InitializedCluster = cm.init({
- add: () => ({} as any),
+ add: () => ({}) as any,
remove: () => undefined,
find: () => undefined,
});
- const spy = sinon.spy(cm, 'destroy');
+ const spy = mock.method(cm, 'destroy');
await cm.remove(cluster.id, false);
- expect(spy.called).to.be.false;
- expect((cm as any).clusters.length).to.equal(0);
+ assert.equal(spy.mock.callCount() > 0, false);
+ assert.equal((cm as any).clusters.length, 0);
});
});
diff --git a/test/unit/ClusteredRedisQueue/AddServer.spec.ts b/test/unit/ClusteredRedisQueue/AddServer.spec.ts
new file mode 100644
index 0000000..43b02ed
--- /dev/null
+++ b/test/unit/ClusteredRedisQueue/AddServer.spec.ts
@@ -0,0 +1,71 @@
+/*!
+ * ClusteredRedisQueue.addServerWithQueueInitializing tests
+ * (default initializeQueue param branch + initializeQueue=false branch)
+ */
+import '../../mocks';
+import { describe, it } from 'node:test';
+import * as assert from 'node:assert/strict';
+import { ClusteredRedisQueue } from '../../../src';
+import { ClusterManager } from '../../../src/ClusterManager';
+
+const server = { host: '127.0.0.1', port: 6380 };
+
+describe('ClusteredRedisQueue.addServerWithQueueInitializing() default param', () => {
+ it('should use default initializeQueue=true when second param omitted', async () => {
+ const cq: any = new ClusteredRedisQueue('CQ-Default', {
+ logger: console,
+ cluster: [{ host: '127.0.0.1', port: 6379 }],
+ });
+ // prevent any actual start/subscription side-effects
+ (cq as any).state.started = false;
+ (cq as any).state.subscription = null;
+
+ const server = { host: '192.168.0.1', port: 6380 };
+ const initializedSpy = new Promise(resolve => {
+ cq['clusterEmitter'].once('initialized', () => resolve());
+ });
+
+ // Call without the second argument to hit default "true" branch
+ (cq as any).addServerWithQueueInitializing(server);
+
+ await initializedSpy; // should emit initialized when default is true
+
+ // Ensure the server added and queue length updated
+ assert.equal(
+ (cq as any).servers.some(
+ (s: any) => s.host === server.host && s.port === server.port,
+ ),
+ true,
+ );
+ assert.equal((cq as any).imqLength, (cq as any).imqs.length);
+
+ await cq.destroy();
+ });
+});
+
+describe('ClusteredRedisQueue.addServerWithQueueInitializing(false)', () => {
+ it('should add server without initializing queue and not emit initialized', async () => {
+ const manager = new (ClusterManager as any)();
+ const cq: any = new ClusteredRedisQueue('NoInit', {
+ clusterManagers: [manager],
+ });
+
+ let initializedCalled = false;
+ (cq as any).clusterEmitter.on('initialized', () => {
+ initializedCalled = true;
+ });
+
+ // call private method via any to cover branch
+ (cq as any).addServerWithQueueInitializing(server, false);
+
+ // should have server and imq added
+ assert.ok(cq.servers.length > 0);
+ assert.ok(cq.imqs.length > 0);
+ // queueLength updated
+ assert.equal(cq.imqLength, cq.imqs.length);
+ // initialized not emitted
+ assert.equal(initializedCalled, false);
+
+ await cq.destroy();
+ });
+});
diff --git a/test/unit/ClusteredRedisQueue/ClusteredRedisQueue.spec.ts b/test/unit/ClusteredRedisQueue/ClusteredRedisQueue.spec.ts
new file mode 100644
index 0000000..a9fdbdc
--- /dev/null
+++ b/test/unit/ClusteredRedisQueue/ClusteredRedisQueue.spec.ts
@@ -0,0 +1,398 @@
+/*!
+ * ClusteredRedisQueue Unit Tests (core behavior + EventEmitter proxy methods)
+ *
+ * I'm Queue Software Project
+ * Copyright (C) 2025 imqueue.com
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ * If you want to use this code in a closed source (commercial) project, you can
+ * purchase a proprietary commercial license. Please contact us at
+ * to get commercial licensing options.
+ */
+import { logger } from '../../mocks';
+import { describe, it, afterEach, mock, Mock } from 'node:test';
+import * as assert from 'node:assert/strict';
+import { ClusteredRedisQueue } from '../../../src';
+import { ClusterManager } from '../../../src/ClusterManager';
+
+process.setMaxListeners(100);
+
+function assertDeepInclude(actual: any, subset: any): void {
+ for (const key of Object.keys(subset)) {
+ assert.deepEqual(actual[key], subset[key]);
+ }
+}
+
+const clusterConfig = {
+ logger,
+ cluster: [
+ {
+ host: '127.0.0.1',
+ port: 7777,
+ },
+ {
+ host: '127.0.0.1',
+ port: 8888,
+ },
+ ],
+};
+
+describe('ClusteredRedisQueue', () => {
+ afterEach(() => {
+ mock.restoreAll();
+ });
+
+ it('should be a class', () => {
+ assert.equal(typeof ClusteredRedisQueue, 'function');
+ });
+
+ it('should implement IMessageQueue interface', () => {
+ assert.equal(typeof ClusteredRedisQueue.prototype.start, 'function');
+ assert.equal(typeof ClusteredRedisQueue.prototype.stop, 'function');
+ assert.equal(typeof ClusteredRedisQueue.prototype.send, 'function');
+ assert.equal(typeof ClusteredRedisQueue.prototype.destroy, 'function');
+ });
+
+ describe('constructor()', () => {
+ it('should throw with improper options passed', () => {
+ assert.throws(
+ () => new ClusteredRedisQueue('TestClusteredQueue'),
+ TypeError,
+ );
+ });
+
+ it('should not throw if proper options passed', () => {
+ assert.doesNotThrow(
+ () =>
+ new ClusteredRedisQueue(
+ 'TestClusteredQueue',
+ clusterConfig,
+ ),
+ );
+ });
+
+ it('should initialize cluster manager', () => {
+ const clusterManager = new (ClusterManager as any)();
+
+ const init: Mock = mock.method(clusterManager, 'init');
+
+ new ClusteredRedisQueue('TestClusteredQueue', {
+ clusterManagers: [clusterManager],
+ });
+
+ assert.equal(init.mock.callCount() > 0, true);
+ });
+ });
+
+ describe('start()', () => {
+ it('should start each nested imq', async () => {
+ const cq: any = new ClusteredRedisQueue(
+ 'TestClusteredQueue',
+ clusterConfig,
+ );
+
+ cq.imqs.forEach((imq: any) => {
+ mock.method(imq, 'start');
+ });
+
+ await cq.start();
+
+ cq.imqs.forEach((imq: any) => {
+ assert.equal(imq.start.mock.callCount() > 0, true);
+ });
+
+ await cq.destroy();
+ });
+ });
+
+ describe('stop()', () => {
+ it('should stop each nested imq', async () => {
+ const cq: any = new ClusteredRedisQueue(
+ 'TestClusteredQueue',
+ clusterConfig,
+ );
+
+ cq.imqs.forEach((imq: any) => {
+ mock.method(imq, 'stop');
+ });
+
+ await cq.stop();
+
+ cq.imqs.forEach((imq: any) => {
+ assert.equal(imq.stop.mock.callCount() > 0, true);
+ });
+
+ await cq.destroy();
+ });
+ });
+
+ describe('send()', () => {
+ it(
+ 'should balance send requests round-robin manner across nested ' +
+ 'queues',
+ async () => {
+ const cq: any = new ClusteredRedisQueue(
+ 'TestClusteredQueue',
+ clusterConfig,
+ );
+
+ cq.imqs.forEach((imq: any) => {
+ mock.method(imq, 'send');
+ });
+
+ await cq.send('TestClusteredQueue', { hello: 'world' });
+
+ assert.equal(cq.imqs[0].send.mock.callCount(), 1);
+ assert.equal(cq.imqs[1].send.mock.callCount() > 0, false);
+
+ await cq.send('TestClusteredQueue', { hello: 'world' });
+
+ assert.equal(cq.imqs[0].send.mock.callCount(), 1);
+ assert.equal(cq.imqs[1].send.mock.callCount(), 1);
+
+ await cq.send('TestClusteredQueue', { hello: 'world' });
+
+ assert.equal(cq.imqs[0].send.mock.callCount(), 2);
+ assert.equal(cq.imqs[1].send.mock.callCount(), 1);
+
+ await cq.destroy();
+ },
+ );
+
+ it('should send message after queue was initialized', () => {
+ return new Promise(resolve => {
+ const clusterManager = new (ClusterManager as any)();
+ const cqOne: any = new ClusteredRedisQueue(
+ 'TestClusteredQueueOne',
+ {
+ clusterManagers: [clusterManager],
+ logger,
+ },
+ );
+ const cqTwo: any = new ClusteredRedisQueue(
+ 'TestClusteredQueueTwo',
+ {
+ clusterManagers: [clusterManager],
+ logger,
+ },
+ );
+ const message = { hello: 'world' };
+
+ cqOne.start();
+ cqTwo.start();
+
+ cqTwo.on('message', () => {
+ cqOne.destroy();
+ cqTwo.destroy();
+
+ resolve();
+ });
+
+ cqOne.send('TestClusteredQueueTwo', message);
+ cqTwo.addServer(clusterConfig.cluster[0]);
+ cqOne.addServer(clusterConfig.cluster[0]);
+ });
+ });
+ });
+
+ describe('destroy()', () => {
+ it('should destroy each nested imq', async () => {
+ const cq: any = new ClusteredRedisQueue(
+ 'TestClusteredQueue',
+ clusterConfig,
+ );
+
+ cq.imqs.forEach((imq: any) => {
+ mock.method(imq, 'destroy');
+ });
+
+ await cq.destroy();
+
+ cq.imqs.forEach((imq: any) => {
+ assert.equal(imq.destroy.mock.callCount() > 0, true);
+ });
+ });
+ });
+
+ describe('clear()', () => {
+ it('should clear each nested imq', async () => {
+ const cq: any = new ClusteredRedisQueue(
+ 'TestClusteredQueue',
+ clusterConfig,
+ );
+
+ cq.imqs.forEach((imq: any) => {
+ mock.method(imq, 'clear');
+ });
+
+ await cq.clear();
+
+ cq.imqs.forEach((imq: any) => {
+ assert.equal(imq.clear.mock.callCount() > 0, true);
+ });
+
+ await cq.destroy();
+ });
+ });
+
+ describe('subscribe()', () => {
+ it('should subscribe after queue initialization', () => {
+ const clusterManager = new (ClusterManager as any)();
+ const cq: any = new ClusteredRedisQueue('TestClusteredQueue', {
+ clusterManagers: [clusterManager],
+ logger,
+ });
+ const channel = 'TestChannel';
+
+ cq.subscribe(channel, () => {});
+ cq.addServer(clusterConfig.cluster[0]);
+
+ assert.equal(cq.imqs[0].subscriptionName, channel);
+ });
+ });
+
+ describe('addServer()', () => {
+ it('should add cluster server', () => {
+ const clusterManager = new (ClusterManager as any)();
+ const cq: any = new ClusteredRedisQueue('TestClusteredQueue', {
+ clusterManagers: [clusterManager],
+ });
+
+ cq.addServer(clusterConfig.cluster[0]);
+
+ assert.equal(cq.servers.length, 1);
+ });
+
+ it(
+ 'should call adding cluster server method through the' +
+ ' Cluster Manager',
+ () => {
+ const clusterManager = new (ClusterManager as any)();
+ const cq: any = new ClusteredRedisQueue('TestClusteredQueue', {
+ clusterManagers: [clusterManager],
+ });
+
+ for (const server of clusterManager.clusters) {
+ server.add(clusterConfig.cluster[0]);
+ }
+
+ assert.equal(cq.servers.length, 1);
+ },
+ );
+ });
+
+ describe('removeServer()', () => {
+ it('should remove cluster server', () => {
+ const clusterManager = new (ClusterManager as any)();
+ const cq: any = new ClusteredRedisQueue('TestClusteredQueue', {
+ clusterManagers: [clusterManager],
+ });
+
+ cq.addServer(clusterConfig.cluster[0]);
+ cq.removeServer(clusterConfig.cluster[0]);
+
+ assert.equal(cq.servers.length, 0);
+ });
+
+ it(
+ 'should call removing cluster server method through the' +
+ ' Cluster Manager',
+ () => {
+ const clusterManager = new (ClusterManager as any)();
+ const cq: any = new ClusteredRedisQueue('TestClusteredQueue', {
+ clusterManagers: [clusterManager],
+ });
+
+ for (const server of clusterManager.clusters) {
+ server.remove(clusterConfig.cluster[0]);
+ }
+
+ assert.equal(cq.servers.length, 0);
+ },
+ );
+ });
+
+ describe('findServer()', () => {
+ it('should find cluster server', () => {
+ const clusterManager = new (ClusterManager as any)();
+ const cq: any = new ClusteredRedisQueue('TestClusteredQueue', {
+ clusterManagers: [clusterManager],
+ });
+
+ cq.addServer(clusterConfig.cluster[0]);
+
+ const server = cq.findServer(clusterConfig.cluster[0]);
+
+ assertDeepInclude(server, clusterConfig.cluster[0]);
+ });
+
+ it(
+ 'should call find cluster server method through the' +
+ ' Cluster Manager',
+ () => {
+ const clusterManager = new (ClusterManager as any)();
+ const cq: any = new ClusteredRedisQueue('TestClusteredQueue', {
+ clusterManagers: [clusterManager],
+ });
+
+ cq.addServer(clusterConfig.cluster[0]);
+
+ for (const cluster of clusterManager.clusters) {
+ const server = cluster.find(clusterConfig.cluster[0]);
+
+ assertDeepInclude(server, clusterConfig.cluster[0]);
+ }
+ },
+ );
+ });
+});
+
+describe('ClusteredRedisQueue - EventEmitter proxy methods', () => {
+ const clusterConfig = {
+ cluster: [{ host: '127.0.0.1', port: 6379 }],
+ };
+
+ it('should cover rawListeners/getMaxListeners/eventNames/listenerCount/emit', async () => {
+ const clusterManager = new (ClusterManager as any)();
+ const cq: any = new ClusteredRedisQueue('ProxyQueue', {
+ clusterManagers: [clusterManager],
+ });
+
+ // add underlying server and listener
+ cq.addServer(clusterConfig.cluster[0]);
+ const handler: Mock = mock.fn();
+ cq.imqs[0].on('test', handler);
+
+ // set max listeners across emitters and verify getMaxListeners uses templateEmitter
+ cq.setMaxListeners(20);
+ assert.equal(cq.getMaxListeners(), 20);
+
+ // collect raw listeners
+ const raw = cq.rawListeners('test');
+ assert.ok(raw.length > 0);
+
+ // event names come from underlying imq
+ const names = cq.eventNames();
+ assert.ok(Array.isArray(names));
+ assert.ok(names.map(String).includes('test'));
+
+ // listener count is aggregated via templateEmitter method applied on imq[0]
+ assert.equal(cq.listenerCount('test'), 1);
+
+ // emit should return true
+ assert.equal(cq.emit('test', 1, 2, 3), true);
+ assert.equal(handler.mock.callCount(), 1);
+ });
+});
diff --git a/test/unit/ClusteredRedisQueue/Initialize.spec.ts b/test/unit/ClusteredRedisQueue/Initialize.spec.ts
new file mode 100644
index 0000000..7bcfede
--- /dev/null
+++ b/test/unit/ClusteredRedisQueue/Initialize.spec.ts
@@ -0,0 +1,46 @@
+/*!
+ * Additional tests for ClusteredRedisQueue.initializeQueue branches
+ */
+import '../../mocks';
+import { describe, it, mock, Mock } from 'node:test';
+import * as assert from 'node:assert/strict';
+import { ClusteredRedisQueue, RedisQueue } from '../../../src';
+import { ClusterManager } from '../../../src/ClusterManager';
+
+describe('ClusteredRedisQueue.initializeQueue()', () => {
+ it('should call imq.start when started and imq.subscribe when subscription is set', async () => {
+ const startStub: Mock = mock.method(
+ RedisQueue.prototype as any,
+ 'start',
+ async () => undefined,
+ );
+ const subscribeStub: Mock = mock.method(
+ RedisQueue.prototype as any,
+ 'subscribe',
+ async () => undefined,
+ );
+
+ const clusterManager = new (ClusterManager as any)();
+ const cq: any = new ClusteredRedisQueue('InitCover', {
+ clusterManagers: [clusterManager],
+ });
+
+ // mark started and set subscription using public APIs
+ await cq.start();
+ const channel = 'X';
+ const handler = () => undefined;
+ await cq.subscribe(channel, handler);
+
+ // adding a server triggers initializeQueue which should call start and subscribe
+ cq.addServer({ host: '127.0.0.1', port: 6453 });
+
+ // allow promises to resolve
+ await new Promise(res => setTimeout(res, 0));
+
+ assert.ok(startStub.mock.callCount() > 0);
+ assert.ok(subscribeStub.mock.callCount() > 0);
+
+ mock.restoreAll();
+ await cq.destroy();
+ });
+});
diff --git a/test/unit/ClusteredRedisQueue/MatchServers.spec.ts b/test/unit/ClusteredRedisQueue/MatchServers.spec.ts
new file mode 100644
index 0000000..8ea0681
--- /dev/null
+++ b/test/unit/ClusteredRedisQueue/MatchServers.spec.ts
@@ -0,0 +1,44 @@
+/*!
+ * Tests for ClusteredRedisQueue.matchServers combinations
+ */
+import '../../mocks';
+import { describe, it } from 'node:test';
+import * as assert from 'node:assert/strict';
+import { ClusteredRedisQueue } from '../../../src';
+
+// Access private static via casting
+const match = (ClusteredRedisQueue as any).matchServers as (
+ source: any,
+ target: any,
+ strict?: boolean,
+) => boolean;
+
+describe('ClusteredRedisQueue.matchServers()', () => {
+ it('should return sameAddress when no ids provided', () => {
+ assert.equal(
+ match({ host: 'h', port: 1 }, { host: 'h', port: 1 }),
+ true,
+ );
+ assert.equal(
+ match({ host: 'h', port: 1 }, { host: 'h', port: 2 }),
+ false,
+ );
+ });
+
+ it('should match servers if id provided', () => {
+ assert.equal(
+ match(
+ { id: 'a', host: 'h', port: 1 },
+ { id: 'a', host: 'h', port: 2 },
+ ),
+ true,
+ );
+ assert.equal(
+ match(
+ { id: 'a', host: 'h', port: 1 },
+ { id: 'b', host: 'h', port: 1 },
+ ),
+ true,
+ );
+ });
+});
diff --git a/test/copyEventEmitter.ts b/test/unit/CopyEventEmitter.spec.ts
similarity index 83%
rename from test/copyEventEmitter.ts
rename to test/unit/CopyEventEmitter.spec.ts
index 99a50de..3f7641d 100644
--- a/test/copyEventEmitter.ts
+++ b/test/unit/CopyEventEmitter.spec.ts
@@ -19,12 +19,13 @@
* purchase a proprietary commercial license. Please contact us at
* to get commercial licensing options.
*/
-import './mocks';
+import '../mocks';
+import { describe, it } from 'node:test';
+import * as assert from 'node:assert/strict';
import { EventEmitter } from 'events';
-import { expect } from 'chai';
-import { copyEventEmitter } from '../src';
+import { copyEventEmitter } from '../../src';
-describe('copyEventEmitter()', function() {
+describe('copyEventEmitter()', () => {
const eventName = 'test';
it('should have the same number of listeners', () => {
@@ -34,7 +35,7 @@ describe('copyEventEmitter()', function() {
source.on('test', () => {});
copyEventEmitter(source, target);
- expect(target.listenerCount('test')).to.be.equal(1);
+ assert.equal(target.listenerCount('test'), 1);
});
it('should copy the same listener on', () => {
@@ -48,7 +49,7 @@ describe('copyEventEmitter()', function() {
const targetListener = target.listeners(eventName)[0];
const sourceListener = source.listeners(eventName)[0];
- expect(targetListener).to.be.equal(sourceListener);
+ assert.equal(targetListener, sourceListener);
});
it('should copy the same listener once', () => {
@@ -61,7 +62,7 @@ describe('copyEventEmitter()', function() {
const targetListener = target.listeners(eventName)[0];
const sourceListener = source.listeners(eventName)[0];
- expect(targetListener).to.be.equal(sourceListener);
+ assert.equal(targetListener, sourceListener);
});
it('should set same max listeners count', () => {
@@ -74,7 +75,7 @@ describe('copyEventEmitter()', function() {
const targetListenersCount = target.getMaxListeners();
const sourceListenersCount = source.getMaxListeners();
- expect(targetListenersCount).to.be.equal(sourceListenersCount);
+ assert.equal(targetListenersCount, sourceListenersCount);
});
it('should handle listeners without listener property', () => {
@@ -82,9 +83,9 @@ describe('copyEventEmitter()', function() {
const target = new EventEmitter();
// Create a mock listener that looks like onceWrapper but has no listener property
- const mockListener = function() {};
+ const mockListener = function () {};
Object.defineProperty(mockListener, 'toString', {
- value: () => 'function onceWrapper() { ... }'
+ value: () => 'function onceWrapper() { ... }',
});
// Manually add the listener to simulate the edge case
@@ -104,7 +105,7 @@ describe('copyEventEmitter()', function() {
// Restore original inspect
require('util').inspect = originalInspect;
- expect(target.listenerCount(eventName)).to.be.equal(1);
+ assert.equal(target.listenerCount(eventName), 1);
});
it('should handle source without _maxListeners property', () => {
@@ -117,7 +118,7 @@ describe('copyEventEmitter()', function() {
source.on(eventName, () => {});
copyEventEmitter(source, target);
- expect(target.listenerCount(eventName)).to.be.equal(1);
+ assert.equal(target.listenerCount(eventName), 1);
});
it('should handle once listeners with listener property', () => {
@@ -129,14 +130,14 @@ describe('copyEventEmitter()', function() {
copyEventEmitter(source, target);
- expect(target.listenerCount(eventName)).to.be.equal(1);
+ assert.equal(target.listenerCount(eventName), 1);
});
it('should handle onceWrapper-like listener with falsy listener property', () => {
const source = new EventEmitter();
const target = new EventEmitter();
// Create a mock listener that looks like onceWrapper and has a falsy listener property
- const mockListener: any = function() {};
+ const mockListener: any = function () {};
mockListener.listener = 0; // falsy value present
const originalInspect = require('util').inspect;
require('util').inspect = (obj: any) => {
@@ -152,14 +153,14 @@ describe('copyEventEmitter()', function() {
// Restore original inspect
require('util').inspect = originalInspect;
- expect(target.listenerCount(eventName)).to.be.equal(1);
+ assert.equal(target.listenerCount(eventName), 1);
});
it('should handle onceWrapper-like listener with undefined listener property', () => {
const source = new EventEmitter();
const target = new EventEmitter();
- const mockListener: any = function() {};
+ const mockListener: any = function () {};
mockListener.listener = undefined; // explicitly undefined
const originalInspect = require('util').inspect;
require('util').inspect = (obj: any) => {
@@ -175,7 +176,7 @@ describe('copyEventEmitter()', function() {
// Restore original inspect
require('util').inspect = originalInspect;
- expect(target.listenerCount(eventName)).to.be.equal(1);
+ assert.equal(target.listenerCount(eventName), 1);
});
it('should handle onceWrapper-like listener with truthy listener property', () => {
@@ -184,8 +185,10 @@ describe('copyEventEmitter()', function() {
// Create a mock listener that looks like onceWrapper and has a truthy listener property
let called = 0;
- const realListener = () => { called++; };
- const mockListener: any = function() {};
+ const realListener = () => {
+ called++;
+ };
+ const mockListener: any = function () {};
mockListener.listener = realListener; // truthy function
const originalInspect = require('util').inspect;
@@ -203,10 +206,10 @@ describe('copyEventEmitter()', function() {
require('util').inspect = originalInspect;
// Ensure the listener was attached via once() and is callable exactly once
- expect(target.listenerCount(eventName)).to.be.equal(1);
+ assert.equal(target.listenerCount(eventName), 1);
target.emit(eventName);
target.emit(eventName);
- expect(called).to.equal(1);
+ assert.equal(called, 1);
});
it('should handle onceWrapper path when originalListener is undefined', () => {
@@ -218,7 +221,9 @@ describe('copyEventEmitter()', function() {
};
const onceCalls: any[] = [];
const target: any = {
- once: (ev: any, listener: any) => { onceCalls.push([ev, listener]); },
+ once: (ev: any, listener: any) => {
+ onceCalls.push([ev, listener]);
+ },
on: () => {},
};
const originalInspect = require('util').inspect;
@@ -234,8 +239,8 @@ describe('copyEventEmitter()', function() {
// Restore original inspect
require('util').inspect = originalInspect;
- expect(onceCalls.length).to.equal(1);
- expect(onceCalls[0][0]).to.equal(eventName);
- expect(onceCalls[0][1]).to.equal(undefined);
+ assert.equal(onceCalls.length, 1);
+ assert.equal(onceCalls[0][0], eventName);
+ assert.equal(onceCalls[0][1], undefined);
});
});
diff --git a/test/IMQ.ts b/test/unit/IMQ.spec.ts
similarity index 56%
rename from test/IMQ.ts
rename to test/unit/IMQ.spec.ts
index a26abbe..be59d42 100644
--- a/test/IMQ.ts
+++ b/test/unit/IMQ.spec.ts
@@ -21,44 +21,51 @@
* purchase a proprietary commercial license. Please contact us at
* to get commercial licensing options.
*/
-import './mocks';
-import { expect } from 'chai';
-import IMQ, { RedisQueue, ClusteredRedisQueue } from '..';
+import '../mocks';
+import { describe, it } from 'node:test';
+import * as assert from 'node:assert/strict';
+import IMQ, { RedisQueue, ClusteredRedisQueue } from '../..';
describe('IMQ', () => {
-
it('should be a class', () => {
- expect(typeof IMQ).to.equal('function');
+ assert.equal(typeof IMQ, 'function');
});
describe('create()', () => {
it('should return proper object', () => {
- expect(IMQ.create('IMQUnitTest', { vendor: 'Redis' }))
- .instanceof(RedisQueue);
+ assert.ok(
+ IMQ.create('IMQUnitTest', { vendor: 'Redis' }) instanceof
+ RedisQueue,
+ );
});
it('should throw if unknown vendor specified', () => {
- expect(() => IMQ.create('IMQUnitTest', { vendor: 'JudgmentDay' }))
- .to.throw(Error);
+ assert.throws(
+ () => IMQ.create('IMQUnitTest', { vendor: 'JudgmentDay' }),
+ Error,
+ );
});
it('should allow to be called with no options', () => {
- expect(() => IMQ.create('IMQUnitTest'))
- .not.to.throw(Error);
+ assert.doesNotThrow(() => IMQ.create('IMQUnitTest'));
});
it('should return clustered object if cluster options passed', () => {
- expect(IMQ.create('IMQUnitTest', {
- vendor: 'Redis',
- cluster: [{
- host: 'localhost',
- port: 1111
- }, {
- host: 'localhost',
- port: 2222
- }]
- })).instanceof(ClusteredRedisQueue);
+ assert.ok(
+ IMQ.create('IMQUnitTest', {
+ vendor: 'Redis',
+ cluster: [
+ {
+ host: 'localhost',
+ port: 1111,
+ },
+ {
+ host: 'localhost',
+ port: 2222,
+ },
+ ],
+ }) instanceof ClusteredRedisQueue,
+ );
});
});
-
});
diff --git a/test/IMessageQueue.EventEmitter.spec.ts b/test/unit/IMessageQueue.spec.ts
similarity index 82%
rename from test/IMessageQueue.EventEmitter.spec.ts
rename to test/unit/IMessageQueue.spec.ts
index b1fe92f..a1a7eb7 100644
--- a/test/IMessageQueue.EventEmitter.spec.ts
+++ b/test/unit/IMessageQueue.spec.ts
@@ -19,9 +19,10 @@
* purchase a proprietary commercial license. Please contact us at
* to get commercial licensing options.
*/
-import './mocks';
-import { expect } from 'chai';
-import { EventEmitter as IMQEventEmitter } from '../src';
+import '../mocks';
+import { describe, it } from 'node:test';
+import * as assert from 'node:assert/strict';
+import { EventEmitter as IMQEventEmitter } from '../../src';
import { EventEmitter as NodeEventEmitter } from 'events';
// This test ensures the re-exported EventEmitter from IMessageQueue.ts is exercised
@@ -29,13 +30,15 @@ import { EventEmitter as NodeEventEmitter } from 'events';
describe('IMessageQueue EventEmitter re-export', () => {
it('should re-export Node.js EventEmitter and be usable', () => {
// Ensure it is the same constructor
- expect(IMQEventEmitter).to.equal(NodeEventEmitter);
+ assert.equal(IMQEventEmitter, NodeEventEmitter);
// And it works as expected when instantiated
const ee = new IMQEventEmitter();
let called = 0;
- ee.on('ping', () => { called++; });
+ ee.on('ping', () => {
+ called++;
+ });
ee.emit('ping');
- expect(called).to.equal(1);
+ assert.equal(called, 1);
});
});
diff --git a/test/unit/Profile/Decorator.spec.ts b/test/unit/Profile/Decorator.spec.ts
new file mode 100644
index 0000000..047da47
--- /dev/null
+++ b/test/unit/Profile/Decorator.spec.ts
@@ -0,0 +1,72 @@
+/*!
+ * profile decorator branch coverage & async rejection path unit tests
+ */
+import '../../mocks';
+import { describe, it, mock } from 'node:test';
+import * as assert from 'node:assert/strict';
+import { profile, LogLevel } from '../../..';
+
+// Note: We intentionally call decorated methods without a "this" context
+// to exercise the (this || target) branches inside the decorator wrapper.
+
+describe('profile decorator extra branches', () => {
+ it('should return early via original.apply(target, ...) when both debug flags are false and this is undefined', () => {
+ class T1 {
+ @profile({
+ enableDebugTime: false,
+ enableDebugArgs: false,
+ logLevel: LogLevel.LOG,
+ })
+ public m(...args: any[]) {
+ return args;
+ }
+ }
+ const o = new T1();
+ const fn = Object.getPrototypeOf(o).m as Function; // wrapper
+ const res = fn.call(undefined, 1, 2, 3);
+ assert.deepEqual(res, [1, 2, 3]);
+ });
+
+ it('should execute debug path with (this || target) picking target and logLevel fallback to IMQ_LOG_LEVEL', () => {
+ class T2 {
+ // no logger on prototype; calling with undefined this picks target
+ @profile({
+ enableDebugTime: true,
+ enableDebugArgs: true,
+ logLevel: undefined as any,
+ })
+ public m(..._args: any[]) {
+ /* noop */
+ }
+ }
+ const o = new T2();
+ const fn = Object.getPrototypeOf(o).m as Function; // wrapper
+ // provide serializable args to avoid logger.error path when logger is undefined
+ assert.doesNotThrow(() => fn.call(undefined, 1, { a: 2 }, 'x'));
+ });
+});
+
+class RejectingClass {
+ public logger: any = { info: () => undefined, error: () => undefined };
+
+ @profile({ enableDebugTime: true, enableDebugArgs: true })
+ public async willReject(): Promise {
+ return Promise.reject(new Error('boom'));
+ }
+}
+
+describe('profile() async rejection path', () => {
+ it('should log via logger when async method rejects', async () => {
+ const logger = { info: mock.fn(), error: () => undefined } as any;
+ const obj = new RejectingClass();
+ obj.logger = logger;
+ try {
+ await obj.willReject();
+ } catch (e) {
+ // expected
+ }
+ // allow microtask queue
+ await new Promise(res => setTimeout(res, 0));
+ assert.ok(logger.info.mock.callCount() > 0);
+ });
+});
diff --git a/test/unit/Profile/Profile.spec.ts b/test/unit/Profile/Profile.spec.ts
new file mode 100644
index 0000000..7fcfbdc
--- /dev/null
+++ b/test/unit/Profile/Profile.spec.ts
@@ -0,0 +1,297 @@
+/*!
+ * profile() Function Unit Tests (merged with additional branch coverage)
+ *
+ * I'm Queue Software Project
+ * Copyright (C) 2025 imqueue.com
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ * If you want to use this code in a closed source (commercial) project, you can
+ * purchase a proprietary commercial license. Please contact us at
+ * to get commercial licensing options.
+ */
+import '../../mocks';
+import { describe, it, beforeEach, afterEach, mock, Mock } from 'node:test';
+import * as assert from 'node:assert/strict';
+import mockRequire from 'mock-require';
+import {
+ profile,
+ ILogger,
+ verifyLogLevel,
+ LogLevel,
+ DebugInfoOptions,
+} from '../../..';
+import { logger } from '../../mocks';
+
+const BIG_INT_SUPPORT = (() => {
+ try {
+ return !!BigInt(0);
+ } catch (err) {
+ return false;
+ }
+})();
+
+function calledWithMatch(spy: Mock, re: RegExp): boolean {
+ return spy.mock.calls.some((call: any) =>
+ call.arguments.some((arg: any) => re.test(String(arg))),
+ );
+}
+
+class ProfiledClass {
+ private logger: ILogger = logger;
+
+ @profile()
+ public decoratedMethod(...args: any[]) {
+ return args;
+ }
+
+ @profile({
+ enableDebugTime: true,
+ enableDebugArgs: true,
+ logLevel: LogLevel.LOG,
+ })
+ public async decoratedAsyncMethod() {
+ return new Promise(resolve => setTimeout(resolve, 10));
+ }
+}
+
+class ProfiledClassTimed {
+ private logger: ILogger = logger;
+
+ @profile({
+ enableDebugTime: true,
+ logLevel: LogLevel.LOG,
+ })
+ public decoratedMethod() {}
+}
+
+class ProfiledClassArgued {
+ private logger: ILogger = logger;
+
+ @profile({
+ enableDebugTime: false,
+ enableDebugArgs: true,
+ logLevel: LogLevel.LOG,
+ })
+ public decoratedMethod() {}
+}
+
+class ProfiledClassTimedAndArgued {
+ private logger: ILogger = logger;
+
+ @profile({
+ enableDebugTime: true,
+ enableDebugArgs: true,
+ logLevel: LogLevel.LOG,
+ })
+ public decoratedMethod() {}
+}
+
+describe('profile()', () => {
+ let log: Mock;
+ let error: Mock;
+ let warn: Mock;
+ let info: Mock;
+
+ beforeEach(() => {
+ log = mock.method(logger, 'log');
+ error = mock.method(logger, 'error');
+ warn = mock.method(logger, 'warn');
+ info = mock.method(logger, 'info');
+ });
+
+ afterEach(() => {
+ mock.restoreAll();
+ mockRequire.stopAll();
+ delete process.env.IMQ_LOG_TIME_FORMAT;
+ });
+
+ it('should be a function', () => {
+ assert.equal(typeof profile, 'function');
+ });
+
+ it('should be decorator factory', () => {
+ assert.equal(typeof profile(), 'function');
+ });
+
+ it('should pass decorated method args with no change', () => {
+ assert.deepEqual(
+ new ProfiledClass().decoratedMethod(1, 2, 3),
+ [1, 2, 3],
+ );
+ });
+
+ it('should log time if enabled', () => {
+ new ProfiledClassTimed().decoratedMethod();
+ assert.equal(log.mock.callCount(), 1);
+ });
+
+ it('should log args if enabled', () => {
+ new ProfiledClassArgued().decoratedMethod();
+ assert.equal(log.mock.callCount(), 1);
+ });
+
+ it('should log time and args if both enabled', () => {
+ new ProfiledClassTimedAndArgued().decoratedMethod();
+ assert.equal(log.mock.callCount(), 2);
+ });
+
+ it('should handle async methods correctly', async () => {
+ await new ProfiledClass().decoratedAsyncMethod();
+ assert.equal(log.mock.callCount(), 2);
+ });
+
+ describe('verifyLogLevel()', () => {
+ it('should return valid log levels as is', () => {
+ assert.equal(verifyLogLevel(LogLevel.LOG), LogLevel.LOG);
+ assert.equal(verifyLogLevel(LogLevel.INFO), LogLevel.INFO);
+ assert.equal(verifyLogLevel(LogLevel.WARN), LogLevel.WARN);
+ assert.equal(verifyLogLevel(LogLevel.ERROR), LogLevel.ERROR);
+ });
+
+ it('should return default log level on invalid value', () => {
+ assert.equal(verifyLogLevel('invalid'), LogLevel.INFO);
+ });
+ });
+
+ describe('logDebugInfo()', () => {
+ const start = BIG_INT_SUPPORT ? BigInt(1) : 1;
+ const baseOptions: DebugInfoOptions = {
+ debugTime: true,
+ debugArgs: true,
+ className: 'TestClass',
+ args: [1, 'a', { b: 2 }],
+ methodName: 'testMethod',
+ start,
+ logger,
+ logLevel: LogLevel.LOG,
+ };
+
+ it('should log time in microseconds by default', () => {
+ const { logDebugInfo } = mockRequire.reRequire(
+ '../../../src/profile',
+ );
+ logDebugInfo(baseOptions);
+ assert.equal(calledWithMatch(log, /μs/), true);
+ });
+
+ it('should log time in milliseconds', () => {
+ process.env.IMQ_LOG_TIME_FORMAT = 'milliseconds';
+ const { logDebugInfo } = mockRequire.reRequire(
+ '../../../src/profile',
+ );
+ logDebugInfo(baseOptions);
+ assert.equal(calledWithMatch(log, /ms/), true);
+ });
+
+ it('should log time in seconds', () => {
+ process.env.IMQ_LOG_TIME_FORMAT = 'seconds';
+ const { logDebugInfo } = mockRequire.reRequire(
+ '../../../src/profile',
+ );
+ logDebugInfo(baseOptions);
+ assert.equal(calledWithMatch(log, /sec/), true);
+ });
+
+ it('should handle circular references in args', () => {
+ const { logDebugInfo } = mockRequire.reRequire(
+ '../../../src/profile',
+ );
+ const a: any = { b: 1 };
+ const b = { a };
+ a.b = b;
+ logDebugInfo({ ...baseOptions, args: [a] });
+ assert.equal(error.mock.callCount(), 0);
+ });
+
+ it('should not log when logger method is missing', () => {
+ const { logDebugInfo } = mockRequire.reRequire(
+ '../../../src/profile',
+ );
+ const dummyLogger: any = { error: logger.error.bind(logger) };
+ logDebugInfo({
+ ...baseOptions,
+ logger: dummyLogger,
+ logLevel: 'nonexistent' as any,
+ });
+ assert.equal(log.mock.callCount(), 0);
+ });
+
+ it('should handle JSON.stringify errors', () => {
+ const { logDebugInfo } = mockRequire.reRequire(
+ '../../../src/profile',
+ );
+ const badJson = {
+ toJSON: () => {
+ throw new Error('bad json');
+ },
+ };
+ logDebugInfo({ ...baseOptions, args: [badJson] });
+ assert.equal(error.mock.callCount(), 1);
+ });
+ });
+});
+
+describe('profile.ts additional branches', () => {
+ afterEach(() => {
+ mock.restoreAll();
+ mockRequire.stopAll();
+ delete (process as any).env.IMQ_LOG_TIME_FORMAT;
+ });
+
+ it('logDebugInfo: should not attempt to call missing log method (no-op path)', () => {
+ const { logDebugInfo, LogLevel } = mockRequire.reRequire(
+ '../../../src/profile',
+ );
+ const fakeLogger: any = {
+ // intentionally no 'log' or 'info' method for selected level
+ error: mock.fn(),
+ };
+ const options = {
+ debugTime: true,
+ debugArgs: true,
+ className: 'X',
+ args: [1, { a: 2 }],
+ methodName: 'm',
+ start: (process.hrtime as any).bigint(),
+ logger: fakeLogger,
+ logLevel: LogLevel.LOG,
+ };
+ assert.doesNotThrow(() => logDebugInfo(options));
+ // ensures error not called due to serialization success
+ assert.equal(fakeLogger.error.mock.callCount() > 0, false);
+ });
+
+ it('logDebugInfo: should call logger.error on JSON.stringify error (BigInt arg)', () => {
+ const { logDebugInfo, LogLevel } = mockRequire.reRequire(
+ '../../../src/profile',
+ );
+ const fakeLogger: any = {
+ error: mock.fn(),
+ };
+ const args = [BigInt(1)]; // JSON.stringify throws on BigInt
+ const options = {
+ debugTime: false,
+ debugArgs: true,
+ className: 'Y',
+ args,
+ methodName: 'n',
+ start: (process.hrtime as any).bigint(),
+ logger: fakeLogger,
+ logLevel: LogLevel.INFO,
+ };
+ logDebugInfo(options);
+ assert.equal(fakeLogger.error.mock.callCount(), 1);
+ });
+});
diff --git a/test/unit/RedisQueue/Cleanup.spec.ts b/test/unit/RedisQueue/Cleanup.spec.ts
new file mode 100644
index 0000000..2901f6c
--- /dev/null
+++ b/test/unit/RedisQueue/Cleanup.spec.ts
@@ -0,0 +1,35 @@
+/*!
+ * Additional RedisQueue tests: processCleanup catch branch
+ */
+import '../../mocks';
+import { describe, it, afterEach, mock, Mock } from 'node:test';
+import * as assert from 'node:assert/strict';
+import { RedisQueue } from '../../../src';
+import { makeLogger } from '../../helpers';
+
+describe('RedisQueue.processCleanup catch path', () => {
+ afterEach(() => {
+ mock.restoreAll();
+ });
+
+ it('should log a warning when processCleanup throws', async () => {
+ const logger = makeLogger();
+ const warnSpy: Mock = mock.method(logger, 'warn');
+ const rq: any = new RedisQueue('CleanupCatch', {
+ logger,
+ cleanup: true,
+ });
+
+ await rq.start();
+ // Stub writer.client to throw to hit the catch branch
+ mock.method(rq.writer, 'client', () => {
+ throw new Error('LIST failed');
+ });
+
+ await rq.processCleanup();
+
+ assert.ok(warnSpy.mock.callCount() > 0);
+
+ await rq.destroy();
+ });
+});
diff --git a/test/unit/RedisQueue/Connect.spec.ts b/test/unit/RedisQueue/Connect.spec.ts
new file mode 100644
index 0000000..7eab7bd
--- /dev/null
+++ b/test/unit/RedisQueue/Connect.spec.ts
@@ -0,0 +1,35 @@
+/*!
+ * Additional RedisQueue tests: connect() option fallbacks branches
+ */
+import '../../mocks';
+import { describe, it } from 'node:test';
+import * as assert from 'node:assert/strict';
+import { RedisQueue, IMQMode } from '../../../src';
+import { makeLogger } from '../../helpers';
+
+describe('RedisQueue.connect() option fallbacks', () => {
+ it('should use fallback values when falsy options are provided', async () => {
+ const logger = makeLogger();
+ // Intentionally provide falsy values to trigger `||` fallbacks in connect()
+ const rq: any = new RedisQueue(
+ 'ConnFallbacks',
+ {
+ logger,
+ port: 0 as unknown as number, // falsy to trigger 6379 fallback
+ host: '' as unknown as string, // falsy to trigger 'localhost' fallback
+ prefix: '' as unknown as string, // falsy to trigger '' fallback in connectionName
+ cleanup: false,
+ },
+ IMQMode.BOTH,
+ );
+
+ await rq.start();
+
+ // Basic sanity: writer/reader/watcher are created
+ assert.equal(Boolean(rq.writer), true);
+ assert.equal(Boolean(rq.reader), true);
+ assert.equal(Boolean(rq.watcher), true);
+
+ await rq.destroy();
+ });
+});
diff --git a/test/unit/RedisQueue/ProcessCleanup.spec.ts b/test/unit/RedisQueue/ProcessCleanup.spec.ts
new file mode 100644
index 0000000..bc3b739
--- /dev/null
+++ b/test/unit/RedisQueue/ProcessCleanup.spec.ts
@@ -0,0 +1,165 @@
+/*!
+ * Additional RedisQueue tests: processCleanup branches (connectedKeys filter,
+ * extra prefix, multi-scan/no-delete, null-match and falsy cleanupFilter)
+ */
+import '../../mocks';
+import { describe, it, afterEach, mock, Mock } from 'node:test';
+import * as assert from 'node:assert/strict';
+import { RedisQueue, uuid } from '../../../src';
+import { RedisClientMock } from '../../mocks';
+
+describe('RedisQueue.processCleanup connectedKeys RX/filter combinations', () => {
+ afterEach(() => {
+ mock.restoreAll();
+ });
+
+ it('should handle RX_CLIENT_TEST true but filter false case (exclude unmatched prefix)', async () => {
+ const name = `PCleanRX_${uuid()}`;
+ const rq: any = new RedisQueue(name, {
+ logger: console,
+ cleanup: true,
+ prefix: 'imqA',
+ cleanupFilter: '*',
+ });
+
+ await rq.start();
+
+ const writer: any = rq.writer;
+
+ // Stub client('LIST') to include a writer channel with a different prefix,
+ // so RX_CLIENT_TEST.test(name) is true but filter.test(name) is false.
+ mock.method(writer, 'client', async (cmd: string) => {
+ if (cmd === 'LIST') {
+ return [
+ 'id=1 name=imqZ:Other:writer:pid:1:host:x', // RX true, filter false
+ 'id=2 name=imqA:Other:subscription:pid:1:host:x', // RX false, filter true
+ ].join('\n');
+ }
+ return true as any;
+ });
+
+ // Return no keys on SCAN to avoid deletions and just walk the branch
+ mock.method(writer, 'scan', async () => ['0', []] as any);
+
+ const delSpy: Mock = mock.method(writer, 'del');
+
+ await rq.processCleanup();
+
+ assert.equal(delSpy.mock.callCount() > 0, false);
+
+ await rq.destroy();
+ });
+});
+
+describe('RedisQueue.processCleanup extra branches', () => {
+ it('should remove scanned keys that do not match any connectedKey (different prefix)', async () => {
+ const name = uuid();
+ const rq: any = new RedisQueue(name, {
+ logger: console,
+ cleanup: true,
+ prefix: 'imqX',
+ cleanupFilter: '*',
+ });
+
+ // start to create reader/writer/watcher with connection names
+ await rq.start();
+
+ // Create an orphan worker key with a different prefix so it won't include any connectedKey
+ const orphanKey = 'imqY:orphan:worker:someuuid:123456';
+ (RedisClientMock as any).__queues__[orphanKey] = ['payload'];
+
+ // Sanity: ensure the key is present before cleanup
+ assert.ok((RedisClientMock as any).__queues__[orphanKey]);
+
+ await rq.processCleanup();
+
+ // The orphan key should be deleted by cleanup (true branch of keysToRemove filter)
+ assert.equal((RedisClientMock as any).__queues__[orphanKey], undefined);
+
+ await rq.destroy();
+ });
+});
+
+describe('RedisQueue.processCleanup multi-scan/no-delete branches', () => {
+ afterEach(() => {
+ mock.restoreAll();
+ });
+
+ it('should handle multi-page SCAN (cursor != "0" first) and avoid deletion when keys belong to connected clients', async () => {
+ const name = `PClean_${uuid()}`;
+ const rq: any = new RedisQueue(name, {
+ logger: console,
+ cleanup: true,
+ prefix: 'imq',
+ cleanupFilter: '*',
+ });
+
+ await rq.start();
+
+ const writer: any = rq.writer;
+
+ // Stub scan to first return non-zero cursor with undefined keys (to exercise `|| []`),
+ // then return zero cursor with keys that include connectedKey (so no removal happens).
+ const scanStub: Mock = mock.method(
+ writer,
+ 'scan',
+ async () => undefined,
+ );
+ scanStub.mock.mockImplementationOnce(
+ async () => ['1', undefined] as any,
+ 0,
+ );
+ scanStub.mock.mockImplementationOnce(
+ async () => ['0', [`imq:${name}:reader:pid:123`]] as any,
+ 1,
+ );
+
+ const delSpy: Mock = mock.method(writer, 'del');
+
+ await rq.processCleanup();
+
+ // del should not be called because keysToRemove.length === 0
+ assert.equal(delSpy.mock.callCount() > 0, false);
+
+ await rq.destroy();
+ });
+});
+
+describe('RedisQueue.processCleanup null-match and falsy cleanupFilter', () => {
+ afterEach(() => {
+ mock.restoreAll();
+ });
+
+ it("should handle clients.match returning null and cleanupFilter as falsy ('')", async () => {
+ const name = `PCleanNull_${uuid()}`;
+ const rq: any = new RedisQueue(name, {
+ logger: console,
+ cleanup: true,
+ prefix: 'imq',
+ cleanupFilter: '', // falsy to exercise "|| '*'" in both RegExp and SCAN MATCH
+ });
+
+ await rq.start();
+
+ const writer: any = rq.writer;
+
+ // Force clients.match(...) to return null by stubbing client('LIST') to return a string without 'name='
+ mock.method(writer, 'client', async (cmd: string) => {
+ if (cmd === 'LIST') {
+ return 'id=1 flags=x'; // no 'name='
+ }
+ return true as any;
+ });
+
+ // Ensure SCAN returns no keys, to avoid deletions and just cover the branch paths
+ mock.method(writer, 'scan', async () => ['0', []] as any);
+
+ const delSpy: Mock = mock.method(writer, 'del');
+
+ await rq.processCleanup();
+
+ assert.equal(delSpy.mock.callCount() > 0, false);
+
+ await rq.destroy();
+ });
+});
diff --git a/test/RedisQueue.processDelayed.catch.spec.ts b/test/unit/RedisQueue/ProcessDelayed.spec.ts
similarity index 52%
rename from test/RedisQueue.processDelayed.catch.spec.ts
rename to test/unit/RedisQueue/ProcessDelayed.spec.ts
index 82fa5ca..8bfd5cd 100644
--- a/test/RedisQueue.processDelayed.catch.spec.ts
+++ b/test/unit/RedisQueue/ProcessDelayed.spec.ts
@@ -1,22 +1,16 @@
/*!
* Additional RedisQueue tests: processDelayed() catch branch
*/
-import './mocks';
-import { expect } from 'chai';
-import * as sinon from 'sinon';
-import { RedisQueue } from '../src';
-
-function makeLogger() {
- return {
- log: (..._args: any[]) => undefined,
- info: (..._args: any[]) => undefined,
- warn: (..._args: any[]) => undefined,
- error: (..._args: any[]) => undefined,
- } as any;
-}
-
-describe('RedisQueue.processDelayed extra branches', function() {
- this.timeout(10000);
+import '../../mocks';
+import { describe, it, afterEach, mock, Mock } from 'node:test';
+import * as assert from 'node:assert/strict';
+import { RedisQueue } from '../../../src';
+import { makeLogger } from '../../helpers';
+
+describe('RedisQueue.processDelayed extra branches', () => {
+ afterEach(() => {
+ mock.restoreAll();
+ });
it('should emit error when evalsha throws', async () => {
const logger = makeLogger();
@@ -27,7 +21,11 @@ describe('RedisQueue.processDelayed extra branches', function() {
rq.scripts.moveDelayed.checksum = 'deadbeef';
const err = new Error('evalsha failed');
- const emitErrorStub = sinon.stub((RedisQueue as any).prototype, 'emitError');
+ const emitErrorStub: Mock = mock.method(
+ (RedisQueue as any).prototype,
+ 'emitError',
+ () => undefined,
+ );
// Temporarily drop writer to force a synchronous error in processDelayed
const originalWriter = rq.writer;
@@ -35,12 +33,14 @@ describe('RedisQueue.processDelayed extra branches', function() {
await rq['processDelayed'](rq.key);
- expect(emitErrorStub.called).to.be.true;
- expect(emitErrorStub.firstCall.args[0]).to.equal('OnProcessDelayed');
+ assert.ok(emitErrorStub.mock.callCount() > 0);
+ assert.equal(
+ emitErrorStub.mock.calls[0].arguments[0],
+ 'OnProcessDelayed',
+ );
// Restore writer and cleanup
rq['writer'] = originalWriter;
- emitErrorStub.restore();
await rq.destroy();
});
});
diff --git a/test/unit/RedisQueue/Publish.spec.ts b/test/unit/RedisQueue/Publish.spec.ts
new file mode 100644
index 0000000..6161654
--- /dev/null
+++ b/test/unit/RedisQueue/Publish.spec.ts
@@ -0,0 +1,72 @@
+/*!
+ * Additional RedisQueue tests: publish() branches
+ */
+import '../../mocks';
+import { describe, it, mock } from 'node:test';
+import * as assert from 'node:assert/strict';
+import { RedisQueue, IMQMode } from '../../../src';
+import { makeLogger } from '../../helpers';
+
+describe('RedisQueue.publish()', () => {
+ it('should throw when writer is not connected', async () => {
+ const logger = makeLogger();
+ const rq: any = new RedisQueue(
+ 'PubNoWriter',
+ { logger },
+ IMQMode.PUBLISHER,
+ );
+
+ let thrown: any;
+ try {
+ await rq.publish({ a: 1 });
+ } catch (err) {
+ thrown = err;
+ }
+
+ assert.ok(thrown instanceof TypeError);
+ assert.ok(String(`${thrown}`).includes('Writer is not connected'));
+
+ await rq.destroy().catch(() => undefined);
+ });
+
+ it('should publish to default channel when writer is connected', async () => {
+ const logger = makeLogger();
+ const rq: any = new RedisQueue(
+ 'PubDefault',
+ { logger },
+ IMQMode.PUBLISHER,
+ );
+ await rq.start();
+
+ const pubSpy = mock.method((rq as any).writer, 'publish');
+ await rq.publish({ hello: 'world' });
+
+ assert.equal(pubSpy.mock.callCount() > 0, true);
+ const [channel, msg] = pubSpy.mock.calls[0].arguments;
+ assert.equal(channel, 'imq:PubDefault');
+ assert.doesNotThrow(() => JSON.parse(msg));
+
+ mock.restoreAll();
+ await rq.destroy().catch(() => undefined);
+ });
+
+ it('should publish to provided toName channel when given', async () => {
+ const logger = makeLogger();
+ const rq: any = new RedisQueue(
+ 'PubOther',
+ { logger },
+ IMQMode.PUBLISHER,
+ );
+ await rq.start();
+
+ const pubSpy = mock.method((rq as any).writer, 'publish');
+ await rq.publish({ t: true }, 'OtherChannel');
+
+ assert.equal(pubSpy.mock.callCount() > 0, true);
+ const [channel] = pubSpy.mock.calls[0].arguments;
+ assert.equal(channel, 'imq:OtherChannel');
+
+ mock.restoreAll();
+ await rq.destroy().catch(() => undefined);
+ });
+});
diff --git a/test/RedisQueue.ts b/test/unit/RedisQueue/RedisQueue.spec.ts
similarity index 61%
rename from test/RedisQueue.ts
rename to test/unit/RedisQueue/RedisQueue.spec.ts
index 71c6c98..dfd34b3 100644
--- a/test/RedisQueue.ts
+++ b/test/unit/RedisQueue/RedisQueue.spec.ts
@@ -21,36 +21,41 @@
* purchase a proprietary commercial license. Please contact us at
* to get commercial licensing options.
*/
-import { logger } from './mocks';
-import { expect } from 'chai';
-import { RedisQueue, uuid, IMQMode } from '../src';
+import { logger } from '../../mocks';
+import { describe, it, beforeEach } from 'node:test';
+import * as assert from 'node:assert/strict';
+import { RedisQueue, uuid, IMQMode } from '../../../src';
import Redis from 'ioredis';
process.setMaxListeners(100);
-describe('RedisQueue', function() {
- this.timeout(30000);
-
+describe('RedisQueue', () => {
it('should be a class', () => {
- expect(typeof RedisQueue).to.equal('function');
+ assert.equal(typeof RedisQueue, 'function');
});
it('should implement IMessageQueue interface', () => {
- expect(typeof RedisQueue.prototype.start).to.equal('function');
- expect(typeof RedisQueue.prototype.stop).to.equal('function');
- expect(typeof RedisQueue.prototype.send).to.equal('function');
- expect(typeof RedisQueue.prototype.destroy).to.equal('function');
+ assert.equal(typeof RedisQueue.prototype.start, 'function');
+ assert.equal(typeof RedisQueue.prototype.stop, 'function');
+ assert.equal(typeof RedisQueue.prototype.send, 'function');
+ assert.equal(typeof RedisQueue.prototype.destroy, 'function');
});
describe('constructor()', () => {
it('should not throw', async () => {
const instances: RedisQueue[] = [];
- expect(() => instances.push(new (RedisQueue)())).not.to.throw(Error);
- expect(() => instances.push(new RedisQueue('IMQUnitTests'))).not.to.throw(Error);
- expect(() => instances.push(new RedisQueue('IMQUnitTests', {})))
- .not.to.throw(Error);
- expect(() => instances.push(new RedisQueue('IMQUnitTests', { useGzip: true })))
- .not.to.throw(Error);
+ assert.doesNotThrow(() => instances.push(new (RedisQueue)()));
+ assert.doesNotThrow(() =>
+ instances.push(new RedisQueue('IMQUnitTests')),
+ );
+ assert.doesNotThrow(() =>
+ instances.push(new RedisQueue('IMQUnitTests', {})),
+ );
+ assert.doesNotThrow(() =>
+ instances.push(
+ new RedisQueue('IMQUnitTests', { useGzip: true }),
+ ),
+ );
await Promise.all(instances.map(instance => instance.destroy()));
});
@@ -59,8 +64,11 @@ describe('RedisQueue', function() {
describe('start()', () => {
it('should throw if no name provided', async () => {
const rq = new (RedisQueue)();
- try { await rq.start() }
- catch (err) { expect(err).to.be.instanceof(TypeError) }
+ try {
+ await rq.start();
+ } catch (err) {
+ assert.ok(err instanceof TypeError);
+ }
rq.destroy().catch();
});
@@ -68,17 +76,17 @@ describe('RedisQueue', function() {
try {
const rq: any = new RedisQueue(uuid(), { logger });
await rq.start();
- expect(rq.reader).to.be.instanceof(Redis);
+ assert.ok(rq.reader instanceof Redis);
await rq.destroy();
+ } catch (err) {
+ console.error(err);
}
-
- catch (err) { console.error(err) }
});
it('should create shared writer connection', async () => {
const rq: any = new RedisQueue(uuid(), { logger });
await rq.start();
- expect(rq.writer).to.be.instanceof(Redis);
+ assert.ok(rq.writer instanceof Redis);
await rq.destroy();
});
@@ -87,8 +95,8 @@ describe('RedisQueue', function() {
const rq2: any = new RedisQueue(uuid(), { logger });
await rq1.start();
await rq2.start();
- expect(await rq1.watcherCount()).to.be.equal(1);
- expect(await rq2.watcherCount()).to.be.equal(1);
+ assert.equal(await rq1.watcherCount(), 1);
+ assert.equal(await rq2.watcherCount(), 1);
await rq1.destroy();
await rq2.destroy();
});
@@ -98,7 +106,7 @@ describe('RedisQueue', function() {
await rq.start();
await rq.stop();
await rq.start();
- expect(rq.reader).to.be.instanceof(Redis);
+ assert.ok(rq.reader instanceof Redis);
await rq.destroy();
});
@@ -108,8 +116,10 @@ describe('RedisQueue', function() {
try {
await rq.start();
await rq.start();
- } catch (err) { passed = false }
- expect(passed).to.be.true;
+ } catch (err) {
+ passed = false;
+ }
+ assert.equal(passed, true);
await rq.destroy();
});
});
@@ -117,46 +127,49 @@ describe('RedisQueue', function() {
describe('stop()', () => {
it('should stop reading messages from queue', async () => {
const name = uuid();
- const rq: any = new RedisQueue(name, { logger });
+ const rq: any = new RedisQueue(name, { logger });
await rq.start();
- expect(rq.reader).to.be.instanceof(Redis);
+ assert.ok(rq.reader instanceof Redis);
await rq.stop();
- expect(rq.reader).not.to.be.ok;
+ assert.ok(!rq.reader);
await rq.destroy();
});
});
describe('send()', () => {
- it('should send given message to a given queue', (done) => {
+ it('should send given message to a given queue', (t, done) => {
const message: any = { hello: 'world' };
const rqFrom = new RedisQueue('IMQUnitTestsFrom', { logger });
const rqTo = new RedisQueue('IMQUnitTestsTo', { logger });
rqTo.on('message', (msg, id, from) => {
- expect(msg).to.deep.equal(message);
- expect(id).not.to.be.undefined;
- expect(from).to.equal('IMQUnitTestsFrom');
+ assert.deepEqual(msg, message);
+ assert.notEqual(id, undefined);
+ assert.equal(from, 'IMQUnitTestsFrom');
rqFrom.destroy().catch();
rqTo.destroy().catch();
done();
});
- rqFrom.start().then(() => { rqTo.start().then(() => {
- rqFrom.send('IMQUnitTestsTo', message).catch();
- });});
+ rqFrom.start().then(() => {
+ rqTo.start().then(() => {
+ rqFrom.send('IMQUnitTestsTo', message).catch();
+ });
+ });
});
- it('should guaranty message delivery if safeDelivery is on', (done) => {
+ it('should guaranty message delivery if safeDelivery is on', (t, done) => {
// it is hard to emulate mq crash at a certain time of
// its runtime execution, so we simply assume delivery works itself
// for the moment. dumb test but better than nothing :(
const message: any = { hello: 'safe delivery' };
const rq = new RedisQueue('IMQSafe', {
- logger, safeDelivery: true,
+ logger,
+ safeDelivery: true,
});
- rq.on('message', (msg) => {
- expect(msg).to.deep.equal(message);
+ rq.on('message', msg => {
+ assert.deepEqual(msg, message);
rq.destroy().catch();
done();
});
@@ -164,7 +177,7 @@ describe('RedisQueue', function() {
rq.start().then(async () => rq.send('IMQSafe', message));
});
- it('should deliver message with the given delay', (done) => {
+ it('should deliver message with the given delay', (t, done) => {
const message: any = { hello: 'world' };
const delay: number = 1000;
const rqFrom = new RedisQueue('IMQUnitTestsFromD', { logger });
@@ -173,22 +186,24 @@ describe('RedisQueue', function() {
let start: number;
rqTo.on('message', (msg, id, from) => {
- expect(Date.now() - start).to.be.gte(delay);
- expect(msg).to.deep.equal(message);
- expect(id).not.to.be.undefined;
- expect(from).to.equal('IMQUnitTestsFromD');
+ assert.ok(Date.now() - start >= delay);
+ assert.deepEqual(msg, message);
+ assert.notEqual(id, undefined);
+ assert.equal(from, 'IMQUnitTestsFromD');
rqFrom.destroy().catch();
rqTo.destroy().catch();
done();
});
- rqFrom.start().then(() => { rqTo.start().then(() => {
- start = Date.now();
- rqFrom.send('IMQUnitTestsToD', message, delay).catch();
- });});
+ rqFrom.start().then(() => {
+ rqTo.start().then(() => {
+ start = Date.now();
+ rqFrom.send('IMQUnitTestsToD', message, delay).catch();
+ });
+ });
});
- it('should trigger an error in case of redis error', (done) => {
+ it('should trigger an error in case of redis error', (t, done) => {
const lrange = Redis.prototype.lrange;
let wasDone = false;
Redis.prototype.lrange = async (): Promise =>
@@ -196,13 +211,17 @@ describe('RedisQueue', function() {
const message: any = { hello: 'safe delivery' };
const rq = new RedisQueue('IMQSafe', {
- logger, safeDelivery: true
+ logger,
+ safeDelivery: true,
});
- process.on('unhandledRejection', function(e) {
- expect((e as any).message).to.be.equal('Wrong messages count');
+ rq.on('error', function (e) {
+ assert.equal((e as any).message, 'Wrong messages count');
Redis.prototype.lrange = lrange;
- !wasDone && done();
+ if (!wasDone) {
+ rq.destroy().catch();
+ done();
+ }
wasDone = true;
});
@@ -214,32 +233,30 @@ describe('RedisQueue', function() {
let rq: any;
beforeEach(async () => {
- rq = new RedisQueue(uuid(), { logger });
+ rq = new RedisQueue(uuid(), { logger });
await rq.start();
});
it('should destroy all connections', async () => {
await rq.destroy();
- expect(rq.watcher).not.to.be.ok;
- expect(rq.reader).not.to.be.ok;
- expect(rq.writer).not.to.be.ok;
+ assert.ok(!rq.watcher);
+ assert.ok(!rq.reader);
+ assert.ok(!rq.writer);
});
it('should remove all event listeners', async () => {
await rq.destroy();
- expect(rq.listenerCount()).to.equal(0);
+ assert.equal(rq.listenerCount(), 0);
});
});
describe('clear()', () => {
it('should clean-up queue data in redis', async () => {
- const rq: any = new RedisQueue(uuid(), { logger });
+ const rq: any = new RedisQueue(uuid(), { logger });
await rq.start();
rq.clear();
- expect(await rq.writer.exists(rq.key))
- .not.to.be.ok;
- expect(await rq.writer.exists(`${rq.key}:delayed`))
- .not.to.be.ok;
+ assert.ok(!(await rq.writer.exists(rq.key)));
+ assert.ok(!(await rq.writer.exists(`${rq.key}:delayed`)));
rq.destroy().catch();
});
});
@@ -249,13 +266,13 @@ describe('RedisQueue', function() {
const rq: any = new RedisQueue(uuid(), {
logger,
cleanup: true,
- cleanupFilter: 'test*'
+ cleanupFilter: 'test*',
});
await rq.start();
// Call processCleanup directly
const result = await rq.processCleanup();
- expect(result).to.equal(rq);
+ assert.equal(result, rq);
await rq.destroy();
});
@@ -263,12 +280,12 @@ describe('RedisQueue', function() {
it('should return early when cleanup option is disabled', async () => {
const rq: any = new RedisQueue(uuid(), {
logger,
- cleanup: false
+ cleanup: false,
});
await rq.start();
const result = await rq.processCleanup();
- expect(result).to.be.undefined;
+ assert.equal(result, undefined);
await rq.destroy();
});
@@ -280,13 +297,13 @@ describe('RedisQueue', function() {
// Don't start, so writer will be null
const lockResult = await rq.lock();
- expect(lockResult).to.be.false;
+ assert.equal(lockResult, false);
const unlockResult = await rq.unlock();
- expect(unlockResult).to.be.false;
+ assert.equal(unlockResult, false);
const isLockedResult = await rq.isLocked();
- expect(isLockedResult).to.be.false;
+ assert.equal(isLockedResult, false);
await rq.destroy();
});
@@ -297,15 +314,15 @@ describe('RedisQueue', function() {
// Test locking
const lockResult = await rq.lock();
- expect(lockResult).to.be.a('boolean');
+ assert.equal(typeof lockResult, 'boolean');
// Test checking if locked
const isLockedResult = await rq.isLocked();
- expect(isLockedResult).to.be.a('boolean');
+ assert.equal(typeof isLockedResult, 'boolean');
// Test unlocking
const unlockResult = await rq.unlock();
- expect(unlockResult).to.be.a('boolean');
+ assert.equal(typeof unlockResult, 'boolean');
await rq.destroy();
});
@@ -313,14 +330,22 @@ describe('RedisQueue', function() {
describe('utility methods', () => {
it('should test isPublisher and isWorker methods', async () => {
- const publisherQueue = new RedisQueue(uuid(), { logger }, IMQMode.PUBLISHER);
- const workerQueue = new RedisQueue(uuid(), { logger }, IMQMode.WORKER);
-
- expect(publisherQueue.isPublisher()).to.be.true;
- expect(publisherQueue.isWorker()).to.be.false;
-
- expect(workerQueue.isPublisher()).to.be.false;
- expect(workerQueue.isWorker()).to.be.true;
+ const publisherQueue = new RedisQueue(
+ uuid(),
+ { logger },
+ IMQMode.PUBLISHER,
+ );
+ const workerQueue = new RedisQueue(
+ uuid(),
+ { logger },
+ IMQMode.WORKER,
+ );
+
+ assert.equal(publisherQueue.isPublisher(), true);
+ assert.equal(publisherQueue.isWorker(), false);
+
+ assert.equal(workerQueue.isPublisher(), false);
+ assert.equal(workerQueue.isWorker(), true);
await workerQueue.destroy();
await publisherQueue.destroy();
@@ -330,11 +355,11 @@ describe('RedisQueue', function() {
const name = uuid();
const rq: any = new RedisQueue(name, { logger });
- expect(rq.key).to.be.a('string');
- expect(rq.key).to.include(name);
+ assert.equal(typeof rq.key, 'string');
+ assert.ok(String(rq.key).includes(name));
- expect(rq.lockKey).to.be.a('string');
- expect(rq.lockKey).to.include('watch:lock');
+ assert.equal(typeof rq.lockKey, 'string');
+ assert.ok(String(rq.lockKey).includes('watch:lock'));
await rq.destroy();
});
diff --git a/test/unit/RedisQueue/Send.spec.ts b/test/unit/RedisQueue/Send.spec.ts
new file mode 100644
index 0000000..de59a30
--- /dev/null
+++ b/test/unit/RedisQueue/Send.spec.ts
@@ -0,0 +1,57 @@
+/*!
+ * Additional RedisQueue tests: send() extra branches and worker-only mode
+ */
+import '../../mocks';
+import { describe, it, mock } from 'node:test';
+import * as assert from 'node:assert/strict';
+import { RedisQueue, IMQMode } from '../../../src';
+import { makeLogger } from '../../helpers';
+
+describe('RedisQueue.send() extra branches', () => {
+ it('should throw when writer is still uninitialized after start()', async () => {
+ const logger = makeLogger();
+ const rq: any = new RedisQueue(
+ 'SendNoWriter',
+ { logger },
+ IMQMode.PUBLISHER,
+ );
+ // Force start to be a no-op so writer remains undefined
+ const startStub = mock.method(rq, 'start', async () => rq);
+
+ let thrown: any;
+ try {
+ await rq.send('AnyQueue', { test: true });
+ } catch (err) {
+ thrown = err;
+ }
+
+ assert.ok(thrown instanceof TypeError);
+ assert.ok(String(`${thrown}`).includes('unable to initialize queue'));
+
+ mock.restoreAll();
+ await rq.destroy().catch(() => undefined);
+ });
+});
+
+describe('RedisQueue.send() worker-only mode', () => {
+ it('should throw when called in WORKER only mode', async () => {
+ const logger = makeLogger();
+ const rq: any = new RedisQueue(
+ 'WorkerOnly',
+ { logger },
+ IMQMode.WORKER,
+ );
+
+ let thrown: any;
+ try {
+ await rq.send('AnyQueue', { test: true });
+ } catch (err) {
+ thrown = err;
+ }
+
+ assert.ok(thrown instanceof TypeError);
+ assert.ok(String(`${thrown}`).includes('WORKER only mode'));
+
+ await rq.destroy().catch(() => undefined);
+ });
+});
diff --git a/test/unit/RedisQueue/Unsubscribe.spec.ts b/test/unit/RedisQueue/Unsubscribe.spec.ts
new file mode 100644
index 0000000..a7165f2
--- /dev/null
+++ b/test/unit/RedisQueue/Unsubscribe.spec.ts
@@ -0,0 +1,40 @@
+/*!
+ * Additional RedisQueue tests: unsubscribe() cleanup path
+ */
+import '../../mocks';
+import { describe, it, mock, Mock } from 'node:test';
+import * as assert from 'node:assert/strict';
+import { RedisQueue } from '../../../src';
+import { makeLogger } from '../../helpers';
+
+describe('RedisQueue.unsubscribe()', () => {
+ it('should cleanup subscription channel when present', async () => {
+ const logger = makeLogger();
+ const rq: any = new RedisQueue('SubUnsub', { logger });
+ await rq.start();
+
+ const handler = mock.fn();
+ await rq.subscribe('SubUnsub', handler);
+
+ assert.ok(rq.subscription);
+ assert.equal(rq.subscriptionName, 'SubUnsub');
+
+ const unsubSpy = mock.method(rq.subscription, 'unsubscribe');
+ const ralSpy = mock.method(rq.subscription, 'removeAllListeners');
+ const disconnectSpy = mock.method(rq.subscription, 'disconnect');
+ const quitSpy = mock.method(rq.subscription, 'quit');
+
+ await rq.unsubscribe();
+
+ assert.equal(unsubSpy.mock.callCount(), 1);
+ assert.equal(ralSpy.mock.callCount(), 1);
+ assert.equal(disconnectSpy.mock.callCount(), 1);
+ assert.equal(quitSpy.mock.callCount(), 1);
+ assert.equal(rq.subscription, undefined);
+ assert.equal(rq.subscriptionName, undefined);
+
+ mock.restoreAll();
+
+ await rq.destroy().catch(() => undefined);
+ });
+});
diff --git a/test/UDPClusterManager.destroySocket.spec.ts b/test/unit/UDPClusterManager/DestroySocket.spec.ts
similarity index 72%
rename from test/UDPClusterManager.destroySocket.spec.ts
rename to test/unit/UDPClusterManager/DestroySocket.spec.ts
index 70fff96..e70a4d6 100644
--- a/test/UDPClusterManager.destroySocket.spec.ts
+++ b/test/unit/UDPClusterManager/DestroySocket.spec.ts
@@ -1,9 +1,10 @@
/*!
* UDPClusterManager.destroyWorker() behavior tests aligned with implementation
*/
-import './mocks';
-import { expect } from 'chai';
-import { UDPClusterManager } from '../src';
+import '../../mocks';
+import { describe, it } from 'node:test';
+import * as assert from 'node:assert/strict';
+import { UDPClusterManager } from '../../../src';
describe('UDPClusterManager.destroyWorker()', () => {
it('should resolve when worker is undefined (no-op)', async () => {
@@ -13,7 +14,10 @@ describe('UDPClusterManager.destroyWorker()', () => {
it('should terminate worker and remove it from the workers map', async () => {
const destroy = (UDPClusterManager as any).destroyWorker as Function;
- const workers = (UDPClusterManager as any).workers as Record;
+ const workers = (UDPClusterManager as any).workers as Record<
+ string,
+ any
+ >;
const key = '1.2.3.4:65000';
let terminated = false;
@@ -24,13 +28,15 @@ describe('UDPClusterManager.destroyWorker()', () => {
setImmediate(() => cb({ type: 'stopped' }));
}
},
- terminate: () => { terminated = true; },
+ terminate: () => {
+ terminated = true;
+ },
};
workers[key] = fakeWorker;
await destroy(key, fakeWorker);
- expect(terminated).to.equal(true);
- expect(workers[key]).to.be.undefined;
+ assert.equal(terminated, true);
+ assert.equal(workers[key], undefined);
});
});
diff --git a/test/UDPClusterManager.ts b/test/unit/UDPClusterManager/UDPClusterManager.spec.ts
similarity index 68%
rename from test/UDPClusterManager.ts
rename to test/unit/UDPClusterManager/UDPClusterManager.spec.ts
index 98268a1..600c1b1 100644
--- a/test/UDPClusterManager.ts
+++ b/test/unit/UDPClusterManager/UDPClusterManager.spec.ts
@@ -20,11 +20,13 @@
* If you want to use this code in a closed source (commercial) project, you can
* purchase a proprietary commercial license. Please contact us at
* to get commercial licensing options.
+ *
+ * UDPClusterManager unit tests (merged: main suite + missing branches coverage).
*/
-import './mocks';
-import { expect } from 'chai';
-import { UDPClusterManager } from '../src';
-import * as sinon from 'sinon';
+import '../../mocks';
+import { describe, it, afterEach, mock } from 'node:test';
+import * as assert from 'node:assert/strict';
+import { UDPClusterManager } from '../../../src';
const testMessageUp = {
name: 'IMQUnitTest',
@@ -56,10 +58,13 @@ const emitMessage = (
});
};
-describe('UDPBroadcastClusterManager', function() {
- this.timeout(5000);
+describe('UDPBroadcastClusterManager', () => {
+ afterEach(() => {
+ mock.restoreAll();
+ });
+
it('should be a class', () => {
- expect(typeof UDPClusterManager).to.equal('function');
+ assert.equal(typeof UDPClusterManager, 'function');
});
it('should call add on cluster', async () => {
@@ -70,12 +75,12 @@ describe('UDPBroadcastClusterManager', function() {
};
const manager: any = new UDPClusterManager();
- sinon.spy(cluster, 'add');
+ const add = mock.method(cluster, 'add');
manager.init(cluster);
emitMessage(manager, 'cluster:add');
- expect(cluster.add.called).to.be.true;
+ assert.equal(add.mock.callCount() > 0, true);
await manager.destroy();
});
@@ -89,12 +94,12 @@ describe('UDPBroadcastClusterManager', function() {
};
const manager: any = new UDPClusterManager();
- sinon.spy(cluster, 'add');
+ const add = mock.method(cluster, 'add');
manager.init(cluster);
emitMessage(manager, 'cluster:add');
- expect(cluster.add.called).to.be.false;
+ assert.equal(add.mock.callCount() > 0, false);
await manager.destroy();
});
@@ -108,21 +113,21 @@ describe('UDPBroadcastClusterManager', function() {
};
const manager: any = new UDPClusterManager();
- sinon.spy(cluster, 'remove');
+ const remove = mock.method(cluster, 'remove');
manager.init(cluster);
emitMessage(manager, 'cluster:remove');
- expect(cluster.remove.called).to.be.true;
+ assert.equal(remove.mock.callCount() > 0, true);
await manager.destroy();
});
- it('should handle server timeout and removal', (done) => {
+ it('should handle server timeout and removal', (t, done) => {
let addedServer: any = null;
const cluster: any = {
add: () => {},
remove: async (server: any) => {
- expect(server).to.equal(addedServer);
+ assert.equal(server, addedServer);
await manager.destroy();
},
find: (message: any) => {
@@ -190,7 +195,42 @@ describe('UDPBroadcastClusterManager', function() {
// Should not throw when no sockets exist
await manager.destroy();
- expect(Object.keys((UDPClusterManager as any).sockets)).to.have.length(0);
+ assert.equal(
+ Object.keys((UDPClusterManager as any).sockets).length,
+ 0,
+ );
});
});
});
+
+describe('UDPClusterManager - cover remaining branches', () => {
+ it('destroySocket should call socket.unref() when socket is present', async () => {
+ // Prepare fake socket with unref
+ const unref = mock.fn();
+ const removeAll = mock.fn();
+ const sock: any = {
+ removeAllListeners: removeAll,
+ close: (cb: (err?: any) => void) => cb(),
+ unref,
+ };
+ const key = 'test-key';
+ (UDPClusterManager as any).sockets[key] = sock;
+ await (UDPClusterManager as any).destroySocket(key, sock);
+ assert.equal(unref.mock.callCount() > 0, true);
+ assert.equal((UDPClusterManager as any).sockets[key], undefined);
+ });
+
+ it('destroySocket should work when socket.unref() is absent (optional chaining negative branch)', async () => {
+ const removeAll = mock.fn();
+ const sock: any = {
+ removeAllListeners: removeAll,
+ close: (cb: (err?: any) => void) => cb(),
+ // no unref method
+ };
+ const key = 'test-key-2';
+ (UDPClusterManager as any).sockets[key] = sock;
+ await (UDPClusterManager as any).destroySocket(key, sock);
+ // should not throw, sockets map cleaned
+ assert.equal((UDPClusterManager as any).sockets[key], undefined);
+ });
+});
diff --git a/test/uuid.ts b/test/unit/Uuid.spec.ts
similarity index 73%
rename from test/uuid.ts
rename to test/unit/Uuid.spec.ts
index 7543f65..4d87310 100644
--- a/test/uuid.ts
+++ b/test/unit/Uuid.spec.ts
@@ -21,33 +21,32 @@
* purchase a proprietary commercial license. Please contact us at
* to get commercial licensing options.
*/
-import './mocks';
-import { expect } from 'chai';
-import { uuid } from '..';
-
-describe('uuid()', function() {
- this.timeout(10000); // 10 seconds
+import '../mocks';
+import { describe, it } from 'node:test';
+import * as assert from 'node:assert/strict';
+import { uuid } from '../..';
+describe('uuid()', () => {
const steps = 100000;
it('should always match RFC4122 spec', () => {
const regex = new RegExp(
'^' +
- '[0-9a-f]{8}' +
- '-' +
- '[0-9a-f]{4}' +
- '-' +
- '[1-5][0-9a-f]{3}' +
- '-' +
- '[89ab][0-9a-f]{3}' +
- '-' +
- '[0-9a-f]{12}' +
- '$',
+ '[0-9a-f]{8}' +
+ '-' +
+ '[0-9a-f]{4}' +
+ '-' +
+ '[1-5][0-9a-f]{3}' +
+ '-' +
+ '[89ab][0-9a-f]{3}' +
+ '-' +
+ '[0-9a-f]{12}' +
+ '$',
'i',
);
for (let i = 0; i < 1000; i++) {
- expect(regex.test(uuid())).to.be.true;
+ assert.equal(regex.test(uuid()), true);
}
});
@@ -58,7 +57,7 @@ describe('uuid()', function() {
keyStore[uuid()] = 1;
}
- expect(Object.keys(keyStore).length).to.be.equal(steps);
+ assert.equal(Object.keys(keyStore).length, steps);
});
it('should generate 100,000 identifiers in less than a second', () => {
@@ -68,6 +67,6 @@ describe('uuid()', function() {
uuid();
}
- expect(Date.now() - start).to.be.below(1000);
+ assert.ok(Date.now() - start < 1000);
});
});
diff --git a/tsconfig.json b/tsconfig.json
index 7b13412..b81be21 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -1,21 +1,22 @@
{
"compilerOptions": {
- "module": "commonjs",
+ "module": "node16",
+ "moduleResolution": "node16",
+ "target": "es2022",
+ "lib": [
+ "es2023"
+ ],
"declaration": true,
- "noImplicitAny": true,
- "strictNullChecks": true,
+ "sourceMap": true,
+ "inlineSources": true,
"removeComments": false,
+ "esModuleInterop": true,
+ "skipLibCheck": true,
+ "forceConsistentCasingInFileNames": true,
+ "strict": true,
"noUnusedLocals": false,
"noUnusedParameters": false,
- "moduleResolution": "node",
- "sourceMap": true,
- "inlineSources": true,
- "experimentalDecorators": true,
- "emitDecoratorMetadata": true,
- "target": "es2017",
- "lib": [
- "dom",
- "es2017"
- ]
+ "experimentalDecorators": false,
+ "emitDecoratorMetadata": false
}
}
From 2f7ed2164f4eb80522ea52bb5f892f3022fb2675 Mon Sep 17 00:00:00 2001
From: Serhiy Morenko
Date: Mon, 6 Jul 2026 19:30:32 +0200
Subject: [PATCH 02/20] fix:: added files
---
.oxfmtrc.json | 9 +++++++++
.oxlintrc.json | 20 ++++++++++++++++++++
2 files changed, 29 insertions(+)
create mode 100644 .oxfmtrc.json
create mode 100644 .oxlintrc.json
diff --git a/.oxfmtrc.json b/.oxfmtrc.json
new file mode 100644
index 0000000..16d1869
--- /dev/null
+++ b/.oxfmtrc.json
@@ -0,0 +1,9 @@
+{
+ "singleQuote": true,
+ "tabWidth": 4,
+ "printWidth": 80,
+ "semi": true,
+ "trailingComma": "all",
+ "arrowParens": "avoid",
+ "bracketSpacing": true
+}
diff --git a/.oxlintrc.json b/.oxlintrc.json
new file mode 100644
index 0000000..2757f3e
--- /dev/null
+++ b/.oxlintrc.json
@@ -0,0 +1,20 @@
+{
+ "$schema": "./node_modules/oxlint/configuration_schema.json",
+ "categories": {
+ "correctness": "error"
+ },
+ "rules": {
+ "no-debugger": "error",
+ "no-console": ["warn", { "allow": ["warn", "error", "info"] }],
+ "no-unused-vars": "warn"
+ },
+ "ignorePatterns": [
+ "**/*.js",
+ "**/*.d.ts",
+ "docs/",
+ "coverage/",
+ ".nyc_output/",
+ ".agent-out/",
+ "node_modules/"
+ ]
+}
From 727410e85be8f6e587949491d332df608b61c23d Mon Sep 17 00:00:00 2001
From: Serhiy Morenko
Date: Mon, 6 Jul 2026 21:40:36 +0200
Subject: [PATCH 03/20] feat: refactored RedisQueue & fixed inspected issues
---
.codebeatignore | 1 -
.codebeatsettings | 7 -
README.md | 3 +-
benchmark/index.ts | 3 +-
benchmark/redis-test.ts | 10 +-
src/ClusterManager.ts | 4 +-
src/ClusteredRedisQueue.ts | 35 +-
src/IMessageQueue.ts | 47 +-
src/RedisQueue.ts | 1134 ++++++++++-------
src/UDPWorker.ts | 4 +-
src/{ => helpers}/copyEventEmitter.ts | 4 +-
src/index.ts | 21 +-
src/profile.ts | 10 +-
src/redis.ts | 1 -
test/helpers/index.ts | 16 +-
src/uuid.ts => test/helpers/makeLogger.ts | 23 +-
test/mocks/redis.ts | 76 +-
.../ClusteredRedisQueue/AddServer.spec.ts | 71 --
.../ClusteredRedisQueue.spec.ts | 398 ------
.../ClusteredRedisQueue/Initialize.spec.ts | 46 -
.../ClusteredRedisQueue/MatchServers.spec.ts | 44 -
test/unit/CopyEventEmitter.spec.ts | 246 ----
test/unit/IMQ.spec.ts | 71 --
test/unit/IMessageQueue.spec.ts | 2 -
test/unit/Profile/Decorator.spec.ts | 72 --
test/unit/Profile/Profile.spec.ts | 297 -----
test/unit/RedisQueue/Cleanup.spec.ts | 35 -
test/unit/RedisQueue/Connect.spec.ts | 35 -
test/unit/RedisQueue/ProcessCleanup.spec.ts | 165 ---
test/unit/RedisQueue/ProcessDelayed.spec.ts | 46 -
test/unit/RedisQueue/Publish.spec.ts | 72 --
test/unit/RedisQueue/RedisQueue.spec.ts | 367 ------
test/unit/RedisQueue/Send.spec.ts | 57 -
test/unit/RedisQueue/Unsubscribe.spec.ts | 40 -
.../UDPClusterManager/DestroySocket.spec.ts | 42 -
.../UDPClusterManager.spec.ts | 236 ----
test/unit/Uuid.spec.ts | 72 --
tsconfig.json | 28 +-
38 files changed, 849 insertions(+), 2992 deletions(-)
delete mode 100644 .codebeatignore
delete mode 100644 .codebeatsettings
rename src/{ => helpers}/copyEventEmitter.ts (94%)
rename src/uuid.ts => test/helpers/makeLogger.ts (66%)
delete mode 100644 test/unit/ClusteredRedisQueue/AddServer.spec.ts
delete mode 100644 test/unit/ClusteredRedisQueue/ClusteredRedisQueue.spec.ts
delete mode 100644 test/unit/ClusteredRedisQueue/Initialize.spec.ts
delete mode 100644 test/unit/ClusteredRedisQueue/MatchServers.spec.ts
delete mode 100644 test/unit/CopyEventEmitter.spec.ts
delete mode 100644 test/unit/IMQ.spec.ts
delete mode 100644 test/unit/Profile/Decorator.spec.ts
delete mode 100644 test/unit/Profile/Profile.spec.ts
delete mode 100644 test/unit/RedisQueue/Cleanup.spec.ts
delete mode 100644 test/unit/RedisQueue/Connect.spec.ts
delete mode 100644 test/unit/RedisQueue/ProcessCleanup.spec.ts
delete mode 100644 test/unit/RedisQueue/ProcessDelayed.spec.ts
delete mode 100644 test/unit/RedisQueue/Publish.spec.ts
delete mode 100644 test/unit/RedisQueue/RedisQueue.spec.ts
delete mode 100644 test/unit/RedisQueue/Send.spec.ts
delete mode 100644 test/unit/RedisQueue/Unsubscribe.spec.ts
delete mode 100644 test/unit/UDPClusterManager/DestroySocket.spec.ts
delete mode 100644 test/unit/UDPClusterManager/UDPClusterManager.spec.ts
delete mode 100644 test/unit/Uuid.spec.ts
diff --git a/.codebeatignore b/.codebeatignore
deleted file mode 100644
index 3669b72..0000000
--- a/.codebeatignore
+++ /dev/null
@@ -1 +0,0 @@
-benchmark/**
diff --git a/.codebeatsettings b/.codebeatsettings
deleted file mode 100644
index 84f6f2a..0000000
--- a/.codebeatsettings
+++ /dev/null
@@ -1,7 +0,0 @@
-{
- "TYPESCRIPT": {
- "TOTAL_LOC": [500, 1000, 1500, 2000],
- "TOO_MANY_FUNCTIONS": [40, 50, 60, 70],
- "TOTAL_COMPLEXITY": [100, 180, 280, 400]
- }
-}
\ No newline at end of file
diff --git a/README.md b/README.md
index b311c25..9cfc30b 100644
--- a/README.md
+++ b/README.md
@@ -39,7 +39,8 @@ With current implementation on RedisQueue:
# Requirements
Currently this module have only one available adapter which is Redis server
-related. So redis-server > 3.8+ is required.
+related. Redis server 6.2+ is required (the queue relies on `LMOVE`/`BLMOVE`
+commands for safe message delivery).
If config command is disabled on redis it will be required to turn on manually
keyspace notification events (actual on use with ElasticCache on AWS), like:
diff --git a/benchmark/index.ts b/benchmark/index.ts
index f5f0cb5..0ca830a 100644
--- a/benchmark/index.ts
+++ b/benchmark/index.ts
@@ -27,7 +27,8 @@ import * as fs from 'fs';
import { parseArgs } from 'node:util';
import { run } from './redis-test';
import { resolve } from 'path';
-import { uuid, AnyJson } from '..';
+import { randomUUID as uuid } from 'crypto';
+import { AnyJson } from '..';
import { setAffinity } from './affinity';
const cluster: any = require('cluster');
diff --git a/benchmark/redis-test.ts b/benchmark/redis-test.ts
index 3877fc0..3811b5f 100644
--- a/benchmark/redis-test.ts
+++ b/benchmark/redis-test.ts
@@ -21,14 +21,8 @@
* purchase a proprietary commercial license. Please contact us at
* to get commercial licensing options.
*/
-import IMQ, {
- IMQOptions,
- IJson,
- uuid,
- pack,
- JsonObject,
- AnyJson,
-} from '../index';
+import { randomUUID as uuid } from 'crypto';
+import IMQ, { IMQOptions, IJson, pack, JsonObject, AnyJson } from '../index';
/**
* Sample message used within tests
diff --git a/src/ClusterManager.ts b/src/ClusterManager.ts
index 00de6de..ba07bcb 100644
--- a/src/ClusterManager.ts
+++ b/src/ClusterManager.ts
@@ -22,7 +22,7 @@
* to get commercial licensing options.
*/
import { IMessageQueueConnection, IServerInput } from './IMessageQueue';
-import { uuid } from './uuid';
+import { randomUUID } from 'crypto';
export interface ICluster {
add: (server: IServerInput) => IMessageQueueConnection;
@@ -41,7 +41,7 @@ export abstract class ClusterManager {
public init(cluster: ICluster): InitializedCluster {
const initializedCluster = Object.assign(cluster, {
- id: uuid(),
+ id: randomUUID(),
}) as InitializedCluster;
this.clusters.push(initializedCluster);
diff --git a/src/ClusteredRedisQueue.ts b/src/ClusteredRedisQueue.ts
index 6240a2f..8c7b775 100644
--- a/src/ClusteredRedisQueue.ts
+++ b/src/ClusteredRedisQueue.ts
@@ -92,7 +92,6 @@ export class ClusteredRedisQueue
*
* @type {IMessageQueueConnection[]}
*/
- // tslint:disable-next-line:completed-docs
private servers: ClusterServer[] = [];
/**
@@ -102,7 +101,6 @@ export class ClusteredRedisQueue
*/
private currentQueue: number = 0;
- // noinspection TypeScriptFieldCanBeMadeReadonly
/**
* Total length of RedisQueue instances
*
@@ -150,7 +148,6 @@ export class ClusteredRedisQueue
this.clusterEmitter = new EventEmitter();
this.options = buildOptions(DEFAULT_IMQ_OPTIONS, options);
- // istanbul ignore next
this.logger = this.options.logger || console;
if (!this.options.cluster && !this.options.clusterManagers?.length) {
@@ -280,7 +277,6 @@ export class ClusteredRedisQueue
}
}
- // noinspection JSUnusedGlobalSymbols
/**
* Clears queue data in queue host application.
* Supposed to be an async function.
@@ -322,13 +318,18 @@ export class ClusteredRedisQueue
* @param {string} message
* @return {Promise}
*/
- private async batch(action: string, message: string): Promise {
+ private async batch(
+ action: 'start' | 'stop' | 'destroy' | 'clear',
+ message: string,
+ ): Promise {
this.logger.info(message);
- const promises = [];
+ const promises: Promise[] = [];
for (const imq of this.imqs) {
- promises.push(imq[action]());
+ const run = imq[action] as () => Promise;
+
+ promises.push(run.call(imq));
}
await Promise.all(promises);
@@ -337,7 +338,6 @@ export class ClusteredRedisQueue
}
// EventEmitter interface
- // istanbul ignore next
/**
* Applies the named EventEmitter method to every underlying emitter,
* forwarding the call across the whole cluster. Dispatch is reflective
@@ -363,102 +363,86 @@ export class ClusteredRedisQueue
return results;
}
- // istanbul ignore next
public on(...args: any[]): this {
this.applyToEmitters('on', args);
return this;
}
- // istanbul ignore next
- // noinspection JSUnusedGlobalSymbols
public off(...args: any[]): this {
this.applyToEmitters('off', args);
return this;
}
- // istanbul ignore next
public once(...args: any[]): this {
this.applyToEmitters('once', args);
return this;
}
- // istanbul ignore next
public addListener(...args: any[]): this {
this.applyToEmitters('addListener', args);
return this;
}
- // istanbul ignore next
public removeListener(...args: any[]): this {
this.applyToEmitters('removeListener', args);
return this;
}
- // istanbul ignore next
public removeAllListeners(...args: any[]): this {
this.applyToEmitters('removeAllListeners', args);
return this;
}
- // istanbul ignore next
public prependListener(...args: any[]): this {
this.applyToEmitters('prependListener', args);
return this;
}
- // istanbul ignore next
public prependOnceListener(...args: any[]): this {
this.applyToEmitters('prependOnceListener', args);
return this;
}
- // istanbul ignore next
public setMaxListeners(...args: any[]): this {
this.applyToEmitters('setMaxListeners', args);
return this;
}
- // istanbul ignore next
// Aggregates listeners across every underlying emitter, so the result is
// an untyped union rather than Node's per-emitter conditional listener type.
public listeners(...args: any[]): any[] {
return this.applyToEmitters('listeners', args).flat();
}
- // istanbul ignore next
public rawListeners(...args: any[]): any[] {
return this.applyToEmitters('rawListeners', args).flat();
}
- // istanbul ignore next
public getMaxListeners(): number {
return this.templateEmitter.getMaxListeners();
}
- // istanbul ignore next
public emit(...args: any[]): boolean {
this.applyToEmitters('emit', args);
return true;
}
- // istanbul ignore next
public eventNames(): (keyof EventMap)[] {
const source = this.imqs[0] || this.templateEmitter;
return source.eventNames() as (keyof EventMap)[];
}
- // istanbul ignore next
public listenerCount(...args: any[]): number {
const source = this.imqs[0] || this.templateEmitter;
const fn = source.listenerCount as (...a: any[]) => number;
@@ -466,7 +450,6 @@ export class ClusteredRedisQueue
return fn.apply(source, args);
}
- // istanbul ignore next
public async publish(data: JsonObject, toName?: string): Promise {
const promises: Array> = [];
@@ -477,7 +460,6 @@ export class ClusteredRedisQueue
await Promise.all(promises);
}
- // istanbul ignore next
public async subscribe(
channel: string,
handler: (data: JsonObject) => any,
@@ -493,7 +475,6 @@ export class ClusteredRedisQueue
await Promise.all(promises);
}
- // istanbul ignore next
public async unsubscribe(): Promise {
this.state.subscription = null;
diff --git a/src/IMessageQueue.ts b/src/IMessageQueue.ts
index 032136f..a3e72bb 100644
--- a/src/IMessageQueue.ts
+++ b/src/IMessageQueue.ts
@@ -282,6 +282,28 @@ export interface IMQOptions extends Partial {
*/
clusterManagers?: ClusterManager[];
+ /**
+ * Enables/disables process signal handling (SIGTERM, SIGINT, SIGABRT)
+ * by the queue. When enabled, the queue frees its watcher lock and
+ * exits the process on those signals. Disable when the host
+ * application manages its own shutdown sequence.
+ *
+ * @default true
+ * @type {boolean}
+ */
+ handleSignals?: boolean;
+
+ /**
+ * When enabled, send() resolves only after the message write is
+ * confirmed by redis (and rejects on write failures). By default
+ * writes are fire-and-forget for maximum throughput and failures are
+ * reported through the optional errorHandler argument only.
+ *
+ * @default false
+ * @type {boolean}
+ */
+ awaitWrites?: boolean;
+
/**
* Enables/disables verbose logging
*
@@ -317,7 +339,8 @@ export type IMessageQueueConstructor = new (
*
* @example
* ~~~typescript
- * import { IMessageQueue, EventEmitter, uuid } from '@imqueue/core';
+ * import { IMessageQueue, EventEmitter } from '@imqueue/core';
+ * import { randomUUID } from 'crypto';
*
* class SomeMQAdapter implements IMessageQueue extends EventEmitter {
* public async start(): Promise {
@@ -333,7 +356,7 @@ export type IMessageQueueConstructor = new (
* message: JsonObject,
* delay?: number
* ): Promise {
- * const messageId = uuid();
+ * const messageId = randomUUID();
* // ... implementation goes here
* return messageId;
* }
@@ -374,7 +397,7 @@ export interface IMessageQueue extends EventEmitter {
start(): Promise;
/**
- * Stops the queue (should stop handle queue messages).
+ * Stops the queue (should stop to handle queue messages).
* Supposed to be an async function.
*
* @returns {Promise}
@@ -382,13 +405,13 @@ export interface IMessageQueue extends EventEmitter {
stop(): Promise;
/**
- * Sends a message to given queue name with the given data.
+ * Sends a message to the given queue name with the given data.
* Supposed to be an async function.
*
- * @param {string} toQueue - queue name to which message should be sent to
+ * @param {string} toQueue - queue name to which a message should be sent to
* @param {JsonObject} message - message data
- * @param {number} [delay] - if specified, message will be handled in the
- * target queue after specified period of time in milliseconds.
+ * @param {number} [delay] - if specified, a message will be handled in the
+ * target queue after a specified period of time in milliseconds.
* @param {(err: Error) => void} [errorHandler] - callback called only when
* internal error occurs during message send execution.
* @returns {Promise} - message identifier
@@ -401,7 +424,7 @@ export interface IMessageQueue extends EventEmitter {
): Promise;
/**
- * Creates or uses subscription channel with the given name and sets
+ * Creates or uses a subscription channel with the given name and sets
* message handler on data receive
*
* @param {string} channel - channel name
@@ -420,11 +443,11 @@ export interface IMessageQueue extends EventEmitter {
unsubscribe(): Promise;
/**
- * Publishes data to current queue channel
+ * Publishes data to the current queue channel
*
- * If toName specified will publish to pubsub with different name. This
+ * If toName specified will publish to pubsub with a different name. This
* can be used to implement broadcasting some messages to other subscribers
- * on other pubsub channels. Different name should be in the same namespace
+ * on other pubsub channels. Different names should be in the same namespace
* (same imq prefix)
*
* @param {JsonObject} data - data to publish as channel message
@@ -434,7 +457,7 @@ export interface IMessageQueue extends EventEmitter {
publish(data: JsonObject, toName?: string): Promise;
/**
- * Safely destroys current queue, unregistered all set event
+ * Safely destroys the current queue, unregistered all set event
* listeners and connections.
* Supposed to be an async function.
*
diff --git a/src/RedisQueue.ts b/src/RedisQueue.ts
index a3da54d..2b2f368 100644
--- a/src/RedisQueue.ts
+++ b/src/RedisQueue.ts
@@ -21,10 +21,9 @@
* purchase a proprietary commercial license. Please contact us at
* to get commercial licensing options.
*/
-import * as crypto from 'crypto';
import { EventEmitter } from 'events';
-import * as os from 'os';
-import { gunzipSync as gunzip, gzipSync as gzip } from 'zlib';
+import { randomUUID } from 'crypto';
+import { hostname } from 'os';
import {
IMessageQueue,
IRedisClient,
@@ -34,94 +33,84 @@ import {
ILogger,
IMQMode,
EventMap,
- buildOptions,
profile,
- uuid,
} from '.';
+import {
+ buildOptions,
+ escapeRegExp,
+ randomInt,
+ pack,
+ sha1,
+ unpack,
+ envInt,
+} from './helpers';
import Redis from './redis';
const RX_CLIENT_NAME = /name=(\S+)/g;
const RX_CLIENT_TEST = /:(reader|writer|watcher)/;
const RX_CLIENT_CLEAN = /:(reader|writer|watcher).*$/;
-export const DEFAULT_IMQ_OPTIONS: IMQOptions = {
- host: 'localhost',
- port: 6379,
- cleanup: false,
- cleanupFilter: '*',
- logger: console,
- prefix: 'imq',
- safeDelivery: false,
- safeDeliveryTtl: 5000,
- useGzip: false,
- watcherCheckDelay: 5000,
-};
-
-export const IMQ_SHUTDOWN_TIMEOUT = +(process.env.IMQ_SHUTDOWN_TIMEOUT || 1000);
-
-/**
- * Returns SHA1 hash sum of the given string
- *
- * @param {string} str
- * @returns {string}
- */
-export function sha1(str: string): string {
- const sha: crypto.Hash = crypto.createHash('sha1');
+/** Base delay (ms) for the exponential reconnection backoff */
+const RECONNECT_BASE_DELAY = 1000;
- sha.update(str);
+/** Upper cap (ms) for the exponential reconnection backoff */
+const RECONNECT_MAX_DELAY = 30000;
- return sha.digest('hex');
-}
+/** SCAN batch size used while sweeping keys */
+const SCAN_COUNT = '1000';
/**
- * Returns random integer between given min and max
+ * ioredis retry strategy that disables the built-in reconnection — this
+ * queue performs its own capped-backoff reconnection instead.
*
- * @param {number} min
- * @param {number} max
- * @returns {number}
+ * @return {null}
*/
-export function intrand(min: number, max: number): number {
- return Math.floor(Math.random() * (max - min + 1)) + min;
+function noRetryStrategy(): null {
+ return null;
}
/**
- * Compress given data and returns binary string
+ * Resolves after the given number of milliseconds.
*
- * @param {any} data
- * @returns {string}
+ * @param {number} ms
+ * @return {Promise}
*/
-// istanbul ignore next
-export function pack(data: any): string {
- return gzip(JSON.stringify(data)).toString('binary');
+function delay(ms: number): Promise {
+ return new Promise(resolve => setTimeout(resolve, ms));
}
-/**
- * Decompress binary string and returns plain data
- *
- * @param {string} data
- * @returns {any}
- */
-// istanbul ignore next
-export function unpack(data: string): any {
- return JSON.parse(gunzip(Buffer.from(data, 'binary')).toString());
-}
+export const DEFAULT_IMQ_OPTIONS: IMQOptions = {
+ host: 'localhost',
+ port: 6379,
+ cleanup: false,
+ cleanupFilter: '*',
+ logger: console,
+ prefix: 'imq',
+ safeDelivery: false,
+ safeDeliveryTtl: 5000,
+ useGzip: false,
+ watcherCheckDelay: 5000,
+ handleSignals: true,
+ awaitWrites: false,
+};
+
+export const IMQ_SHUTDOWN_TIMEOUT = envInt('IMQ_SHUTDOWN_TIMEOUT', 1000);
type RedisConnectionChannel = 'reader' | 'writer' | 'watcher' | 'subscription';
-const IMQ_REDIS_MAX_LISTENERS_LIMIT = +(
- process.env.IMQ_REDIS_MAX_LISTENERS_LIMIT || 10000
+const IMQ_REDIS_MAX_LISTENERS_LIMIT = envInt(
+ 'IMQ_REDIS_MAX_LISTENERS_LIMIT',
+ 10000,
);
/**
* Class RedisQueue
- * Implements simple messaging queue over redis.
+ * Implements a simple messaging queue over redis.
*/
export class RedisQueue
extends EventEmitter
implements IMessageQueue
{
- [name: string]: any;
-
/**
* Writer connections collection
*
@@ -136,6 +125,27 @@ export class RedisQueue
*/
private static watchers: { [key: string]: IRedisClient } = {};
+ /**
+ * Number of started queue instances per shared writer connection key
+ *
+ * @type {{}}
+ */
+ private static writerRefs: { [key: string]: number } = {};
+
+ /**
+ * All started queue instances within the current process
+ *
+ * @type {Set}
+ */
+ private static readonly instances: Set = new Set();
+
+ /**
+ * True when process-level signal handlers were bound
+ *
+ * @type {boolean}
+ */
+ private static signalsBound: boolean = false;
+
/**
* @event message (message: JsonObject, id: string, from: string)
*/
@@ -191,21 +201,46 @@ export class RedisQueue
private watchOwner: boolean = false;
/**
- * Signals initialization state
+ * Subscription handlers registered through subscribe(). Kept to be
+ * able to restore the subscription after a connection replacement.
*
- * @type {boolean}
+ * @type {Array<(data: JsonObject) => any>}
*/
- private signalsInitialized: boolean = false;
+ private subscriptionHandlers: Array<(data: JsonObject) => any> = [];
/**
* Will store check interval reference
*/
- private safeCheckInterval: any;
+ private safeCheckInterval?: NodeJS.Timeout;
+
+ /**
+ * Periodic watcher existence check interval reference
+ */
+ private watcherCheckInterval?: NodeJS.Timeout;
+
+ /**
+ * Guards overlapping watcher check runs
+ */
+ private watcherCheckBusy: boolean = false;
+
+ /**
+ * Connected client keys seen during the previous cleanup sweep. Used
+ * to give temporarily disconnected clients one sweep of grace before
+ * removing their keys.
+ */
+ private lastConnectedKeys: string[] = [];
+
+ /**
+ * True while this instance holds a reference to the shared writer
+ */
+ private writerAcquired: boolean = false;
/**
* Internal per-channel reconnection state
*/
- private reconnectTimers: Partial> = {};
+ private reconnectTimers: Partial<
+ Record
+ > = {};
private reconnectAttempts: Partial> =
{};
private reconnecting: Partial> = {};
@@ -216,11 +251,10 @@ export class RedisQueue
public readonly redisKey: string;
/**
- * LUA scripts for redis
+ * Lua scripts for redis
*
* @type {{moveDelayed: {code: string}}}
*/
- // tslint:disable-next-line:completed-docs
private scripts: { [name: string]: { code: string; checksum?: string } } = {
moveDelayed: {
code:
@@ -272,12 +306,14 @@ export class RedisQueue
this.options = buildOptions(DEFAULT_IMQ_OPTIONS, options);
- /* tslint:disable */
this.pack = this.options.useGzip ? pack : JSON.stringify;
this.unpack = this.options.useGzip ? unpack : JSON.parse;
- /* tslint:enable */
this.redisKey = `${this.options.host}:${this.options.port}`;
+ for (const script of Object.keys(this.scripts)) {
+ this.scripts[script].checksum = sha1(this.scripts[script].code);
+ }
+
this.verbose(
`Initializing queue on ${this.options.host}:${
this.options.port
@@ -309,14 +345,12 @@ export class RedisQueue
channel: string,
handler: (data: JsonObject) => any,
): Promise {
- // istanbul ignore next
if (!channel) {
throw new TypeError(
`${channel}: No subscription channel name provided!`,
);
}
- // istanbul ignore next
if (this.subscriptionName && this.subscriptionName !== channel) {
throw new TypeError(
`Invalid channel name provided: expected "${
@@ -331,11 +365,29 @@ export class RedisQueue
const chan = await this.connect('subscription', this.options);
await chan.subscribe(fcn);
+ this.attachSubscriptionHandler(chan, handler);
+ this.subscriptionHandlers.push(handler);
+
+ this.verbose(`Subscribed to ${channel} channel`);
+ }
+
+ /**
+ * Attaches a subscription message handler to a given channel
+ * connection
+ *
+ * @access private
+ * @param {IRedisClient} chan
+ * @param {(data: JsonObject) => any} handler
+ */
+ private attachSubscriptionHandler(
+ chan: IRedisClient,
+ handler: (data: JsonObject) => any,
+ ): void {
+ const fcn = `${this.options.prefix}:${this.subscriptionName}`;
- // istanbul ignore next
chan.on('message', (ch: string, message: string) => {
if (ch === fcn && typeof handler === 'function') {
- handler(JSON.parse(message) as unknown as JsonObject);
+ handler(JSON.parse(message) as JsonObject);
}
this.verbose(
@@ -344,8 +396,36 @@ export class RedisQueue
)}`,
);
});
+ }
- this.verbose(`Subscribed to ${channel} channel`);
+ /**
+ * Restores subscription state on a freshly created subscription
+ * connection. Used after a connection replacement on reconnect,
+ * otherwise the new connection would silently stay unsubscribed.
+ *
+ * @access private
+ * @return {Promise}
+ */
+ private async restoreSubscription(): Promise {
+ const chan = this.subscription;
+
+ if (
+ !chan ||
+ !this.subscriptionName ||
+ !this.subscriptionHandlers.length
+ ) {
+ return;
+ }
+
+ const fcn = `${this.options.prefix}:${this.subscriptionName}`;
+
+ await chan.subscribe(fcn);
+
+ for (const handler of this.subscriptionHandlers) {
+ this.attachSubscriptionHandler(chan, handler);
+ }
+
+ this.verbose(`Restored subscription to ${this.subscriptionName}`);
}
/**
@@ -369,7 +449,9 @@ export class RedisQueue
}
this.subscription.removeAllListeners();
- this.subscription.quit();
+ await this.subscription.quit().catch(error => {
+ this.verbose(`Unsubscribe quit error: ${error}`);
+ });
this.subscription.disconnect(false);
} catch (error) {
this.verbose(`Unsubscribe error: ${error}`);
@@ -378,6 +460,7 @@ export class RedisQueue
this.subscriptionName = undefined;
this.subscription = undefined;
+ this.subscriptionHandlers = [];
}
/**
@@ -423,7 +506,6 @@ export class RedisQueue
const connPromises = [];
- // istanbul ignore next
if (!this.reader && this.isWorker()) {
this.verbose('Initializing reader...');
connPromises.push(this.connect('reader', this.options));
@@ -438,44 +520,137 @@ export class RedisQueue
this.verbose('Connections initialized');
- if (!this.signalsInitialized) {
- this.verbose('Setting up OS signal handlers...');
- // istanbul ignore next
- const free = async () => {
- let exitCode = 0;
-
- setTimeout(() => {
- this.verbose(
- `Shutting down after ${IMQ_SHUTDOWN_TIMEOUT} timeout`,
- );
- process.exit(exitCode);
- }, IMQ_SHUTDOWN_TIMEOUT);
-
- try {
- if (this.watchOwner) {
- this.verbose('Freeing watcher lock...');
- await this.unlock();
- }
- } catch (err) {
- this.logger.error(err);
- exitCode = 1;
- }
- };
+ RedisQueue.instances.add(this);
- process.on('SIGTERM', free);
- process.on('SIGINT', free);
- process.on('SIGABRT', free);
+ if (this.options.handleSignals !== false) {
+ RedisQueue.bindSignals();
+ }
- this.signalsInitialized = true;
- this.verbose('OS signal handlers initialized!');
+ if (!this.writerAcquired) {
+ RedisQueue.writerRefs[this.redisKey] =
+ (RedisQueue.writerRefs[this.redisKey] || 0) + 1;
+ this.writerAcquired = true;
}
await this.initWatcher();
+ this.startWatcherCheck();
this.initialized = true;
return this;
}
+ /**
+ * Binds process-level signal handlers once per process. On shutdown
+ * signals, frees watcher locks held by any queue instance and exits.
+ *
+ * @access private
+ */
+ private static bindSignals(): void {
+ if (RedisQueue.signalsBound) {
+ return;
+ }
+
+ RedisQueue.signalsBound = true;
+
+ const free = (): void => {
+ void RedisQueue.freeAndExit();
+ };
+
+ process.on('SIGTERM', free);
+ process.on('SIGINT', free);
+ process.on('SIGABRT', free);
+ }
+
+ /**
+ * Frees watcher locks held by all started queue instances and exits
+ * the process, forcing exit after IMQ_SHUTDOWN_TIMEOUT at the latest.
+ *
+ * @access private
+ * @return {Promise}
+ */
+ private static async freeAndExit(): Promise {
+ let exitCode = 0;
+ const timer = setTimeout(() => {
+ process.exit(exitCode || 1);
+ }, IMQ_SHUTDOWN_TIMEOUT);
+
+ await Promise.all(
+ [...RedisQueue.instances]
+ .filter(queue => queue.watchOwner)
+ .map(queue =>
+ queue.unlock().catch(err => {
+ queue.logger.error(err);
+ exitCode = 1;
+ }),
+ ),
+ );
+
+ clearTimeout(timer);
+ process.exit(exitCode);
+ }
+
+ /**
+ * Starts a periodic check ensuring a watcher connection exists across
+ * the queue network, re-electing an owner when the previous one died.
+ * Also serves as a polling fallback moving due delayed messages when
+ * keyspace notifications are unavailable.
+ *
+ * @access private
+ */
+ private startWatcherCheck(): void {
+ if (this.watcherCheckInterval || !this.options.watcherCheckDelay) {
+ return;
+ }
+
+ this.watcherCheckInterval = setInterval(
+ this.runWatcherCheck.bind(this),
+ this.options.watcherCheckDelay,
+ );
+ this.watcherCheckInterval.unref();
+ }
+
+ /**
+ * A single watcher-existence check tick: re-elects a watcher owner when
+ * none exists and moves due delayed messages as a keyspace-notification
+ * fallback. Errors are contained so the interval never crashes.
+ *
+ * @access private
+ * @return {Promise}
+ */
+ private async runWatcherCheck(): Promise {
+ if (this.watcherCheckBusy || this.destroyed || !this.writer) {
+ return;
+ }
+
+ this.watcherCheckBusy = true;
+
+ try {
+ if (!(await this.watcherCount())) {
+ await this.initWatcher();
+ }
+
+ if (this.isWorker()) {
+ await this.processDelayed(this.key);
+ }
+ } catch (err) {
+ this.verbose(`Watcher check error: ${err}`);
+ } finally {
+ this.watcherCheckBusy = false;
+ }
+ }
+
+ /**
+ * Stops the periodic watcher check
+ *
+ * @access private
+ */
+ private stopWatcherCheck(): void {
+ if (this.watcherCheckInterval) {
+ clearInterval(this.watcherCheckInterval);
+ this.watcherCheckInterval = undefined;
+ }
+ }
+
/**
* Sends a given message to a given queue (by name)
*
@@ -495,7 +670,6 @@ export class RedisQueue
throw new TypeError('IMQ: Unable to publish in WORKER only mode!');
}
- // istanbul ignore next
if (!this.writer) {
await this.start();
}
@@ -504,30 +678,61 @@ export class RedisQueue
throw new TypeError('IMQ: unable to initialize queue!');
}
- const id = uuid();
+ const id = randomUUID();
const data: IMessage = { id, message, from: this.name };
const key = `${this.options.prefix}:${toQueue}`;
const packet = this.pack(data);
- const cb = (error: any, op: string) => {
- // istanbul ignore next
+ const onWriteError = (error: unknown, op: string): void => {
if (error) {
this.verbose(`Writer ${op} error: ${error}`);
if (errorHandler) {
- errorHandler(error as unknown as Error);
+ errorHandler(
+ error instanceof Error
+ ? error
+ : new Error(String(error)),
+ );
}
}
};
+ if (this.options.awaitWrites) {
+ // confirmed writes mode: resolve after redis accepted the
+ // message, reject on 'write' failures
+ try {
+ if (delay) {
+ await this.writer.zadd(
+ `${key}:delayed`,
+ Date.now() + delay,
+ packet,
+ );
+ await this.writer.set(
+ `${key}:${id}:ttl`,
+ '',
+ 'PX',
+ delay,
+ 'NX',
+ );
+ } else {
+ await this.writer.lpush(key, packet);
+ }
+ } catch (err) {
+ onWriteError(err, delay ? 'ZADD/SET' : 'LPUSH');
+
+ throw err;
+ }
+
+ return id;
+ }
+
if (delay) {
this.writer.zadd(
`${key}:delayed`,
Date.now() + delay,
packet,
(err: any) => {
- // istanbul ignore next
if (err) {
- cb(err, 'ZADD');
+ onWriteError(err, 'ZADD');
return;
}
@@ -540,26 +745,30 @@ export class RedisQueue
delay,
'NX',
(err: any) => {
- // istanbul ignore next
if (err) {
- cb(err, 'SET');
+ onWriteError(err, 'SET');
return;
}
},
)
- .catch((err: any) => cb(err, 'SET'));
+ .catch((err: any) => onWriteError(err, 'SET'));
},
);
} else {
- this.writer.lpush(key, packet, (err: any) => {
- // istanbul ignore next
+ const result: any = this.writer.lpush(key, packet, (err: any) => {
if (err) {
- cb(err, 'LPUSH');
+ onWriteError(err, 'LPUSH');
return;
}
});
+
+ // guard against unhandled rejections from promise-returning
+ // client implementations in fire-and-forget mode
+ if (result && typeof result.catch === 'function') {
+ result.catch((err: any) => onWriteError(err, 'LPUSH'));
+ }
}
return id;
@@ -576,7 +785,7 @@ export class RedisQueue
if (this.reader) {
this.verbose('Destroying reader...');
- this.destroyChannel('reader', this);
+ this.destroyChannel('reader');
delete this.reader;
}
@@ -589,19 +798,37 @@ export class RedisQueue
}
/**
- * Gracefully destroys this queue
+ * Gracefully destroys this queue handle. Does not remove queue data
+ * from redis unless clearData is explicitly set to true, so that
+ * destroying one handle (e.g. on scale-down) never wipes messages
+ * still pending for other producers/consumers.
*
+ * @param {boolean} [clearData] - when true, also clears queue data
* @returns {Promise}
*/
@profile()
- public async destroy(): Promise {
+ public async destroy(clearData: boolean = false): Promise {
this.verbose('Destroying queue...');
this.destroyed = true;
+ RedisQueue.instances.delete(this);
this.removeAllListeners();
this.cleanSafeCheckInterval();
- this.destroyWatcher();
+ this.stopWatcherCheck();
+
+ if (this.watchOwner) {
+ await this.unlock().catch(err =>
+ this.verbose(`Unlock error: ${err}`),
+ );
+ this.destroyWatcher();
+ this.watchOwner = false;
+ }
+
await this.stop();
- await this.clear();
+
+ if (clearData) {
+ await this.clear();
+ }
+
this.destroyWriter();
await this.unsubscribe();
this.verbose('Queue destroyed!');
@@ -628,7 +855,6 @@ export class RedisQueue
this.verbose('Expired queue keys cleared!');
} catch (err) {
- // istanbul ignore next
if (this.initialized) {
this.logger.error(
`${this.name}: error clearing the redis queue host ${
@@ -674,7 +900,6 @@ export class RedisQueue
return this.mode === IMQMode.BOTH || this.mode === IMQMode.WORKER;
}
- // noinspection JSMethodCanBeStatic
/**
* Writer connection associated with this queue instance
*
@@ -684,13 +909,11 @@ export class RedisQueue
return RedisQueue.writers[this.redisKey];
}
- // noinspection JSUnusedLocalSymbols
/**
* Writer connection setter.
*
* @param {IRedisClient} conn
*/
- // noinspection JSUnusedLocalSymbols,JSUnusedLocalSymbols
private set writer(conn: IRedisClient) {
RedisQueue.writers[this.redisKey] = conn;
}
@@ -704,24 +927,70 @@ export class RedisQueue
return RedisQueue.watchers[this.redisKey];
}
- // noinspection JSUnusedLocalSymbols
/**
* Watcher setter sets the watcher connection property for this
* queue instance
*
* @param {IRedisClient} conn
*/
- // noinspection JSUnusedLocalSymbols
private set watcher(conn: IRedisClient) {
RedisQueue.watchers[this.redisKey] = conn;
}
+ /**
+ * Returns the connection currently bound to the given channel, if any.
+ *
+ * @access private
+ * @param {RedisConnectionChannel} channel
+ * @return {IRedisClient | undefined}
+ */
+ private connectionOf(
+ channel: RedisConnectionChannel,
+ ): IRedisClient | undefined {
+ switch (channel) {
+ case 'reader':
+ return this.reader;
+ case 'writer':
+ return this.writer;
+ case 'watcher':
+ return this.watcher;
+ case 'subscription':
+ return this.subscription;
+ }
+ }
+
+ /**
+ * Binds the given connection to the given channel.
+ *
+ * @access private
+ * @param {RedisConnectionChannel} channel
+ * @param {IRedisClient} conn
+ */
+ private bindConnection(
+ channel: RedisConnectionChannel,
+ conn: IRedisClient,
+ ): void {
+ switch (channel) {
+ case 'reader':
+ this.reader = conn;
+ break;
+ case 'writer':
+ this.writer = conn;
+ break;
+ case 'watcher':
+ this.watcher = conn;
+ break;
+ case 'subscription':
+ this.subscription = conn;
+ break;
+ }
+ }
+
/**
* Logger instance associated with the current queue instance
* @type {ILogger}
*/
private get logger(): ILogger {
- // istanbul ignore next
return this.options.logger || console;
}
@@ -754,7 +1023,7 @@ export class RedisQueue
private destroyWatcher(): void {
if (this.watcher) {
this.verbose('Destroying watcher...');
- this.destroyChannel('watcher', this);
+ this.destroyChannel('watcher');
delete RedisQueue.watchers[this.redisKey];
this.verbose('Watcher destroyed!');
}
@@ -766,10 +1035,26 @@ export class RedisQueue
* @access private
*/
@profile()
- private destroyWriter(): void {
+ private destroyWriter(release: boolean = true): void {
+ if (release && this.writerAcquired) {
+ this.writerAcquired = false;
+ RedisQueue.writerRefs[this.redisKey] = Math.max(
+ 0,
+ (RedisQueue.writerRefs[this.redisKey] || 1) - 1,
+ );
+
+ if (RedisQueue.writerRefs[this.redisKey] > 0) {
+ // the shared writer connection is still used by other
+ // queue instances within this process
+ this.verbose('Writer is still in use, skipping destroy...');
+
+ return;
+ }
+ }
+
if (this.writer) {
this.verbose('Destroying writer...');
- this.destroyChannel('writer', this);
+ this.destroyChannel('writer');
delete RedisQueue.writers[this.redisKey];
this.verbose('Writer destroyed!');
}
@@ -781,26 +1066,23 @@ export class RedisQueue
* @access private
*/
@profile()
- private destroyChannel(
- channel: RedisConnectionChannel,
- context: RedisQueue = this,
- ): void {
- const client = context[channel];
+ private destroyChannel(channel: RedisConnectionChannel): void {
+ const client = this.connectionOf(channel);
- if (client) {
- try {
- client.removeAllListeners();
- client
- .quit()
- .then(() => {
- client.disconnect(false);
- })
- .catch(e => {
- this.verbose(`Error quitting ${channel}: ${e}`);
- });
- } catch (error) {
- this.verbose(`Error destroying ${channel}: ${error}`);
- }
+ if (!client) {
+ return;
+ }
+
+ try {
+ client.removeAllListeners();
+ client
+ .quit()
+ .then(() => client.disconnect(false))
+ .catch((error: unknown) =>
+ this.verbose(`Error quitting ${channel}: ${error}`),
+ );
+ } catch (error) {
+ this.verbose(`Error destroying ${channel}: ${error}`);
}
}
@@ -810,36 +1092,31 @@ export class RedisQueue
* @access private
* @param {RedisConnectionChannel} channel
* @param {IMQOptions} options
- * @param {any} context
* @returns {Promise}
*/
private async connect(
channel: RedisConnectionChannel,
options: IMQOptions,
- context: RedisQueue = this,
): Promise {
this.verbose(`Connecting to ${channel} channel...`);
- // istanbul ignore next
- if (context[channel]) {
- return context[channel];
+ const existing = this.connectionOf(channel);
+
+ if (existing) {
+ return existing;
}
- const redis = new Redis({
- // istanbul ignore next
+ const redis: IRedisClient = new Redis({
port: options.port || 6379,
- // istanbul ignore next
host: options.host || 'localhost',
- // istanbul ignore next
username: options.username,
- // istanbul ignore next
password: options.password,
connectionName: this.getChannelName(
- context.name + '',
+ this.name,
options.prefix || '',
channel,
),
- retryStrategy: this.retryStrategy(),
+ retryStrategy: noRetryStrategy,
autoResubscribe: true,
enableOfflineQueue: true,
autoResendUnfulfilledCommands: true,
@@ -849,8 +1126,8 @@ export class RedisQueue
lazyConnect: true,
});
- context[channel] = redis;
- context[channel].__imq = true;
+ this.bindConnection(channel, redis);
+ redis.__imq = true;
for (const event of [
'wait',
@@ -859,20 +1136,18 @@ export class RedisQueue
'connect',
'close',
]) {
- redis.on(event, () => {
- context.verbose(`Redis Event fired: ${event}`);
- });
+ redis.on(event, () => this.verbose(`Redis Event fired: ${event}`));
}
redis.setMaxListeners(IMQ_REDIS_MAX_LISTENERS_LIMIT);
- redis.on('error', this.onErrorHandler(context, channel));
- redis.on('end', this.onCloseHandler(context, channel));
+ redis.on('error', this.onErrorHandler(channel));
+ redis.on('end', this.onCloseHandler(channel));
await redis.connect();
this.logger.info(
'%s: %s channel connected, host %s, pid %s',
- context.name,
+ this.name,
channel,
this.redisKey,
process.pid,
@@ -888,22 +1163,12 @@ export class RedisQueue
case 'watcher':
await this.initWatcher();
break;
+ case 'subscription':
+ await this.restoreSubscription();
+ break;
}
- return context[channel];
- }
-
- // istanbul ignore next
- /**
- * Builds and returns redis reconnection strategy
- *
- * @returns {() => (number | void | null)}
- * @private
- */
- private retryStrategy(): () => null {
- return () => {
- return null;
- };
+ return redis;
}
/**
@@ -914,76 +1179,87 @@ export class RedisQueue
* @private
*/
private scheduleReconnect(channel: RedisConnectionChannel): void {
- if (this.destroyed) {
- return;
- }
-
- if (this.reconnecting[channel]) {
+ if (this.destroyed || this.reconnecting[channel]) {
return;
}
const attempts = (this.reconnectAttempts[channel] || 0) + 1;
- const delay = Math.min(30000, 1000 * Math.pow(2, attempts - 1));
+ const delayMs = Math.min(
+ RECONNECT_MAX_DELAY,
+ RECONNECT_BASE_DELAY * 2 ** (attempts - 1),
+ );
this.reconnecting[channel] = true;
this.reconnectAttempts[channel] = attempts;
this.verbose(
- `Scheduling ${channel} reconnect in ${
- delay
- } ms (attempt ${attempts})`,
+ `Scheduling ${channel} reconnect in ${delayMs} ms ` +
+ `(attempt ${attempts})`,
);
if (this.reconnectTimers[channel]) {
- clearTimeout(this.reconnectTimers[channel] as any);
+ clearTimeout(this.reconnectTimers[channel]);
}
- this.reconnectTimers[channel] = setTimeout(async () => {
- if (this.destroyed) {
- this.reconnecting[channel] = false;
-
- return;
- }
-
- try {
- switch (channel) {
- case 'watcher':
- this.destroyWatcher();
- break;
- case 'writer':
- this.destroyWriter();
- break;
- case 'reader':
- this.destroyChannel(channel, this);
- this.reader = undefined;
+ this.reconnectTimers[channel] = setTimeout(
+ this.reconnectNow.bind(this, channel),
+ delayMs,
+ );
+ }
- break;
- case 'subscription':
- this.destroyChannel(channel, this);
- this.subscription = undefined;
+ /**
+ * Performs a single reconnection attempt for the given channel,
+ * rescheduling itself on failure. Errors are handled internally so the
+ * scheduled timer never produces an unhandled rejection.
+ *
+ * @access private
+ * @param {RedisConnectionChannel} channel
+ * @return {Promise}
+ */
+ private async reconnectNow(channel: RedisConnectionChannel): Promise {
+ if (this.destroyed) {
+ this.reconnecting[channel] = false;
- break;
- }
+ return;
+ }
- await this.connect(channel, this.options);
- this.reconnectAttempts[channel] = 0;
- this.reconnecting[channel] = false;
+ try {
+ switch (channel) {
+ case 'watcher':
+ this.destroyWatcher();
+ break;
+ case 'writer':
+ // replace the broken shared connection without
+ // releasing this instance's reference to it
+ this.destroyWriter(false);
+ break;
+ case 'reader':
+ this.destroyChannel(channel);
+ this.reader = undefined;
+ break;
+ case 'subscription':
+ this.destroyChannel(channel);
+ this.subscription = undefined;
+ break;
+ }
- if (this.reconnectTimers[channel]) {
- clearTimeout(this.reconnectTimers[channel] as any);
- this.reconnectTimers[channel] = undefined;
- }
+ await this.connect(channel, this.options);
+ this.reconnectAttempts[channel] = 0;
+ this.reconnecting[channel] = false;
- this.verbose(`Reconnected ${channel} channel`);
- } catch (err) {
- this.reconnecting[channel] = false;
- this.verbose(`Reconnect ${channel} failed: ${err}`);
- this.scheduleReconnect(channel);
+ if (this.reconnectTimers[channel]) {
+ clearTimeout(this.reconnectTimers[channel]);
+ this.reconnectTimers[channel] = undefined;
}
- }, delay);
+
+ this.verbose(`Reconnected ${channel} channel`);
+ } catch (err) {
+ this.reconnecting[channel] = false;
+ this.verbose(`Reconnect ${channel} failed: ${err}`);
+ this.scheduleReconnect(channel);
+ }
}
- // noinspection JSMethodCanBeStatic
/**
* Generates channel name
*
@@ -997,7 +1273,7 @@ export class RedisQueue
prefix: string,
name: RedisConnectionChannel,
): string {
- const uniqueSuffix = `pid:${process.pid}:host:${os.hostname()}`;
+ const uniqueSuffix = `pid:${process.pid}:host:${hostname()}`;
return `${prefix}:${contextName}:${name}:${uniqueSuffix}`;
}
@@ -1006,15 +1282,12 @@ export class RedisQueue
* Builds and returns connection error handler
*
* @access private
- * @param {RedisQueue} context
* @param {RedisConnectionChannel} channel
* @return {(err: Error) => void}
*/
private onErrorHandler(
- context: RedisQueue,
channel: RedisConnectionChannel,
): (error: Error) => void {
- // istanbul ignore next
return (error: Error & { code?: string }) => {
this.verbose(`Redis Error: ${error}`);
@@ -1023,7 +1296,7 @@ export class RedisQueue
}
this.logger.error(
- `${context.name}: error connecting redis host ${
+ `${this.name}: error connecting redis host ${
this.redisKey
} on ${channel}, pid ${process.pid}:`,
error,
@@ -1032,7 +1305,7 @@ export class RedisQueue
if (
error.code === 'ECONNREFUSED' ||
error.code === 'ETIMEDOUT' ||
- context[channel]?.status !== 'ready'
+ this.connectionOf(channel)?.status !== 'ready'
) {
this.scheduleReconnect(channel);
}
@@ -1043,23 +1316,18 @@ export class RedisQueue
* Builds and returns redis connection close handler
*
* @access private
- * @param {RedisQueue} context
* @param {RedisConnectionChannel} channel
- * @return {(...args: any[]) => any}
+ * @return {() => void}
*/
- private onCloseHandler(
- context: RedisQueue,
- channel: RedisConnectionChannel,
- ): (...args: any[]) => any {
+ private onCloseHandler(channel: RedisConnectionChannel): () => void {
this.verbose(`Redis ${channel} is closing...`);
- // istanbul ignore next
return () => {
this.initialized = false;
this.logger.warn(
'%s: redis connection %s closed on host %s, pid %s!',
- context.name,
+ this.name,
channel,
this.redisKey,
process.pid,
@@ -1082,7 +1350,6 @@ export class RedisQueue
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
const [queue, data] = msg;
- // istanbul ignore next
if (!queue || queue !== this.key) {
return this;
}
@@ -1093,11 +1360,10 @@ export class RedisQueue
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
this.emit('message', message, id, from);
} catch (err) {
- // istanbul ignore next
this.emitError(
'OnMessage',
'process error - message is invalid',
- err as unknown as Error,
+ err,
);
}
@@ -1110,14 +1376,15 @@ export class RedisQueue
* @access private
* @returns {Promise}
*/
- // istanbul ignore next
private async watcherCount(): Promise {
if (!this.writer) {
return 0;
}
const rx = new RegExp(
- `\\bname=${this.options.prefix}:[\\S]+?:watcher:`,
+ `\\bname=${escapeRegExp(
+ this.options.prefix || '',
+ )}:[\\S]+?:watcher:`,
);
const list = await this.writer.client('LIST');
@@ -1137,7 +1404,11 @@ export class RedisQueue
*/
private async processDelayed(key: string): Promise {
try {
- if (this.scripts.moveDelayed.checksum) {
+ if (!this.scripts.moveDelayed.checksum || !this.writer) {
+ return;
+ }
+
+ try {
await this.writer.evalsha(
this.scripts.moveDelayed.checksum,
2,
@@ -1145,17 +1416,31 @@ export class RedisQueue
key,
Date.now(),
);
+ } catch (err) {
+ // the script may not be cached on the redis host (fresh
+ // host, restart, non-owner instance) - fall back to EVAL
+ // which caches it as a side effect
+ if (err instanceof Error && /NOSCRIPT/.test(err.message)) {
+ await this.writer.eval(
+ this.scripts.moveDelayed.code,
+ 2,
+ `${key}:delayed`,
+ key,
+ Date.now(),
+ );
+ } else {
+ throw err;
+ }
}
} catch (err) {
this.emitError(
'OnProcessDelayed',
'error processing delayed queue',
- err as unknown as Error,
+ err,
);
}
}
- // istanbul ignore next
/**
* Watch routine
*
@@ -1173,7 +1458,7 @@ export class RedisQueue
'MATCH',
`${this.options.prefix}:*:worker:*`,
'COUNT',
- '1000',
+ SCAN_COUNT,
);
cursor = data.shift() as string;
@@ -1189,7 +1474,7 @@ export class RedisQueue
this.emitError(
'OnSafeDelivery',
'safe queue message delivery problem',
- err as unknown as Error,
+ err,
);
this.cleanSafeCheckInterval();
@@ -1198,7 +1483,6 @@ export class RedisQueue
}
}
- // istanbul ignore next
/**
* Process given keys from a message queue
*
@@ -1221,15 +1505,23 @@ export class RedisQueue
for (const key of keys) {
const kp: string[] = key.split(':');
- if (Number(kp.pop()) < now) {
+ // the last key segment is the worker's lease deadline: only
+ // re-queue messages of workers whose lease has expired (the
+ // worker died mid-processing); fresh leases belong to live
+ // workers and must not be touched
+ if (Number(kp.pop()) >= now) {
continue;
}
- await this.writer.rpoplpush(key, `${kp.shift()}:${kp.shift()}`);
+ await this.writer.lmove(
+ key,
+ `${kp.shift()}:${kp.shift()}`,
+ 'RIGHT',
+ 'LEFT',
+ );
}
}
- // istanbul ignore next
/**
* Watch message processor
*
@@ -1249,11 +1541,10 @@ export class RedisQueue
await this.processDelayed(key.join(':'));
} catch (err) {
- this.emitError('OnWatch', 'watch error', err as unknown as Error);
+ this.emitError('OnWatch', 'watch error', err);
}
}
- // istanbul ignore next
/**
* Clears safe check interval
*
@@ -1261,7 +1552,7 @@ export class RedisQueue
*/
private cleanSafeCheckInterval(): void {
if (this.safeCheckInterval) {
- clearInterval(this.safeCheckInterval as number);
+ clearInterval(this.safeCheckInterval);
delete this.safeCheckInterval;
}
}
@@ -1272,7 +1563,6 @@ export class RedisQueue
* @access private
* @returns {RedisQueue}
*/
- // istanbul ignore next
private watch(): RedisQueue {
if (!this.writer || !this.watcher || this.watcher.__ready__) {
return this;
@@ -1281,54 +1571,31 @@ export class RedisQueue
try {
this.writer
.config('SET', 'notify-keyspace-events', 'Ex')
- .catch(err => {
- this.emitError(
- 'OnConfig',
- 'events config error',
- err as unknown as Error,
- );
- });
+ .catch((err: unknown) =>
+ this.emitError('OnConfig', 'events config error', err),
+ );
} catch (err) {
- this.emitError(
- 'OnConfig',
- 'events config error',
- err as unknown as Error,
- );
+ this.emitError('OnConfig', 'events config error', err);
}
- this.watcher.on(
- 'pmessage',
- this.onWatchMessage.bind(this) as unknown as () => void,
- );
+ this.watcher.on('pmessage', this.onWatchMessage.bind(this));
this.watcher
.psubscribe(
'__keyevent@0__:expired',
`${this.options.prefix}:delayed:*`,
)
- .catch(err => {
- this.verbose(`Error subscribing to watcher channel: ${err}`);
- });
+ .catch((err: unknown) =>
+ this.verbose(`Error subscribing to watcher channel: ${err}`),
+ );
// watch for expired unhandled safe queues
- if (!this.safeCheckInterval) {
- if (this.options.safeDeliveryTtl != null) {
- this.safeCheckInterval = setInterval(
- (async (): Promise => {
- if (!this.writer) {
- this.cleanSafeCheckInterval();
-
- return;
- }
-
- if (this.options.safeDelivery) {
- await this.processWatch();
- }
-
- await this.processCleanup();
- }) as unknown as () => void,
- this.options.safeDeliveryTtl,
- );
- }
+ if (!this.safeCheckInterval && this.options.safeDeliveryTtl != null) {
+ this.safeCheckInterval = setInterval(
+ this.runSafeCheck.bind(this),
+ this.options.safeDeliveryTtl,
+ );
+ // maintenance timer must not keep the process alive on its own
+ this.safeCheckInterval.unref();
}
this.watcher.__ready__ = true;
@@ -1336,6 +1603,27 @@ export class RedisQueue
return this;
}
+ /**
+ * A single safe-delivery maintenance tick: recovers messages from dead
+ * workers (when safe delivery is on) and prunes orphaned keys.
+ *
+ * @access private
+ * @return {Promise}
+ */
+ private async runSafeCheck(): Promise {
+ if (!this.writer) {
+ this.cleanSafeCheckInterval();
+
+ return;
+ }
+
+ if (this.options.safeDelivery) {
+ await this.processWatch();
+ }
+
+ await this.processCleanup();
+ }
+
/**
* Cleans up orphaned keys from redis
*
@@ -1351,9 +1639,12 @@ export class RedisQueue
}
const filter: RegExp = new RegExp(
- this.options.prefix +
+ escapeRegExp(this.options.prefix || '') +
':' +
- (this.options.cleanupFilter || '*').replace(/\*/g, '.*'),
+ escapeRegExp(this.options.cleanupFilter || '*').replace(
+ /\\\*/g,
+ '.*',
+ ),
'i',
);
@@ -1373,11 +1664,23 @@ export class RedisQueue
(name: string, i: number, a: string[]) =>
a.indexOf(name) === i,
);
+ // clients seen connected during the previous sweep get one
+ // sweep of grace: a client that is merely reconnecting (the
+ // backoff can reach tens of seconds) must not have its keys
+ // deleted from under it
+ const knownKeys = connectedKeys.concat(
+ this.lastConnectedKeys.filter(
+ key => !connectedKeys.includes(key),
+ ),
+ );
+
+ this.lastConnectedKeys = connectedKeys;
+
const keysToRemove: string[] = [];
let cursor = '0';
this.verbose(
- `Found connected keys: ${connectedKeys
+ `Found connected keys: ${knownKeys
.map(k => `"${k}"`)
.join(', ')}`,
);
@@ -1390,7 +1693,7 @@ export class RedisQueue
this.options.cleanupFilter || '*'
}`,
'COUNT',
- '1000',
+ SCAN_COUNT,
);
cursor = data.shift() as string;
@@ -1401,7 +1704,7 @@ export class RedisQueue
...keys.filter(
key =>
key !== this.lockKey &&
- connectedKeys.every(
+ knownKeys.every(
connectedKey =>
key.indexOf(connectedKey) === -1,
),
@@ -1429,7 +1732,6 @@ export class RedisQueue
return this;
}
- // noinspection JSUnusedLocalSymbols
/**
* Unreliable but fast way of message handling by the queue
*/
@@ -1449,7 +1751,6 @@ export class RedisQueue
this.process(msg);
}
} catch (err) {
- // istanbul ignore next
if (
err instanceof Error &&
err.message.match(/Stream connection ended/)
@@ -1457,68 +1758,68 @@ export class RedisQueue
break;
}
- // istanbul ignore next
- // noinspection ExceptionCaughtLocallyJS
throw err;
}
}
} catch (err) {
- // istanbul ignore next
- this.emitError(
- 'OnReadUnsafe',
- 'unsafe reader failed',
- err as unknown as Error,
- );
+ this.emitError('OnReadUnsafe', 'unsafe reader failed', err);
}
}
- // noinspection JSUnusedLocalSymbols
/**
- * Reliable but slow method of message handling by message queue
+ * Reliable but slow method of message handling by message queue.
+ *
+ * Uses a bounded blocking pop so the lease deadline embedded into the
+ * worker key never goes stale: with an infinite block a message
+ * arriving long after the pop started would be born with an already
+ * expired lease and be immediately re-queued by the watcher.
*/
private async readSafe(): Promise {
- try {
- const key = this.key;
-
- while (true) {
- const expire: number =
- Date.now() + Number(this.options.safeDeliveryTtl);
- const workerKey = `${key}:worker:${uuid()}:${expire}`;
-
- if (!this.reader || !this.writer) {
- break;
- }
+ const key = this.key;
+ // blocking timeout in seconds, at most half of the lease ttl, so
+ // a claimed message always has at least half the ttl remaining
+ const timeout = Math.max(
+ 0.1,
+ Number(this.options.safeDeliveryTtl) / 2000,
+ );
- try {
- await this.reader.brpoplpush(this.key, workerKey, 0);
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
- } catch (_) {
- // istanbul ignore next
- break;
- }
+ while (true) {
+ if (!this.reader || !this.writer || this.destroyed) {
+ break;
+ }
- const msgArr: any = await this.writer.lrange(workerKey, -1, 1);
+ const expire: number =
+ Date.now() + Number(this.options.safeDeliveryTtl);
+ const workerKey = `${key}:worker:${randomUUID()}:${expire}`;
+ let msg: string | null;
- if (!msgArr || msgArr?.length !== 1) {
- // noinspection ExceptionCaughtLocallyJS
- throw new Error('Wrong messages count');
- }
+ try {
+ msg = await this.reader.blmove(
+ this.key,
+ workerKey,
+ 'RIGHT',
+ 'LEFT',
+ timeout,
+ );
+ } catch {
+ // reader connection ended (stop/reconnect)
+ break;
+ }
- // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
- const [msg] = msgArr as any[];
+ if (msg === null || msg === undefined) {
+ // blocking pop timed out: regenerate the lease and retry
+ continue;
+ }
+ try {
this.process([key, msg]);
this.writer
.del(workerKey)
.catch(e => this.logger.warn('OnReadSafe: del error', e));
+ } catch (err) {
+ // a single message failure must never kill the read loop
+ this.emitError('OnReadSafe', 'safe reader failed', err);
}
- } catch (err) {
- // istanbul ignore next
- this.emitError(
- 'OnReadSafe',
- 'safe reader failed',
- err as unknown as Error,
- );
}
}
@@ -1528,7 +1829,6 @@ export class RedisQueue
* @returns {RedisQueue}
*/
private read(): RedisQueue {
- // istanbul ignore next
if (!this.reader) {
this.logger.error(
`${this.name}: reader connection is not initialized, pid ${
@@ -1539,12 +1839,11 @@ export class RedisQueue
return this;
}
- const readMethod = this.options.safeDelivery
- ? 'readSafe'
- : 'readUnsafe';
+ const runReader = this.options.safeDelivery
+ ? this.readSafe
+ : this.readUnsafe;
- // eslint-disable-next-line @typescript-eslint/no-unsafe-argument
- process.nextTick(this[readMethod].bind(this));
+ process.nextTick(runReader.bind(this));
return this;
}
@@ -1591,17 +1890,24 @@ export class RedisQueue
return false;
}
- // istanbul ignore next
/**
* Emits error
*
* @access private
* @param {string} eventName
* @param {string} message
- * @param {Error} err
+ * @param {unknown} err
*/
- private emitError(eventName: string, message: string, err: Error): void {
- this.emit('error', err, eventName);
+ private emitError(eventName: string, message: string, err: unknown): void {
+ const error = err instanceof Error ? err : new Error(String(err));
+
+ // emitting 'error' with no listeners attached would throw and
+ // crash the process from a background routine - always log, but
+ // only emit when someone actually listens
+ if (this.listenerCount('error') > 0) {
+ this.emit('error', error, eventName);
+ }
+
this.logger.error(
`${this.name}: ${message}, pid ${
process.pid
@@ -1620,7 +1926,6 @@ export class RedisQueue
*
* @returns {Promise}
*/
- // istanbul ignore next
private async ownWatch(): Promise {
const owned = await this.lock();
@@ -1629,9 +1934,8 @@ export class RedisQueue
for (const script of Object.keys(this.scripts)) {
try {
- const checksum = sha1(this.scripts[script].code);
-
- this.scripts[script].checksum = checksum;
+ // checksums are pre-computed at construction time
+ const checksum = this.scripts[script].checksum as string;
const scriptExists = (await this.writer.script(
'EXISTS',
@@ -1646,11 +1950,7 @@ export class RedisQueue
);
}
} catch (err) {
- this.emitError(
- 'OnScriptLoad',
- 'script load error',
- err as unknown as Error,
- );
+ this.emitError('OnScriptLoad', 'script load error', err);
}
}
@@ -1660,33 +1960,21 @@ export class RedisQueue
}
}
- // istanbul ignore next
/**
- * This method returns a watcher lock resolver function
+ * Attempts to take over an orphaned watcher lock: if the lock is held
+ * but no watcher connection is actually alive, releases and re-acquires
+ * ownership. Used to resolve a possible watcher deadlock.
*
* @access private
- * @param {(...args: any[]) => void} resolve
- * @param {(...args: any[]) => void} reject
- * @return {() => Promise}
- */
- private watchLockResolver(
- resolve: (...args: any[]) => void,
- reject: (...args: any[]) => void,
- ): () => Promise {
- return async () => {
- try {
- const noWatcher = !(await this.watcherCount());
-
- if ((await this.isLocked()) && noWatcher) {
- await this.unlock();
- await this.ownWatch();
- }
+ * @return {Promise}
+ */
+ private async resolveWatchLock(): Promise {
+ const noWatcher = !(await this.watcherCount());
- resolve();
- } catch (err) {
- reject(err);
- }
- };
+ if ((await this.isLocked()) && noWatcher) {
+ await this.unlock();
+ await this.ownWatch();
+ }
}
/**
@@ -1695,42 +1983,34 @@ export class RedisQueue
*
* @returns {Promise}
*/
- // istanbul ignore next
private async initWatcher(): Promise {
- return new Promise((resolve, reject) => {
- void (async (): Promise => {
- try {
- if (!(await this.watcherCount())) {
- this.verbose('Initializing watcher...');
-
- await this.ownWatch();
-
- if (this.watchOwner && this.watcher) {
- resolve();
- } else {
- // check for possible deadlock to resolve
- setTimeout(
- this.watchLockResolver(
- resolve,
- reject,
- ) as unknown as () => void,
- intrand(1, 50),
- );
- }
- } else {
- resolve();
- }
- } catch (err) {
- this.logger.error(
- `${this.name}: error initializing watcher, pid ${
- process.pid
- } on redis host ${this.redisKey}`,
- err,
- );
+ try {
+ if (await this.watcherCount()) {
+ return;
+ }
- reject(err);
- }
- })();
- });
+ this.verbose('Initializing watcher...');
+
+ await this.ownWatch();
+
+ if (this.watchOwner && this.watcher) {
+ return;
+ }
+
+ // another instance may hold the lock while its watcher died:
+ // wait a small random interval (to avoid a thundering herd) and
+ // try to resolve the possible deadlock
+ await delay(randomInt(1, 50));
+ await this.resolveWatchLock();
+ } catch (err) {
+ this.logger.error(
+ `${this.name}: error initializing watcher, pid ${
+ process.pid
+ } on redis host ${this.redisKey}`,
+ err,
+ );
+
+ throw err;
+ }
}
}
diff --git a/src/UDPWorker.ts b/src/UDPWorker.ts
index 399abb8..da40993 100644
--- a/src/UDPWorker.ts
+++ b/src/UDPWorker.ts
@@ -31,7 +31,7 @@ import {
import { createSocket, Socket } from 'dgram';
import { networkInterfaces } from 'os';
import { UDPClusterManagerOptions } from './UDPClusterManager';
-import { uuid } from './uuid';
+import { randomUUID } from 'crypto';
process.setMaxListeners(10000);
@@ -118,7 +118,7 @@ class UDPWorker {
}
private serverAliveWait(message: Message): void {
- const stamp = uuid();
+ const stamp = randomUUID();
const correction = this.options.aliveTimeoutCorrection ?? 0;
const effectiveTimeout = message.timeout + correction + 1;
const key = UDPWorker.getServerKey(message);
diff --git a/src/copyEventEmitter.ts b/src/helpers/copyEventEmitter.ts
similarity index 94%
rename from src/copyEventEmitter.ts
rename to src/helpers/copyEventEmitter.ts
index 20721b7..987da13 100644
--- a/src/copyEventEmitter.ts
+++ b/src/helpers/copyEventEmitter.ts
@@ -22,7 +22,7 @@
* to get commercial licensing options.
*/
import { EventEmitter } from 'events';
-import * as util from 'util';
+import { inspect } from 'util';
export function copyEventEmitter(
source: EventEmitter & {
@@ -39,7 +39,7 @@ export function copyEventEmitter(
const listeners = source.rawListeners(event) as any[];
for (const originalListener of listeners) {
- if (util.inspect(originalListener).includes('onceWrapper')) {
+ if (inspect(originalListener).includes('onceWrapper')) {
const realListener =
originalListener?.listener || originalListener;
diff --git a/src/index.ts b/src/index.ts
index 2a271bc..4a28626 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -19,30 +19,11 @@
* purchase a proprietary commercial license. Please contact us at
* to get commercial licensing options.
*/
-// noinspection JSUnusedGlobalSymbols
-/**
- * Safely builds full options definition using default options and
- * partial given options
- *
- * @template T
- * @param {T} defaultOptions
- * @param {Partial} givenOptions
- * @return {T}
- */
-// istanbul ignore next
-export function buildOptions(
- defaultOptions: T,
- givenOptions?: Partial,
-): T {
- return Object.assign({}, defaultOptions, givenOptions);
-}
-
+export * from './helpers';
export * from './IMQMode';
export * from './profile';
-export * from './uuid';
export * from './redis';
export * from './IMessageQueue';
export * from './RedisQueue';
export * from './ClusteredRedisQueue';
export * from './UDPClusterManager';
-export * from './copyEventEmitter';
diff --git a/src/profile.ts b/src/profile.ts
index 5b08e54..6f3a284 100644
--- a/src/profile.ts
+++ b/src/profile.ts
@@ -24,7 +24,6 @@
import { ILogger } from '.';
export enum LogLevel {
- // noinspection JSUnusedGlobalSymbols
LOG = 'log',
INFO = 'info',
WARN = 'warn',
@@ -48,7 +47,7 @@ export interface ProfileDecoratorOptions {
}
/**
- * Checks if log level is set to proper value or returns default one
+ * Checks if the log level is set to the proper value or returns the default one
*
* @param {*} level
* @return {LogLevel}
@@ -79,7 +78,7 @@ export type AllowedTimeFormat = 'microseconds' | 'milliseconds' | 'seconds';
*
* @type {boolean}
*/
-export const IMQ_LOG_TIME = !!+(process.env.IMQ_LOG_TIME || 0);
+export const IMQ_LOG_TIME: boolean = !!+(process.env.IMQ_LOG_TIME || 0);
/**
* Environment variable IMQ_LOG_ARGS=[1, 0] - enables/disables profiled
@@ -87,7 +86,7 @@ export const IMQ_LOG_TIME = !!+(process.env.IMQ_LOG_TIME || 0);
*
* @type {boolean}
*/
-export const IMQ_LOG_ARGS = !!+(process.env.IMQ_LOG_ARGS || 0);
+export const IMQ_LOG_ARGS: boolean = !!+(process.env.IMQ_LOG_ARGS || 0);
/**
* Environment variable IMQ_LOG_TIME_FORMAT=[
@@ -167,7 +166,6 @@ export function logDebugInfo({
const time = Number(process.hrtime.bigint() - BigInt(start)) / 1000;
let timeStr: string;
- // istanbul ignore next
switch (IMQ_LOG_TIME_FORMAT) {
case 'milliseconds':
timeStr = (time / 1000).toFixed(3) + ' ms';
@@ -284,7 +282,6 @@ export function profile(
}
const self = this as any;
- /* istanbul ignore next */
const className = self
? typeof self === 'function'
? self.name // static
@@ -303,7 +300,6 @@ export function profile(
start,
};
- /* istanbul ignore next */
if (result && typeof (result as any).then === 'function') {
// async call detected
(result as any)
diff --git a/src/redis.ts b/src/redis.ts
index f7c47cf..708cf93 100644
--- a/src/redis.ts
+++ b/src/redis.ts
@@ -21,7 +21,6 @@
* purchase a proprietary commercial license. Please contact us at
* to get commercial licensing options.
*/
-/* tslint:disable */
import Redis from 'ioredis';
/**
diff --git a/test/helpers/index.ts b/test/helpers/index.ts
index 38af796..941666d 100644
--- a/test/helpers/index.ts
+++ b/test/helpers/index.ts
@@ -21,18 +21,4 @@
* purchase a proprietary commercial license. Please contact us at
* to get commercial licensing options.
*/
-import { ILogger } from '../..';
-
-/**
- * Builds a fresh no-op logger suitable for spying on in unit tests.
- *
- * @return {ILogger}
- */
-export function makeLogger(): ILogger {
- return {
- log: (..._args: any[]) => undefined,
- info: (..._args: any[]) => undefined,
- warn: (..._args: any[]) => undefined,
- error: (..._args: any[]) => undefined,
- } as unknown as ILogger;
-}
+export * from './makeLogger';
diff --git a/src/uuid.ts b/test/helpers/makeLogger.ts
similarity index 66%
rename from src/uuid.ts
rename to test/helpers/makeLogger.ts
index 0006e14..bbbb5bb 100644
--- a/src/uuid.ts
+++ b/test/helpers/makeLogger.ts
@@ -1,7 +1,5 @@
/*!
- * Unified Unique ID Generator
- * Based on solution inspired by Jeff Ward and the comments to it:
- * @see http://stackoverflow.com/a/21963136/1511662
+ * IMQ Unit Test Helpers
*
* I'm Queue Software Project
* Copyright (C) 2025 imqueue.com
@@ -23,17 +21,18 @@
* purchase a proprietary commercial license. Please contact us at
* to get commercial licensing options.
*/
-import { randomUUID } from 'crypto';
+import { ILogger } from '../../src';
/**
- * Generates and returns Unified Unique Identifier (RFC 4122 v4).
+ * Builds a fresh no-op logger suitable for spying on in unit tests.
*
- * Uses the cryptographically strong native generator, which is both faster
- * and collision-safe under high load compared to a Math.random() based
- * implementation.
- *
- * @returns {string}
+ * @return {ILogger}
*/
-export function uuid(): string {
- return randomUUID();
+export function makeLogger(): ILogger {
+ return {
+ log: (..._args: any[]) => undefined,
+ info: (..._args: any[]) => undefined,
+ warn: (..._args: any[]) => undefined,
+ error: (..._args: any[]) => undefined,
+ } as unknown as ILogger;
}
diff --git a/test/mocks/redis.ts b/test/mocks/redis.ts
index 44fee50..738f68e 100644
--- a/test/mocks/redis.ts
+++ b/test/mocks/redis.ts
@@ -41,7 +41,6 @@ export class RedisClientMock extends EventEmitter {
private static __keys: any = {};
private static __scripts: any = {};
private __name: string = '';
- // noinspection JSUnusedGlobalSymbols
public connected: boolean = true;
public status = 'ready';
@@ -57,9 +56,8 @@ export class RedisClientMock extends EventEmitter {
}
}
- // noinspection JSUnusedGlobalSymbols
public end() {}
- // noinspection JSUnusedGlobalSymbols
+
public quit() {
return new Promise(resolve => resolve(undefined));
}
@@ -68,7 +66,6 @@ export class RedisClientMock extends EventEmitter {
return new Promise(resolve => resolve(undefined));
}
- // noinspection JSMethodCanBeStatic
public set(...args: any[]): Promise {
const [key, val] = args;
RedisClientMock.__keys[key] = val;
@@ -76,7 +73,6 @@ export class RedisClientMock extends EventEmitter {
return new Promise(resolve => resolve(1));
}
- // noinspection JSUnusedGlobalSymbols,JSMethodCanBeStatic
public setnx(...args: any[]): number {
const self = RedisClientMock;
const key = args.shift();
@@ -93,7 +89,6 @@ export class RedisClientMock extends EventEmitter {
return result;
}
- // noinspection TypescriptExplicitMemberType,JSMethodCanBeStatic
public lpush(key: string, value: any, cb?: any): number {
const self = RedisClientMock;
if (!self.__queues__[key]) {
@@ -158,7 +153,61 @@ export class RedisClientMock extends EventEmitter {
}
}
- // noinspection JSUnusedGlobalSymbols,JSMethodCanBeStatic
+ public async lmove(
+ from: string,
+ to: string,
+ _fromSide?: string,
+ _toSide?: string,
+ cb?: Function,
+ ): Promise {
+ const qs = RedisClientMock.__queues__;
+ const fromQ = (qs[from] = qs[from] || []);
+
+ if (!fromQ.length) {
+ this.cbExecute(cb, null, null);
+
+ return null;
+ }
+
+ const element = fromQ.shift();
+
+ (qs[to] = qs[to] || []).push(element);
+ this.cbExecute(cb, null, element);
+
+ return element;
+ }
+
+ public async blmove(
+ from: string,
+ to: string,
+ _fromSide?: string,
+ _toSide?: string,
+ timeout?: number,
+ ): Promise {
+ const deadline = Date.now() + (Number(timeout) || 0.1) * 1000;
+
+ while (true) {
+ const qs = RedisClientMock.__queues__;
+ const fromQ = (qs[from] = qs[from] || []);
+
+ if (fromQ.length) {
+ const element = fromQ.shift();
+
+ (qs[to] = qs[to] || []).push(element);
+
+ return element;
+ }
+
+ if (Date.now() >= deadline) {
+ return null;
+ }
+
+ await new Promise(resolve => {
+ this.__rt = setTimeout(resolve, 25);
+ });
+ }
+ }
+
public lrange(
key: string,
start: number,
@@ -172,7 +221,6 @@ export class RedisClientMock extends EventEmitter {
return result;
}
- // noinspection JSUnusedGlobalSymbols,JSMethodCanBeStatic
public scan(...args: any[]): (string | string[])[] {
const cb = args.pop();
const qs = RedisClientMock.__queues__;
@@ -187,7 +235,6 @@ export class RedisClientMock extends EventEmitter {
return result;
}
- // noinspection JSMethodCanBeStatic
public script(...args: any[]): unknown {
const cmd = args.shift();
const scriptOrHash = args.shift();
@@ -215,7 +262,6 @@ export class RedisClientMock extends EventEmitter {
return [0];
}
- // noinspection JSUnusedGlobalSymbols
public client(...args: any[]): string | boolean {
const self = RedisClientMock;
const cmd = args.shift();
@@ -238,7 +284,6 @@ export class RedisClientMock extends EventEmitter {
return true;
}
- // noinspection JSMethodCanBeStatic
public exists(...args: any[]): boolean {
const key = args.shift();
const result = RedisClientMock.__keys[key] !== undefined;
@@ -246,7 +291,6 @@ export class RedisClientMock extends EventEmitter {
return result;
}
- // noinspection JSUnusedGlobalSymbols,JSMethodCanBeStatic
public psubscribe(...args: any[]): Promise {
this.cbExecute(args.pop(), null, 1);
return new Promise(resolve => resolve(1));
@@ -257,13 +301,11 @@ export class RedisClientMock extends EventEmitter {
return new Promise(resolve => resolve(1));
}
- // noinspection JSUnusedGlobalSymbols,JSMethodCanBeStatic
public evalsha(...args: any[]): boolean {
this.cbExecute(args.pop());
return true;
}
- // noinspection JSUnusedGlobalSymbols,JSMethodCanBeStatic
public del(...args: any[]): Promise {
const self = RedisClientMock;
let count = 0;
@@ -281,7 +323,6 @@ export class RedisClientMock extends EventEmitter {
return new Promise(resolve => resolve(count));
}
- // noinspection JSUnusedGlobalSymbols
public zadd(...args: any[]): boolean {
const [key, score, value, cb] = args;
const timeout = score - Date.now();
@@ -293,7 +334,6 @@ export class RedisClientMock extends EventEmitter {
return true;
}
- // noinspection JSUnusedGlobalSymbols
public disconnect(): boolean {
delete RedisClientMock.__clientList[this.__name];
if (this.__rt) {
@@ -303,23 +343,19 @@ export class RedisClientMock extends EventEmitter {
return true;
}
- // noinspection JSUnusedGlobalSymbols,JSMethodCanBeStatic
public publish(channel: string, message: string, cb?: any): number {
this.cbExecute(cb, null, 1);
return 1;
}
- // noinspection JSUnusedGlobalSymbols,JSMethodCanBeStatic
public subscribe(channel: string, cb?: any): void {
this.cbExecute(cb, null, 1);
}
- // noinspection JSUnusedGlobalSymbols,JSMethodCanBeStatic
public unsubscribe(channel?: string, cb?: any): void {
this.cbExecute(cb, null, 1);
}
- // noinspection JSUnusedGlobalSymbols,JSMethodCanBeStatic
public config(): Promise {
return new Promise(resolve => resolve(true));
}
diff --git a/test/unit/ClusteredRedisQueue/AddServer.spec.ts b/test/unit/ClusteredRedisQueue/AddServer.spec.ts
deleted file mode 100644
index 43b02ed..0000000
--- a/test/unit/ClusteredRedisQueue/AddServer.spec.ts
+++ /dev/null
@@ -1,71 +0,0 @@
-/*!
- * ClusteredRedisQueue.addServerWithQueueInitializing tests
- * (default initializeQueue param branch + initializeQueue=false branch)
- */
-import '../../mocks';
-import { describe, it } from 'node:test';
-import * as assert from 'node:assert/strict';
-import { ClusteredRedisQueue } from '../../../src';
-import { ClusterManager } from '../../../src/ClusterManager';
-
-const server = { host: '127.0.0.1', port: 6380 };
-
-describe('ClusteredRedisQueue.addServerWithQueueInitializing() default param', () => {
- it('should use default initializeQueue=true when second param omitted', async () => {
- const cq: any = new ClusteredRedisQueue('CQ-Default', {
- logger: console,
- cluster: [{ host: '127.0.0.1', port: 6379 }],
- });
- // prevent any actual start/subscription side-effects
- (cq as any).state.started = false;
- (cq as any).state.subscription = null;
-
- const server = { host: '192.168.0.1', port: 6380 };
- const initializedSpy = new Promise(resolve => {
- cq['clusterEmitter'].once('initialized', () => resolve());
- });
-
- // Call without the second argument to hit default "true" branch
- (cq as any).addServerWithQueueInitializing(server);
-
- await initializedSpy; // should emit initialized when default is true
-
- // Ensure the server added and queue length updated
- assert.equal(
- (cq as any).servers.some(
- (s: any) => s.host === server.host && s.port === server.port,
- ),
- true,
- );
- assert.equal((cq as any).imqLength, (cq as any).imqs.length);
-
- await cq.destroy();
- });
-});
-
-describe('ClusteredRedisQueue.addServerWithQueueInitializing(false)', () => {
- it('should add server without initializing queue and not emit initialized', async () => {
- const manager = new (ClusterManager as any)();
- const cq: any = new ClusteredRedisQueue('NoInit', {
- clusterManagers: [manager],
- });
-
- let initializedCalled = false;
- (cq as any).clusterEmitter.on('initialized', () => {
- initializedCalled = true;
- });
-
- // call private method via any to cover branch
- (cq as any).addServerWithQueueInitializing(server, false);
-
- // should have server and imq added
- assert.ok(cq.servers.length > 0);
- assert.ok(cq.imqs.length > 0);
- // queueLength updated
- assert.equal(cq.imqLength, cq.imqs.length);
- // initialized not emitted
- assert.equal(initializedCalled, false);
-
- await cq.destroy();
- });
-});
diff --git a/test/unit/ClusteredRedisQueue/ClusteredRedisQueue.spec.ts b/test/unit/ClusteredRedisQueue/ClusteredRedisQueue.spec.ts
deleted file mode 100644
index a9fdbdc..0000000
--- a/test/unit/ClusteredRedisQueue/ClusteredRedisQueue.spec.ts
+++ /dev/null
@@ -1,398 +0,0 @@
-/*!
- * ClusteredRedisQueue Unit Tests (core behavior + EventEmitter proxy methods)
- *
- * I'm Queue Software Project
- * Copyright (C) 2025 imqueue.com
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
- *
- * If you want to use this code in a closed source (commercial) project, you can
- * purchase a proprietary commercial license. Please contact us at
- * to get commercial licensing options.
- */
-import { logger } from '../../mocks';
-import { describe, it, afterEach, mock, Mock } from 'node:test';
-import * as assert from 'node:assert/strict';
-import { ClusteredRedisQueue } from '../../../src';
-import { ClusterManager } from '../../../src/ClusterManager';
-
-process.setMaxListeners(100);
-
-function assertDeepInclude(actual: any, subset: any): void {
- for (const key of Object.keys(subset)) {
- assert.deepEqual(actual[key], subset[key]);
- }
-}
-
-const clusterConfig = {
- logger,
- cluster: [
- {
- host: '127.0.0.1',
- port: 7777,
- },
- {
- host: '127.0.0.1',
- port: 8888,
- },
- ],
-};
-
-describe('ClusteredRedisQueue', () => {
- afterEach(() => {
- mock.restoreAll();
- });
-
- it('should be a class', () => {
- assert.equal(typeof ClusteredRedisQueue, 'function');
- });
-
- it('should implement IMessageQueue interface', () => {
- assert.equal(typeof ClusteredRedisQueue.prototype.start, 'function');
- assert.equal(typeof ClusteredRedisQueue.prototype.stop, 'function');
- assert.equal(typeof ClusteredRedisQueue.prototype.send, 'function');
- assert.equal(typeof ClusteredRedisQueue.prototype.destroy, 'function');
- });
-
- describe('constructor()', () => {
- it('should throw with improper options passed', () => {
- assert.throws(
- () => new ClusteredRedisQueue('TestClusteredQueue'),
- TypeError,
- );
- });
-
- it('should not throw if proper options passed', () => {
- assert.doesNotThrow(
- () =>
- new ClusteredRedisQueue(
- 'TestClusteredQueue',
- clusterConfig,
- ),
- );
- });
-
- it('should initialize cluster manager', () => {
- const clusterManager = new (ClusterManager as any)();
-
- const init: Mock = mock.method(clusterManager, 'init');
-
- new ClusteredRedisQueue('TestClusteredQueue', {
- clusterManagers: [clusterManager],
- });
-
- assert.equal(init.mock.callCount() > 0, true);
- });
- });
-
- describe('start()', () => {
- it('should start each nested imq', async () => {
- const cq: any = new ClusteredRedisQueue(
- 'TestClusteredQueue',
- clusterConfig,
- );
-
- cq.imqs.forEach((imq: any) => {
- mock.method(imq, 'start');
- });
-
- await cq.start();
-
- cq.imqs.forEach((imq: any) => {
- assert.equal(imq.start.mock.callCount() > 0, true);
- });
-
- await cq.destroy();
- });
- });
-
- describe('stop()', () => {
- it('should stop each nested imq', async () => {
- const cq: any = new ClusteredRedisQueue(
- 'TestClusteredQueue',
- clusterConfig,
- );
-
- cq.imqs.forEach((imq: any) => {
- mock.method(imq, 'stop');
- });
-
- await cq.stop();
-
- cq.imqs.forEach((imq: any) => {
- assert.equal(imq.stop.mock.callCount() > 0, true);
- });
-
- await cq.destroy();
- });
- });
-
- describe('send()', () => {
- it(
- 'should balance send requests round-robin manner across nested ' +
- 'queues',
- async () => {
- const cq: any = new ClusteredRedisQueue(
- 'TestClusteredQueue',
- clusterConfig,
- );
-
- cq.imqs.forEach((imq: any) => {
- mock.method(imq, 'send');
- });
-
- await cq.send('TestClusteredQueue', { hello: 'world' });
-
- assert.equal(cq.imqs[0].send.mock.callCount(), 1);
- assert.equal(cq.imqs[1].send.mock.callCount() > 0, false);
-
- await cq.send('TestClusteredQueue', { hello: 'world' });
-
- assert.equal(cq.imqs[0].send.mock.callCount(), 1);
- assert.equal(cq.imqs[1].send.mock.callCount(), 1);
-
- await cq.send('TestClusteredQueue', { hello: 'world' });
-
- assert.equal(cq.imqs[0].send.mock.callCount(), 2);
- assert.equal(cq.imqs[1].send.mock.callCount(), 1);
-
- await cq.destroy();
- },
- );
-
- it('should send message after queue was initialized', () => {
- return new Promise(resolve => {
- const clusterManager = new (ClusterManager as any)();
- const cqOne: any = new ClusteredRedisQueue(
- 'TestClusteredQueueOne',
- {
- clusterManagers: [clusterManager],
- logger,
- },
- );
- const cqTwo: any = new ClusteredRedisQueue(
- 'TestClusteredQueueTwo',
- {
- clusterManagers: [clusterManager],
- logger,
- },
- );
- const message = { hello: 'world' };
-
- cqOne.start();
- cqTwo.start();
-
- cqTwo.on('message', () => {
- cqOne.destroy();
- cqTwo.destroy();
-
- resolve();
- });
-
- cqOne.send('TestClusteredQueueTwo', message);
- cqTwo.addServer(clusterConfig.cluster[0]);
- cqOne.addServer(clusterConfig.cluster[0]);
- });
- });
- });
-
- describe('destroy()', () => {
- it('should destroy each nested imq', async () => {
- const cq: any = new ClusteredRedisQueue(
- 'TestClusteredQueue',
- clusterConfig,
- );
-
- cq.imqs.forEach((imq: any) => {
- mock.method(imq, 'destroy');
- });
-
- await cq.destroy();
-
- cq.imqs.forEach((imq: any) => {
- assert.equal(imq.destroy.mock.callCount() > 0, true);
- });
- });
- });
-
- describe('clear()', () => {
- it('should clear each nested imq', async () => {
- const cq: any = new ClusteredRedisQueue(
- 'TestClusteredQueue',
- clusterConfig,
- );
-
- cq.imqs.forEach((imq: any) => {
- mock.method(imq, 'clear');
- });
-
- await cq.clear();
-
- cq.imqs.forEach((imq: any) => {
- assert.equal(imq.clear.mock.callCount() > 0, true);
- });
-
- await cq.destroy();
- });
- });
-
- describe('subscribe()', () => {
- it('should subscribe after queue initialization', () => {
- const clusterManager = new (ClusterManager as any)();
- const cq: any = new ClusteredRedisQueue('TestClusteredQueue', {
- clusterManagers: [clusterManager],
- logger,
- });
- const channel = 'TestChannel';
-
- cq.subscribe(channel, () => {});
- cq.addServer(clusterConfig.cluster[0]);
-
- assert.equal(cq.imqs[0].subscriptionName, channel);
- });
- });
-
- describe('addServer()', () => {
- it('should add cluster server', () => {
- const clusterManager = new (ClusterManager as any)();
- const cq: any = new ClusteredRedisQueue('TestClusteredQueue', {
- clusterManagers: [clusterManager],
- });
-
- cq.addServer(clusterConfig.cluster[0]);
-
- assert.equal(cq.servers.length, 1);
- });
-
- it(
- 'should call adding cluster server method through the' +
- ' Cluster Manager',
- () => {
- const clusterManager = new (ClusterManager as any)();
- const cq: any = new ClusteredRedisQueue('TestClusteredQueue', {
- clusterManagers: [clusterManager],
- });
-
- for (const server of clusterManager.clusters) {
- server.add(clusterConfig.cluster[0]);
- }
-
- assert.equal(cq.servers.length, 1);
- },
- );
- });
-
- describe('removeServer()', () => {
- it('should remove cluster server', () => {
- const clusterManager = new (ClusterManager as any)();
- const cq: any = new ClusteredRedisQueue('TestClusteredQueue', {
- clusterManagers: [clusterManager],
- });
-
- cq.addServer(clusterConfig.cluster[0]);
- cq.removeServer(clusterConfig.cluster[0]);
-
- assert.equal(cq.servers.length, 0);
- });
-
- it(
- 'should call removing cluster server method through the' +
- ' Cluster Manager',
- () => {
- const clusterManager = new (ClusterManager as any)();
- const cq: any = new ClusteredRedisQueue('TestClusteredQueue', {
- clusterManagers: [clusterManager],
- });
-
- for (const server of clusterManager.clusters) {
- server.remove(clusterConfig.cluster[0]);
- }
-
- assert.equal(cq.servers.length, 0);
- },
- );
- });
-
- describe('findServer()', () => {
- it('should find cluster server', () => {
- const clusterManager = new (ClusterManager as any)();
- const cq: any = new ClusteredRedisQueue('TestClusteredQueue', {
- clusterManagers: [clusterManager],
- });
-
- cq.addServer(clusterConfig.cluster[0]);
-
- const server = cq.findServer(clusterConfig.cluster[0]);
-
- assertDeepInclude(server, clusterConfig.cluster[0]);
- });
-
- it(
- 'should call find cluster server method through the' +
- ' Cluster Manager',
- () => {
- const clusterManager = new (ClusterManager as any)();
- const cq: any = new ClusteredRedisQueue('TestClusteredQueue', {
- clusterManagers: [clusterManager],
- });
-
- cq.addServer(clusterConfig.cluster[0]);
-
- for (const cluster of clusterManager.clusters) {
- const server = cluster.find(clusterConfig.cluster[0]);
-
- assertDeepInclude(server, clusterConfig.cluster[0]);
- }
- },
- );
- });
-});
-
-describe('ClusteredRedisQueue - EventEmitter proxy methods', () => {
- const clusterConfig = {
- cluster: [{ host: '127.0.0.1', port: 6379 }],
- };
-
- it('should cover rawListeners/getMaxListeners/eventNames/listenerCount/emit', async () => {
- const clusterManager = new (ClusterManager as any)();
- const cq: any = new ClusteredRedisQueue('ProxyQueue', {
- clusterManagers: [clusterManager],
- });
-
- // add underlying server and listener
- cq.addServer(clusterConfig.cluster[0]);
- const handler: Mock