Compare commits

..

14 Commits

30 changed files with 784 additions and 8674 deletions

View File

@ -1,11 +1,11 @@
# FarmControl WebSocket Service
A WebSocket microservice for FarmControl that handles real-time communication and distributed locking using etcd.
A WebSocket microservice for FarmControl that handles real-time communication and distributed locking.
## Features
- Real-time WebSocket communication
- Distributed locking using etcd
- Distributed locking
- Keycloak authentication integration
- MongoDB integration for user management
- Event streaming and notifications
@ -13,8 +13,8 @@ A WebSocket microservice for FarmControl that handles real-time communication an
## Prerequisites
- Node.js (v16 or higher)
- etcd server
- MongoDB server
- Redis server
- Keycloak server (for authentication)
## Installation
@ -29,19 +29,6 @@ A WebSocket microservice for FarmControl that handles real-time communication an
The application uses `config.json` for configuration. Update the following sections:
### Etcd Configuration
```json
{
"database": {
"etcd": {
"host": "localhost",
"port": 2379
}
}
}
```
### MongoDB Configuration
```json
@ -84,59 +71,6 @@ npm run dev
npm start
```
## Etcd Setup
### Installation
#### Using Docker
```bash
docker run -d --name etcd \
-p 2379:2379 \
-p 2380:2380 \
quay.io/coreos/etcd:v3.5.0 \
/usr/local/bin/etcd \
--advertise-client-urls http://0.0.0.0:2379 \
--listen-client-urls http://0.0.0.0:2379
```
#### Using Homebrew (macOS)
```bash
brew install etcd
etcd
```
#### Using apt (Ubuntu/Debian)
```bash
sudo apt-get install etcd
sudo systemctl start etcd
```
### Verification
Test that etcd is running:
```bash
curl http://localhost:2379/version
```
## Migration from Redis
This application was migrated from Redis to etcd. The main changes include:
1. **Stream-like functionality**: Redis streams are replaced with etcd key-value pairs using a prefix pattern
2. **Hash-like functionality**: Redis hashes are replaced with etcd key-value pairs using a prefix pattern
3. **Pub/Sub**: Redis pub/sub is replaced with etcd watchers
4. **Connection management**: Simplified connection handling with automatic reconnection
### Key Differences
- **Data structure**: etcd uses a flat key-value store, so we simulate Redis data structures using key prefixes
- **Streams**: Instead of Redis streams, we use etcd keys with timestamps and random suffixes
- **Watching**: etcd watchers provide real-time notifications for key changes
- **Transactions**: etcd supports atomic operations for distributed locking
## API Endpoints
@ -153,7 +87,7 @@ The service exposes WebSocket endpoints for:
```
src/
├── auth/ # Authentication logic
├── database/ # Database connections (etcd, mongo)
├── database/ # Database connections (redis, mongo)
├── lock/ # Distributed locking
├── notification/ # Notification management
├── socket/ # WebSocket handling
@ -162,7 +96,7 @@ src/
### Adding New Features
1. **Database operations**: Use the `etcdServer` instance for etcd operations
1. **Database operations**: Use the `redisServer` instance for Redis operations
2. **WebSocket events**: Extend the `SocketUser` class
3. **Authentication**: Extend the `KeycloakAuth` class
@ -170,7 +104,7 @@ src/
### Common Issues
1. **Etcd connection failed**: Ensure etcd is running on the configured host and port
1. **Redis connection failed**: Ensure Redis is running on the configured host and port
2. **Authentication errors**: Verify Keycloak configuration and credentials
3. **MongoDB connection issues**: Check MongoDB server status and connection string

View File

@ -15,10 +15,6 @@
"requiredRoles": []
},
"database": {
"etcd": {
"host": "localhost",
"port": 2379
},
"mongo": {
"url": "mongodb://127.0.0.1:27017/farmcontrol"
},
@ -42,10 +38,6 @@
"requiredRoles": []
},
"database": {
"etcd": {
"host": "localhost",
"port": 2379
},
"mongo": {
"url": "mongodb://farmcontrol.tombutcher.local:27017/farmcontrol"
}

7721
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -25,7 +25,6 @@
"dayjs": "^1.11.19",
"dotenv": "^17.2.3",
"ejs": "^3.1.10",
"etcd3": "^1.1.2",
"express": "^5.1.0",
"he": "^1.2.0",
"jsonwebtoken": "^9.0.2",
@ -38,7 +37,7 @@
"object-hash": "^3.0.0",
"posthtml": "^0.16.7",
"puppeteer": "^24.31.0",
"redis": "^4.6.14",
"redis": "^5.10.0",
"socket.io": "^4.8.1",
"socket.io-adapter-mongo": "^2.0.5",
"socketio-jwt": "^4.6.2"

View File

@ -133,7 +133,9 @@ export class CodeAuth {
// Verify a code with the database
async verifyCode(id, authCode) {
try {
logger.trace('Verifying code:', { id, authCode });
const host = await getObject({ model: hostModel, id, cached: true });
logger.trace('Host retrieved:', host);
if (host == undefined) {
const error = 'Host not found.';
logger.warn(error, 'Host:', id);
@ -154,6 +156,7 @@ export class CodeAuth {
logger.warn(error, 'Host:', id);
return { valid: false, error: error };
}
logger.trace('Code verification successful:', host);
return { valid: true, host: host };
} catch (error) {
logger.error('Code verification error:', error.message);

View File

@ -5,12 +5,14 @@ import {
editAuditLog,
distributeUpdate,
newAuditLog,
distributeNew
distributeNew,
distributeStats
} from './utils.js';
import log4js from 'log4js';
import { loadConfig } from '../config.js';
import { jsonToCacheKey } from '../utils.js';
import { getQueryToCacheKey } from '../utils.js';
import { redisServer } from './redis.js';
import { auditLogModel } from './schemas/management/auditlog.schema.js';
const config = loadConfig();
@ -23,27 +25,19 @@ cacheLogger.level = config.server.logLevel;
const CACHE_TTL_SECONDS = config.database?.redis?.ttlSeconds || 5;
export const retrieveObjectCache = async ({ model, id, populate = [] }) => {
const cacheKeyObject = {
model: model.modelName,
id: id?.toString()
};
const cacheKey = getQueryToCacheKey({ model: model.modelName, id, populate });
const cacheKey = jsonToCacheKey(cacheKeyObject);
cacheLogger.trace('Retrieving:', cacheKeyObject);
cacheLogger.trace('Retrieving:', cacheKey);
try {
const cachedObject = await redisServer.getKey(cacheKey);
if (cachedObject == null) {
cacheLogger.trace('Miss:', cacheKeyObject);
cacheLogger.trace('Miss:', cacheKey);
return undefined;
}
cacheLogger.trace('Hit:', {
model: model.modelName,
id: cacheKeyObject.id
});
cacheLogger.trace('Hit:', cacheKey);
return cachedObject;
} catch (err) {
@ -52,65 +46,43 @@ export const retrieveObjectCache = async ({ model, id, populate = [] }) => {
}
};
export const retrieveListCache = async ({
export const updateObjectCache = async ({
model,
populate = [],
filter = {},
sort = '',
order = 'ascend',
project = {}
id,
object,
populate = []
}) => {
const cacheKeyObject = {
model: model.modelName,
populate,
filter,
sort,
project,
order
};
const cacheKeyFilter = `${model.modelName}:${id?.toString()}*`;
const cacheKey = getQueryToCacheKey({ model: model.modelName, id, populate });
const cacheKey = jsonToCacheKey(cacheKeyObject);
cacheLogger.trace('Retrieving:', cacheKeyObject);
cacheLogger.trace('Updating:', cacheKeyFilter);
try {
const cachedList = await redisServer.getKey(cacheKey);
// Get all keys matching the filter pattern
const matchingKeys = await redisServer.getKeysByPattern(cacheKeyFilter);
if (cachedList != null) {
cacheLogger.trace('Hit:', {
...cacheKeyObject,
length: cachedList.length
});
return cachedList;
logger.trace('Matching keys:', matchingKeys);
// Merge the object with each cached object and update
const mergedObjects = [];
for (const key of matchingKeys) {
logger.trace('Updating object cache:', key);
const cachedObject = (await redisServer.getKey(key)) || {};
const mergedObject = _.merge(cachedObject, object);
await redisServer.setKey(key, mergedObject, CACHE_TTL_SECONDS);
mergedObjects.push(mergedObject);
}
cacheLogger.trace('Miss:', {
model: model.modelName
});
return undefined;
} catch (err) {
cacheLogger.error('Error retrieving list from Redis cache:', err);
return undefined;
}
};
export const updateObjectCache = async ({ model, id, object }) => {
const cacheKeyObject = {
model: model.modelName,
id: id?.toString()
};
const cacheKey = jsonToCacheKey(cacheKeyObject);
cacheLogger.trace('Updating:', cacheKeyObject);
try {
const cachedObject = (await redisServer.getKey(cacheKey)) || {};
const mergedObject = _.merge(cachedObject, object);
const cacheObject = (await redisServer.getKey(cacheKey)) || {};
const mergedObject = _.merge(cacheObject, object);
await redisServer.setKey(cacheKey, mergedObject, CACHE_TTL_SECONDS);
cacheLogger.trace('Updated:', { ...cacheKeyObject });
cacheLogger.trace('Updated:', {
filter: cacheKeyFilter,
keysUpdated: matchingKeys.length
});
// Return the merged object
return mergedObject;
} catch (err) {
cacheLogger.error('Error updating object in Redis cache:', err);
@ -120,69 +92,27 @@ export const updateObjectCache = async ({ model, id, object }) => {
};
export const deleteObjectCache = async ({ model, id }) => {
const cacheKeyObject = {
model: model.modelName,
id: id?.toString()
};
const cacheKeyFilter = `${model.modelName}:${id?.toString()}*`;
cacheLogger.trace('Deleting:', {
...cacheKeyObject
});
cacheLogger.trace('Deleting:', cacheKeyFilter);
try {
// Note: we currently delete the non-populated key; populated variants will expire via TTL.
const cacheKey = jsonToCacheKey({ ...cacheKeyObject, populate: [] });
await redisServer.deleteKey(cacheKey);
// Get all keys matching the filter pattern and delete them
const matchingKeys = await redisServer.getKeysByPattern(cacheKeyFilter);
for (const cacheKey of matchingKeys) {
await redisServer.deleteKey(cacheKey);
}
cacheLogger.trace('Deleted:', {
...cacheKeyObject
filter: cacheKeyFilter,
keysDeleted: matchingKeys.length
});
} catch (err) {
cacheLogger.error('Error deleting object from Redis cache:', err);
}
};
export const updateListCache = ({
model,
objects,
populate = [],
filter = {},
sort = '',
order = 'ascend',
project = {}
}) => {
const cacheKeyObject = {
model: model.modelName,
populate,
filter,
sort,
project,
order
};
cacheLogger.trace('Updating:', {
...cacheKeyObject,
length: objects.length
});
const cacheKey = jsonToCacheKey(cacheKeyObject);
return (async () => {
try {
await redisServer.setKey(cacheKey, objects, CACHE_TTL_SECONDS);
cacheLogger.trace('Updated:', {
...cacheKeyObject,
length: objects.length
});
} catch (err) {
cacheLogger.error('Error updating list in Redis cache:', err);
}
return objects;
})();
};
// Reusable function to list objects with aggregation, filtering, search, sorting, and pagination
export const listObjects = async ({
model,
@ -204,20 +134,6 @@ export const listObjects = async ({
cached
});
if (cached == true) {
const objectsCache = await retrieveListCache({
model,
populate,
filter,
sort,
order,
project
});
if (objectsCache != undefined) {
return objectsCache;
}
}
// Fix: descend should be -1, ascend should be 1
const sortOrder = order === 'descend' ? -1 : 1;
@ -261,16 +177,6 @@ export const listObjects = async ({
const finalResult = expandObjectIds(queryResult);
updateListCache({
model,
objects: finalResult,
populate,
filter,
sort,
order,
project
});
logger.trace('Retreived from database:', {
model,
populate,
@ -338,7 +244,9 @@ export const getObject = async ({
populate
});
updateObjectCache({
logger.trace(finalResult);
await updateObjectCache({
model: model,
id: finalResult._id.toString(),
populate,
@ -353,6 +261,152 @@ export const getObject = async ({
}
};
// Utility to run one or many rollup aggregations in a single query via $facet.
export const aggregateRollups = async ({
model,
baseFilter = {},
rollupConfigs = []
}) => {
if (!rollupConfigs.length) {
return {};
}
const facetStage = rollupConfigs.reduce((facets, definition, index) => {
const key = definition.name || `rollup${index}`;
const matchStage = {
$match: { ...baseFilter, ...(definition.filter || {}) }
};
const groupStage = { $group: { _id: null } };
(definition.rollups || []).forEach(rollup => {
switch (rollup.operation) {
case 'sum':
groupStage.$group[rollup.name] = { $sum: `$${rollup.property}` };
break;
case 'count':
groupStage.$group[rollup.name] = { $sum: 1 };
break;
case 'avg':
groupStage.$group[rollup.name] = { $avg: `$${rollup.property}` };
break;
default:
throw new Error(`Unsupported rollup operation: ${rollup.operation}`);
}
});
facets[key] = [matchStage, groupStage];
return facets;
}, {});
const [results] = await model.aggregate([{ $facet: facetStage }]);
return rollupConfigs.reduce((acc, definition, index) => {
const key = definition.name || `rollup${index}`;
const rawResult = results?.[key]?.[0] || {};
// Transform the result to nest rollup values under operation type
const transformedResult = {};
(definition.rollups || []).forEach(rollup => {
const value = rawResult[rollup.name] || 0;
// If there's only one rollup and its name matches the key, flatten the structure
if (definition.rollups.length === 1 && rollup.name === key) {
transformedResult[rollup.operation] = value;
} else {
transformedResult[rollup.name] = { [rollup.operation]: value };
}
});
acc[key] = transformedResult;
return acc;
}, {});
};
// Reusable function to aggregate rollups over history using audit logs
export const aggregateRollupsHistory = async ({
model,
baseFilter = {},
rollupConfigs = [],
startDate,
endDate
}) => {
if (!rollupConfigs.length) {
return {};
}
// Helper to map filter keys to audit log structure
const mapFilterToAudit = filter => {
if (Array.isArray(filter)) {
return filter.map(mapFilterToAudit);
}
if (_.isPlainObject(filter)) {
const newFilter = {};
for (const key in filter) {
if (['$or', '$and', '$nor', '$in', '$nin'].includes(key)) {
newFilter[key] = mapFilterToAudit(filter[key]);
} else if (key.startsWith('$')) {
newFilter[key] = mapFilterToAudit(filter[key]);
} else if (
[
'operation',
'parent',
'parentType',
'owner',
'ownerType',
'createdAt',
'updatedAt',
'_id',
'_reference',
'changes'
].includes(key)
) {
newFilter[key] = mapFilterToAudit(filter[key]);
} else {
newFilter[`changes.new.${key}`] = mapFilterToAudit(filter[key]);
}
}
return newFilter;
}
return filter;
};
const matchQuery = {
parentType: model.modelName,
...mapFilterToAudit(baseFilter)
};
if (startDate || endDate) {
matchQuery.createdAt = {};
if (startDate) matchQuery.createdAt.$gte = new Date(startDate);
if (endDate) matchQuery.createdAt.$lte = new Date(endDate);
if (Object.keys(matchQuery.createdAt).length === 0)
delete matchQuery.createdAt;
}
const mappedRollupConfigs = rollupConfigs.map(config => ({
...config,
filter: config.filter ? mapFilterToAudit(config.filter) : undefined,
rollups: (config.rollups || []).map(rollup => ({
...rollup,
property: `changes.new.${rollup.property}`
}))
}));
return await aggregateRollups({
model: auditLogModel,
baseFilter: matchQuery,
rollupConfigs: mappedRollupConfigs
});
};
export const getModelStats = async ({ model }) => {
if (!model.stats) {
return { error: 'Model does not have a stats method.', code: 500 };
}
return await model.stats();
};
// Reusable function to edit an object by ID, with audit logging and distribution
export const editObject = async ({
model,
@ -405,13 +459,24 @@ export const editObject = async ({
// Distribute update
await distributeUpdate(updateData, id, parentType);
updateObjectCache({
await updateObjectCache({
model: model,
id: id.toString(),
object: { ...previousExpandedObject, ...updateData },
populate
});
if (model.recalculate) {
logger.debug(`Recalculating ${model.modelName}`);
await model.recalculate(newExpandedObject, owner, ownerType);
}
if (model.stats) {
logger.debug(`Getting stats for ${model.modelName}`);
const statsData = await model.stats(newExpandedObject);
await distributeStats(statsData, parentType);
}
return { ...previousExpandedObject, ...updateData };
} catch (error) {
logger.error('editObject error:', error);
@ -441,10 +506,11 @@ export const newObject = async ({
await distributeNew(created._id, parentType);
updateObjectCache({
await updateObjectCache({
model: model,
id: created._id.toString(),
object: { _id: created._id, ...newData }
object: { _id: created._id, ...newData },
populate: []
});
return created;

View File

@ -1,386 +0,0 @@
import { Etcd3 } from 'etcd3';
import log4js from 'log4js';
import { loadConfig } from '../config.js';
const config = loadConfig();
const logger = log4js.getLogger('Etcd');
logger.level = config.server.logLevel;
class EtcdServer {
constructor() {
this.client = null;
this.prefixPutWatchers = new Map(); // prefix → { watcher, callbacks }
this.prefixDeleteWatchers = new Map(); // prefix → { watcher, callbacks }
this.keyPutWatchers = new Map(); // key → { watcher, callbacks }
this.keyDeleteWatchers = new Map(); // key → { watcher, callbacks }
const etcdConfig = config.database?.etcd || config.database; // fallback for production config
const host = etcdConfig.host || 'localhost';
const port = etcdConfig.port || 2379;
this.hosts = [`${host}:${port}`];
logger.trace(`EtcdServer: hosts set to ${JSON.stringify(this.hosts)}`);
}
async connect() {
if (!this.client) {
logger.info('Connecting to Etcd...');
logger.trace(
`Creating Etcd client with hosts ${JSON.stringify(this.hosts)}`
);
this.client = new Etcd3({
hosts: this.hosts
});
// Test connection
try {
await this.client.get('test-connection').string();
logger.trace('Etcd client connected successfully.');
} catch (error) {
if (error.code === 'NOT_FOUND') {
logger.trace(
'Etcd client connected successfully (test key not found as expected).'
);
} else {
throw error;
}
}
} else {
logger.trace('Etcd client already exists, skipping connection.');
}
return this.client;
}
async getClient() {
if (!this.client) {
logger.trace('No client found, calling connect().');
await this.connect();
}
return this.client;
}
async setKey(key, value) {
const client = await this.getClient();
const stringValue =
typeof value === 'string' ? value : JSON.stringify(value);
await client.put(key).value(stringValue);
logger.trace(`Set key: ${key}, value: ${stringValue}`);
return true;
}
async getKey(key) {
const client = await this.getClient();
try {
const value = await client.get(key).string();
logger.trace(`Retrieved key: ${key}, value: ${value}`);
// Try to parse as JSON, fallback to string
try {
return JSON.parse(value);
} catch {
return value;
}
} catch (error) {
if (error.code === 'NOT_FOUND') {
logger.trace(`Key not found: ${key}`);
return null;
}
throw error;
}
}
async deleteKey(key) {
const client = await this.getClient();
try {
await client.delete().key(key);
logger.trace(`Deleted key: ${key}`);
return { success: true };
} catch (error) {
if (error.code === 'NOT_FOUND') {
const error = `Key not found for deletion.`;
console.log(error, 'Key:', key);
return { error: error };
}
throw error;
}
}
async onPrefixPutEvent(prefix, owner, callback) {
const client = await this.getClient();
const watcherKey = prefix;
if (this.prefixPutWatchers.has(watcherKey)) {
this.prefixPutWatchers.get(watcherKey).callbacks.set(owner, callback);
logger.trace(`Added put callback for owner=${owner} on prefix=${prefix}`);
return;
}
logger.trace(`Creating new put watcher for prefix: ${prefix}`);
const watcher = await client.watch().prefix(prefix).create();
const callbacks = new Map();
callbacks.set(owner, callback);
watcher.on('put', (kv, previous) => {
logger.trace(`Prefix put event detected: ${prefix}, key: ${kv.key}`);
const valueStr = kv.value.toString();
let parsedValue;
try {
parsedValue = JSON.parse(valueStr);
} catch {
parsedValue = valueStr;
}
for (const [ownerId, cb] of callbacks) {
try {
cb(kv.key.toString(), parsedValue, kv, previous);
} catch (err) {
logger.error(
`Error in onPrefixPutEvent callback for owner=${ownerId}, prefix=${prefix}:`,
err
);
}
}
});
this.prefixPutWatchers.set(watcherKey, { watcher, callbacks });
return { success: true };
}
async onPrefixDeleteEvent(prefix, owner, callback) {
const client = await this.getClient();
const watcherKey = prefix;
if (this.prefixDeleteWatchers.has(watcherKey)) {
this.prefixDeleteWatchers.get(watcherKey).callbacks.set(owner, callback);
logger.trace(
`Added delete callback for owner=${owner} on prefix=${prefix}`
);
return;
}
logger.trace(`Creating new delete watcher for prefix: ${prefix}`);
const watcher = await client.watch().prefix(prefix).create();
const callbacks = new Map();
callbacks.set(owner, callback);
watcher.on('delete', (kv, previous) => {
logger.trace(`Prefix delete event detected: ${prefix}, key: ${kv.key}`);
for (const [ownerId, cb] of callbacks) {
try {
cb(kv.key.toString(), kv, previous);
} catch (err) {
logger.error(
`Error in onPrefixDeleteEvent callback for owner=${ownerId}, prefix=${prefix}:`,
err
);
}
}
});
this.prefixDeleteWatchers.set(watcherKey, { watcher, callbacks });
return { success: true };
}
async onKeyPutEvent(key, owner, callback) {
const client = await this.getClient();
const watcherKey = key;
if (this.keyPutWatchers.has(watcherKey)) {
this.keyPutWatchers.get(watcherKey).callbacks.set(owner, callback);
logger.trace(`Added put callback for owner: ${owner}, on key: ${key}`);
return;
}
logger.trace(`Creating new put watcher for key: ${key}`);
const watcher = await client.watch().key(key).create();
const callbacks = new Map();
callbacks.set(owner, callback);
watcher.on('put', (kv, previous) => {
logger.trace(`Key put event detected: ${key}, key: ${kv.key}`);
const valueStr = kv.value.toString();
let parsedValue;
try {
parsedValue = JSON.parse(valueStr);
} catch {
parsedValue = valueStr;
}
for (const [ownerId, cb] of callbacks) {
try {
cb(kv.key.toString(), parsedValue, kv, previous);
} catch (err) {
logger.error(
`Error in onKeyPutEvent callback for owner: ${ownerId}, key: ${key}:`,
err
);
}
}
});
this.keyPutWatchers.set(watcherKey, { watcher, callbacks });
return { success: true };
}
async onKeyDeleteEvent(key, owner, callback) {
const client = await this.getClient();
const watcherKey = key;
if (this.keyDeleteWatchers.has(watcherKey)) {
this.keyDeleteWatchers.get(watcherKey).callbacks.set(owner, callback);
logger.trace(`Added delete callback for owner: ${owner} on key: ${key}`);
return;
}
logger.trace(`Creating new delete watcher for key: ${key}`);
const watcher = await client.watch().key(key).create();
const callbacks = new Map();
callbacks.set(owner, callback);
watcher.on('delete', (kv, previous) => {
logger.trace(`Key delete event detected: ${key}, key: ${kv.key}`);
for (const [ownerId, cb] of callbacks) {
try {
cb(kv.key.toString(), kv, previous);
} catch (err) {
logger.error(
`Error in onKeyDeleteEvent callback for owner=${ownerId}, key=${key}:`,
err
);
}
}
});
this.keyDeleteWatchers.set(watcherKey, { watcher, callbacks });
}
async onKeyEvent(key, callback) {
const client = await this.getClient();
logger.trace(`Setting up watcher for key events: ${key}`);
client
.watch()
.key(key)
.create()
.then(watcher => {
// Handle put events
watcher.on('put', (kv, previous) => {
logger.trace(`Key put event detected: ${key}`);
try {
const value = kv.value.toString();
let parsedValue;
try {
parsedValue = JSON.parse(value);
} catch {
parsedValue = value;
}
callback(key, parsedValue, kv, previous);
} catch (error) {
logger.error(
`Error in onKeyEvent put callback for key ${key}:`,
error
);
}
});
// Handle delete events
watcher.on('delete', (kv, previous) => {
logger.trace(`Key delete event detected: ${key}`);
try {
callback(key, null, kv, previous);
} catch (error) {
logger.error(
`Error in onKeyEvent delete callback for key ${key}:`,
error
);
}
});
// Store watcher with a unique key
const watcherKey = `event:key:${key}`;
this.watchers.set(watcherKey, watcher);
});
}
async removePrefixWatcher(prefix, owner, type = 'put') {
const store =
type === 'put' ? this.prefixPutWatchers : this.prefixDeleteWatchers;
const entry = store.get(prefix);
if (!entry) {
logger.trace(`Watcher not found for prefix: ${prefix}, type: ${type}`);
return false;
}
if (entry.callbacks.delete(owner)) {
logger.trace(
`Removed ${type} callback for owner: ${owner} on prefix: ${prefix}`
);
} else {
logger.trace(
`No ${type} callback found for owner: ${owner} on prefix: ${prefix}`
);
}
if (entry.callbacks.size === 0) {
logger.trace(`No callbacks left, stopping ${type} watcher for ${prefix}`);
entry.watcher.removeAllListeners();
await entry.watcher.cancel();
store.delete(prefix);
}
return true;
}
async removeKeyWatcher(key, owner, type = 'put') {
const store = type === 'put' ? this.keyPutWatchers : this.keyDeleteWatchers;
const entry = store.get(key);
if (!entry) {
logger.trace(`Watcher not found for key: ${key}, type: ${type}`);
return false;
}
if (entry.callbacks.delete(owner)) {
logger.trace(
`Removed ${type} callback for owner: ${owner} on key: ${key}`
);
} else {
logger.trace(
`No ${type} callback found for owner: ${owner} on key: ${key}`
);
}
if (entry.callbacks.size === 0) {
logger.trace(`No callbacks left, stopping ${type} watcher for ${key}`);
entry.watcher.removeAllListeners();
await entry.watcher.cancel();
store.delete(key);
}
return true;
}
async disconnect() {
logger.info('Disconnecting from Etcd...');
// Stop all watchers
for (const [key, watcher] of this.watchers) {
logger.trace(`Stopping watcher: ${key}`);
watcher.removeAllListeners();
await watcher.close();
}
this.watchers.clear();
if (this.client) {
await this.client.close();
this.client = null;
logger.info('Disconnected from Etcd');
}
}
}
const etcdServer = new EtcdServer();
export { EtcdServer, etcdServer };

View File

@ -44,6 +44,14 @@ class MongoServer {
}
return mongoose.connection;
}
async disconnect() {
if (!this.connected) return;
logger.info('Disconnecting from MongoDB...');
await mongoose.connection.close();
this.connected = false;
logger.info('Disconnected from MongoDB');
}
}
const mongoServer = new MongoServer();

View File

@ -28,11 +28,20 @@ class RedisServer {
async connect() {
if (this.connected) return;
logger.info('Connecting to Redis...');
await this.client.connect();
this.connected = true;
logger.info('Connected to Redis');
}
async disconnect() {
if (!this.connected) return;
logger.info('Disconnecting from Redis...');
await this.client.quit();
this.connected = false;
logger.info('Disconnected from Redis');
}
async setKey(key, value, ttlSeconds) {
await this.connect();
const payload = typeof value === 'string' ? value : JSON.stringify(value);
@ -58,6 +67,24 @@ class RedisServer {
await this.connect();
await this.client.del(key);
}
async getKeysByPattern(pattern) {
await this.connect();
const keys = [];
let cursor = '0';
logger.trace('Getting keys by pattern:', pattern);
do {
logger.trace('Scanning keys:', cursor);
const result = await this.client.scan(cursor, {
MATCH: pattern,
COUNT: 100
});
cursor = result.cursor;
logger.trace('Result:', result);
keys.push(...result.keys);
} while (cursor !== '0');
return keys;
}
}
const redisServer = new RedisServer();

View File

@ -1,6 +1,7 @@
import mongoose from 'mongoose';
import { generateId } from '../../utils.js';
const { Schema } = mongoose;
import { aggregateRollups, aggregateRollupsHistory } from '../../database.js';
// Define the main filamentStock schema
const filamentStockSchema = new Schema(
@ -23,6 +24,35 @@ const filamentStockSchema = new Schema(
{ timestamps: true }
);
const rollupConfigs = [
{
name: 'totalCurrentWeight',
filter: {},
rollups: [{ name: 'totalCurrentWeight', property: 'currentWeight.net', operation: 'sum' }],
},
];
filamentStockSchema.statics.stats = async function () {
const results = await aggregateRollups({
model: this,
rollupConfigs: rollupConfigs,
});
return results;
};
filamentStockSchema.statics.history = async function (from, to) {
const results = await aggregateRollupsHistory({
model: this,
startDate: from,
endDate: to,
rollupConfigs: rollupConfigs,
});
// Return time-series data array
return results;
};
// Add virtual id getter
filamentStockSchema.virtual('id').get(function () {
return this._id;

View File

@ -1,4 +1,6 @@
import mongoose from 'mongoose';
import { purchaseOrderModel } from './purchaseorder.schema.js';
import { aggregateRollups, editObject } from '../../database.js';
import { generateId } from '../../utils.js';
const { Schema } = mongoose;
@ -20,6 +22,52 @@ const orderItemSchema = new Schema(
{ timestamps: true }
);
orderItemSchema.statics.recalculate = async function (orderItem, user) {
// Only purchase orders are supported for now
if (orderItem.orderType !== 'purchaseOrder') {
return;
}
const orderId = orderItem.order?._id || orderItem.order;
if (!orderId) {
return;
}
const rollupResults = await aggregateRollups({
model: this,
baseFilter: {
order: new mongoose.Types.ObjectId(orderId),
orderType: orderItem.orderType,
},
rollupConfigs: [
{
name: 'orderTotals',
rollups: [
{ name: 'totalAmount', property: 'totalAmount', operation: 'sum' },
{
name: 'totalAmountWithTax',
property: 'totalAmountWithTax',
operation: 'sum',
},
],
},
],
});
const totals = rollupResults.orderTotals || {};
const totalAmount = totals.totalAmount.sum?.toFixed(2) || 0;
const totalAmountWithTax = totals.totalAmountWithTax.sum?.toFixed(2) || 0;
await editObject({
model: purchaseOrderModel,
id: orderId,
updateData: {
cost: { net: totalAmount, gross: totalAmountWithTax },
},
user,
});
};
// Add virtual id getter
orderItemSchema.virtual('id').get(function () {
return this._id;

View File

@ -1,6 +1,7 @@
import mongoose from 'mongoose';
import { generateId } from '../../utils.js';
const { Schema } = mongoose;
import { aggregateRollups, aggregateRollupsHistory } from '../../database.js';
// Define the main partStock schema
const partStockSchema = new Schema(
@ -11,12 +12,42 @@ const partStockSchema = new Schema(
progress: { type: Number, required: false },
},
part: { type: mongoose.Schema.Types.ObjectId, ref: 'part', required: true },
startingQuantity: { type: Number, required: true },
currentQuantity: { type: Number, required: true },
sourceType: { type: String, required: true },
source: { type: Schema.Types.ObjectId, refPath: 'sourceType', required: true },
},
{ timestamps: true }
);
const rollupConfigs = [
{
name: 'totalCurrentQuantity',
filter: {},
rollups: [{ name: 'totalCurrentQuantity', property: 'currentQuantity', operation: 'sum' }],
},
];
partStockSchema.statics.stats = async function () {
const results = await aggregateRollups({
model: this,
rollupConfigs: rollupConfigs,
});
return results;
};
partStockSchema.statics.history = async function (from, to) {
const results = await aggregateRollupsHistory({
model: this,
startDate: from,
endDate: to,
rollupConfigs: rollupConfigs,
});
// Return time-series data array
return results;
};
// Add virtual id getter
partStockSchema.virtual('id').get(function () {
return this._id;

View File

@ -18,7 +18,8 @@ const documentJobSchema = new Schema(
},
state: {
type: { type: String, required: true, default: 'queued' },
percent: { type: Number, required: false },
progress: { type: Number, required: false },
message: { type: String, required: false },
},
documentTemplate: {
type: Schema.Types.ObjectId,

View File

@ -12,6 +12,8 @@ const filamentSchema = new mongoose.Schema({
vendor: { type: Schema.Types.ObjectId, ref: 'vendor', required: true },
type: { required: true, type: String },
cost: { required: true, type: Number },
costTaxRate: { type: Schema.Types.ObjectId, ref: 'taxRate', required: true },
costWithTax: { required: true, type: Number },
diameter: { required: true, type: Number },
density: { required: true, type: Number },
createdAt: { required: true, type: Date },

View File

@ -41,22 +41,25 @@ const deviceInfoSchema = new mongoose.Schema(
{ _id: false }
);
const hostSchema = new mongoose.Schema({
_reference: { type: String, default: () => generateId()() },
name: { required: true, type: String },
tags: [{ required: false, type: String }],
online: { required: true, type: Boolean, default: false },
state: {
type: { type: String, required: true, default: 'offline' },
message: { type: String, required: false },
percent: { type: Number, required: false },
const hostSchema = new mongoose.Schema(
{
_reference: { type: String, default: () => generateId()() },
name: { required: true, type: String },
tags: [{ required: false, type: String }],
online: { required: true, type: Boolean, default: false },
state: {
type: { type: String, required: true, default: 'offline' },
message: { type: String, required: false },
percent: { type: Number, required: false },
},
active: { required: true, type: Boolean, default: true },
connectedAt: { required: false, type: Date },
authCode: { type: { required: false, type: String } },
deviceInfo: { deviceInfoSchema },
files: [{ type: mongoose.Schema.Types.ObjectId, ref: 'file' }],
},
active: { required: true, type: Boolean, default: true },
connectedAt: { required: false, type: Date },
authCode: { type: { required: false, type: String } },
deviceInfo: { deviceInfoSchema },
files: [{ type: mongoose.Schema.Types.ObjectId, ref: 'file' }],
});
{ timestamps: true }
);
hostSchema.virtual('id').get(function () {
return this._id;

View File

@ -8,6 +8,7 @@ import { productModel } from './management/product.schema.js';
import { vendorModel } from './management/vendor.schema.js';
import { filamentStockModel } from './inventory/filamentstock.schema.js';
import { purchaseOrderModel } from './inventory/purchaseorder.schema.js';
import { orderItemModel } from './inventory/orderitem.schema.js';
import { stockEventModel } from './inventory/stockevent.schema.js';
import { stockAuditModel } from './inventory/stockaudit.schema.js';
import { partStockModel } from './inventory/partstock.schema.js';
@ -82,6 +83,12 @@ export const models = {
type: 'purchaseOrder',
referenceField: '_reference',
},
ODI: {
model: orderItemModel,
idField: '_id',
type: 'orderItem',
referenceField: '_reference',
},
COS: {
model: courierServiceModel,
idField: '_id',

View File

@ -1,6 +1,7 @@
import mongoose from 'mongoose';
import { generateId } from '../../utils.js';
const { Schema } = mongoose;
import { aggregateRollups, aggregateRollupsHistory } from '../../database.js';
const jobSchema = new mongoose.Schema(
{
@ -31,6 +32,56 @@ const jobSchema = new mongoose.Schema(
{ timestamps: true }
);
const rollupConfigs = [
{
name: 'queued',
filter: { 'state.type': 'queued' },
rollups: [{ name: 'queued', property: 'state.type', operation: 'count' }],
},
{
name: 'printing',
filter: { 'state.type': 'printing' },
rollups: [{ name: 'printing', property: 'state.type', operation: 'count' }],
},
{
name: 'draft',
filter: { 'state.type': 'draft' },
rollups: [{ name: 'draft', property: 'state.type', operation: 'count' }],
},
{
name: 'complete',
filter: { 'state.type': 'complete' },
rollups: [{ name: 'complete', property: 'state.type', operation: 'count' }],
},
{
name: 'failed',
filter: { 'state.type': 'failed' },
rollups: [{ name: 'failed', property: 'state.type', operation: 'count' }],
},
];
jobSchema.statics.stats = async function () {
const results = await aggregateRollups({
model: this,
rollupConfigs: rollupConfigs,
});
// Transform the results to match the expected format
return results;
};
jobSchema.statics.history = async function (from, to) {
const results = await aggregateRollupsHistory({
model: this,
startDate: from,
endDate: to,
rollupConfigs: rollupConfigs,
});
// Return time-series data array
return results;
};
jobSchema.virtual('id').get(function () {
return this._id;
});

View File

@ -1,6 +1,7 @@
import mongoose from 'mongoose';
import { generateId } from '../../utils.js';
const { Schema } = mongoose;
import { aggregateRollups, aggregateRollupsHistory } from '../../database.js';
// Define the moonraker connection schema
const moonrakerSchema = new Schema(
@ -56,6 +57,59 @@ const printerSchema = new Schema(
{ timestamps: true }
);
const rollupConfigs = [
{
name: 'standby',
filter: { 'state.type': 'standby' },
rollups: [{ name: 'standby', property: 'state.type', operation: 'count' }],
},
{
name: 'complete',
filter: { 'state.type': 'complete' },
rollups: [{ name: 'complete', property: 'state.type', operation: 'count' }],
},
{
name: 'printing',
filter: { 'state.type': 'printing' },
rollups: [{ name: 'printing', property: 'state.type', operation: 'count' }],
},
{
name: 'error',
filter: { 'state.type': 'error' },
rollups: [{ name: 'error', property: 'state.type', operation: 'count' }],
},
{
name: 'offline',
filter: { 'state.type': 'offline' },
rollups: [{ name: 'offline', property: 'state.type', operation: 'count' }],
},
];
printerSchema.statics.stats = async function () {
const results = await aggregateRollups({
model: this,
baseFilter: { active: true },
rollupConfigs: rollupConfigs,
});
console.log(results);
// Transform the results to match the expected format
return results;
};
printerSchema.statics.history = async function (from, to) {
const results = await aggregateRollupsHistory({
model: this,
startDate: from,
endDate: to,
rollupConfigs: rollupConfigs,
});
// Return time-series data array
return results;
};
// Add virtual id getter
printerSchema.virtual('id').get(function () {
return this._id;

View File

@ -436,6 +436,10 @@ async function distributeUpdate(value, id, type) {
await natsServer.publish(`${type}s.${id}.object`, value);
}
async function distributeStats(value, type) {
await natsServer.publish(`${type}s.stats`, value);
}
async function distributeNew(id, type) {
await natsServer.publish(`${type}s.new`, id);
}
@ -541,6 +545,7 @@ export {
expandObjectIds,
distributeUpdate,
distributeNew,
distributeStats,
getFilter, // <-- add here
convertPropertiesString
};

View File

@ -1,7 +1,7 @@
import { loadConfig } from './config.js';
import { SocketManager } from './socket/socketmanager.js';
import { etcdServer } from './database/etcd.js';
import { natsServer } from './database/nats.js';
import { redisServer } from './database/redis.js';
import express from 'express';
import log4js from 'log4js';
import http from 'http';
@ -21,19 +21,9 @@ import { mongoServer } from './database/mongo.js';
new SocketManager(server);
// Connect to Etcd (await)
try {
//await etcdServer.connect();
// logger.info('Connected to Etcd');
} catch (err) {
logger.error('Failed to connect to Etcd:', err);
throw err;
}
// Connect to NATS (await)
try {
await natsServer.connect();
logger.info('Connected to NATS');
} catch (err) {
logger.error('Failed to connect to NATS:', err);
throw err;
@ -42,12 +32,19 @@ import { mongoServer } from './database/mongo.js';
// Connect to Mongo DB (await)
try {
await mongoServer.connect();
logger.info('Connected to Mongo DB');
} catch (err) {
logger.error('Failed to connect to Mongo DB:', err);
throw err;
}
// Connect to Redis (await)
try {
await redisServer.connect();
} catch (err) {
logger.error('Failed to connect to Redis:', err);
throw err;
}
// Start HTTP server
server.listen(config.server.port, () => {
logger.info(`Server listening on port ${config.server.port}`);
@ -55,6 +52,11 @@ import { mongoServer } from './database/mongo.js';
process.on('SIGINT', async () => {
logger.info('Shutting down...');
await etcdServer.disconnect();
await redisServer.disconnect();
await mongoServer.disconnect();
await natsServer.disconnect();
await server.close();
logger.info('Shutdown complete');
process.exit(0);
});
})();

View File

@ -9,7 +9,7 @@ const logger = log4js.getLogger('Lock Manager');
logger.level = config.server.logLevel;
/**
* LockManager handles distributed locking using Etcd and broadcasts lock events via websockets.
* LockManager handles distributed locking and broadcasts lock events via websockets.
*/
export class LockManager {
constructor(socketClient) {

View File

@ -7,6 +7,7 @@ import { LockManager } from '../lock/lockmanager.js';
import { UpdateManager } from '../updates/updatemanager.js';
import { ActionManager } from '../actions/actionmanager.js';
import { EventManager } from '../events/eventmanager.js';
import { StatsManager } from '../stats/statsmanager.js';
const config = loadConfig();
@ -25,6 +26,7 @@ export class SocketUser {
this.updateManager = new UpdateManager(this);
this.actionManager = new ActionManager(this);
this.eventManager = new EventManager(this);
this.statsManager = new StatsManager(this);
this.templateManager = socketManager.templateManager;
this.keycloakAuth = new KeycloakAuth();
this.setupSocketEventHandlers();
@ -60,6 +62,14 @@ export class SocketUser {
'unsubscribeObjectEvent',
this.handleUnsubscribeObjectEventEvent.bind(this)
);
this.socket.on(
'subscribeToModelStats',
this.handleSubscribeToStatsEvent.bind(this)
);
this.socket.on(
'unsubscribeModelStats',
this.handleUnsubscribeToStatsEvent.bind(this)
);
this.socket.on(
'previewTemplate',
this.handlePreviewTemplateEvent.bind(this)
@ -191,6 +201,14 @@ export class SocketUser {
);
}
async handleSubscribeToStatsEvent(data) {
await this.statsManager.subscribeToStats(data.objectType);
}
async handleUnsubscribeToStatsEvent(data) {
await this.statsManager.removeStatsListener(data.objectType);
}
async handlePreviewTemplateEvent(data, callback) {
const result = await this.templateManager.renderTemplate(
data._id,
@ -227,6 +245,7 @@ export class SocketUser {
async handleDisconnect() {
await this.actionManager.removeAllListeners();
await this.eventManager.removeAllListeners();
await this.statsManager.removeAllListeners();
logger.info('External user disconnected:', this.socket.user?.username);
}
}

82
src/stats/statsmanager.js Normal file
View File

@ -0,0 +1,82 @@
import log4js from 'log4js';
import { loadConfig } from '../config.js';
import { natsServer } from '../database/nats.js';
const config = loadConfig();
// Setup logger
const logger = log4js.getLogger('Stats Manager');
logger.level = config.server.logLevel;
/**
* StatsManager handles tracking stats updates using NATS and broadcasts stats updates via websockets.
*/
export class StatsManager {
constructor(socketClient) {
this.socketClient = socketClient;
this.subscriptions = new Set();
}
async subscribeToStats(type) {
logger.debug('Subscribing to stats:', type);
const subject = `${type}s.stats`;
const subscriptionKey = `${subject}:${this.socketClient.socketId}`;
await natsServer.subscribe(
subject,
this.socketClient.socketId,
(key, value) => {
if (!value?.result) {
logger.trace('Stats update detected:', type);
console.log('Stats update detected:', type, value);
this.socketClient.socket.emit('modelStats', {
objectType: type,
stats: { ...value }
});
}
}
);
this.subscriptions.add(subscriptionKey);
return { success: true };
}
async removeStatsListener(type) {
// Remove stats subscription for this type
const subject = `${type}s.stats`;
const subscriptionKey = `${subject}:${this.socketClient.socketId}`;
await natsServer.removeSubscription(subject, this.socketClient.socketId);
this.subscriptions.delete(subscriptionKey);
return { success: true };
}
async sendStats(type, stats) {
try {
logger.trace(`Publishing stats: type=${type}, stats:`, stats);
await natsServer.publish(`${type}s.stats`, stats);
return { success: true };
} catch (error) {
logger.error(`Failed to publish stats for ${type}s.stats:`, error);
return {
error: error?.message || `Failed to publish stats for ${type}s.stats.`
};
}
}
async removeAllListeners() {
logger.debug('Removing all stats listeners...');
const removePromises = Array.from(this.subscriptions).map(
subscriptionKey => {
const [subject, socketId] = subscriptionKey.split(':');
return natsServer.removeSubscription(subject, socketId);
}
);
await Promise.all(removePromises);
this.subscriptions.clear();
logger.debug(`Removed ${removePromises.length} stats listener(s)`);
return { success: true };
}
}

View File

@ -18,18 +18,18 @@
}
.previewWrapper {
width: <%= (width * scale) + 'mm' || '50mm' %>;
height: <%= (height * scale) + 'mm' || '50mm' %>;
width: <%= (scaledWidth) || '50mm' %>;
height: <%= (scaledHeight) || '50mm' %>;
}
.previewDocument {
width: <%= (width) + 'mm' || '50mm' %>;
height: <%= (height) + 'mm' || '50mm' %>;
width: <%= (width) || '50mm' %>;
height: <%= (height) || '50mm' %>;
transform: scale(<%= scale || '1' %>);
transform-origin: top left;
}
.renderDocument {
width: <%= (width * scale) + 'mm' || '50mm' %>;
height: <%= (height * scale) + 'mm' || '50mm' %>;
width: <%= (scaledWidth) || '50mm' %>;
height: <%= (scaledHeight) || '50mm' %>;
transform: scale(<%= scale || '1' %>);
}
</style>

View File

@ -1 +1 @@
<div class="renderDocument"><%- content %></div>
<div class="renderDocument" id="content"><%- content %></div>

View File

@ -30,13 +30,22 @@ export async function generatePDF(html, options = {}) {
waitUntil: 'networkidle0'
});
var height = `${options?.height || '50'}mm`;
if (options.height == 'auto') {
console.log('Calculating height');
const calculatedHeight = await page.evaluate(() => {
return document.getElementById('content').scrollHeight;
});
height = `${calculatedHeight}px`;
}
// Generate PDF with specified dimensions
const pdfBuffer = await page.pdf({
format: options.format || undefined,
width: options.width ? `${options.width}mm` : undefined,
height: options.height ? `${options.height}mm` : undefined,
printBackground: true,
preferCSSPageSize: true,
width: options.width ? `${options.width}mm` : undefined,
height: height ? `${height}` : undefined,
margin: {
top: '0mm',
right: '0mm',

View File

@ -430,8 +430,14 @@ export class TemplateManager {
documentTemplate.parent.content == null ||
typeof documentTemplate.parent.content !== 'string'
) {
console.log(
'Parent template content is required and must be a string.',
documentTemplate.parent.content
);
return {
error: 'Parent template content is required and must be a string.'
error:
'Parent template content is required and must be a string.' +
documentTemplate.parent.content
};
}
templateWithParentContent = await ejs.render(
@ -481,12 +487,18 @@ export class TemplateManager {
return { error: 'Failed to render inner template content.' };
}
const infiniteHeight = documentSize.infiniteHeight == true;
const baseHtml = await ejs.render(
baseTemplate,
{
content: innerHtml,
width: documentSize.width,
height: documentSize.height,
width: `${documentSize.width}mm`,
height: infiniteHeight ? 'fit-content' : `${documentSize.height}mm`,
scaledWidth: `${documentSize.width * scale}mm`,
scaledHeight: infiniteHeight
? 'auto'
: `${documentSize.height * scale}mm`,
scale: `${scale}`,
baseCSS: baseCSS,
previewPaginationScript: preview ? previewPaginationScript : ''
@ -497,7 +509,8 @@ export class TemplateManager {
const previewObject = {
html: baseHtml,
width: documentSize.width,
height: documentSize.height
height: infiniteHeight ? 'auto' : documentSize.height,
infiniteHeight: infiniteHeight
};
return previewObject;
@ -552,7 +565,8 @@ export class TemplateManager {
// Generate PDF using PDF factory
const pdfBuffer = await generatePDF(baseHtml, {
width: renderedTemplate.width,
height: renderedTemplate.height
height: renderedTemplate.height,
infiniteHeight: renderedTemplate.infiniteHeight
});
const pdfObject = {

View File

@ -8,7 +8,7 @@ const logger = log4js.getLogger('Update Manager');
logger.level = config.server.logLevel;
/**
* UpdateManager handles tracking object updates using Etcd and broadcasts update events via websockets.
* UpdateManager handles tracking object updates and broadcasts update events via websockets.
*/
export class UpdateManager {
constructor(socketClient) {

View File

@ -76,8 +76,14 @@ export function getModelByName(modelName) {
return modelList.filter(model => model.modelName == modelName)[0];
}
export function jsonToCacheKey(obj) {
const normalized = canonicalize(obj);
const hash = crypto.createHash('sha256').update(normalized).digest('hex');
return hash;
export function getQueryToCacheKey({ model, id, populate }) {
const populateKey = [];
for (const pop of populate) {
if (typeof pop === 'string') {
populateKey.push(pop);
} else if (typeof pop === 'object') {
populateKey.push(pop.path);
}
}
return `${model}:${id?.toString()}-${populateKey.join(',')}`;
}

406
yarn.lock
View File

@ -104,34 +104,6 @@
"@eslint/core" "^0.17.0"
levn "^0.4.1"
"@grpc/grpc-js@^1.8.20":
version "1.14.1"
resolved "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.14.1.tgz"
integrity sha512-sPxgEWtPUR3EnRJCEtbGZG2iX8LQDUls2wUS3o27jg07KqJFMq6YDeWvMo1wfpmy3rqRdS0rivpLwhqQtEyCuQ==
dependencies:
"@grpc/proto-loader" "^0.8.0"
"@js-sdsl/ordered-map" "^4.4.2"
"@grpc/proto-loader@^0.7.8":
version "0.7.15"
resolved "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.7.15.tgz"
integrity sha512-tMXdRCfYVixjuFK+Hk0Q1s38gV9zDiDJfWL3h1rv4Qc39oILCu1TRTDt7+fGUI8K4G1Fj125Hx/ru3azECWTyQ==
dependencies:
lodash.camelcase "^4.3.0"
long "^5.0.0"
protobufjs "^7.2.5"
yargs "^17.7.2"
"@grpc/proto-loader@^0.8.0":
version "0.8.0"
resolved "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.8.0.tgz"
integrity sha512-rc1hOQtjIWGxcxpb9aHAfLpIctjEnsDehj0DAiVfBlmT84uvR0uUtN2hEi/ecvWVjXUGf5qPF4qEgiLOx1YIMQ==
dependencies:
lodash.camelcase "^4.3.0"
long "^5.0.0"
protobufjs "^7.5.3"
yargs "^17.7.2"
"@humanfs/core@^0.19.1":
version "0.19.1"
resolved "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz"
@ -169,11 +141,6 @@
resolved "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz"
integrity sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==
"@js-sdsl/ordered-map@^4.4.2":
version "4.4.2"
resolved "https://registry.npmjs.org/@js-sdsl/ordered-map/-/ordered-map-4.4.2.tgz"
integrity sha512-iUKgm52T8HOE/makSxjqoWhe95ZJA1/G1sYsGev2JDKUSS14KAgg1LHb+Ba+IPow0xflbnSkOsZcO08C7w1gYw==
"@mongodb-js/saslprep@^1.3.0":
version "1.3.2"
resolved "https://registry.npmjs.org/@mongodb-js/saslprep/-/saslprep-1.3.2.tgz"
@ -181,7 +148,7 @@
dependencies:
sparse-bitfield "^3.0.3"
"@nats-io/nats-core@^3.2.0", "@nats-io/nats-core@3.2.0":
"@nats-io/nats-core@3.2.0", "@nats-io/nats-core@^3.2.0":
version "3.2.0"
resolved "https://registry.npmjs.org/@nats-io/nats-core/-/nats-core-3.2.0.tgz"
integrity sha512-yHMsS0RTkKRh80COCGY/kuhEj0KPU47qkVU6IPEXmqjmskH6mX2roykxY5CXCAQc78FKYrpKQp6EHv02dRAjDw==
@ -236,59 +203,6 @@
resolved "https://registry.npmjs.org/@pkgr/core/-/core-0.2.9.tgz"
integrity sha512-QNqXyfVS2wm9hweSYD2O7F0G06uurj9kZ96TRQE5Y9hU7+tgdZwIkbAKc5Ocy1HxEY2kuDQa6cQ1WRs/O5LFKA==
"@protobufjs/aspromise@^1.1.1", "@protobufjs/aspromise@^1.1.2":
version "1.1.2"
resolved "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz"
integrity sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==
"@protobufjs/base64@^1.1.2":
version "1.1.2"
resolved "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz"
integrity sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==
"@protobufjs/codegen@^2.0.4":
version "2.0.4"
resolved "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.4.tgz"
integrity sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==
"@protobufjs/eventemitter@^1.1.0":
version "1.1.0"
resolved "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz"
integrity sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==
"@protobufjs/fetch@^1.1.0":
version "1.1.0"
resolved "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz"
integrity sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==
dependencies:
"@protobufjs/aspromise" "^1.1.1"
"@protobufjs/inquire" "^1.1.0"
"@protobufjs/float@^1.0.2":
version "1.0.2"
resolved "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz"
integrity sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==
"@protobufjs/inquire@^1.1.0":
version "1.1.0"
resolved "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.0.tgz"
integrity sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q==
"@protobufjs/path@^1.1.2":
version "1.1.2"
resolved "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz"
integrity sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==
"@protobufjs/pool@^1.1.0":
version "1.1.0"
resolved "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz"
integrity sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==
"@protobufjs/utf8@^1.1.0":
version "1.1.0"
resolved "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz"
integrity sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==
"@puppeteer/browsers@2.10.13":
version "2.10.13"
resolved "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-2.10.13.tgz"
@ -302,39 +216,32 @@
tar-fs "^3.1.1"
yargs "^17.7.2"
"@redis/bloom@1.2.0":
version "1.2.0"
resolved "https://registry.npmjs.org/@redis/bloom/-/bloom-1.2.0.tgz"
integrity sha512-HG2DFjYKbpNmVXsa0keLHp/3leGJz1mjh09f2RLGGLQZzSHpkmZWuwJbAvo3QcRY8p80m5+ZdXZdYOSBLlp7Cg==
"@redis/bloom@5.10.0":
version "5.10.0"
resolved "https://registry.yarnpkg.com/@redis/bloom/-/bloom-5.10.0.tgz#1079373583b82a8b61b0d3721755245686b4a602"
integrity sha512-doIF37ob+l47n0rkpRNgU8n4iacBlKM9xLiP1LtTZTvz8TloJB8qx/MgvhMhKdYG+CvCY2aPBnN2706izFn/4A==
"@redis/client@^1.0.0", "@redis/client@1.6.1":
version "1.6.1"
resolved "https://registry.npmjs.org/@redis/client/-/client-1.6.1.tgz"
integrity sha512-/KCsg3xSlR+nCK8/8ZYSknYxvXHwubJrU82F3Lm1Fp6789VQ0/3RJKfsmRXjqfaTA++23CvC3hqmqe/2GEt6Kw==
"@redis/client@5.10.0":
version "5.10.0"
resolved "https://registry.yarnpkg.com/@redis/client/-/client-5.10.0.tgz#621d5de1898a4f2c3a813769779ca5902ef9d57a"
integrity sha512-JXmM4XCoso6C75Mr3lhKA3eNxSzkYi3nCzxDIKY+YOszYsJjuKbFgVtguVPbLMOttN4iu2fXoc2BGhdnYhIOxA==
dependencies:
cluster-key-slot "1.1.2"
generic-pool "3.9.0"
yallist "4.0.0"
"@redis/graph@1.1.1":
version "1.1.1"
resolved "https://registry.npmjs.org/@redis/graph/-/graph-1.1.1.tgz"
integrity sha512-FEMTcTHZozZciLRl6GiiIB4zGm5z5F3F6a6FZCyrfxdKOhFlGkiAqlexWMBzCi4DcRoyiOsuLfW+cjlGWyExOw==
"@redis/json@5.10.0":
version "5.10.0"
resolved "https://registry.yarnpkg.com/@redis/json/-/json-5.10.0.tgz#a550a7859c3cbae45535aad157fcaa8e6bd3e7d3"
integrity sha512-B2G8XlOmTPUuZtD44EMGbtoepQG34RCDXLZbjrtON1Djet0t5Ri7/YPXvL9aomXqP8lLTreaprtyLKF4tmXEEA==
"@redis/json@1.0.7":
version "1.0.7"
resolved "https://registry.npmjs.org/@redis/json/-/json-1.0.7.tgz"
integrity sha512-6UyXfjVaTBTJtKNG4/9Z8PSpKE6XgSyEb8iwaqDcy+uKrd/DGYHTWkUdnQDyzm727V7p21WUMhsqz5oy65kPcQ==
"@redis/search@5.10.0":
version "5.10.0"
resolved "https://registry.yarnpkg.com/@redis/search/-/search-5.10.0.tgz#587851479c6abb9b40b31ab9b31c234db7a04919"
integrity sha512-3SVcPswoSfp2HnmWbAGUzlbUPn7fOohVu2weUQ0S+EMiQi8jwjL+aN2p6V3TI65eNfVsJ8vyPvqWklm6H6esmg==
"@redis/search@1.2.0":
version "1.2.0"
resolved "https://registry.npmjs.org/@redis/search/-/search-1.2.0.tgz"
integrity sha512-tYoDBbtqOVigEDMAcTGsRlMycIIjwMCgD8eR2t0NANeQmgK/lvxNAvYyb6bZDD4frHRhIHkJu2TBRvB0ERkOmw==
"@redis/time-series@1.1.0":
version "1.1.0"
resolved "https://registry.npmjs.org/@redis/time-series/-/time-series-1.1.0.tgz"
integrity sha512-c1Q99M5ljsIuc4YdaCwfUEXsofakb9c8+Zse2qxTadu8TalLXuAESzLvFAvNVbkmSlvlzIQOLpBCmWI9wTOt+g==
"@redis/time-series@5.10.0":
version "5.10.0"
resolved "https://registry.yarnpkg.com/@redis/time-series/-/time-series-5.10.0.tgz#9c7de35fe023e36233fba5c8478ce25832ead64c"
integrity sha512-cPkpddXH5kc/SdRhF0YG0qtjL+noqFT0AcHbQ6axhsPsO7iqPi1cjxgdkE9TNeKiBUUdCaU1DbqkR/LzbzPBhg==
"@rtsao/scc@^1.1.0":
version "1.1.0"
@ -373,7 +280,7 @@
resolved "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz"
integrity sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==
"@types/node@*", "@types/node@>=10.0.0", "@types/node@>=13.7.0":
"@types/node@*", "@types/node@>=10.0.0":
version "24.10.1"
resolved "https://registry.npmjs.org/@types/node/-/node-24.10.1.tgz"
integrity sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ==
@ -425,7 +332,7 @@ acorn-jsx@^5.3.2:
resolved "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz"
integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==
"acorn@^6.0.0 || ^7.0.0 || ^8.0.0", acorn@^8.15.0, acorn@^8.9.0:
acorn@^8.15.0, acorn@^8.9.0:
version "8.15.0"
resolved "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz"
integrity sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==
@ -609,7 +516,7 @@ balanced-match@^1.0.0:
resolved "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz"
integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==
bare-events@*, bare-events@^2.5.4, bare-events@^2.7.0:
bare-events@^2.5.4, bare-events@^2.7.0:
version "2.8.2"
resolved "https://registry.npmjs.org/bare-events/-/bare-events-2.8.2.tgz"
integrity sha512-riJjyv1/mHLIPX4RwiK+oW9/4c3TEUeORHKefKAKnZ5kyslbN+HXowtbaVEqt4IMUB7OXlfixcs6gsFeo/jhiQ==
@ -656,7 +563,7 @@ base64-js@0.0.2:
resolved "https://registry.npmjs.org/base64-js/-/base64-js-0.0.2.tgz"
integrity sha512-Pj9L87dCdGcKlSqPVUjD+q96pbIx1zQQLb2CUiWURfjiBELv84YX+0nGnKmyT/9KkC7PQk7UN1w+Al8bBozaxQ==
base64id@~2.0.0, base64id@2.0.0:
base64id@2.0.0, base64id@~2.0.0:
version "2.0.0"
resolved "https://registry.npmjs.org/base64id/-/base64id-2.0.0.tgz"
integrity sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==
@ -666,11 +573,6 @@ basic-ftp@^5.0.2:
resolved "https://registry.npmjs.org/basic-ftp/-/basic-ftp-5.0.5.tgz"
integrity sha512-4Bcg1P8xhUuqcii/S0Z9wiHIrQVPMermM1any+MX5GeGD7faD3/msQUDGLol9wOcz4/jbg/WJnGqoJF6LiBdtg==
bignumber.js@^9.1.1:
version "9.3.1"
resolved "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.3.1.tgz"
integrity sha512-Ko0uX15oIUS7wJ3Rb30Fs6SkVbLmPBAKdlm7q9+ak9bbIeFf0MwuBsQV6z7+X768/cHsfg+WlysDWJcmthjsjQ==
binary-extensions@^2.0.0:
version "2.3.0"
resolved "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz"
@ -753,7 +655,7 @@ builtins@^5.0.1:
dependencies:
semver "^7.0.0"
bytes@^3.1.2, bytes@3.1.2:
bytes@3.1.2, bytes@^3.1.2:
version "3.1.2"
resolved "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz"
integrity sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==
@ -844,11 +746,6 @@ cluster-key-slot@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==
cockatiel@^3.1.1:
version "3.2.1"
resolved "https://registry.npmjs.org/cockatiel/-/cockatiel-3.2.1.tgz"
integrity sha512-gfrHV6ZPkquExvMh9IOkKsBzNDk6sDuZ6DdBGUBkvFnTCqCxzpuq48RySgP0AnaqQkw2zynOFj9yly6T1Q2G5Q==
color-convert@^2.0.1:
version "2.0.1"
resolved "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz"
@ -974,6 +871,20 @@ dayjs@^1.11.19:
resolved "https://registry.npmjs.org/dayjs/-/dayjs-1.11.19.tgz"
integrity sha512-t5EcLVS6QPBNqM2z8fakk/NKel+Xzshgt8FFKAn+qwlD1pzZWxh0nVCrvFK7ZDb6XucZeF9z8C7CBWTRIVApAw==
debug@2.6.9:
version "2.6.9"
resolved "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz"
integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==
dependencies:
ms "2.0.0"
debug@4, debug@4.x, debug@^4, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2, debug@^4.3.4, debug@^4.3.5, debug@^4.4.0, debug@^4.4.3:
version "4.4.3"
resolved "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz"
integrity sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==
dependencies:
ms "^2.1.3"
debug@^3.2.7:
version "3.2.7"
resolved "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz"
@ -981,34 +892,13 @@ debug@^3.2.7:
dependencies:
ms "^2.1.1"
debug@^4, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2, debug@^4.3.4, debug@^4.3.5, debug@^4.4.0, debug@^4.4.3, debug@4, debug@4.x:
version "4.4.3"
resolved "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz"
integrity sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==
dependencies:
ms "^2.1.3"
debug@~4.3.1:
debug@~4.3.1, debug@~4.3.2, debug@~4.3.4:
version "4.3.7"
resolved "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz"
integrity sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==
dependencies:
ms "^2.1.3"
debug@~4.3.2, debug@~4.3.4:
version "4.3.7"
resolved "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz"
integrity sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==
dependencies:
ms "^2.1.3"
debug@2.6.9:
version "2.6.9"
resolved "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz"
integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==
dependencies:
ms "2.0.0"
deep-is@^0.1.3:
version "0.1.4"
resolved "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz"
@ -1046,12 +936,12 @@ delayed-stream@~1.0.0:
resolved "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz"
integrity sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==
depd@^2.0.0, depd@2.0.0:
depd@2.0.0, depd@^2.0.0:
version "2.0.0"
resolved "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz"
integrity sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==
devtools-protocol@*, devtools-protocol@0.0.1521046:
devtools-protocol@0.0.1521046:
version "0.0.1521046"
resolved "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1521046.tgz"
integrity sha512-vhE6eymDQSKWUXwwA37NtTTVEzjtGVfDr3pRbsWEQ5onH/Snp2c+2xZHWJJawG/0hCCJLRGt4xVtEVUVILol4w==
@ -1348,7 +1238,7 @@ escodegen@^2.1.0:
optionalDependencies:
source-map "~0.6.1"
eslint-config-prettier@^10.1.8, "eslint-config-prettier@>= 7.0.0 <10.0.0 || >=10.1.0":
eslint-config-prettier@^10.1.8:
version "10.1.8"
resolved "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-10.1.8.tgz"
integrity sha512-82GZUjRS0p/jganf6q1rEO25VSoHH0hKPCTrgillPjdI/3bgBhAE1QzHrHTizjpRvy6pGAvKjDJtk2pF9NDq8w==
@ -1387,7 +1277,7 @@ eslint-plugin-es@^4.1.0:
eslint-utils "^2.0.0"
regexpp "^3.0.0"
eslint-plugin-import@^2.25.2, eslint-plugin-import@^2.27.5:
eslint-plugin-import@^2.27.5:
version "2.32.0"
resolved "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.32.0.tgz"
integrity sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA==
@ -1412,7 +1302,7 @@ eslint-plugin-import@^2.25.2, eslint-plugin-import@^2.27.5:
string.prototype.trimend "^1.0.9"
tsconfig-paths "^3.15.0"
"eslint-plugin-n@^15.0.0 || ^16.0.0 ", eslint-plugin-n@^15.7.0:
eslint-plugin-n@^15.7.0:
version "15.7.0"
resolved "https://registry.npmjs.org/eslint-plugin-n/-/eslint-plugin-n-15.7.0.tgz"
integrity sha512-jDex9s7D/Qial8AGVIHq4W7NswpUD5DPDL2RH8Lzd9EloWUuvUkHfv4FRLMipH5q2UtyurorBkPeNi1wVWNh3Q==
@ -1434,12 +1324,12 @@ eslint-plugin-prettier@^5.5.4:
prettier-linter-helpers "^1.0.0"
synckit "^0.11.7"
eslint-plugin-promise@^6.0.0, eslint-plugin-promise@^6.1.1:
eslint-plugin-promise@^6.1.1:
version "6.6.0"
resolved "https://registry.npmjs.org/eslint-plugin-promise/-/eslint-plugin-promise-6.6.0.tgz"
integrity sha512-57Zzfw8G6+Gq7axm2Pdo3gW/Rx3h9Yywgn61uE/3elTCOePEHVrn2i5CdfBwA1BLK0Q0WqctICIUSqXZW/VprQ==
eslint-plugin-react@^7.28.0, eslint-plugin-react@^7.36.1:
eslint-plugin-react@^7.36.1:
version "7.37.5"
resolved "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.37.5.tgz"
integrity sha512-Qteup0SqU15kdocexFNAJMvCJEfa2xUKNV4CC1xsVMrIIqEy3SQ/rqyxCWNzfrd3/ldy6HMlD2e0JDVpDg2qIA==
@ -1513,7 +1403,7 @@ eslint-visitor-keys@^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==
"eslint@^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8 || ^9", "eslint@^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9.7", "eslint@^7.0.0 || ^8.0.0 || ^9.0.0", eslint@^8.0.1, eslint@^8.41.0, eslint@^8.8.0, eslint@>=4.19.1, eslint@>=5:
eslint@^8.41.0:
version "8.57.1"
resolved "https://registry.npmjs.org/eslint/-/eslint-8.57.1.tgz"
integrity sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA==
@ -1557,7 +1447,7 @@ eslint-visitor-keys@^4.2.1:
strip-ansi "^6.0.1"
text-table "^0.2.0"
"eslint@^6.0.0 || ^7.0.0 || >=8.0.0", eslint@^9.39.1, eslint@>=7.0.0, eslint@>=8.0.0:
eslint@^9.39.1:
version "9.39.1"
resolved "https://registry.npmjs.org/eslint/-/eslint-9.39.1.tgz"
integrity sha512-BhHmn2yNOFA9H9JmmIVKJmd288g9hrVRDkdoIgRCRuSySRUHH7r/DI6aAXW9T1WwUuY3DFgrcaqB+deURBLR5g==
@ -1649,16 +1539,6 @@ etag@^1.8.1:
resolved "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz"
integrity sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==
etcd3@^1.1.2:
version "1.1.2"
resolved "https://registry.npmjs.org/etcd3/-/etcd3-1.1.2.tgz"
integrity sha512-YIampCz1/OmrVo/tR3QltAVUtYCQQOSFoqmHKKeoHbalm+WdXe3l4rhLIylklu8EzR/I3PBiOF4dC847dDskKg==
dependencies:
"@grpc/grpc-js" "^1.8.20"
"@grpc/proto-loader" "^0.7.8"
bignumber.js "^9.1.1"
cockatiel "^3.1.1"
events-universal@^1.0.0:
version "1.0.1"
resolved "https://registry.npmjs.org/events-universal/-/events-universal-1.0.1.tgz"
@ -1905,11 +1785,6 @@ generator-function@^2.0.0:
resolved "https://registry.npmjs.org/generator-function/-/generator-function-2.0.1.tgz"
integrity sha512-SFdFmIJi+ybC0vjlHN0ZGVGHc3lgE0DxPAT0djjVg+kjOnSqclqmj0KQ7ykTOLP6YxoqOvuAODGdcHJn+43q3g==
generic-pool@3.9.0:
version "3.9.0"
resolved "https://registry.npmjs.org/generic-pool/-/generic-pool-3.9.0.tgz"
integrity sha512-hymDOu5B53XvN4QT9dBmZxPX4CWhBPPLguTZ9MMFeFa/Kg0xWVfylOVNlJji/E7yTZWFd/q9GO5TxDLq156D7g==
get-caller-file@^2.0.5:
version "2.0.5"
resolved "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz"
@ -2093,7 +1968,7 @@ htmlparser2@^7.1.1:
domutils "^2.8.0"
entities "^3.0.1"
http-errors@^2.0.0, http-errors@2.0.0:
http-errors@2.0.0, http-errors@^2.0.0:
version "2.0.0"
resolved "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz"
integrity sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==
@ -2120,13 +1995,6 @@ https-proxy-agent@^7.0.6:
agent-base "^7.1.2"
debug "4"
iconv-lite@^0.6.3:
version "0.6.3"
resolved "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz"
integrity sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==
dependencies:
safer-buffer ">= 2.1.2 < 3.0.0"
iconv-lite@0.7.0:
version "0.7.0"
resolved "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.0.tgz"
@ -2134,6 +2002,13 @@ iconv-lite@0.7.0:
dependencies:
safer-buffer ">= 2.1.2 < 3.0.0"
iconv-lite@^0.6.3:
version "0.6.3"
resolved "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz"
integrity sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==
dependencies:
safer-buffer ">= 2.1.2 < 3.0.0"
ignore-by-default@^1.0.1:
version "1.0.1"
resolved "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz"
@ -2165,7 +2040,7 @@ inflight@^1.0.4:
once "^1.3.0"
wrappy "1"
inherits@~2.0.1, inherits@2, inherits@2.0.4:
inherits@2, inherits@2.0.4, inherits@~2.0.1:
version "2.0.4"
resolved "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz"
integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==
@ -2601,11 +2476,6 @@ locate-path@^6.0.0:
dependencies:
p-locate "^5.0.0"
lodash.camelcase@^4.3.0:
version "4.3.0"
resolved "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz"
integrity sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==
lodash.includes@^4.3.0:
version "4.3.0"
resolved "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz"
@ -2662,11 +2532,6 @@ log4js@^6.9.1:
rfdc "^1.3.0"
streamroller "^3.1.5"
long@^5.0.0:
version "5.3.2"
resolved "https://registry.npmjs.org/long/-/long-5.3.2.tgz"
integrity sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==
loose-envify@^1.4.0:
version "1.4.0"
resolved "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz"
@ -2699,17 +2564,17 @@ merge-descriptors@^2.0.0:
resolved "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz"
integrity sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==
mime-db@^1.54.0:
version "1.54.0"
resolved "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz"
integrity sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==
mime-db@1.52.0:
version "1.52.0"
resolved "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz"
integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==
mime-types@^2.1.12:
mime-db@^1.54.0:
version "1.54.0"
resolved "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz"
integrity sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==
mime-types@^2.1.12, mime-types@~2.1.34:
version "2.1.35"
resolved "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz"
integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==
@ -2723,13 +2588,6 @@ mime-types@^3.0.0, mime-types@^3.0.1:
dependencies:
mime-db "^1.54.0"
mime-types@~2.1.34:
version "2.1.35"
resolved "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz"
integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==
dependencies:
mime-db "1.52.0"
minimatch@^3.0.5, minimatch@^3.1.1, minimatch@^3.1.2:
version "3.1.2"
resolved "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz"
@ -2775,6 +2633,15 @@ mongodb-uri@^0.9.7:
resolved "https://registry.npmjs.org/mongodb-uri/-/mongodb-uri-0.9.7.tgz"
integrity sha512-s6BdnqNoEYfViPJgkH85X5Nw5NpzxN8hoflKLweNa7vBxt2V7kaS06d74pAtqDxde8fn4r9h4dNdLiFGoNV0KA==
mongodb@6:
version "6.21.0"
resolved "https://registry.npmjs.org/mongodb/-/mongodb-6.21.0.tgz"
integrity sha512-URyb/VXMjJ4da46OeSXg+puO39XH9DeQpWCslifrRn9JWugy0D+DvvBvkm2WxmHe61O/H19JM66p1z7RHVkZ6A==
dependencies:
"@mongodb-js/saslprep" "^1.3.0"
bson "^6.10.4"
mongodb-connection-string-url "^3.0.2"
mongodb@^2.0.35:
version "2.2.36"
resolved "https://registry.npmjs.org/mongodb/-/mongodb-2.2.36.tgz"
@ -2793,15 +2660,6 @@ mongodb@~6.20.0:
bson "^6.10.4"
mongodb-connection-string-url "^3.0.2"
mongodb@6:
version "6.21.0"
resolved "https://registry.npmjs.org/mongodb/-/mongodb-6.21.0.tgz"
integrity sha512-URyb/VXMjJ4da46OeSXg+puO39XH9DeQpWCslifrRn9JWugy0D+DvvBvkm2WxmHe61O/H19JM66p1z7RHVkZ6A==
dependencies:
"@mongodb-js/saslprep" "^1.3.0"
bson "^6.10.4"
mongodb-connection-string-url "^3.0.2"
mongoose@^8.19.4:
version "8.19.4"
resolved "https://registry.npmjs.org/mongoose/-/mongoose-8.19.4.tgz"
@ -2827,16 +2685,16 @@ mquery@5.0.0:
dependencies:
debug "4.x"
ms@^2.1.1, ms@^2.1.3, ms@2.1.3:
version "2.1.3"
resolved "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz"
integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==
ms@2.0.0:
version "2.0.0"
resolved "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz"
integrity sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==
ms@2.1.3, ms@^2.1.1, ms@^2.1.3:
version "2.1.3"
resolved "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz"
integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==
msgpack-js@0.3.0:
version "0.3.0"
resolved "https://registry.npmjs.org/msgpack-js/-/msgpack-js-0.3.0.tgz"
@ -2861,16 +2719,16 @@ natural-compare@^1.4.0:
resolved "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz"
integrity sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==
negotiator@^1.0.0:
version "1.0.0"
resolved "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz"
integrity sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==
negotiator@0.6.3:
version "0.6.3"
resolved "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz"
integrity sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==
negotiator@^1.0.0:
version "1.0.0"
resolved "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz"
integrity sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==
netmask@^2.0.2:
version "2.0.2"
resolved "https://registry.npmjs.org/netmask/-/netmask-2.0.2.tgz"
@ -3197,7 +3055,7 @@ prettier-linter-helpers@^1.0.0:
dependencies:
fast-diff "^1.1.2"
prettier@^3.6.2, prettier@>=3.0.0:
prettier@^3.6.2:
version "3.6.2"
resolved "https://registry.npmjs.org/prettier/-/prettier-3.6.2.tgz"
integrity sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==
@ -3221,24 +3079,6 @@ prop-types@^15.8.1:
object-assign "^4.1.1"
react-is "^16.13.1"
protobufjs@^7.2.5, protobufjs@^7.5.3:
version "7.5.4"
resolved "https://registry.npmjs.org/protobufjs/-/protobufjs-7.5.4.tgz"
integrity sha512-CvexbZtbov6jW2eXAvLukXjXUW1TzFaivC46BpWc/3BpcCysb5Vffu+B3XHMm8lVEuy2Mm4XGex8hBSg1yapPg==
dependencies:
"@protobufjs/aspromise" "^1.1.2"
"@protobufjs/base64" "^1.1.2"
"@protobufjs/codegen" "^2.0.4"
"@protobufjs/eventemitter" "^1.1.0"
"@protobufjs/fetch" "^1.1.0"
"@protobufjs/float" "^1.0.2"
"@protobufjs/inquire" "^1.1.0"
"@protobufjs/path" "^1.1.2"
"@protobufjs/pool" "^1.1.0"
"@protobufjs/utf8" "^1.1.0"
"@types/node" ">=13.7.0"
long "^5.0.0"
proxy-addr@^2.0.7:
version "2.0.7"
resolved "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz"
@ -3361,17 +3201,16 @@ readdirp@~3.6.0:
dependencies:
picomatch "^2.2.1"
redis@^4.6.14:
version "4.7.1"
resolved "https://registry.npmjs.org/redis/-/redis-4.7.1.tgz"
integrity sha512-S1bJDnqLftzHXHP8JsT5II/CtHWQrASX5K96REjWjlmWKrviSOLWmM7QnRLstAWsu1VBBV1ffV6DzCvxNP0UJQ==
redis@^5.10.0:
version "5.10.0"
resolved "https://registry.yarnpkg.com/redis/-/redis-5.10.0.tgz#c1b26ba2acd9c5fcc0d1724a3c7f0984ca43f48b"
integrity sha512-0/Y+7IEiTgVGPrLFKy8oAEArSyEJkU0zvgV5xyi9NzNQ+SLZmyFbUsWIbgPcd4UdUh00opXGKlXJwMmsis5Byw==
dependencies:
"@redis/bloom" "1.2.0"
"@redis/client" "1.6.1"
"@redis/graph" "1.1.1"
"@redis/json" "1.0.7"
"@redis/search" "1.2.0"
"@redis/time-series" "1.1.0"
"@redis/bloom" "5.10.0"
"@redis/client" "5.10.0"
"@redis/json" "5.10.0"
"@redis/search" "5.10.0"
"@redis/time-series" "5.10.0"
reflect.getprototypeof@^1.0.6, reflect.getprototypeof@^1.0.9:
version "1.0.10"
@ -3404,6 +3243,11 @@ regexpp@^3.0.0:
resolved "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz"
integrity sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==
require-directory@^2.1.1:
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==
require_optional@~1.0.0:
version "1.0.1"
resolved "https://registry.npmjs.org/require_optional/-/require_optional-1.0.1.tgz"
@ -3412,11 +3256,6 @@ require_optional@~1.0.0:
resolve-from "^2.0.0"
semver "^5.1.0"
require-directory@^2.1.1:
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==
resolve-from@^2.0.0:
version "2.0.0"
resolved "https://registry.npmjs.org/resolve-from/-/resolve-from-2.0.0.tgz"
@ -3491,7 +3330,7 @@ safe-array-concat@^1.1.3:
has-symbols "^1.1.0"
isarray "^2.0.5"
safe-buffer@^5.0.1, safe-buffer@5.2.1:
safe-buffer@5.2.1, safe-buffer@^5.0.1:
version "5.2.1"
resolved "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz"
integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==
@ -3523,12 +3362,7 @@ safe-regex-test@^1.1.0:
resolved "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz"
integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==
semver@^5.1.0:
version "5.7.2"
resolved "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz"
integrity sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==
semver@^5.6.0:
semver@^5.1.0, semver@^5.6.0:
version "5.7.2"
resolved "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz"
integrity sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==
@ -3538,12 +3372,7 @@ semver@^6.3.1:
resolved "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz"
integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==
semver@^7.0.0, semver@^7.5.3, semver@^7.5.4, semver@^7.7.3:
version "7.7.3"
resolved "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz"
integrity sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==
semver@^7.3.8:
semver@^7.0.0, semver@^7.3.8, semver@^7.5.3, semver@^7.5.4, semver@^7.7.3:
version "7.7.3"
resolved "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz"
integrity sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==
@ -3743,7 +3572,7 @@ socks-proxy-agent@^8.0.5:
debug "^4.3.4"
socks "^2.8.3"
socks@^2.7.1, socks@^2.8.3:
socks@^2.8.3:
version "2.8.7"
resolved "https://registry.npmjs.org/socks/-/socks-2.8.7.tgz"
integrity sha512-HLpt+uLy/pxB+bum/9DzAgiKS8CX1EvbWxI4zlmgGCExImLdiad2iCwXT5Z4c9c3Eq8rP2318mPW2c+QbtjK8A==
@ -3788,16 +3617,16 @@ standard@^17.1.2:
standard-engine "^15.1.0"
version-guard "^1.1.1"
statuses@^2.0.1:
version "2.0.2"
resolved "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz"
integrity sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==
statuses@2.0.1:
version "2.0.1"
resolved "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz"
integrity sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==
statuses@^2.0.1:
version "2.0.2"
resolved "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz"
integrity sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==
stop-iteration-iterator@^1.1.0:
version "1.1.0"
resolved "https://registry.npmjs.org/stop-iteration-iterator/-/stop-iteration-iterator-1.1.0.tgz"
@ -3824,13 +3653,6 @@ streamx@^2.15.0, streamx@^2.21.0:
fast-fifo "^1.3.2"
text-decoder "^1.1.0"
string_decoder@~1.0.0:
version "1.0.3"
resolved "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz"
integrity sha512-4AH6Z5fzNNBcH+6XDMfA/BTt87skxqJlO0lAh3Dker5zThcAxG6mKz+iGu308UKoPPQ8Dcqx/4JhujzltRa+hQ==
dependencies:
safe-buffer "~5.1.0"
string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3:
version "4.2.3"
resolved "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz"
@ -3899,6 +3721,13 @@ string.prototype.trimstart@^1.0.8:
define-properties "^1.2.1"
es-object-atoms "^1.0.0"
string_decoder@~1.0.0:
version "1.0.3"
resolved "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz"
integrity sha512-4AH6Z5fzNNBcH+6XDMfA/BTt87skxqJlO0lAh3Dker5zThcAxG6mKz+iGu308UKoPPQ8Dcqx/4JhujzltRa+hQ==
dependencies:
safe-buffer "~5.1.0"
strip-ansi@^6.0.0, strip-ansi@^6.0.1:
version "6.0.1"
resolved "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz"
@ -4280,11 +4109,6 @@ y18n@^5.0.5:
resolved "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz"
integrity sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==
yallist@4.0.0:
version "4.0.0"
resolved "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz"
integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==
yargs-parser@^21.1.1:
version "21.1.1"
resolved "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz"