commit f9284ed1f2e75919a1682b1e27e985443ffa83a0 Author: Tom Butcher Date: Sun Sep 6 23:06:27 2026 +0100 Initial Commit diff --git a/.eslintignore b/.eslintignore new file mode 100644 index 0000000..f40f58f --- /dev/null +++ b/.eslintignore @@ -0,0 +1,13 @@ +node_modules/ +dist/ +build/ +coverage/ +*.min.js +*.bundle.js +package-lock.json +yarn.lock +pnpm-lock.yaml +.env +.env.* +logs/ +*.log \ No newline at end of file diff --git a/.eslintrc.json b/.eslintrc.json new file mode 100644 index 0000000..d18799f --- /dev/null +++ b/.eslintrc.json @@ -0,0 +1,100 @@ +{ + "env": { + "node": true, + "es2022": true + }, + "extends": ["eslint:recommended", "prettier"], + "plugins": ["prettier"], + "parserOptions": { + "ecmaVersion": 2022, + "sourceType": "module" + }, + "rules": { + "prettier/prettier": "error", + "indent": ["error", 2], + "linebreak-style": ["error", "unix"], + "quotes": ["error", "single"], + "semi": ["error", "always"], + "no-unused-vars": ["error", { "argsIgnorePattern": "^_" }], + "no-console": "warn", + "prefer-const": "error", + "no-var": "error", + "object-shorthand": "error", + "prefer-template": "error", + "template-curly-spacing": ["error", "never"], + "arrow-spacing": "error", + "no-duplicate-imports": "error", + "no-useless-rename": "error", + "prefer-destructuring": [ + "error", + { + "array": true, + "object": true + } + ], + "prefer-rest-params": "error", + "prefer-spread": "error", + "no-useless-constructor": "error", + "no-useless-computed-key": "error", + "no-useless-escape": "error", + "no-useless-return": "error", + "no-constant-condition": ["error", { "checkLoops": false }], + "no-empty": ["error", { "allowEmptyCatch": true }], + "no-extra-boolean-cast": "error", + "no-extra-semi": "error", + "no-irregular-whitespace": "error", + "no-multiple-empty-lines": ["error", { "max": 2 }], + "no-trailing-spaces": "error", + "eol-last": "error", + "comma-dangle": ["error", "never"], + "comma-spacing": ["error", { "before": false, "after": true }], + "comma-style": ["error", "last"], + "key-spacing": ["error", { "beforeColon": false, "afterColon": true }], + "keyword-spacing": ["error", { "before": true, "after": true }], + "object-curly-spacing": ["error", "always"], + "array-bracket-spacing": ["error", "never"], + "space-before-blocks": "error", + "space-before-function-paren": [ + "error", + { + "anonymous": "always", + "named": "never", + "asyncArrow": "always" + } + ], + "space-in-parens": ["error", "never"], + "space-infix-ops": "error", + "space-unary-ops": [ + "error", + { + "words": true, + "nonwords": false + } + ], + "spaced-comment": ["error", "always"], + "brace-style": ["error", "1tbs", { "allowSingleLine": true }], + "camelcase": ["error", { "properties": "never" }], + "new-cap": ["error", { "newIsCap": true, "capIsNew": false }], + "new-parens": "error", + "no-array-constructor": "error", + "no-new-object": "error", + "no-new-require": "error", + "no-path-concat": "error", + "no-process-exit": "error", + "no-return-assign": "error", + "no-self-compare": "error", + "no-sequences": "error", + "no-throw-literal": "error", + "no-unmodified-loop-condition": "error", + "no-unused-expressions": "error", + "no-useless-call": "error", + "no-useless-concat": "error", + "no-useless-escape": "error", + "no-void": "error", + "no-warning-comments": "warn", + "prefer-promise-reject-errors": "error", + "require-await": "error", + "yoda": "error" + }, + "ignorePatterns": ["node_modules/", "dist/", "build/", "*.min.js"] +} diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4226d61 --- /dev/null +++ b/.gitignore @@ -0,0 +1,144 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +lerna-debug.log* +.pnpm-debug.log* + +# Diagnostic reports (https://nodejs.org/api/report.html) +report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json + +# Runtime data +pids +*.pid +*.seed +*.pid.lock + +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov + +# Coverage directory used by tools like istanbul +coverage +*.lcov + +# nyc test coverage +.nyc_output + +# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +# Bower dependency directory (https://bower.io/) +bower_components + +# node-waf configuration +.lock-wscript + +# Compiled binary addons (https://nodejs.org/api/addons.html) +build/Release + +# Dependency directories +node_modules/ +jspm_packages/ + +# Snowpack dependency directory (https://snowpack.dev/) +web_modules/ + +# TypeScript cache +*.tsbuildinfo + +# Optional npm cache directory +.npm + +# Optional eslint cache +.eslintcache + +# Optional stylelint cache +.stylelintcache + +# Microbundle cache +.rpt2_cache/ +.rts2_cache_cjs/ +.rts2_cache_es/ +.rts2_cache_umd/ + +# Optional REPL history +.node_repl_history + +# Output of 'npm pack' +*.tgz + +# Yarn Integrity file +.yarn-integrity + +# dotenv environment variable files +.env +.env.development.local +.env.test.local +.env.production.local +.env.local + +# parcel-bundler cache (https://parceljs.org/) +.cache +.parcel-cache + +# Next.js build output +.next +out + +# Nuxt.js build / generate output +.nuxt +dist + +# Gatsby files +.cache/ +# Comment in the public line in if your project uses Gatsby and not Next.js +# https://nextjs.org/blog/next-9-1#public-directory-support +# public + +# vuepress build output +.vuepress/dist + +# vuepress v2.x temp and cache directory +.temp +.cache + +# Docusaurus cache and generated files +.docusaurus + +# Serverless directories +.serverless/ + +# FuseBox cache +.fusebox/ + +# DynamoDB Local files +.dynamodb/ + +# TernJS port file +.tern-port + +# Stores VSCode versions used for testing VSCode extensions +.vscode-test + +# yarn v2 +.yarn/cache +.yarn/unplugged +.yarn/build-state.yml +.yarn/install-state.gz +.pnp.* + +*.DS_STORE + +*.env + +test-results.xml + +# Jenkins generated build metadata +src/buildInfo.json + +DS_STORE +**/DS_Store + +test-results.xml diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 0000000..6059523 --- /dev/null +++ b/.prettierignore @@ -0,0 +1,7 @@ +node_modules/ +dist/ +build/ +*.min.js +package-lock.json +.git/ +.vscode/ diff --git a/Jenkinsfile b/Jenkinsfile new file mode 100644 index 0000000..062bb04 --- /dev/null +++ b/Jenkinsfile @@ -0,0 +1,104 @@ +properties([ + buildDiscarder(logRotator(numToKeepStr: '25')) +]) + +pipeline { + agent { + label 'ubuntu' + } + + environment { + NODE_ENV = 'production' + } + + stages { + stage('Checkout') { + steps { + checkout scm + } + } + + stage('Setup Node.js') { + steps { + nodejs(nodeJSInstallationName: 'Node23') { + sh 'node -v' + sh 'pnpm -v' + } + } + } + + stage('Write Build Metadata') { + steps { + nodejs(nodeJSInstallationName: 'Node23') { + sh ''' + node -e "const fs = require('fs'); fs.writeFileSync('src/buildInfo.json', JSON.stringify({ buildNumber: process.env.BUILD_NUMBER || 'dev' }, null, 2) + '\\n');" + ''' + } + } + } + + stage('Install Dependencies') { + steps { + nodejs(nodeJSInstallationName: 'Node23') { + sh 'pnpm install --frozen-lockfile --production=false' + } + } + } + + stage('Run Tests') { + steps { + nodejs(nodeJSInstallationName: 'Node23') { + sh ''' + export NODE_ENV=test + pnpm test + ''' + } + } + post { + always { + junit 'test-results.xml' + } + } + } + + stage('Deploy via SSH') { + steps { + sshPublisher(publishers: [ + sshPublisherDesc( + configName: 'farmcontrol.tombutcher.local', + transfers: [ + sshTransfer( + cleanRemote: false, + excludes: 'node_modules/**', + execCommand: ''' + cd /home/farmcontrol/farmcontrol-scheduler + pnpm install --production + sudo systemctl restart farmcontrol-scheduler + ''', + execTimeout: 120000, + flatten: false, + makeEmptyDirs: false, + noDefaultExcludes: false, + patternSeparator: '[, ]+', + remoteDirectory: 'farmcontrol-scheduler', + remoteDirectorySDF: false, + removePrefix: '', + sourceFiles: '**/*' + ) + ], + usePromotionTimestamp: false, + useWorkspaceInPromotion: false, + verbose: true + ) + ]) + } + } + } + + post { + always { + cleanWs() + } + } +} + diff --git a/README.md b/README.md new file mode 100644 index 0000000..719de0a --- /dev/null +++ b/README.md @@ -0,0 +1,83 @@ +# Farm Control Scheduler + +[![Build Status](https://ci.tombutcher.work/buildStatus/icon?job=farmcontrol%2Ffarmcontrol-scheduler%2Fmain&style=flat-square)](https://ci.tombutcher.work/job/farmcontrol/job/farmcontrol-scheduler/job/main/) + +A background service for Farm Control ERP that fires time-sensitive events on model objects once their scheduled date/time has passed. + +## Features + +- Scans configured models for scheduled properties (e.g. a "publish at" or "expires at" date) and picks up any that are now due. +- Runs the scheduling loop on a dedicated worker thread, so checks never block the main application event loop. +- Calls `onSchedulerEvent(property, value)` on the relevant model instance once its scheduled time arrives. + +## How it works + +1. On startup, `SchedulerManager` inspects every registered model for a `scheduledProperties` definition and queries the database for any matching objects that are due (or already overdue). +2. Each match is wrapped in a `Scheduler` (`id`, `objectType`, `object`, `property`, `value`) and added to the manager's schedulers list. +3. A worker thread (`schedulerWorker.js`) polls the list on an interval, comparing each scheduler's `value` against the current date/time. +4. When a scheduler is due, the worker notifies the main thread, which calls `object.onSchedulerEvent(property, value)` and removes the scheduler from the list. + +Models opt in to this behaviour by defining `scheduledProperties` on their Mongoose schema and implementing an `onSchedulerEvent(property, value)` method to handle the callback. + +## Prerequisites + +- Node.js (v16 or higher) +- MongoDB server +- Redis server + +## Installation + +1. Clone the repository +2. Install dependencies: + ```bash + npm install + ``` + +## Configuration + +The application uses `config.json` for configuration. At minimum it expects a `server` block controlling logging, plus connection details for MongoDB and Redis: + +```json +{ + "server": { + "logLevel": "debug" + }, + "mongo": { + "uri": "mongodb://localhost:27017/farmcontrol" + }, + "redis": { + "host": "localhost", + "port": 6379 + } +} +``` + +See `config.js` for the full set of supported options. + +## Running the Application + +### Development + +```bash +npm run dev +``` + +### Production + +```bash +npm start +``` + +## Logging + +The application uses [log4js](https://log4js-node.github.io/log4js-node/) for logging. Set the log level under `server.logLevel` in your configuration: + +```json +{ + "server": { + "logLevel": "debug" + } +} +``` + +Available log levels (least to most verbose): `error`, `warn`, `info`, `debug`, `trace`. \ No newline at end of file diff --git a/config.json b/config.json new file mode 100644 index 0000000..70dc026 --- /dev/null +++ b/config.json @@ -0,0 +1,59 @@ +{ + "development": { + "server": { + "logLevel": "trace" + }, + "database": { + "mongo": { + "url": "mongodb://127.0.0.1:27017/farmcontrol" + }, + "redis": { + "host": "localhost", + "port": 6379, + "password": "" + }, + "nats": { + "host": "localhost", + "port": 4222 + } + } + }, + "test": { + "server": { + "logLevel": "error" + }, + "database": { + "mongo": { + "url": "mongodb://127.0.0.1:27017/farmcontrol-test" + }, + "redis": { + "host": "localhost", + "port": 6379, + "password": "" + }, + "nats": { + "host": "localhost", + "port": 4222 + } + } + }, + "production": { + "server": { + "logLevel": "info" + }, + "database": { + "mongo": { + "url": "mongodb://192.168.68.38:27017/farmcontrol" + }, + "redis": { + "host": "localhost", + "port": 6379, + "password": "" + }, + "nats": { + "host": "localhost", + "port": 4222 + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..85eb005 --- /dev/null +++ b/package.json @@ -0,0 +1,63 @@ +{ + "name": "farmcontrol-scheduler", + "version": "1.0.0", + "description": "", + "main": "index.js", + "dependencies": { + "@nats-io/transport-node": "^3.2.0", + "body-parser": "^2.2.0", + "canonical-json": "^0.2.0", + "dayjs": "^1.11.23", + "dotenv": "^17.2.3", + "i": "^0.3.7", + "ioredis": "^6.0.0", + "keycloak-connect": "^26.1.1", + "lodash": "^4.17.23", + "log4js": "^6.9.1", + "mongodb": "^6.21.0", + "mongoose": "^9.9.1", + "nanoid": "^5.1.6", + "node-cache": "^5.1.2", + "node-cron": "^4.2.1", + "nodemon": "^3.1.11", + "pg": "^8.16.3", + "sequelize": "^6.37.7" + }, + "type": "module", + "devDependencies": { + "@babel/cli": "^7.28.3", + "@babel/core": "^7.28.5", + "@babel/node": "^7.28.0", + "@babel/plugin-proposal-class-properties": "^7.18.6", + "@babel/plugin-proposal-object-rest-spread": "^7.20.7", + "@babel/preset-env": "^7.28.5", + "@babel/register": "^7.28.3", + "@jest/globals": "^30.2.0", + "babel-jest": "^30.2.0", + "babel-plugin-transform-import-meta": "^2.3.3", + "concurrently": "^9.2.1", + "eslint": "^9.39.1", + "eslint-config-prettier": "^10.1.8", + "eslint-plugin-prettier": "^5.5.4", + "jest": "^30.2.0", + "jest-junit": "^16.0.0", + "prettier": "^3.6.2", + "sequelize-cli": "^6.6.3", + "standard": "^17.1.2", + "supertest": "^7.1.4" + }, + "scripts": { + "test": "node --experimental-vm-modules ./node_modules/jest/bin/jest.js", + "start": "node src/index.js", + "dev": "nodemon src/index.js", + "lint": "eslint src/", + "lint:fix": "eslint src/ --fix", + "format": "prettier --write \"src/**/*.{js,json}\"", + "format:check": "prettier --check \"src/**/*.{js,json}\"", + "fix": "npm run lint:fix && npm run format", + "ensure-references": "node scripts/ensure-references.js" + }, + "author": "Tom Butcher", + "license": "ISC", + "packageManager": "pnpm@10.28.0" +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml new file mode 100644 index 0000000..aa39347 --- /dev/null +++ b/pnpm-lock.yaml @@ -0,0 +1,11030 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + dependencies: + '@aws-sdk/client-s3': + specifier: ^3.932.0 + version: 3.1127.0 + '@aws-sdk/s3-request-presigner': + specifier: ^3.932.0 + version: 3.1127.0 + '@nats-io/transport-node': + specifier: ^3.2.0 + version: 3.4.0 + axios: + specifier: ^1.13.2 + version: 1.20.0 + bcrypt: + specifier: ^6.0.0 + version: 6.0.0 + blurhash: + specifier: ^2.0.5 + version: 2.0.5 + body-parser: + specifier: ^2.2.0 + version: 2.3.0 + canonical-json: + specifier: ^0.2.0 + version: 0.2.0 + cors: + specifier: ^2.8.5 + version: 2.8.6 + dayjs: + specifier: ^1.11.23 + version: 1.11.23 + diff: + specifier: ^9.0.0 + version: 9.0.0 + dotenv: + specifier: ^17.2.3 + version: 17.4.2 + ejs: + specifier: ^3.1.10 + version: 3.1.10 + exceljs: + specifier: ^4.4.0 + version: 4.4.0 + exifr: + specifier: ^7.1.3 + version: 7.1.3 + express: + specifier: ^5.1.0 + version: 5.2.1 + express-session: + specifier: ^1.18.2 + version: 1.19.0 + i: + specifier: ^0.3.7 + version: 0.3.7 + ioredis: + specifier: ^6.0.0 + version: 6.0.0 + js-beautify: + specifier: ^2.0.3 + version: 2.0.3 + jsonwebtoken: + specifier: ^9.0.2 + version: 9.0.3 + keycloak-connect: + specifier: ^26.1.1 + version: 26.1.1 + lodash: + specifier: ^4.17.23 + version: 4.18.1 + log4js: + specifier: ^6.9.1 + version: 6.9.1 + mongodb: + specifier: ^6.21.0 + version: 6.21.0(socks@2.8.10) + mongoose: + specifier: ^9.9.1 + version: 9.9.5(socks@2.8.10) + multer: + specifier: ^2.0.2 + version: 2.3.0 + nanoid: + specifier: ^5.1.6 + version: 5.1.16 + node-cache: + specifier: ^5.1.2 + version: 5.1.2 + node-cron: + specifier: ^4.2.1 + version: 4.6.0 + nodemailer: + specifier: '*' + version: 10.0.0 + nodemon: + specifier: ^3.1.11 + version: 3.1.14 + pdf-lib: + specifier: ^1.17.1 + version: 1.17.1 + pdf-to-img: + specifier: ^6.2.0 + version: 6.3.0 + pg: + specifier: ^8.16.3 + version: 8.23.0 + posthtml: + specifier: ^0.16.7 + version: 0.16.7 + puppeteer: + specifier: ^24.37.5 + version: 24.43.1 + sequelize: + specifier: ^6.37.7 + version: 6.37.8(pg@8.23.0) + sharp: + specifier: ^0.34.3 + version: 0.34.5 + speedgoose: + specifier: ^2.5.11 + version: 2.5.11(socks@2.8.10) + devDependencies: + '@babel/cli': + specifier: ^7.28.3 + version: 7.29.7(@babel/core@7.29.7) + '@babel/core': + specifier: ^7.28.5 + version: 7.29.7 + '@babel/node': + specifier: ^7.28.0 + version: 7.29.7(@babel/core@7.29.7) + '@babel/plugin-proposal-class-properties': + specifier: ^7.18.6 + version: 7.18.6(@babel/core@7.29.7) + '@babel/plugin-proposal-object-rest-spread': + specifier: ^7.20.7 + version: 7.20.7(@babel/core@7.29.7) + '@babel/preset-env': + specifier: ^7.28.5 + version: 7.29.7(@babel/core@7.29.7) + '@babel/register': + specifier: ^7.28.3 + version: 7.29.7(@babel/core@7.29.7) + '@jest/globals': + specifier: ^30.2.0 + version: 30.5.1 + babel-jest: + specifier: ^30.2.0 + version: 30.5.1(@babel/core@7.29.7) + babel-plugin-transform-import-meta: + specifier: ^2.3.3 + version: 2.3.3(@babel/core@7.29.7) + concurrently: + specifier: ^9.2.1 + version: 9.2.4 + eslint: + specifier: ^9.39.1 + version: 9.39.5 + eslint-config-prettier: + specifier: ^10.1.8 + version: 10.1.8(eslint@9.39.5) + eslint-plugin-prettier: + specifier: ^5.5.4 + version: 5.5.6(eslint-config-prettier@10.1.8(eslint@9.39.5))(eslint@9.39.5)(prettier@3.9.6) + jest: + specifier: ^30.2.0 + version: 30.5.1(@types/node@26.4.1) + jest-junit: + specifier: ^16.0.0 + version: 16.0.0 + prettier: + specifier: ^3.6.2 + version: 3.9.6 + sequelize-cli: + specifier: ^6.6.3 + version: 6.6.5 + standard: + specifier: ^17.1.2 + version: 17.1.2 + supertest: + specifier: ^7.1.4 + version: 7.2.2 + +packages: + + '@aws-sdk/checksums@3.1000.29': + resolution: {integrity: sha512-Dtu0gr4dnATZAPwEYbpCsG+MpLM7OAliy2gTepEFQwl1vZ6DL3QMH2FveMa3HLvPsOdhJsPRB3KtxVhph9T75A==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/client-s3@3.1127.0': + resolution: {integrity: sha512-0ZSAgmEda33xPqVPt+bx2KzrXV1cUCKRRVPGliLu+V7DzHPa04CqPSi1maGEM4O0LDP0iI6HRSW5ULloNwayNw==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/core@3.977.9': + resolution: {integrity: sha512-reqPFEQrZxDZpeGj4PFMepBeR5LGYHRqq/L0motTzgFkCRBA4rFdaVXDSLYyGHhxVz7sT2PDnPN9CluGSfgyJA==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/credential-provider-env@3.972.70': + resolution: {integrity: sha512-H404B7dJl2mCrBqahDEYsanB0xhdDp6tXnXcTUnXmmpy2Q3J0Ho0bUajZ2jr/RdwzCyS59Gi8xXIFwPLGBl6Uw==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/credential-provider-http@3.972.72': + resolution: {integrity: sha512-X98zYOrVOeuosCX+6ktf29FC2N2GHPLia7qv6mzPzTc+RPAuHWCDS++Z6JK7eGYqb/v6uaW7bAXaOvDBfol+0w==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/credential-provider-ini@3.973.15': + resolution: {integrity: sha512-Rykg6s5ceBuynMOGWgoowO4N+27JfnqXAnVaSunZl0hOO1XodSrxGNz6sCEbnmS0lAfQZDKyb3fbr46gSuv6Sg==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/credential-provider-login@3.972.77': + resolution: {integrity: sha512-Jb59xfEISoN5mmbnA+HYqdtrSX3CgCtJoof+V5D8/TgUI56W63GEEd5Y58WijU3Ou6+WEgaLD1feVzaRXV5IDQ==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/credential-provider-node@3.972.82': + resolution: {integrity: sha512-znDkEOGXB8W3kG1LJUKP3foBZY/9qLM0eil/DxWXSp37XsdsRLQHE/d/OaCGGVgKpA6znR38h/+INk8do1FjiA==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/credential-provider-process@3.972.70': + resolution: {integrity: sha512-2ry03fGRJr4sV3jI+ocjj5JqALnFD6ymM5KiNCDZMvq8bX2GSbE0vji4aM43TVCl2nXqqLRZaUxdq/KeWRAY4Q==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/credential-provider-sso@3.973.14': + resolution: {integrity: sha512-jkhg/8ocAAoc0RFyLMhCw+/zZh7gystQgd4F4hznNa8P4Cc501PQmxd+jGLiMHodPJ+7Zv/3znM62gZojyasmA==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/credential-provider-web-identity@3.972.76': + resolution: {integrity: sha512-d3AGyVu759PGr35mEB2s22xxlNEA5rpdxtSPJthfPFJvoQ8dt357iVPECqWfUxXp1toJAvKmbtcIYVGigaGsCA==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/middleware-sdk-s3@3.972.75': + resolution: {integrity: sha512-wMIsNumRVKaNMKhvU/s9VrdEwE8S6gSzXp4RygFG5BEMnGkkXf8cjh8zf7cKJBpUDpqTWqwbz5isEgp9rH6Lng==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/nested-clients@3.997.44': + resolution: {integrity: sha512-NhEgryjlBF9w38ZXqGymQV28IhkYa1mKhlbYnqIis57AYwWGVYfUPgg/qC2rLRqOUfblxx++irvju10kVTa8Vw==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/s3-request-presigner@3.1127.0': + resolution: {integrity: sha512-vl3WBaE2fMqfnEhyqulKT6ZIPNsCR2caCps92tyt1yGfLVlDXScSXBDXf4m3E2ejXBb4pplX0EdwBukJyBXtBg==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/signature-v4-multi-region@3.996.46': + resolution: {integrity: sha512-L+2xZTye/2T96f3lwCws0Zw6GG2JHZW9e8FpVgGBeeExSKyeoZ6CWRpBml/7DNiK/O26jrgPM9F+Ay8VkgzUWQ==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/token-providers@3.1116.0': + resolution: {integrity: sha512-ygIivKqh8aHzNkucOCXHyIBgBpLPfrSI0mCqXF+vLBsPTUKqj0VSqAY0GFPe7lQl4HntjOcQ+KSyS7oUV2C54Q==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/types@3.974.5': + resolution: {integrity: sha512-LkwLL2BLbC6wNNm4JaH9mbEqBMdOZCct6VAYqhdN4U1xrWM+fUJQEfbHwQgDypapOWTRtlk25akb5afM0P8CIQ==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/xml-builder@3.972.40': + resolution: {integrity: sha512-wlFmCIGUlwF4zx/kncw+bmxTQh1HeSJq4mYV/V5cZUSJadDP3kXvGW8Rn21cimj/7y9ju+47oYWXi97vF7czaA==} + engines: {node: '>=20.0.0'} + + '@aws/lambda-invoke-store@0.3.0': + resolution: {integrity: sha512-sl4Bm6yiMNYrZKkqqDFWN0UfnWhlS8ivKxrYl+6t0gCLrqr8y3B2IqZZbFRkfaVVp7C/baApyh71P+LeE1A2sQ==} + engines: {node: '>=18.0.0'} + + '@babel/cli@7.29.7': + resolution: {integrity: sha512-/75HwRbAYPqXv/Ax1h7Fg3IZfXgdU98jnA8H93/m/QBaPV3Hp5ICoLqzGYye1yHBCgpmXvtqgSUN8oOKX5tojQ==} + engines: {node: '>=6.9.0'} + hasBin: true + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/code-frame@7.29.7': + resolution: {integrity: sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==} + engines: {node: '>=6.9.0'} + + '@babel/compat-data@7.29.7': + resolution: {integrity: sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg==} + engines: {node: '>=6.9.0'} + + '@babel/core@7.29.7': + resolution: {integrity: sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==} + engines: {node: '>=6.9.0'} + + '@babel/generator@7.29.8': + resolution: {integrity: sha512-gZbepsdh3WDtgZKWL+vTPh71LSBrm/Y4/QDZBVCcYfmeTEEuoOYwlSy+G1StfJg+/Zy550u/3TATbm7qDbbMtg==} + engines: {node: '>=6.9.0'} + + '@babel/helper-annotate-as-pure@7.29.7': + resolution: {integrity: sha512-OoK6239jHPuSQOoS0kfTVKn0b/rVTk0seKq4Gd2UMLtmOVLjDC0ki3e+c90Trqv2gMfvJFqkiljrr568+qddiw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-compilation-targets@7.29.7': + resolution: {integrity: sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g==} + engines: {node: '>=6.9.0'} + + '@babel/helper-create-class-features-plugin@7.29.7': + resolution: {integrity: sha512-IY3ZD9Tmooqr3TUhc3DUWxiuo8xx1DWLhd5M7hQ+ZWJamqM2BbalrBJb2MisSLoYorOj75U03qULCxQTY9r3hg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/helper-create-regexp-features-plugin@7.29.7': + resolution: {integrity: sha512-907Uymvqgg1dwUA+7IGwFAOSYzQOuzPXKNJ1yxzwPffzkYFg2q2eHi1fIOs6sXkG9NbIUMunnUlkYsfRFNvomg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/helper-define-polyfill-provider@0.6.8': + resolution: {integrity: sha512-47UwBLPpQi1NoWzLuHNjRoHlYXMwIJoBf7MFou6viC/sIHWYygpvr0B6IAyh5sBdA2nr2LPIRww8lfaUVQINBA==} + peerDependencies: + '@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0 + + '@babel/helper-globals@7.29.7': + resolution: {integrity: sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==} + engines: {node: '>=6.9.0'} + + '@babel/helper-member-expression-to-functions@7.29.7': + resolution: {integrity: sha512-j+7JYmk1JYDtACIGj0QJqqWZjoUpMoEikQGADMaHgCMCSDqd2+P32rfcibUNrGOMWrlzK1WJBdxrB3JJQZwWtg==} + engines: {node: '>=6.9.0'} + + '@babel/helper-module-imports@7.29.7': + resolution: {integrity: sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g==} + engines: {node: '>=6.9.0'} + + '@babel/helper-module-transforms@7.29.7': + resolution: {integrity: sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/helper-optimise-call-expression@7.29.7': + resolution: {integrity: sha512-+kmGVjcT9RGYzoDwdwEqEvGgKe3BYq+O1iGzjFubaNgZHwYHP6lsF2Yghf4kEuv9BV7tYDZ913aBW9am6YKong==} + engines: {node: '>=6.9.0'} + + '@babel/helper-plugin-utils@7.29.7': + resolution: {integrity: sha512-G7sHYigPY17oO5SYWnfD/0MTBwVR781S/JI643e/JhUYgVgWE/61SoW3NH9KWUKyKq5LVh3npif99Wkt6j86Jw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-remap-async-to-generator@7.29.7': + resolution: {integrity: sha512-16AMiW26DbXWBbr3B8wNozKM0ydMLB892vaOaJW/fPJdnT8vJk5sdkQcU/isqUxyCE0cEoa8wZOcbgDuC4b6Og==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/helper-replace-supers@7.29.7': + resolution: {integrity: sha512-atfGXWSeCiF4DnKZIfmJfQRkSw9b9gNNXR1kqKjbhG4pGYCOnkp8OcTB8E3NXjBu8NpheSnOeNKz8KT7UNFTmQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/helper-skip-transparent-expression-wrappers@7.29.7': + resolution: {integrity: sha512-brcMGQaVzIeUb+6/bs1Av0f8YuNNjKY2JyvfRCsFuFsdKccEQ5Ges2y74D74NZ1Rz8lKJ9ksJkfqwQFJ/iNEyQ==} + engines: {node: '>=6.9.0'} + + '@babel/helper-string-parser@7.29.7': + resolution: {integrity: sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-identifier@7.29.7': + resolution: {integrity: sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-option@7.29.7': + resolution: {integrity: sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-wrap-function@7.29.7': + resolution: {integrity: sha512-iES0Skag9ERIF68aXadpO6dbXa03mNWK3sEqJaMnLNs/eC3l0lkImdfoy6Y09/SfkpawdAB4RjQ7PVA7TcVGdw==} + engines: {node: '>=6.9.0'} + + '@babel/helpers@7.29.7': + resolution: {integrity: sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg==} + engines: {node: '>=6.9.0'} + + '@babel/node@7.29.7': + resolution: {integrity: sha512-nfdPXz8/mD3/t+1nE1DKwGR14Ccjt5xeF7u3g7sqWnLi4yR6n+9Z0kThIROF8SRM07ZKpEtiWSKpWKxsMiJeew==} + engines: {node: '>=6.9.0'} + hasBin: true + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/parser@7.29.8': + resolution: {integrity: sha512-E8lTAYNB1KW+FH+VGJuZM1ioAx2E6oVlvQFRrf5P8ZZmsiJXYAD9vTFV7yyEURNzgh1dFqMZuO6tUwcARbqFCA==} + engines: {node: '>=6.0.0'} + hasBin: true + + '@babel/plugin-bugfix-firefox-class-in-computed-class-key@7.29.7': + resolution: {integrity: sha512-j8SrR0zLZrRsC09DlszEx8FpMiwukKffYXMK0d5LmOglO7vGG6sz/BR/20yHqWH+Lnn31JTt2PE3hIWNgM2J6w==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/plugin-bugfix-safari-class-field-initializer-scope@7.29.7': + resolution: {integrity: sha512-r8j8escF+U2FUHo0KOhPUdMzUO+jp9fInva6+ACVAF3Y97Ev+5iNZwiqTghmzNeWwDkOPlYuTcfb1vDaoZKmAQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@7.29.7': + resolution: {integrity: sha512-GE1TFSiuFeGsCxmYXZl8HwoPrVlwe4rHPFE8weieGKZqnDORK+Ar3vgWMgW+AOxQ6/2TgLSKx9p6W7O4rC6qgQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/plugin-bugfix-safari-rest-destructuring-rhs-array@7.29.7': + resolution: {integrity: sha512-oBNVCvnO5tND+xSopWvV8WNGfpTfgP4Zr/YXXSj8zfmcPktp5Ku/aZlsIowgSD4fjmgHn6sGmB9APVsU5zOdhA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@7.29.7': + resolution: {integrity: sha512-QQt9qKHZ2sg/kivaLr7lnQr8HVrQDdBNSfCsTjiDxRuX/K5ORyKq+Bu8Xr0cDE3Dfkv0cw28Ve0EKyKMvulkOw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.13.0 + + '@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly@7.29.7': + resolution: {integrity: sha512-pn6QacGLgvCcwc+syUhKE/qSjV2D1IHDB84RNxWYSt1mW3K/SCtjinZ2p0cETJxAWBjPy3K/1lHwG5BjjPxNlw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/plugin-proposal-class-properties@7.18.6': + resolution: {integrity: sha512-cumfXOF0+nzZrrN8Rf0t7M+tF6sZc7vhQwYQck9q1/5w2OExlD+b4v4RpMJFaV1Z7WcDRgO6FqvxqxGlwo+RHQ==} + engines: {node: '>=6.9.0'} + deprecated: This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-class-properties instead. + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-proposal-object-rest-spread@7.20.7': + resolution: {integrity: sha512-d2S98yCiLxDVmBmE8UjGcfPvNEUbA1U5q5WxaWFUGRzJSVAZqm5W6MbPct0jxnegUZ0niLeNX+IOzEs7wYg9Dg==} + engines: {node: '>=6.9.0'} + deprecated: This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-object-rest-spread instead. + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-proposal-private-property-in-object@7.21.0-placeholder-for-preset-env.2': + resolution: {integrity: sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-async-generators@7.8.4': + resolution: {integrity: sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-bigint@7.8.3': + resolution: {integrity: sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-class-properties@7.12.13': + resolution: {integrity: sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-class-static-block@7.14.5': + resolution: {integrity: sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-import-assertions@7.29.7': + resolution: {integrity: sha512-/An1OCBN93thpBAGyfsK2pcf0jvju1SAtKkL2Ny++B5Sy6sqgzXDQH1cZxWbF96Wuk+bn41MDA9bLd4VVAw6rw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-import-attributes@7.29.7': + resolution: {integrity: sha512-zGYcYfq/WmZ4V+kBIXQon9dSSc8ircGZqw9ZaNhhGj9nZkeBu1jHLBDQqYYi5WA9uawvA2sIMbry2nCFhf5Djg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-import-meta@7.10.4': + resolution: {integrity: sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-json-strings@7.8.3': + resolution: {integrity: sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-jsx@7.29.7': + resolution: {integrity: sha512-TSu8+mHCoEaaCDEZ0I3+6mvTBYR4PCxQwf2z9/r5Tbztv6NaLR3B9thGTTxX2WGuGHJqRiAbKPeGTJ5XWXVg6A==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-logical-assignment-operators@7.10.4': + resolution: {integrity: sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-nullish-coalescing-operator@7.8.3': + resolution: {integrity: sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-numeric-separator@7.10.4': + resolution: {integrity: sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-object-rest-spread@7.8.3': + resolution: {integrity: sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-optional-catch-binding@7.8.3': + resolution: {integrity: sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-optional-chaining@7.8.3': + resolution: {integrity: sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-private-property-in-object@7.14.5': + resolution: {integrity: sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-top-level-await@7.14.5': + resolution: {integrity: sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-typescript@7.29.7': + resolution: {integrity: sha512-ngr+82Sh0xMz25TPCZi+nC2iTzjfCdWS2ONXTp/PtSCHCgaCNBpdMqgvJ2ccdLlClVZ7sisIgB914j/JFe+RZA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-unicode-sets-regex@7.18.6': + resolution: {integrity: sha512-727YkEAPwSIQTv5im8QHz3upqp92JTWhidIC81Tdx4VJYIte/VndKf1qKrfnnhPLiPghStWfvC/iFaMCQu7Nqg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/plugin-transform-arrow-functions@7.29.7': + resolution: {integrity: sha512-N7zArUXWzAMzm+/N0uPBeVB3Fam5lMxtUwMmDK5f/IBBS7a7p1qeUoxd/6CckXoxUdgsntq1Dh8xNW06maZbDQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-async-generator-functions@7.29.7': + resolution: {integrity: sha512-d98gXZkgswvkyohMBABkhm3GeXhYj8psWfwQ2C7gtfrKGTykQa/iOIi+JJhwMjPlZ6Vm2XN+DCf3Es1EoG4ZLA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-async-to-generator@7.29.7': + resolution: {integrity: sha512-pcUb2SS+RMo9TWVBwKGI5ShtoG7R+zBsFmCKDa6fe8c+hPr3XJlZgoE5j6i8W7gDjhyvy+85vmYexanvXh3d1w==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-block-scoped-functions@7.29.7': + resolution: {integrity: sha512-cUSmjh72N+rN4PrkFlN1dJwNCwjVp5d38/CQrEsFggkD10UiFlBFgdH3tv5dNsLuHY+3S8db2xCHjhZcv5WgvA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-block-scoping@7.29.7': + resolution: {integrity: sha512-ONyr4+AZhKh8yKWInVxU9AXA9EbsyeLcL6V0dJy6M2/62vuvpGm29zzuymbTpdc451GEpDIdAyPLP3r+P61yKQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-class-properties@7.29.7': + resolution: {integrity: sha512-GtcpjFvanPfzNQi3eTitsCqtRRmmqzpy/A+yhTR1HaZo1Ly3EA8ZXxlPyHdR8/IuRMYc3E4wdGBewB2QKQjAaA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-class-static-block@7.29.7': + resolution: {integrity: sha512-kibJgmEdX2iMwsHY2tSZNDgj8PwIlCQz7FK9KuGKO8zsuoUwSEhoNnNVp/emKWrbY4HeO6kkXfdMqRKKKXBm2A==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.12.0 + + '@babel/plugin-transform-classes@7.29.7': + resolution: {integrity: sha512-qV0OGGBVacduzQHE649JyCneOFI/maT+YKsO+K4Yi3xv2wTPNjM/W2o2gdzMwEAZz7fXNTHAe0NcSg30bIN69g==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-computed-properties@7.29.7': + resolution: {integrity: sha512-RK7/IyU5phpuCdBAuig5VkzG/EnbDaui5SQGdU9BFrHdV+mV4cUjLMQ9lJDjLNtWHsqtiefpGZUXQP2BiTYMsA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-destructuring@7.29.7': + resolution: {integrity: sha512-iPX8aD6H9zV5s7ZsqTdNocPN/MGQ5sSMnElKrktxjJRMnB2jN/1p2+R7GkfD6CAYoVFqy5A4XnSIUeGgJzIWpg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-dotall-regex@7.29.7': + resolution: {integrity: sha512-3qc18hsD2RdZiyJNDNc7HQpv6xbncwh8FYtxNFFzclSyh/trPD9KkVR9BDECUjDLvb7yJVF15GfYUuC+LMkkiQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-duplicate-keys@7.29.7': + resolution: {integrity: sha512-6IvRRriEMqnBwD6chtxdLpMYCHWEzN+oL5cyQtjykya19UgzbmKhxmhZgKC/LHxS2nYr9Q/qYPZ5Lr6jOL9+yQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-duplicate-named-capturing-groups-regex@7.29.7': + resolution: {integrity: sha512-2wiIyo2BjtgU7HufSeDnL9L2O7zr8jmhFKuSr65VpRkUiRKRNpb0mdlk56+XPPKoIrfHqzbMuglDvZun0RISsA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/plugin-transform-dynamic-import@7.29.7': + resolution: {integrity: sha512-giOlEm/EFjfjr+te9NsdjkUo2v4f8rS/SXPumRVHAtbNcyNlvtREkU1dZzaIDclNpnaVhlCqRdFKhJBjBikzLg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-explicit-resource-management@7.29.7': + resolution: {integrity: sha512-Rstj7coNz8sE+7Ju7ihpHLI564lsK5pUpNNlvptCIC/16E/S5hbl6n3kESPKdNRmqEWlpn5xpS5Q2dvXBsySLw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-exponentiation-operator@7.29.7': + resolution: {integrity: sha512-zFpMOTLZBdW5LfObqcSbL6kefg4R4eLdmvS0wbN9M6D5Mym/sKm9toOoWyVOa+xDjvCnuWcHls2YonXwHvH3CQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-export-namespace-from@7.29.7': + resolution: {integrity: sha512-24B2nOy2TeJSMheqwPD4DDQOV/elLSIlKxjZt4i05H5AgdPdWR3n18HnNrcJ+j76WJd9gbwb9jPjNYUy6RautA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-for-of@7.29.7': + resolution: {integrity: sha512-zeSIHh0+E1Um1WJRXCFlHQYu2ieJNdivLLjlBEp+dIBu3S51n+SZZmIXjxnItw6pz56Cn+KvK68BIBVsxq2JiQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-function-name@7.29.7': + resolution: {integrity: sha512-otRWaHXE6fbAGkePvaj/kvs3HsqXfPhlnzwSOlnFgbqCPMd975dW+4wZ00WFBt+/YlBGcJwNrARQTOJOb4ZrIg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-json-strings@7.29.7': + resolution: {integrity: sha512-RRnE2+eon1rJAq8MnoF1b5kTpY1vU88twHcvcKMrsqP/jxIRqDVs9iJB5fqPuqyeFAW0wJo4MlUIPpQCq/aRsg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-literals@7.29.7': + resolution: {integrity: sha512-DZ/oLP21ZuWx1vKqnoNv6/tvEK48AQOBRai40CX9dTjGluvT/YZCyY3rryDtyUqCEoyNroy5KKPwX2iQCiRvyw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-logical-assignment-operators@7.29.7': + resolution: {integrity: sha512-A0H91hh6W8MFRkp5TqJmMr39jzGD1A1E1Ysiv2O06Sfbhkapm+XyIzxWCEh5kqwOZ1/8QZ0dY3SeQ7XBqfJd5Q==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-member-expression-literals@7.29.7': + resolution: {integrity: sha512-hl1kwFZCCiDyfH25Xmco9jTrkPgnS9pmOzSG7W5I4SaGbLeqKv417hcU2RKmaxoPEgsoJh7ZPOrnPGq99bHoUg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-modules-amd@7.29.7': + resolution: {integrity: sha512-fxtQoH3m5ywUSIfaH0FGCzWu4McsYon5bD3K4XnskC7f+OyQMj7rsOMi4NvvmJ83WwBAg4UCe+ov4VZlqEvyew==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-modules-commonjs@7.29.7': + resolution: {integrity: sha512-j0vCldybPC5b5dwCQOJ21uKtHzt7hxLygJTg9eF1ScfaikEDNfzn94XoW5Fi+seBR0nCyL23xaBFFkq7dTM8XQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-modules-systemjs@7.29.8': + resolution: {integrity: sha512-6iSnEK0zlkLKU4heofK/AdmRD4e2SHVpJMtrwnTCzhnaM98ria4rTrOXBBi45BTTYnJtO8txnPsX4fChYXkmeA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-modules-umd@7.29.7': + resolution: {integrity: sha512-B4UkaTK3QpgCwJnrxKfMPKdo92CN7OKXAlpAAnM3UPu0Q0lCCk57ylA9AJbRy2v8dDKOPAAWcoR6CMyeoHwRCA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-named-capturing-groups-regex@7.29.7': + resolution: {integrity: sha512-vuFoLwr4qnv2xbZ16SQd6uPcH5FNrLHhk/Jzo++0XJFcaDsr4gjJVg6j398oMHiC+83k/GiBzviwF5KBJkPUtQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/plugin-transform-new-target@7.29.7': + resolution: {integrity: sha512-fEo41GmsOUhOBlw8ioo6zvjX5Xc2Lqkzlyfqbpsk3eB6TReV18uhxZ0esfEokVbY2+PVJAQHNKxER6lGrzNd3A==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-nullish-coalescing-operator@7.29.7': + resolution: {integrity: sha512-idmp1dFaekP9GbcMvG24Kvw2BfhFZjHnNJCkV4WuIY4PskJzwI3f1N5OdgYke38T7rftO6ERulFRn2cFeZwRkg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-numeric-separator@7.29.7': + resolution: {integrity: sha512-zR7fv/z14OjgHl4AgRtkDBvBMhIzCxqV/qN/2BCRC7LjFwvuzjYe7gDWxC4Wl/SNsLM6SE1IWvRPYMgSJaUvNw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-object-rest-spread@7.29.7': + resolution: {integrity: sha512-Ld98jn4c0smUywL57m7SgsHq3OpThOa6LqZJif3G6jYOovPleoFhVrBJ1WegRApSFB2wu4+RelAj9AC9G08Z4A==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-object-super@7.29.7': + resolution: {integrity: sha512-Ea/diGcw0twB5IlZPO5sgET6fJsLJqPABqTuFWIR+iMPGPZJkATEIWx0wa+aEQ5UY1CBQyP/gkAiLEqn1vBiQA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-optional-catch-binding@7.29.7': + resolution: {integrity: sha512-sLsyndxK2VwX6yNUOakMb7Sh553ZTe/vVM1XJ+9Z5aW1ytsc8xOIwmyk05NNjN60vkc5/KqoTH6hB4V41LJhng==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-optional-chaining@7.29.7': + resolution: {integrity: sha512-6GM1dhvK3gNODkXcEcMCOLEDCLSoZ/sBbro2Ax8HURyasQ4NshagQixkRFdh5niI6E4gmA/jYI/4aT7rRos3ZQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-parameters@7.29.7': + resolution: {integrity: sha512-ZDOBqV/qLYJI0YElr8DcENEyARsFQeESqWXH6gZlghYXuPPjvweuDhP4VyEi4BlUBlLRFZVjxoZDMjxhLW766g==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-private-methods@7.29.7': + resolution: {integrity: sha512-/6Rz4DK1ETDEM/bWHsPHcaEe7ZaT1EqSXjtSP/L0DijOYuaUhiRiOKcwpZ8P7zR4xXEHc2ITdiCgBm9Tpyv9ug==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-private-property-in-object@7.29.7': + resolution: {integrity: sha512-+BNo06dnrzdNNqCm1X6YUaVv0DKk8Q+JYcoZfOkLhYWNCXzlwTSRq8zGWayT1csjcpNXV9CQTBRRbmTLZac5cA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-property-literals@7.29.7': + resolution: {integrity: sha512-bOMRLQuI0A5ZqHq3OWJ89/rXpJ/NJrbVhXiP4zwPGMs6kpcVsuTUNjwoE30K0Qm3mf48a/TnRYYD6vPNqcg6jA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-regenerator@7.29.8': + resolution: {integrity: sha512-0UpIXPtdDtMXfnV2OJAVMLpj3H/92vmkA6lpSRakmycJvj3VUy6Xs1dM8tXRugupykr5WB+LpiVl0J8LMVg2mg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-regexp-modifiers@7.29.7': + resolution: {integrity: sha512-mB5Fs0VWrJ42ZCmc8114v60qetdaUVNkj9PmSZRmanCZM3S9hm0CFRLjRmYIsuXav14l2jvZ+4T8iiCGnhj3nQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/plugin-transform-reserved-words@7.29.7': + resolution: {integrity: sha512-5+YhdpVgmfSmwZyLMftfaiffLRMHjzIRHFHHLdibcSyJm2pasMrKHrO3Ptrt2DRshjvpgjEJJ1zVW14WPq/6QA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-shorthand-properties@7.29.7': + resolution: {integrity: sha512-I+WYbGBAiCn7nA6xBrlgPH+MB7HWb4u8pv5S0Pv7OtwNvIFvCCb24YlttKEeUFVurfBCEaOTnuhlqsb7f0Z5Dg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-spread@7.29.8': + resolution: {integrity: sha512-4S9ksMGVWUshvgK0mKfvZky7leuG5/uoFVwMpAomJ8bMoDJiNHRVmc1EglwW/CmGVSqqWpEbXm9FmbRit22qoA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-sticky-regex@7.29.7': + resolution: {integrity: sha512-BCHzNYJGe9l7EpwwDBN/ztlL2NYFFq8hp9ddjtUEM9f2O7S7kKV/lL6Fwo7IF7NSkYhPK2vO+86nIGltA90MsA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-template-literals@7.29.7': + resolution: {integrity: sha512-NCSEJ4sLFU2gqAub45HYh4fus2yQ36rr6ei6vpU7NdoJqCpxvEG8E6eJpscGyXP3VHD2Ny+fSXr04k1hoUrFqA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-typeof-symbol@7.29.7': + resolution: {integrity: sha512-223mNGoTkBiTEWFoK+Q6Go3tueMRclO8vxxxxquNCYuNI4jWOofFKJRRDu6SDrB8Sgo1UEGW9T4GAQ8ZyRso1A==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-unicode-escapes@7.29.7': + resolution: {integrity: sha512-jCfXxSjf94lf4E0hKE0AByxF6F3/pVFqRdUUNkDJhsY0m1ZKjnN6ZYyMeHNpzflxb/0q5b7t3p+BE+SLF1WOtA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-unicode-property-regex@7.29.7': + resolution: {integrity: sha512-OgZ+zoAJgZLUCunsTRQ5LAjOywDv5zzZ2/hQ5aMw1pGXyY2rtE8/chXYUmu3AlVHKpm10KEdG9aMwbI/K76ZGw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-unicode-regex@7.29.7': + resolution: {integrity: sha512-7D/x/23/d/3VqZ0QA+LGbZMlGwZjztBygSWWWsfTPoQ1oQ6Q1P6Mr3d0kk42XabyUVw+fha3LqdRsFqeKqvCyA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-unicode-sets-regex@7.29.7': + resolution: {integrity: sha512-BLOhLht9DOJwIxlmp91wHvkXv1lguuHS3/FwUO8HL1H0u8s4hR1gASVFyilu9iGtcTRYqjTZmlsFFeQletntEg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/preset-env@7.29.7': + resolution: {integrity: sha512-GYzX36n1nsciIb0uyH0GHwxwtNwPQIcpxSeiVLDtG/B7jB5xXgchnmL1f/jCX5o+pwnaDBtO60ONSJhEBJfxYA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/preset-modules@0.1.6-no-external-plugins': + resolution: {integrity: sha512-HrcgcIESLm9aIR842yhJ5RWan/gebQUJ6E/E5+rf0y9o6oj7w0Br+sWuL6kEQ/o/AdfvR1Je9jG18/gnpwjEyA==} + peerDependencies: + '@babel/core': ^7.0.0-0 || ^8.0.0-0 <8.0.0 + + '@babel/register@7.29.7': + resolution: {integrity: sha512-AMGJoWuES861riy6pcB0fphE1YXybtQnBYQMuIyPv6mKLiosfa79BKTnAOyx215c/3RJPJpdQwoHZ3earVH7AA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/template@7.29.7': + resolution: {integrity: sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==} + engines: {node: '>=6.9.0'} + + '@babel/traverse@7.29.8': + resolution: {integrity: sha512-I5z7H3bf/41ktsNVLtpN0wAa336HkqIHQ5BuPLEhTkt1jVSyZpeNKIzTgEWmlxjdg81R0IgUCcaE+Ok3NvrfZg==} + engines: {node: '>=6.9.0'} + + '@babel/types@7.29.8': + resolution: {integrity: sha512-Vj1jF3cPfxg7OAfoI7QnVKLoILlm2JF9pnVHrX8qx7AHMiYWT+NDAA7jChlNgRS4WTLc/fD1lXLmPixluj+3Gg==} + engines: {node: '>=6.9.0'} + + '@bcoe/v8-coverage@0.2.3': + resolution: {integrity: sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==} + + '@emnapi/core@1.10.0': + resolution: {integrity: sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==} + + '@emnapi/runtime@1.10.0': + resolution: {integrity: sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==} + + '@emnapi/runtime@1.11.3': + resolution: {integrity: sha512-Xz4Tpyki7XyrpbUK1jR1AhdAdaXyhhY4lZ3neLodmhpuWfy2PAQN5B46sAiU4liOXGLkHypn/qU+jvfWSCYYLA==} + + '@emnapi/wasi-threads@1.2.1': + resolution: {integrity: sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==} + + '@eslint-community/eslint-utils@4.10.1': + resolution: {integrity: sha512-cuadcxVFE8sDK6iWJbs8Sn0av2Nrh2QSGQhVlBW9AaAHqHwjWsZHT8LJ4hFGPh7ASBV2deFdM7H/DPjulmh8rg==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 + + '@eslint-community/regexpp@4.12.2': + resolution: {integrity: sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==} + engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} + + '@eslint/config-array@0.21.2': + resolution: {integrity: sha512-nJl2KGTlrf9GjLimgIru+V/mzgSK0ABCDQRvxw5BjURL7WfH5uoWmizbH7QB6MmnMBd8cIC9uceWnezL1VZWWw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/config-helpers@0.4.2': + resolution: {integrity: sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/core@0.17.0': + resolution: {integrity: sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/eslintrc@2.1.4': + resolution: {integrity: sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + '@eslint/eslintrc@3.3.7': + resolution: {integrity: sha512-F42g89Qd5oAWtp0k0nnSrjziAKza7w8SVT4mStc18LZMaRb4J1HQAHLCalEtDCxrTuksx7NU9qsmeLwpOfPqWw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/js@8.57.1': + resolution: {integrity: sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + '@eslint/js@9.39.5': + resolution: {integrity: sha512-QywQuszQh77pIXCsq998c8hbhSTI/azTty1Z6N53dmAudKHhy573j3yvRLsX2BSp8YpLtoCEG8E9DJe+8zUh4A==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/object-schema@2.1.7': + resolution: {integrity: sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/plugin-kit@0.4.1': + resolution: {integrity: sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@fast-csv/format@4.3.5': + resolution: {integrity: sha512-8iRn6QF3I8Ak78lNAa+Gdl5MJJBM5vRHivFtMRUWINdevNo00K7OXxS2PshawLKTejVwieIlPmK5YlLu6w4u8A==} + + '@fast-csv/parse@4.3.6': + resolution: {integrity: sha512-uRsLYksqpbDmWaSmzvJcuApSEe38+6NQZBUsuAyMZKqHxH0g1wcJgsKUvN3WC8tewaqFjBMMGrkHmC+T7k8LvA==} + + '@humanfs/core@0.19.2': + resolution: {integrity: sha512-UhXNm+CFMWcbChXywFwkmhqjs3PRCmcSa/hfBgLIb7oQ5HNb1wS0icWsGtSAUNgefHeI+eBrA8I1fxmbHsGdvA==} + engines: {node: '>=18.18.0'} + + '@humanfs/node@0.16.8': + resolution: {integrity: sha512-gE1eQNZ3R++kTzFUpdGlpmy8kDZD/MLyHqDwqjkVQI0JMdI1D51sy1H958PNXYkM2rAac7e5/CnIKZrHtPh3BQ==} + engines: {node: '>=18.18.0'} + + '@humanfs/types@0.15.0': + resolution: {integrity: sha512-ZZ1w0aoQkwuUuC7Yf+7sdeaNfqQiiLcSRbfI08oAxqLtpXQr9AIVX7Ay7HLDuiLYAaFPu8oBYNq/QIi9URHJ3Q==} + engines: {node: '>=18.18.0'} + + '@humanwhocodes/config-array@0.13.0': + resolution: {integrity: sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==} + engines: {node: '>=10.10.0'} + deprecated: Use @eslint/config-array instead + + '@humanwhocodes/module-importer@1.0.1': + resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==} + engines: {node: '>=12.22'} + + '@humanwhocodes/object-schema@2.0.3': + resolution: {integrity: sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==} + deprecated: Use @eslint/object-schema instead + + '@humanwhocodes/retry@0.4.3': + resolution: {integrity: sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==} + engines: {node: '>=18.18'} + + '@img/colour@1.1.0': + resolution: {integrity: sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ==} + engines: {node: '>=18'} + + '@img/sharp-darwin-arm64@0.34.5': + resolution: {integrity: sha512-imtQ3WMJXbMY4fxb/Ndp6HBTNVtWCUI0WdobyheGf5+ad6xX8VIDO8u2xE4qc/fr08CKG/7dDseFtn6M6g/r3w==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm64] + os: [darwin] + + '@img/sharp-darwin-x64@0.34.5': + resolution: {integrity: sha512-YNEFAF/4KQ/PeW0N+r+aVVsoIY0/qxxikF2SWdp+NRkmMB7y9LBZAVqQ4yhGCm/H3H270OSykqmQMKLBhBJDEw==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [x64] + os: [darwin] + + '@img/sharp-libvips-darwin-arm64@1.2.4': + resolution: {integrity: sha512-zqjjo7RatFfFoP0MkQ51jfuFZBnVE2pRiaydKJ1G/rHZvnsrHAOcQALIi9sA5co5xenQdTugCvtb1cuf78Vf4g==} + cpu: [arm64] + os: [darwin] + + '@img/sharp-libvips-darwin-x64@1.2.4': + resolution: {integrity: sha512-1IOd5xfVhlGwX+zXv2N93k0yMONvUlANylbJw1eTah8K/Jtpi15KC+WSiaX/nBmbm2HxRM1gZ0nSdjSsrZbGKg==} + cpu: [x64] + os: [darwin] + + '@img/sharp-libvips-linux-arm64@1.2.4': + resolution: {integrity: sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw==} + cpu: [arm64] + os: [linux] + + '@img/sharp-libvips-linux-arm@1.2.4': + resolution: {integrity: sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A==} + cpu: [arm] + os: [linux] + + '@img/sharp-libvips-linux-ppc64@1.2.4': + resolution: {integrity: sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA==} + cpu: [ppc64] + os: [linux] + + '@img/sharp-libvips-linux-riscv64@1.2.4': + resolution: {integrity: sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA==} + cpu: [riscv64] + os: [linux] + + '@img/sharp-libvips-linux-s390x@1.2.4': + resolution: {integrity: sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ==} + cpu: [s390x] + os: [linux] + + '@img/sharp-libvips-linux-x64@1.2.4': + resolution: {integrity: sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw==} + cpu: [x64] + os: [linux] + + '@img/sharp-libvips-linuxmusl-arm64@1.2.4': + resolution: {integrity: sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw==} + cpu: [arm64] + os: [linux] + + '@img/sharp-libvips-linuxmusl-x64@1.2.4': + resolution: {integrity: sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg==} + cpu: [x64] + os: [linux] + + '@img/sharp-linux-arm64@0.34.5': + resolution: {integrity: sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm64] + os: [linux] + + '@img/sharp-linux-arm@0.34.5': + resolution: {integrity: sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm] + os: [linux] + + '@img/sharp-linux-ppc64@0.34.5': + resolution: {integrity: sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [ppc64] + os: [linux] + + '@img/sharp-linux-riscv64@0.34.5': + resolution: {integrity: sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [riscv64] + os: [linux] + + '@img/sharp-linux-s390x@0.34.5': + resolution: {integrity: sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [s390x] + os: [linux] + + '@img/sharp-linux-x64@0.34.5': + resolution: {integrity: sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [x64] + os: [linux] + + '@img/sharp-linuxmusl-arm64@0.34.5': + resolution: {integrity: sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm64] + os: [linux] + + '@img/sharp-linuxmusl-x64@0.34.5': + resolution: {integrity: sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [x64] + os: [linux] + + '@img/sharp-wasm32@0.34.5': + resolution: {integrity: sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [wasm32] + + '@img/sharp-win32-arm64@0.34.5': + resolution: {integrity: sha512-WQ3AgWCWYSb2yt+IG8mnC6Jdk9Whs7O0gxphblsLvdhSpSTtmu69ZG1Gkb6NuvxsNACwiPV6cNSZNzt0KPsw7g==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm64] + os: [win32] + + '@img/sharp-win32-ia32@0.34.5': + resolution: {integrity: sha512-FV9m/7NmeCmSHDD5j4+4pNI8Cp3aW+JvLoXcTUo0IqyjSfAZJ8dIUmijx1qaJsIiU+Hosw6xM5KijAWRJCSgNg==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [ia32] + os: [win32] + + '@img/sharp-win32-x64@0.34.5': + resolution: {integrity: sha512-+29YMsqY2/9eFEiW93eqWnuLcWcufowXewwSNIT6UwZdUUCrM3oFjMWH/Z6/TMmb4hlFenmfAVbpWeup2jryCw==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [x64] + os: [win32] + + '@ioredis/commands@1.10.0': + resolution: {integrity: sha512-UmeW7z4LfctwoQ5wkhVzgq8tXkreED2xZGpX+Bg+zA+WJFZCT6c062AfCK/Dfk81xZnnwdhJCUMkitihRaoC2Q==} + + '@ioredis/commands@2.0.0': + resolution: {integrity: sha512-vrx0AE/T0h7cRZwfo1M39Cr+ZhZrkf0V8mQN75wucKCxCLD9l/VX6no3gFvrLqD1IlG/1LtzWovqEw3t0Vr9zg==} + + '@isaacs/cliui@8.0.2': + resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} + engines: {node: '>=12'} + + '@istanbuljs/load-nyc-config@1.1.0': + resolution: {integrity: sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==} + engines: {node: '>=8'} + + '@istanbuljs/schema@0.1.6': + resolution: {integrity: sha512-+Sg6GCR/wy1oSmQDFq4LQDAhm3ETKnorxN+y5nbLULOR3P0c14f2Wurzj3/xqPXtasLFfHd5iRFQ7AJt4KH2cw==} + engines: {node: '>=8'} + + '@jest/console@30.5.1': + resolution: {integrity: sha512-u5Ncuc+gXVUwjNMFQOnphHo2Qx2DxyC8Dvpmf4HCx4y0kvSkRRNiQYRixrvOP4V7y52JcSuNxqochCt0PpdHVg==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + '@jest/core@30.5.1': + resolution: {integrity: sha512-BL9g6CJUUhIbdoAflz/Va658erSSXUIvU8XUYiWNTbZljJjZ5yaC9EJ9/dQ19rpbYV3ZOK4sBACqhPaTyhoUIA==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + peerDependencies: + node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 + peerDependenciesMeta: + node-notifier: + optional: true + + '@jest/diff-sequences@30.5.0': + resolution: {integrity: sha512-OsqBjHXCn8cadasoAZBP6nWYvMsRhpMzGXTpxJ5aO04NlbdhIz+FVe3q49l0AwVhsz/cEmIpBes6gAFl1/dWQg==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + '@jest/environment@30.5.1': + resolution: {integrity: sha512-eYJAkOsrpwDXPcoLNEG6lN9Zo3Cy35pxnVQD74vyIfsi/Q8wB/lZFsEjU4wrecOgT4ZviQDZaX/cFIJyMdMxTw==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + '@jest/expect-utils@30.5.1': + resolution: {integrity: sha512-WcRWhHQdTMRDpyWKZ/6MINBmovI7zeD+bL8wFjCncRV3NQOwKy1X45IfyblfHR4k/XciIlNEdFL9QjFO+HNKOg==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + '@jest/expect@30.5.1': + resolution: {integrity: sha512-uOGd40P/COyUp9xHf5jeGiGJC2/ANg+2+Tk9/xN5/LxmlY/r/gxsPrx3DTEtaRZsLAX4wgNSLEDELZ5bmVs2bA==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + '@jest/fake-timers@30.5.1': + resolution: {integrity: sha512-rEkV6YzpBXo/L9cnj2ibyuYZuyGiNWaPUsyCu3HYJbqCV4jnEiKmf7hId20z8eKjZ0JQ9jtIYKxRSmYB/OYSsA==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + '@jest/get-type@30.5.0': + resolution: {integrity: sha512-9/2VUPitAjmBzbvDvqrxmvB7BzWsBW0WmkkojX1ODuxX1NLGxx9gfaZpHB0z8DtJ9uhGNmZG/VXBhf8uO0OV8Q==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + '@jest/globals@30.5.1': + resolution: {integrity: sha512-VhqvQ251XIC7pk46YymI3HCeBLOdvriDw9zibekodAHEwoVvbVVgpU5H13GvmyOAzxtZCfsm1uvzqkaqJf2I+g==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + '@jest/pattern@30.5.0': + resolution: {integrity: sha512-HdNQYSdRTEBNrginaqzQtTjG0HRMfrra/z6Ok7uL3S87vSlarIVohEsJsSj5edu3MiHoHjAkvPROz5ZjoKai+w==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + '@jest/reporters@30.5.1': + resolution: {integrity: sha512-RbUXIfv85KxitJn4l3MpAoMilkvXe2QCO5lXxHWftywo/VdZ5vkEoHRDgphcxP6qmoIkUaN8/UZj6NhdkIFdJg==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + peerDependencies: + node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 + peerDependenciesMeta: + node-notifier: + optional: true + + '@jest/schemas@30.5.0': + resolution: {integrity: sha512-/hunigyNpc4RCjC0VaW3f5RCUZVM2+WQ65qP7z083Gmvac7or2LI50XVNOtE4YPgBpV0yxYiAgorAPGniCoJmg==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + '@jest/snapshot-utils@30.5.1': + resolution: {integrity: sha512-V3wnxNtiVmw5PPVg433Cn3VdXnsOeu/ofLw3KC04Bn2y1wIlU5kozXQn48rhrgj/02LrCVtntTEF1yBha0XSUw==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + '@jest/source-map@30.5.0': + resolution: {integrity: sha512-xWpTJP9D0bDFGbPGT8XuWSwwha/iHADyyKzUnMx4UbdgnHugxrDaQFO4RZ8x4ZsFzRP6pNii8uvlgKCDxCIuDg==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + '@jest/test-result@30.5.1': + resolution: {integrity: sha512-A/1S6ZBdpic50E0pxLgvaB9XNPL4k7AksmG69OO2oiotxciWZwHkbOec+qbIi+uUtIIByOVlXJUl97oZUsz+Jw==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + '@jest/test-sequencer@30.5.1': + resolution: {integrity: sha512-SHcPnrjdVRYJv6y6l4JUTy4Jccu7zmO6BmiOfOA34UiVBto22s19WiDC0A/2qfM7MWB/qdcoqfSIRMitpjTT4A==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + '@jest/transform@30.5.1': + resolution: {integrity: sha512-EDnDhn0jleU9ZhpVoA4gvqw+Ev0iw/r5upNT2b79RiwiaTiYAMMhvNJP3lmocjIe5J2ZkoNr0B3K/J6O5GIm4Q==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + '@jest/types@30.5.1': + resolution: {integrity: sha512-LvVYn83nnXPl+Rg98nvcFgjx6nRMTArhSn6RAX/w3ELn54S8A42TYZvsCMdGUqTM8S0wyXbtlQdU6Hi6dykj9g==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + '@jridgewell/gen-mapping@0.3.13': + resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==} + + '@jridgewell/remapping@2.3.5': + resolution: {integrity: sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==} + + '@jridgewell/resolve-uri@3.1.2': + resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} + engines: {node: '>=6.0.0'} + + '@jridgewell/sourcemap-codec@1.6.0': + resolution: {integrity: sha512-T7jf+5zgsZHwNJ4lvQ7/aezbyk0nNX+zJVWpmHA7VYsEx7a7qr5Rg5IbtJFqkgze5Y2sruq1RUY8Q837Od7iFw==} + + '@jridgewell/trace-mapping@0.3.31': + resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} + + '@mongodb-js/saslprep@1.5.0': + resolution: {integrity: sha512-Hk1SKJCMcCos38+vqDnZzlIo4XRj9yCGzYkjB4LcqpeXRIYfia1UWTz+VrueLxoU+uSRJzgkufxoRZg8gi52YA==} + + '@napi-rs/canvas-android-arm64@0.1.100': + resolution: {integrity: sha512-hjhCKhntPv9+t4ckHymdx0phYNcVW+GKQR6Lzw2zE+pOVjOplSmtx9nNNknTjbEDLcuLZqA1y8ufKg1XfgftzQ==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [android] + + '@napi-rs/canvas-darwin-arm64@0.1.100': + resolution: {integrity: sha512-2PcswRaC7Ly645DGt88///zuFDhJxJYdKAs1uU3mfk1atYkXufgcgLfBpk6Tm12nCQBaNt1wpybuPZ4qOhTo8A==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [darwin] + + '@napi-rs/canvas-darwin-x64@0.1.100': + resolution: {integrity: sha512-ePNZtj7pNIva/siZMg+HmbeozkIjqUIYdoymH8HaA3qK7LfzFN4WMBM8G6HQ9ZC+H3+Dnn5pqtiXpgLykaPOhw==} + engines: {node: '>= 10'} + cpu: [x64] + os: [darwin] + + '@napi-rs/canvas-linux-arm-gnueabihf@0.1.100': + resolution: {integrity: sha512-d5cDB48oWFGU8/XPhUOFAlySgb/VAu7D+s8fi55K1Pcfg8aPplHWqMgibhVLU8ky7Pyg/fuiVLz4Nf3JrSTuUA==} + engines: {node: '>= 10'} + cpu: [arm] + os: [linux] + + '@napi-rs/canvas-linux-arm64-gnu@0.1.100': + resolution: {integrity: sha512-rDxgxRu69RvDlX/bh9o22DxLsGr8EqsNgotL9+RwQE1S0b0cqeatqsw6aW45mukm0B42DIAaAacKaYQ8cqS1nw==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + + '@napi-rs/canvas-linux-arm64-musl@0.1.100': + resolution: {integrity: sha512-K3mDW66N+xT2/V439u1alFANiBUjdEx2gLiNYnCmUsva5jZMxWTjafBYwTzYK+EMFMHrUoabuU+T1BIP5CgbYQ==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + + '@napi-rs/canvas-linux-riscv64-gnu@0.1.100': + resolution: {integrity: sha512-mooqUBTIsccZpnoQC4NgrC1v6C1vof39etLNMnBwCY+p0gajWJvAHLGQ6g/gGyS5YrpDW+GefSN4+Cvcr08UWw==} + engines: {node: '>= 10'} + cpu: [riscv64] + os: [linux] + + '@napi-rs/canvas-linux-x64-gnu@0.1.100': + resolution: {integrity: sha512-1eCvkDCazm7FFhsT7DfGOdSaHgZVK3bt/dSBl5EWHOWmnz+I7j8tPseJqqD81NF+MH21jKUK4wQSDjN0mdhnTg==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + + '@napi-rs/canvas-linux-x64-musl@0.1.100': + resolution: {integrity: sha512-20arT6lnI19S68qNlii73TSEDbECNgzMz2EpldC1V3mZFuRkeujXkcebRk0LRJe9SEUAooYiLokfMViY8IX7yA==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + + '@napi-rs/canvas-win32-arm64-msvc@0.1.100': + resolution: {integrity: sha512-DZFFT1wIAg37LJw37yhMRFfjATd3vTQzjZ1Yki8u2vhO6Hi5VE6BVaGQ1aaDu7xb4iMErz+9EOwjpS7xcxFeBw==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [win32] + + '@napi-rs/canvas-win32-x64-msvc@0.1.100': + resolution: {integrity: sha512-MyT1j3mHC2+Lu4pBi9mKyMJhtP6U7k7EldY7sj/uS5gJA65gTXt8MefJQXLJo5d/vZbuWmfxzkEUNc/urV3pHA==} + engines: {node: '>= 10'} + cpu: [x64] + os: [win32] + + '@napi-rs/canvas@0.1.100': + resolution: {integrity: sha512-xglYA6q3XO5P3BNJYxVZ1IV7DLVjp1Py6nwag88YntrS+3vKHyYcMqXVS4ZztJmwz2uGvz1FWhI/4LgbR5uQDA==} + engines: {node: '>= 10'} + + '@napi-rs/wasm-runtime@1.2.3': + resolution: {integrity: sha512-UMduMbqO5s5zF2NkNacMT/yK5Y5QiKvWr2+50bzIIxFDwVJ2h49b+oyjaCGPhJxd2/gC2x39EHv/gHVuu36x2Q==} + engines: {node: ^20.19.0 || ^22.13.0 || >=23.5.0} + peerDependencies: + '@emnapi/core': ^1.7.1 || ^2.0.0-alpha.4 + '@emnapi/runtime': ^1.7.1 || ^2.0.0-alpha.4 + + '@nats-io/nats-core@3.4.0': + resolution: {integrity: sha512-QMDM86EUNm+wudlKC67HLar/KHHQUvJGLfb4jbahje3pUk3K9afeck9fwsxBZF0eUFox6T/TA6m/t+lQqf+QsQ==} + + '@nats-io/nkeys@2.0.3': + resolution: {integrity: sha512-JVt56GuE6Z89KUkI4TXUbSI9fmIfAmk6PMPknijmuL72GcD+UgIomTcRWiNvvJKxA01sBbmIPStqJs5cMRBC3A==} + engines: {node: '>=18.0.0'} + + '@nats-io/nuid@3.0.0': + resolution: {integrity: sha512-QbXZDrxmYlrn5AD06gYcUTmEHwxn96HBQMIk7XTjDlEcx6FPzVBBPjp4AMRh1lEv6B4iJ6Xb/Uz61JugPXFRQg==} + engines: {node: '>= 22.x'} + + '@nats-io/transport-node@3.4.0': + resolution: {integrity: sha512-hH7u7ejIBTFEJIZ8rIcMrHJI6wl+HhpO5sVFs1+ppmXa8RuB2+Lh1+UwTzZ5xTNNm1TKcRkYy+2qCV56qp8RxA==} + engines: {node: '>= 18.0.0'} + + '@nicolo-ribaudo/chokidar-2@2.1.8-no-fsevents.3': + resolution: {integrity: sha512-s88O1aVtXftvp5bCPB7WnmXc5IwOZZ7YPuwNPt+GtOOXpPvad1LfbmjYv+qII7zP6RU2QGnqve27dnLycEnyEQ==} + + '@noble/hashes@1.8.0': + resolution: {integrity: sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==} + engines: {node: ^14.21.3 || >=16} + + '@nodelib/fs.scandir@2.1.5': + resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} + engines: {node: '>= 8'} + + '@nodelib/fs.stat@2.0.5': + resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==} + engines: {node: '>= 8'} + + '@nodelib/fs.walk@1.2.8': + resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} + engines: {node: '>= 8'} + + '@one-ini/wasm@0.1.1': + resolution: {integrity: sha512-XuySG1E38YScSJoMlqovLru4KTUNSjgVTIjyh7qMX6aNN5HY5Ct5LhRJdxO79JtTzKfzV/bnWpz+zquYrISsvw==} + + '@one-ini/wasm@0.2.1': + resolution: {integrity: sha512-TUqERXGNTifZ9y2g3wPxQrw3HpHv/02DsW3D90T9x0hhonrL1ZqpSmNrU2XkoIq0fP1N6gZfVQzy2Fw1ZvGBNg==} + + '@paralleldrive/cuid2@2.3.1': + resolution: {integrity: sha512-XO7cAxhnTZl0Yggq6jOgjiOHhbgcO4NqFqwSmQpjK3b6TEE6Uj/jfSk6wzYyemh3+I0sHirKSetjQwn5cZktFw==} + + '@parcel/watcher-android-arm64@2.6.0': + resolution: {integrity: sha512-trgpLSCKRC/huFjXX/Smh+0sWe4+YtKfktIToiMl59ghz7z+qkH6kMvNnUbLyRs9N11t8l4svSCs1+5B3rOAhA==} + engines: {node: '>= 10.0.0'} + cpu: [arm64] + os: [android] + + '@parcel/watcher-darwin-arm64@2.6.0': + resolution: {integrity: sha512-Y3QV0gl7Q1zbfueunkWIERICbEojQFCgpyG7YqOGNFLsckXyI1xu9mAIUpKY9QBYzBtSkN8dBPwd3yiAO9ovMw==} + engines: {node: '>= 10.0.0'} + cpu: [arm64] + os: [darwin] + + '@parcel/watcher-darwin-x64@2.6.0': + resolution: {integrity: sha512-Ohv6OpzhUfKYD7Beb8kDvG0jbIxORCYY1JRdZnaBtnjjkJxgD7ZVL0nw2sCYd0yTMKTvz3nnTnOF3cDifK+kvw==} + engines: {node: '>= 10.0.0'} + cpu: [x64] + os: [darwin] + + '@parcel/watcher-freebsd-x64@2.6.0': + resolution: {integrity: sha512-5HmXvDgs8VK+74jF9y9/2FE3/OnlcKmc56tjmSrEuZjpSZOGL+fvAu+HKJBdPs9uwoP2hE6TlSUpXZ/C5jUFmQ==} + engines: {node: '>= 10.0.0'} + cpu: [x64] + os: [freebsd] + + '@parcel/watcher-linux-arm-glibc@2.6.0': + resolution: {integrity: sha512-Ps/hui3A+vMbjdqlqAowK2ZL8+BO8dBjxeWXj6npTBs3jx4wWmbPpaLuqwrQrSqIVMCnpWo238bJ1U37GhQOYg==} + engines: {node: '>= 10.0.0'} + cpu: [arm] + os: [linux] + + '@parcel/watcher-linux-arm-musl@2.6.0': + resolution: {integrity: sha512-9c6AUHgHoG+IY88MRIHupztQiQnrbqHYQjkM2btA+Bf/wQnQMuiD0Wfk1EVv3TlNT3x41uU71rn6E4xh/+zvkw==} + engines: {node: '>= 10.0.0'} + cpu: [arm] + os: [linux] + + '@parcel/watcher-linux-arm64-glibc@2.6.0': + resolution: {integrity: sha512-yHRqS2owEXe6Hic9z6Mh1ECsCd+ODVOGvZDyciqRd21+v+o+DnXMOrw50DSpIG2sb8GPEaPPmfeCAWKPJdq46g==} + engines: {node: '>= 10.0.0'} + cpu: [arm64] + os: [linux] + + '@parcel/watcher-linux-arm64-musl@2.6.0': + resolution: {integrity: sha512-WhB2e/V7rqdHHWZusBSPuy5Ei8S6lSz6FE5TKKQz5h3a0O+C+mhY7vxU9b/stqvMb8beLnPY82ZrFTLKs+SrKA==} + engines: {node: '>= 10.0.0'} + cpu: [arm64] + os: [linux] + + '@parcel/watcher-linux-x64-glibc@2.6.0': + resolution: {integrity: sha512-ulGE6x6Oz6iAwg75T8YQSoguBWasniIbX+QWpaYPcCnDOpdWX3k+4xbEYPZVLxOuoJI+svJJPD3sEj8G7lrQ3A==} + engines: {node: '>= 10.0.0'} + cpu: [x64] + os: [linux] + + '@parcel/watcher-linux-x64-musl@2.6.0': + resolution: {integrity: sha512-tkBYKt7YQrjIJWYDnto2YgO8MRkjlMTSNoRHzsXinBqbLdeOM3L32wPZJvIZxqaLMfSlS/4sUjH/6STVP/XDLw==} + engines: {node: '>= 10.0.0'} + cpu: [x64] + os: [linux] + + '@parcel/watcher-win32-arm64@2.6.0': + resolution: {integrity: sha512-gIZAP23jaHjGWasY/TY6yL7NHFClf0Ga7FN+iINvk+KN94rhm94lYZhFsbYFNcA04/onvGD9kKmiJLJB2HbNwQ==} + engines: {node: '>= 10.0.0'} + cpu: [arm64] + os: [win32] + + '@parcel/watcher-win32-x64@2.6.0': + resolution: {integrity: sha512-cA+/pXV2YkfxlIcXOQ5fSWqAzzPyD78/x5qbK/I0vUkrlYHA8TIz+MXjAbGouguKVSI4bOmkTSJ1/poVSsgt+A==} + engines: {node: '>= 10.0.0'} + cpu: [x64] + os: [win32] + + '@parcel/watcher@2.6.0': + resolution: {integrity: sha512-7FNeNl8NCE7aINx7WXiKQrPYZWC/hvrTsmk6zmxbI7LTXE7hVek/n8AfVgpe2y82zl3w0HvCHN0bVKMBoJcC0w==} + engines: {node: '>= 10.0.0'} + + '@pdf-lib/standard-fonts@1.0.0': + resolution: {integrity: sha512-hU30BK9IUN/su0Mn9VdlVKsWBS6GyhVfqjwl1FjZN4TxP6cCw0jP2w7V3Hf5uX7M0AZJ16vey9yE0ny7Sa59ZA==} + + '@pdf-lib/upng@1.0.1': + resolution: {integrity: sha512-dQK2FUMQtowVP00mtIksrlZhdFXQZPC+taih1q4CvPZ5vqdxR/LKBaFg0oAfzd1GlHZXXSPdQfzQnt+ViGvEIQ==} + + '@pkgjs/parseargs@0.11.0': + resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} + engines: {node: '>=14'} + + '@pkgr/core@0.3.6': + resolution: {integrity: sha512-SEeaJLb3qBNF/OaXnaR1NmmBbFYk1zC0ZH/52fATcRPLFg/p791YrcyFFy44Bo9sLaGuSuLp5Q6axbb/O+v/RA==} + engines: {node: ^14.18.0 || >=16.0.0} + + '@puppeteer/browsers@2.13.2': + resolution: {integrity: sha512-5EUZSUIc37H6aIXyWO0Z4y8NlF8NnjgmqeQgOGiswAU7pY0HOo16ho4+alIWmSfdZnjqBRawMsP3I5YqLSn6kw==} + engines: {node: '>=18'} + hasBin: true + + '@rtsao/scc@1.1.0': + resolution: {integrity: sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==} + + '@sinclair/typebox@0.34.52': + resolution: {integrity: sha512-XiMQh7qqVlxZzcVD+kkGMNGMzcTrDMLWI7S4x7z1MkCkbDPrekpZXEUK0eZqZFMuHQg2a2DZOcDIh9o5v3Gonw==} + + '@sinonjs/commons@3.0.1': + resolution: {integrity: sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==} + + '@sinonjs/fake-timers@15.4.0': + resolution: {integrity: sha512-DsG+8/LscQIQg68J6Ef3dv10u6nVyetYn923s3/sus5eaGfTo1of5WMZSLf0UJc9KDuKPilPH0UDJCjvNbDNCA==} + + '@smithy/core@3.33.3': + resolution: {integrity: sha512-CsOeKq/9kA3y6VJHt+/+VTCtBaxJ4OTFpgrjIUhPpDIKxBci1k2bJaQASF2h/ELWrulGp+t97DZ0mevfAD8idg==} + engines: {node: '>=18.0.0'} + + '@smithy/credential-provider-imds@4.5.2': + resolution: {integrity: sha512-A9uSdn72ozbRUSit0eib0TW7nXuNPlaeM0zcGkJ+nE6tFcSDbnmtwoxbTCFBukVQcszDAyvsd7+rTduPTXpygg==} + engines: {node: '>=18.0.0'} + + '@smithy/fetch-http-handler@5.8.0': + resolution: {integrity: sha512-ycSJu3tFAQ4v04CBB0agqFMVsSQ1iG3yw+SpgxRqKfaURpQD4CZ8Wn0zPMmSnOuTpTh65Vz+EA0rMrw089wvkA==} + engines: {node: '>=18.0.0'} + + '@smithy/node-http-handler@4.12.1': + resolution: {integrity: sha512-ThMkboGeONWXAelq9FvGsuJC4rOi+qyC4/zhUF58xYpxUg5sQKx2VXZYJmtNjr4dSuBJ1HeJXETQILCz3wOHvw==} + engines: {node: '>=18.0.0'} + + '@smithy/signature-v4@5.7.3': + resolution: {integrity: sha512-7ImGm+FkHRLcBaRttIAMZ6bzJZWb2cJGoYjq46F2UjycujWzrL9GEN9h4w7eQyXJYnltrUhxbbieBAIRrdqpow==} + engines: {node: '>=18.0.0'} + + '@smithy/types@4.18.0': + resolution: {integrity: sha512-CgB6HHWer/vrKps24ulRIbpcpb7K4xAU7SkZ7YHzBPlwHsvsrCJFEXK421s+cJzX+ZrqtA/TuU5w1HzI7k9N8A==} + engines: {node: '>=18.0.0'} + + '@standard-schema/spec@1.1.0': + resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==} + + '@testim/chrome-version@1.1.4': + resolution: {integrity: sha512-kIhULpw9TrGYnHp/8VfdcneIcxKnLixmADtukQRtJUmsVlMg0niMkwV0xZmi8hqa57xqilIHjWFA0GKvEjVU5g==} + + '@tootallnate/quickjs-emscripten@0.23.0': + resolution: {integrity: sha512-C5Mc6rdnsaJDjO3UpGW/CQTHtCKaYlScZTly4JIu97Jxo/odCiH0ITnDXSJPTOrEKk/ycSZ0AOgTmkDtkOsvIA==} + + '@tybys/wasm-util@0.10.3': + resolution: {integrity: sha512-F3fo1MYrRJYL3zER0OUOmkutjr1Vp23m7OsSgp7nq4SP6OqX6C/56XFIPAl5bt3zaBRjmW7SGz3u/6LwFpYcOg==} + + '@types/babel__core@7.20.5': + resolution: {integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==} + + '@types/babel__generator@7.27.0': + resolution: {integrity: sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==} + + '@types/babel__template@7.4.4': + resolution: {integrity: sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==} + + '@types/babel__traverse@7.28.0': + resolution: {integrity: sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==} + + '@types/debug@4.1.13': + resolution: {integrity: sha512-KSVgmQmzMwPlmtljOomayoR89W4FynCAi3E8PPs7vmDVPe84hT+vGPKkJfThkmXs0x0jAaa9U8uW8bbfyS2fWw==} + + '@types/estree@1.0.9': + resolution: {integrity: sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==} + + '@types/istanbul-lib-coverage@2.0.6': + resolution: {integrity: sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==} + + '@types/istanbul-lib-report@3.0.3': + resolution: {integrity: sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==} + + '@types/istanbul-reports@3.0.4': + resolution: {integrity: sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==} + + '@types/json-schema@7.0.15': + resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} + + '@types/json5@0.0.29': + resolution: {integrity: sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==} + + '@types/ms@2.1.0': + resolution: {integrity: sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==} + + '@types/node@14.18.63': + resolution: {integrity: sha512-fAtCfv4jJg+ExtXhvCkCqUKZ+4ok/JQk01qDKhL5BDDoS3AxKXhV5/MAVUZyQnSEd2GT92fkgZl0pz0Q0AzcIQ==} + + '@types/node@26.4.1': + resolution: {integrity: sha512-k97ENvZWtvA6yqz5/FS6a7duDgOPEeOQOc2iKS/nY6mX6qJUKtLnWzQS+Xj6tXweyj6ZcTAK2Qecetnvi9nCLA==} + + '@types/stack-utils@2.0.3': + resolution: {integrity: sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==} + + '@types/validator@13.15.10': + resolution: {integrity: sha512-T8L6i7wCuyoK8A/ZeLYt1+q0ty3Zb9+qbSSvrIVitzT3YjZqkTZ40IbRsPanlB4h1QB3JVL1SYCdR6ngtFYcuA==} + + '@types/webidl-conversions@7.0.3': + resolution: {integrity: sha512-CiJJvcRtIgzadHCYXw7dqEnMNRjhGZlYK05Mj9OyktqV8uVT8fD2BFOB7S1uwBE3Kj2Z+4UyPmFw/Ixgw/LAlA==} + + '@types/whatwg-url@11.0.5': + resolution: {integrity: sha512-coYR071JRaHa+xoEvvYqvnIHaVqaYrLPbsufM9BF63HkwI5Lgmy2QR8Q5K/lYDYo5AK82wOvSOS0UsLTpTG7uQ==} + + '@types/whatwg-url@13.0.0': + resolution: {integrity: sha512-N8WXpbE6Wgri7KUSvrmQcqrMllKZ9uxkYWMt+mCSGwNc0Hsw9VQTW7ApqI4XNrx6/SaM2QQJCzMPDEXE058s+Q==} + + '@types/yargs-parser@21.0.3': + resolution: {integrity: sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==} + + '@types/yargs@17.0.35': + resolution: {integrity: sha512-qUHkeCyQFxMXg79wQfTtfndEC+N9ZZg76HJftDJp+qH2tV7Gj4OJi7l+PiWwJ+pWtW8GwSmqsDj/oymhrTWXjg==} + + '@types/yauzl@2.10.3': + resolution: {integrity: sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==} + + '@ungap/structured-clone@1.4.0': + resolution: {integrity: sha512-1mEZtMKPM09vDmQt5y7YvmN2+DFTP7Tg0EWXdic8/C6VRnpb33e4ghisCIE3WZjsE2N8mf+QV1Zqh7ZFYLWInQ==} + + '@unrs/resolver-binding-android-arm-eabi@1.12.2': + resolution: {integrity: sha512-g5T90pqg1bo/7mytQx6F4iBNC0Wsh9cu+z9veDbFjc7HjpesJFWD7QMS0NGStXM075+7dJPPVvBbpZlnrdpi/w==} + cpu: [arm] + os: [android] + + '@unrs/resolver-binding-android-arm64@1.12.2': + resolution: {integrity: sha512-YGCRZv/9GLhwmz6mYDeTsm/92BAyR28l6c2ReweVW5pWgfsitWLY8upvfRlGdoyD8HjeTHSYJWyZGD4KJA/nFQ==} + cpu: [arm64] + os: [android] + + '@unrs/resolver-binding-darwin-arm64@1.12.2': + resolution: {integrity: sha512-u9DiNT1auQMO20A9SyTuG3wUgQWB9Z7KjAg0uFuCDR1FsAY8A0CG2S6JpHS1xwm/w1G08bjXZDcyOCjv1WAm2w==} + cpu: [arm64] + os: [darwin] + + '@unrs/resolver-binding-darwin-x64@1.12.2': + resolution: {integrity: sha512-f7rPLi/T1HVKZu/u6t87lroib16n8vrSzcyxI7lg4BGO9UF26KhQL44sd9eOUgrTYhvRXtWOIZT5PejdPyJfUA==} + cpu: [x64] + os: [darwin] + + '@unrs/resolver-binding-freebsd-x64@1.12.2': + resolution: {integrity: sha512-BpcOjWCJub6nRZUS2zA20pmLvjtqAtGejETaIyRLiZiQf++cbrjltLA5NN/xaXfqeOBOSlMFbemIl5/S5tljmg==} + cpu: [x64] + os: [freebsd] + + '@unrs/resolver-binding-linux-arm-gnueabihf@1.12.2': + resolution: {integrity: sha512-vZTDvdSISZjJx66OzJqtsOhzifbqRjbmI1Mnu49fQDwog5GtDI4QidRiEAYbZCRj9C8YZEW+3ZjqsyS9GR4k2A==} + cpu: [arm] + os: [linux] + + '@unrs/resolver-binding-linux-arm-musleabihf@1.12.2': + resolution: {integrity: sha512-BiPI+IrIlwcW4nLLMM21+B1dFPzd55yAVgVGrdgDjNef+ch03GdxrcyaIz8X9SsQirh/kCQ7mviyWlMxdh2D7g==} + cpu: [arm] + os: [linux] + + '@unrs/resolver-binding-linux-arm64-gnu@1.12.2': + resolution: {integrity: sha512-zJc0H99FEPoFfSrNpa91HYfxzfAJCr502oxNK1cfdC9hlaFI43RT+JFCann9JUgZmLzzntChHyn13Sgn9ljHNg==} + cpu: [arm64] + os: [linux] + + '@unrs/resolver-binding-linux-arm64-musl@1.12.2': + resolution: {integrity: sha512-KQ3Lki6l+Pz1k/eBipN41ES+YUK30beLGb9YqcB1O542cyLCNE6GaxrfcY3T6EezmGGk84wb5XyO9loTM9tkcA==} + cpu: [arm64] + os: [linux] + + '@unrs/resolver-binding-linux-loong64-gnu@1.12.2': + resolution: {integrity: sha512-3SJGEh1DborhG6pyxvhPzCT4bbSIVihsvgJc13P1bHG7KLdNDaF9T3gsTwFc7Jw/5Y5/iWOjkEx7Zy0NvCGX3Q==} + cpu: [loong64] + os: [linux] + + '@unrs/resolver-binding-linux-loong64-musl@1.12.2': + resolution: {integrity: sha512-jiuG/Obbel7uw1PwHNFfrkiKhLAF6mnyZ6aWlOAVN9WqKm8v0OFGnciJIHu8+CMvXLQ8AD51LPzAoUfT21D5Ew==} + cpu: [loong64] + os: [linux] + + '@unrs/resolver-binding-linux-ppc64-gnu@1.12.2': + resolution: {integrity: sha512-q7xRvVpmcfeL+LlZg8Pbbo6QaTZwDU5BaGZbwfhkEsXJn3Was8xYfE0RBH266xZt0rM6B7i8xAYIvjthuUIWHg==} + cpu: [ppc64] + os: [linux] + + '@unrs/resolver-binding-linux-riscv64-gnu@1.12.2': + resolution: {integrity: sha512-0CVdx6lcnT3Q9inOH8tsMIOJ6ImndllMjqJHg8RLVdB7Vq4SfkEXl9mCSsVNuNA4MCYycRicCUxPCabVHJRr6A==} + cpu: [riscv64] + os: [linux] + + '@unrs/resolver-binding-linux-riscv64-musl@1.12.2': + resolution: {integrity: sha512-iOwlRo9vnp6R6ohHQS11n0NnfdXx/omhkocmIfaPRpQhKZ+3BDMkkdRVh53qjkFkpPddf+FETA28NwGN7l5l+w==} + cpu: [riscv64] + os: [linux] + + '@unrs/resolver-binding-linux-s390x-gnu@1.12.2': + resolution: {integrity: sha512-HYJtLfXq94q8iZNFT1lknx258wlkkWhZeUXJRqzKBBUJ00CvZ+N33zgbCqimLjsyw5Va6uUxhVa12mI+kaveEw==} + cpu: [s390x] + os: [linux] + + '@unrs/resolver-binding-linux-x64-gnu@1.12.2': + resolution: {integrity: sha512-mPsUhunKKDih5O96Y6enDQyHc1SqBPlY1E/SfMWDM3EdJ95Z9CArPeCVwCCqbP45ljvivdEk8Fxn+SIb1rDAJQ==} + cpu: [x64] + os: [linux] + + '@unrs/resolver-binding-linux-x64-musl@1.12.2': + resolution: {integrity: sha512-azrt6+5ydLd8Vt210AAFis/lZevSfPw93EJRIJG+xPu4WCJ8K0kppCTpMyLPcKT7H15M4Jnt2tMp5bOvCkRC6A==} + cpu: [x64] + os: [linux] + + '@unrs/resolver-binding-openharmony-arm64@1.12.2': + resolution: {integrity: sha512-YZ9hP4O0X9PQb8eO980qmLNGH4zT3I9+SZTdt0Pr0YyuGQhYKoOZkV02VzrzyOZJ5xIJ3UFIenKkUkGg8GjgWQ==} + cpu: [arm64] + os: [openharmony] + + '@unrs/resolver-binding-wasm32-wasi@1.12.2': + resolution: {integrity: sha512-tYFDIkMxSflfEc/h92ZWNsZlHSwgimbNHSO3PL2JWQHfCuC2q316jMyYU9TIWZsFK2bQwyK5VAdYgn8ygPj69A==} + engines: {node: '>=14.0.0'} + cpu: [wasm32] + + '@unrs/resolver-binding-win32-arm64-msvc@1.12.2': + resolution: {integrity: sha512-qzNyg3xL0VPQmCaUh+N5jSitce6k+uCBfMDesWRnlULOZaqUkaJ0ybdT+UqlAWJoQjuqfIU/0Ptx9bteN4D82g==} + cpu: [arm64] + os: [win32] + + '@unrs/resolver-binding-win32-ia32-msvc@1.12.2': + resolution: {integrity: sha512-WD9sY00OfpHVGfsnHZoA8jVT+esS/Bg8z8jzxp5BnDCjjwsuKsPQrzswwpFy4J1AUJbXPRfkpcX0mXrzeXW79g==} + cpu: [ia32] + os: [win32] + + '@unrs/resolver-binding-win32-x64-msvc@1.12.2': + resolution: {integrity: sha512-nAB74NfSNKknqQ1RrYj6uz8FcXEomu/MATJZxh/x+BArzN2U3JbOYC0APYzUIGhVY3m5hRxA8VPNdPBoG8txlA==} + cpu: [x64] + os: [win32] + + abbrev@2.0.0: + resolution: {integrity: sha512-6/mh1E2u2YgEsCHdY0Yx5oW+61gZU+1vXaoiHHrpKeuRNNgFvS+/jrwHiQhB5apAf5oB7UB7E19ol2R2LKH8hQ==} + engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + + abbrev@5.0.0: + resolution: {integrity: sha512-/XrFJgzQQQHpti1raDJC6m4ws6aNktmjBlhk8Fdlk7LwCEuDoieEJJY9OFHjfiFJFFRM2tK+Ky/IsfbbmlMu1w==} + engines: {node: ^22.22.2 || ^24.15.0 || >=26.0.0} + + accepts@2.0.0: + resolution: {integrity: sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==} + engines: {node: '>= 0.6'} + + acorn-jsx@5.3.2: + resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} + peerDependencies: + acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 + + acorn@8.18.0: + resolution: {integrity: sha512-lGq+9yr1/GuAWaVYIHRjvvySG5/4VfKIvC8EWxStPdcDh/Ka7FG3twP6v4d5BkravUilhIAsG4Qj83t02LWUPQ==} + engines: {node: '>=0.4.0'} + hasBin: true + + adm-zip@0.6.0: + resolution: {integrity: sha512-XleryMhbuksdKtofnWZ9Sk+4CUTbms4Mb/EU32SZwToAyZ5RgVos/ki8n+yr0LWHOGKuakbXTuuYNHLQjhddgg==} + engines: {node: '>=14.0'} + + agent-base@6.0.2: + resolution: {integrity: sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==} + engines: {node: '>= 6.0.0'} + + agent-base@7.1.4: + resolution: {integrity: sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==} + engines: {node: '>= 14'} + + agent-base@9.0.0: + resolution: {integrity: sha512-TQf59BsZnytt8GdJKLPfUZ54g/iaUL2OWDSFCCvMOhsHduDQxO8xC4PNeyIkVcA5KwL2phPSv0douC0fgWzmnA==} + engines: {node: '>= 20'} + + ajv@6.15.0: + resolution: {integrity: sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==} + + ansi-escapes@4.3.2: + resolution: {integrity: sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==} + engines: {node: '>=8'} + + ansi-regex@5.0.1: + resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} + engines: {node: '>=8'} + + ansi-regex@6.3.0: + resolution: {integrity: sha512-WpDfL7NO6j7tH88IDBNVdUJxDh9nmCteAVW9dsep846XdwF4naCBK+/tGLX3KJgcpgMRXCFlTM2hKGoK9FsdrQ==} + engines: {node: '>=12'} + + ansi-styles@4.3.0: + resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} + engines: {node: '>=8'} + + ansi-styles@5.2.0: + resolution: {integrity: sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==} + engines: {node: '>=10'} + + ansi-styles@6.2.3: + resolution: {integrity: sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==} + engines: {node: '>=12'} + + anymatch@3.1.3: + resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} + engines: {node: '>= 8'} + + append-field@1.0.0: + resolution: {integrity: sha512-klpgFSWLW1ZEs8svjfb7g4qWY0YS5imI82dTg+QahUvJ8YqAY0P10Uk8tTyh9ZGuYEZEMaeJYCF5BFuX552hsw==} + + archiver-utils@2.1.0: + resolution: {integrity: sha512-bEL/yUb/fNNiNTuUz979Z0Yg5L+LzLxGJz8x79lYmR54fmTIb6ob/hNQgkQnIUDWIFjZVQwl9Xs356I6BAMHfw==} + engines: {node: '>= 6'} + + archiver-utils@3.0.4: + resolution: {integrity: sha512-KVgf4XQVrTjhyWmx6cte4RxonPLR9onExufI1jhvw/MQ4BB6IsZD5gT8Lq+u/+pRkWna/6JoHpiQioaqFP5Rzw==} + engines: {node: '>= 10'} + + archiver@5.3.2: + resolution: {integrity: sha512-+25nxyyznAXF7Nef3y0EbBeqmGZgeN/BxHX29Rs39djAfaFalmQ89SE6CWyDCHzGL0yt/ycBtNOmGTW0FyGWNw==} + engines: {node: '>= 10'} + + argparse@1.0.10: + resolution: {integrity: sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==} + + argparse@2.0.1: + resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} + + array-buffer-byte-length@1.0.2: + resolution: {integrity: sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==} + engines: {node: '>= 0.4'} + + array-includes@3.1.9: + resolution: {integrity: sha512-FmeCCAenzH0KH381SPT5FZmiA/TmpndpcaShhfgEN9eCVjnFBqq3l1xrI42y8+PPLI6hypzou4GXw00WHmPBLQ==} + engines: {node: '>= 0.4'} + + array.prototype.findlast@1.2.5: + resolution: {integrity: sha512-CVvd6FHg1Z3POpBLxO6E6zr+rSKEQ9L6rZHAaY7lLfhKsWYUBBOuMs0e9o24oopj6H+geRCX0YJ+TJLBK2eHyQ==} + engines: {node: '>= 0.4'} + + array.prototype.findlastindex@1.2.6: + resolution: {integrity: sha512-F/TKATkzseUExPlfvmwQKGITM3DGTK+vkAsCZoDc5daVygbJBnjEUCbgkAvVFsgfXfX4YIqZ/27G3k3tdXrTxQ==} + engines: {node: '>= 0.4'} + + array.prototype.flat@1.3.3: + resolution: {integrity: sha512-rwG/ja1neyLqCuGZ5YYrznA62D4mZXg0i1cIskIUKSiqF3Cje9/wXAls9B9s1Wa2fomMsIv8czB8jZcPmxCXFg==} + engines: {node: '>= 0.4'} + + array.prototype.flatmap@1.3.3: + resolution: {integrity: sha512-Y7Wt51eKJSyi80hFrJCePGGNo5ktJCslFuboqJsbf57CCPcm5zztluPlc4/aD8sWsKvlwatezpV4U1efk8kpjg==} + engines: {node: '>= 0.4'} + + array.prototype.reduce@1.0.8: + resolution: {integrity: sha512-DwuEqgXFBwbmZSRqt3BpQigWNUoqw9Ml2dTWdF3B2zQlQX4OeUE0zyuzX0fX0IbTvjdkZbcBTU3idgpO78qkTw==} + engines: {node: '>= 0.4'} + + array.prototype.tosorted@1.1.4: + resolution: {integrity: sha512-p6Fx8B7b7ZhL/gmUsAy0D15WhvDccw3mnGNbZpi3pmeJdxtWsj2jEaI4Y6oo3XiHfzuSgPwKc04MYt6KgvC/wA==} + engines: {node: '>= 0.4'} + + arraybuffer.prototype.slice@1.0.4: + resolution: {integrity: sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==} + engines: {node: '>= 0.4'} + + asap@2.0.6: + resolution: {integrity: sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==} + + asn1.js@5.4.1: + resolution: {integrity: sha512-+I//4cYPccV8LdmBLiX8CYvf9Sp3vQsrqu2QNXRcrbiWvcx/UdlFiqUJJzxRQxgsZmvhXhn4cSKeSmoFjVdupA==} + + ast-types@0.13.4: + resolution: {integrity: sha512-x1FCFnFifvYDDzTaLII71vG5uvDwgtmDTEVWAxrgeiR8VjMONcCXJx7E+USjDtHlwFmt9MysbqgF9b9Vjr6w+w==} + engines: {node: '>=4'} + + async-function@1.0.0: + resolution: {integrity: sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==} + engines: {node: '>= 0.4'} + + async@3.2.6: + resolution: {integrity: sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==} + + asynckit@0.4.0: + resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} + + at-least-node@1.0.0: + resolution: {integrity: sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==} + engines: {node: '>= 4.0.0'} + + available-typed-arrays@1.0.7: + resolution: {integrity: sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==} + engines: {node: '>= 0.4'} + + axios@1.20.0: + resolution: {integrity: sha512-r8aOh8j9cGKpgQAqpzrUHnSIc6a59Y3Xf/cv8sy1DrHCkZHzQGEuoq1tARk6qSyDdtQGSDgpb9kFlruzPvrgwg==} + + b4a@1.8.1: + resolution: {integrity: sha512-aiqre1Nr0B/6DgE2N5vwTc+2/oQZ4Wh1t4NznYY4E00y8LCt6NqdRv81so00oo27D8MVKTpUa/MwUUtBLXCoDw==} + peerDependencies: + react-native-b4a: '*' + peerDependenciesMeta: + react-native-b4a: + optional: true + + babel-jest@30.5.1: + resolution: {integrity: sha512-ge1xUVZS91ml09YRMgRGgeKJ4YJpcOiuwteAxFBYLugQyp7cRw+hHej6Ho0vPjvLrjq60bb7JPHH9LAMA1/krA==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + peerDependencies: + '@babel/core': ^7.11.0 || ^8.0.0-0 + + babel-plugin-istanbul@8.0.0: + resolution: {integrity: sha512-18wCskrN3DgbuBmp1gr7LBGT8xdz5xhQQqFvFhVxbkl8VBCrMKQ2YtqBWtUal1Zrc1HTuX0011+Brjw78TCFkg==} + engines: {node: '>=18'} + + babel-plugin-jest-hoist@30.5.0: + resolution: {integrity: sha512-gtGo1B+u14jrZQv6TdSWIWkTqclboo7Qn+dFAGUOIuXLFuJKAdg3U5MIWzZnW4vfUb4dX9skmAMiby86e/SF4A==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + babel-plugin-polyfill-corejs2@0.4.17: + resolution: {integrity: sha512-aTyf30K/rqAsNwN76zYrdtx8obu0E4KoUME29B1xj+B3WxgvWkp943vYQ+z8Mv3lw9xHXMHpvSPOBxzAkIa94w==} + peerDependencies: + '@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0 + + babel-plugin-polyfill-corejs3@0.14.2: + resolution: {integrity: sha512-coWpDLJ410R781Npmn/SIBZEsAetR4xVi0SxLMXPaMO4lSf1MwnkGYMtkFxew0Dn8B3/CpbpYxN0JCgg8mn67g==} + peerDependencies: + '@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0 + + babel-plugin-polyfill-regenerator@0.6.8: + resolution: {integrity: sha512-M762rNHfSF1EV3SLtnCJXFoQbbIIz0OyRwnCmV0KPC7qosSfCO0QLTSuJX3ayAebubhE6oYBAYPrBA5ljowaZg==} + peerDependencies: + '@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0 + + babel-plugin-transform-import-meta@2.3.3: + resolution: {integrity: sha512-bbh30qz1m6ZU1ybJoNOhA2zaDvmeXMnGNBMVMDOJ1Fni4+wMBoy/j7MTRVmqAUCIcy54/rEnr9VEBsfcgbpm3Q==} + peerDependencies: + '@babel/core': ^7.10.0 + + babel-preset-current-node-syntax@1.2.0: + resolution: {integrity: sha512-E/VlAEzRrsLEb2+dv8yp3bo4scof3l9nR4lrld+Iy5NyVqgVYUJnDAmunkhPMisRI32Qc4iRiz425d8vM++2fg==} + peerDependencies: + '@babel/core': ^7.0.0 || ^8.0.0-0 + + babel-preset-jest@30.5.0: + resolution: {integrity: sha512-ZGPn5ClP4lBDpuOK8W1yQIOy359HmbnZv3suucAlIe+SEE9yDsxV4S2PjSbH2Vc97U+WmzYD7vw3kr8NsQ/i6w==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + peerDependencies: + '@babel/core': ^7.11.0 || ^8.0.0-beta.1 || ^8.0.0 + + balanced-match@1.0.2: + resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + + balanced-match@4.0.4: + resolution: {integrity: sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==} + engines: {node: 18 || 20 || >=22} + + bare-events@2.9.2: + resolution: {integrity: sha512-AIPKioV7/Y/8KfZ3AAhjPJxLLbY49S64Ym5DakZlUg75qQiTgUq9hEJoEwa4eUezPUlXRy/i5NpsKvo9jgKmoA==} + peerDependencies: + bare-abort-controller: '*' + peerDependenciesMeta: + bare-abort-controller: + optional: true + + bare-fs@4.8.1: + resolution: {integrity: sha512-N1nnXdHZAOSstz0XiHikGS4HGMH4CnSwhqWdGQQMqqdvp4Jybm9sE3R1WVnpWVd4SFkc8ryPDBLViNLwiEqECg==} + engines: {bare: '>=1.28.0'} + peerDependencies: + bare-buffer: '*' + peerDependenciesMeta: + bare-buffer: + optional: true + + bare-path@3.1.2: + resolution: {integrity: sha512-ZyKbsuuqK6Ag0K8pX6V5Txq6XeJRvY+wXucnFGRjiyVYP9YWDpIQugk/b+enRYrEYBJaqLzghRQpXPMR7341Nw==} + + bare-stream@2.13.4: + resolution: {integrity: sha512-PcrQ8lVLbiJscNm1Kez+Yp4Gy4AHGcN1lzwjvf5NybWen7VvEgUfyfnXYJ2zNqWnzOfCb1Abq6lH8ti0syQszA==} + peerDependencies: + bare-abort-controller: '*' + bare-buffer: '*' + bare-events: '*' + peerDependenciesMeta: + bare-abort-controller: + optional: true + bare-buffer: + optional: true + bare-events: + optional: true + + bare-url@2.5.4: + resolution: {integrity: sha512-Gxa7UVWBr0/edU1b+TJhn/AZvMQUj9OGspvYsaTYQrAbZA4BOTZGL3LiZxvD+CeMlDH4juwD84+eTAp/bLYW5g==} + + base64-js@1.5.1: + resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} + + baseline-browser-mapping@2.11.21: + resolution: {integrity: sha512-uh8vpY/1/YyFkunIDFH/12p7/7VdPKA1hejMVEbdkEaWnUz0Hesvx5EbiU6XxjyHZIOju+ZMbQJkRh+es3/spQ==} + engines: {node: '>=6.0.0'} + hasBin: true + + basic-ftp@5.3.1: + resolution: {integrity: sha512-bopVNp6ugyA150DDuZfPFdt1KZ5a94ZDiwX4hMgZDzF+GttD80lEy8kj98kbyhLXnPvhtIo93mdnLIjpCAeeOw==} + engines: {node: '>=10.0.0'} + + bcrypt@6.0.0: + resolution: {integrity: sha512-cU8v/EGSrnH+HnxV2z0J7/blxH8gq7Xh2JFT6Aroax7UohdmiJJlxApMxtKfuI7z68NvvVcmR78k2LbT6efhRg==} + engines: {node: '>= 18'} + + big-integer@1.6.52: + resolution: {integrity: sha512-QxD8cf2eVqJOOz63z6JIN9BzvVs/dlySa5HGSBH5xtR8dPteIRQnBxxKqkNTiT6jbDTF6jAfrd4oMcND9RGbQg==} + engines: {node: '>=0.6'} + + binary-extensions@2.3.0: + resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==} + engines: {node: '>=8'} + + binary@0.3.0: + resolution: {integrity: sha512-D4H1y5KYwpJgK8wk1Cue5LLPgmwHKYSChkbspQg5JtVuR5ulGckxfR62H3AE9UDkdMC8yyXlqYihuz3Aqg2XZg==} + + bl@4.1.0: + resolution: {integrity: sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==} + + bluebird@3.4.7: + resolution: {integrity: sha512-iD3898SR7sWVRHbiQv+sHUtHnMvC1o3nW5rAcqnq3uOn07DSAppZYUkIGslDz6gXC7HfunPe7YVBgoEJASPcHA==} + + bluebird@3.7.2: + resolution: {integrity: sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==} + + blurhash@2.0.5: + resolution: {integrity: sha512-cRygWd7kGBQO3VEhPiTgq4Wc43ctsM+o46urrmPOiuAe+07fzlSB9OJVdpgDL0jPqXUVQ9ht7aq7kxOeJHRK+w==} + + bn.js@4.12.5: + resolution: {integrity: sha512-3aRg6/JxfffFD+OlOjOFR3Vo79l39ooBTFucxx+MT3dhCtzn3EmiUPQo+6/OZuI2jbXi3YKgmiTFBgChQMwIRQ==} + + body-parser@2.3.0: + resolution: {integrity: sha512-2cGmJupaNgg+QUwVLAucDuWuoMZ6EX9iHDRswZ5lsNYEmwPaRknMPCLZz07yTzVq/83p4o/wzbDZbBrTvGGTIw==} + engines: {node: '>=18'} + + bowser@2.14.1: + resolution: {integrity: sha512-tzPjzCxygAKWFOJP011oxFHs57HzIhOEracIgAePE4pqB3LikALKnSzUyU4MGs9/iCEUuHlAJTjTc5M+u7YEGg==} + + brace-expansion@1.1.18: + resolution: {integrity: sha512-Edep/X9fGqVNmzKBVsDYIOtD+z1tuezV70LBjdCst9Tqu76lsnvRiZ6oTic1n+/BIwX6QDGAO94PN4N2SADvtw==} + + brace-expansion@2.1.4: + resolution: {integrity: sha512-hGfVzPxthbf3+2yjg/RBs60cB0FhqBS/zvdV/4wn4/BmN0bNMMHPc4V/BbFieqf1TKAGGAHnY4eSjajCl0f2Xg==} + + brace-expansion@5.0.9: + resolution: {integrity: sha512-ScQ4IuvIEF1TMlP7Zt+vjJ//9zlPb2SDcxWxM3bk8s6t6GGdJ7KO1dCcTidOPJKePW30LE/2cT7wCyPho9/Wxg==} + engines: {node: 20 || >=22} + + braces@3.0.3: + resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} + engines: {node: '>=8'} + + brorand@1.1.0: + resolution: {integrity: sha512-cKV8tMCEpQs4hK/ik71d6LrPOnpkpGBR0wzxqr68g2m/LB2GxVYQroAjMJZRVM1Y4BCjCKc3vAamxSzOY2RP+w==} + + browserslist@4.28.9: + resolution: {integrity: sha512-EWazOblFYUvlGZcfGhPUPmYh3nikUxBVb+y9MJun5f3hBi812X+8MSQTujLBtgK3cf51fJWbWfOjyeO954d+Eg==} + engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} + hasBin: true + + bser@2.1.1: + resolution: {integrity: sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==} + + bson@6.10.4: + resolution: {integrity: sha512-WIsKqkSC0ABoBJuT1LEX+2HEvNmNKKgnTAyd0fL8qzK4SH2i9NXg+t08YtdZp/V9IZ33cxe3iV4yM0qg8lMQng==} + engines: {node: '>=16.20.1'} + + bson@7.3.2: + resolution: {integrity: sha512-1w0ra+ho1cuE+w8jzwgzTFIimFtCfZeCoOsvIPQg6uyFCsp8M29U7bNNf5GrFh88TXbYg1g3TyKUGpd6O7q6zA==} + engines: {node: '>=20.19.0'} + + buffer-crc32@0.2.13: + resolution: {integrity: sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==} + + buffer-equal-constant-time@1.0.1: + resolution: {integrity: sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==} + + buffer-from@1.1.2: + resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} + + buffer-indexof-polyfill@1.0.2: + resolution: {integrity: sha512-I7wzHwA3t1/lwXQh+A5PbNvJxgfo5r3xulgpYDB5zckTu/Z9oUK9biouBKQUjEqzaz3HnAT6TYoovmE+GqSf7A==} + engines: {node: '>=0.10'} + + buffer@5.7.1: + resolution: {integrity: sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==} + + buffers@0.1.1: + resolution: {integrity: sha512-9q/rDEGSb/Qsvv2qvzIzdluL5k7AaJOTrw23z9reQthrbF7is4CtlT0DXyO1oei2DCp4uojjzQ7igaSHp1kAEQ==} + engines: {node: '>=0.2.0'} + + builtins@5.1.0: + resolution: {integrity: sha512-SW9lzGTLvWTP1AY8xeAMZimqDrIaSdLQUcVr9DMef51niJ022Ri87SwRRKYm4A6iHfkPaiVUu/Duw2Wc4J7kKg==} + + busboy@1.6.0: + resolution: {integrity: sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==} + engines: {node: '>=10.16.0'} + + bytes@3.1.2: + resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==} + engines: {node: '>= 0.8'} + + call-bind-apply-helpers@1.0.2: + resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} + engines: {node: '>= 0.4'} + + call-bind@1.0.9: + resolution: {integrity: sha512-a/hy+pNsFUTR+Iz8TCJvXudKVLAnz/DyeSUo10I5yvFDQJBFU2s9uqQpoSrJlroHUKoKqzg+epxyP9lqFdzfBQ==} + engines: {node: '>= 0.4'} + + call-bound@1.0.4: + resolution: {integrity: sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==} + engines: {node: '>= 0.4'} + + callsites@3.1.0: + resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} + engines: {node: '>=6'} + + camelcase@5.3.1: + resolution: {integrity: sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==} + engines: {node: '>=6'} + + camelcase@6.3.0: + resolution: {integrity: sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==} + engines: {node: '>=10'} + + caniuse-lite@1.0.30001810: + resolution: {integrity: sha512-TITQPUkaz+aVk5GL6NhOdwk1aEaNTSDPsGFWrTuhKGtjTF70jL/Oht2W4c6rXUe5fu7Ie19VIahAXHIIiWWNeg==} + + canonical-json@0.2.0: + resolution: {integrity: sha512-xeH/NgtNA7kIuKSxopJVdXqCKWyDB79aqxQRQ9FV02fvmqW7DSnjoFyzAUrBpfbwjU6lwTYOLc+HM6KupbsfVQ==} + hasBin: true + + chainsaw@0.1.0: + resolution: {integrity: sha512-75kWfWt6MEKNC8xYXIdRpDehRYY/tNSgwKaJq+dbbDcxORuVrrQ+SEHoWsniVn9XPYfP4gmdWIeDk/4YNp1rNQ==} + + chalk@4.1.2: + resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} + engines: {node: '>=10'} + + char-regex@1.0.2: + resolution: {integrity: sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==} + engines: {node: '>=10'} + + chokidar@3.6.0: + resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==} + engines: {node: '>= 8.10.0'} + + chromedriver@152.0.3: + resolution: {integrity: sha512-6v3pquS2kgb1HedzALRZeaoFZVCgghW2u1QjlzaoZruw75QBxKQtv/PKzJ8Scb3L8Vx32gcFwvUKcHmW7xjIVg==} + engines: {node: '>=22'} + hasBin: true + + chromium-bidi@14.0.0: + resolution: {integrity: sha512-9gYlLtS6tStdRWzrtXaTMnqcM4dudNegMXJxkR0I/CXObHalYeYcAMPrL19eroNZHtJ8DQmu1E+ZNOYu/IXMXw==} + peerDependencies: + devtools-protocol: '*' + + ci-info@4.4.0: + resolution: {integrity: sha512-77PSwercCZU2Fc4sX94eF8k8Pxte6JAwL4/ICZLFjJLqegs7kCuAsqqj/70NQF6TvDpgFjkubQB2FW2ZZddvQg==} + engines: {node: '>=8'} + + cjs-module-lexer@2.2.1: + resolution: {integrity: sha512-Ca8swihM+/4yKecYHY52kgJd300hi2lADU/a1RxNTRe+RJ9jvqQlESpbz9DnG9mowez8qwXHB8qYdIUw9e+F5Q==} + + cliui@7.0.4: + resolution: {integrity: sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==} + + cliui@8.0.1: + resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==} + engines: {node: '>=12'} + + clone-deep@4.0.1: + resolution: {integrity: sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==} + engines: {node: '>=6'} + + clone@2.1.2: + resolution: {integrity: sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w==} + engines: {node: '>=0.8'} + + cluster-key-slot@1.1.1: + resolution: {integrity: sha512-rwHwUfXL40Chm1r08yrhU3qpUvdVlgkKNeyeGPOxnW8/SyVDvgRaed/Uz54AqWNaTCAThlj6QAs3TZcKI0xDEw==} + engines: {node: '>=0.10.0'} + + co@4.6.0: + resolution: {integrity: sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==} + engines: {iojs: '>= 1.0.0', node: '>= 0.12.0'} + + collect-v8-coverage@1.0.3: + resolution: {integrity: sha512-1L5aqIkwPfiodaMgQunkF1zRhNqifHBmtbbbxcr6yVxxBnliw4TDOW6NxpO8DJLgJ16OT+Y4ztZqP6p/FtXnAw==} + + color-convert@2.0.1: + resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} + engines: {node: '>=7.0.0'} + + color-name@1.1.4: + resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + + combined-stream@1.0.8: + resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} + engines: {node: '>= 0.8'} + + commander@10.0.1: + resolution: {integrity: sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==} + engines: {node: '>=14'} + + commander@14.0.3: + resolution: {integrity: sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw==} + engines: {node: '>=20'} + + commander@6.2.1: + resolution: {integrity: sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA==} + engines: {node: '>= 6'} + + commondir@1.0.1: + resolution: {integrity: sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==} + + compare-versions@6.1.1: + resolution: {integrity: sha512-4hm4VPpIecmlg59CHXnRDnqGplJFrbLG4aFEl5vl6cK1u76ws3LLvX7ikFnTDl5vo39sjWD6AaDPYodJp/NNHg==} + + component-emitter@1.3.1: + resolution: {integrity: sha512-T0+barUSQRTUQASh8bx02dl+DhF54GtIDY13Y3m9oWTklKbb3Wv974meRpeZ3lp1JpLVECWWNHC4vaG2XHXouQ==} + + compress-commons@4.1.2: + resolution: {integrity: sha512-D3uMHtGc/fcO1Gt1/L7i1e33VOvD4A9hfQLP+6ewd+BvG/gQ84Yh4oftEhAdjSMgBgwGL+jsppT7JYNpo6MHHg==} + engines: {node: '>= 10'} + + concat-map@0.0.1: + resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} + + concat-stream@2.0.0: + resolution: {integrity: sha512-MWufYdFw53ccGjCA+Ol7XJYpAlW6/prSMzuPOTRnJGcGzuhLn4Scrz7qf6o8bROZ514ltazcIFJZevcfbo0x7A==} + engines: {'0': node >= 6.0} + + concurrently@9.2.4: + resolution: {integrity: sha512-TZ0CEhyzvFjgtAvHTusDMgj7wNdihCh7LLLrzdUOXIhdlnL2JBBGA9eJxR24rtqgmdjh3OA3hrN1rCHj6HM8qA==} + engines: {node: '>=18'} + hasBin: true + + config-chain@1.1.13: + resolution: {integrity: sha512-qj+f8APARXHrM0hraqXYb2/bOVSV4PvJQlNZ/DVj0QrmNM2q2euizkeuVckQ57J+W0mRH6Hvi+k50M4Jul2VRQ==} + + content-disposition@1.1.0: + resolution: {integrity: sha512-5jRCH9Z/+DRP7rkvY83B+yGIGX96OYdJmzngqnw2SBSxqCFPd0w2km3s5iawpGX8krnwSGmF0FW5Nhr0Hfai3g==} + engines: {node: '>=18'} + + content-type@1.0.5: + resolution: {integrity: sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==} + engines: {node: '>= 0.6'} + + content-type@2.1.0: + resolution: {integrity: sha512-mj7UPXE0jaqaOsukNZRUEfEi2AcL7C/vwmwcHV0O97eO1E1pxBZuyjlZrx5seTaNBg1U6+o35wpa35Qfcc+7ag==} + engines: {node: '>=18'} + + convert-source-map@2.0.0: + resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} + + cookie-signature@1.0.7: + resolution: {integrity: sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA==} + + cookie-signature@1.2.2: + resolution: {integrity: sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==} + engines: {node: '>=6.6.0'} + + cookie@0.7.2: + resolution: {integrity: sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==} + engines: {node: '>= 0.6'} + + cookiejar@2.1.4: + resolution: {integrity: sha512-LDx6oHrK+PhzLKJU9j5S7/Y3jM/mUHvD/DeI1WQmJn652iPC5Y4TBzC9l+5OMOXlyTTA+SmVUPm0HQUwpD5Jqw==} + + core-js-compat@3.50.0: + resolution: {integrity: sha512-XGpFGbMLHwSt74YLTKho7Ib242qi6O8MSX+sRokV4oz7iKXvQWGYZthjIhjRGMxjzVkAubBO512dKGYcefmX3Q==} + engines: {node: '>=6.4.0'} + + core-js@3.50.0: + resolution: {integrity: sha512-BRWgOLKkFeCgRudR6zrs8p9XJZcE14grzKMMssoYrk6krtuEZ7MTKPIY5RzOnqsEKIR9kst7wNzphttraT+Yqw==} + + core-util-is@1.0.3: + resolution: {integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==} + + cors@2.8.6: + resolution: {integrity: sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==} + engines: {node: '>= 0.10'} + + cosmiconfig@9.0.2: + resolution: {integrity: sha512-gtTZxTDau1wL7Y7zifc2dd8jHSK/k6BTx/2Xp/BpdlAdnlYWFVt7qhJqgwi7637yRwRQ3qL4ZidbB4I8tA5VOg==} + engines: {node: '>=14'} + peerDependencies: + typescript: '>=4.9.5' + peerDependenciesMeta: + typescript: + optional: true + + crc-32@1.2.2: + resolution: {integrity: sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==} + engines: {node: '>=0.8'} + hasBin: true + + crc32-stream@4.0.3: + resolution: {integrity: sha512-NT7w2JVU7DFroFdYkeq8cywxrgjPHWkdX1wjpRQXPX5Asews3tA+Ght6lddQO5Mkumffp3X7GEqku3epj2toIw==} + engines: {node: '>= 10'} + + cross-spawn@7.0.6: + resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} + engines: {node: '>= 8'} + + data-uri-to-buffer@6.0.2: + resolution: {integrity: sha512-7hvf7/GW8e86rW0ptuwS3OcBGDjIi6SZva7hCyWC0yYry2cOPmLIjXAUHI6DK2HsnwJd9ifmt57i8eV2n4YNpw==} + engines: {node: '>= 14'} + + data-uri-to-buffer@8.0.0: + resolution: {integrity: sha512-6UHfyCux51b8PTGDgveqtz1tvphBku5DrMKKJbFAZAJOI2zsjDpDoYE1+QGj7FOMS4BdTFNJsJiR3zEB0xH0yQ==} + engines: {node: '>= 20'} + + data-view-buffer@1.0.2: + resolution: {integrity: sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ==} + engines: {node: '>= 0.4'} + + data-view-byte-length@1.0.2: + resolution: {integrity: sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ==} + engines: {node: '>= 0.4'} + + data-view-byte-offset@1.0.1: + resolution: {integrity: sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==} + engines: {node: '>= 0.4'} + + date-format@4.0.14: + resolution: {integrity: sha512-39BOQLs9ZjKh0/patS9nrT8wc3ioX3/eA/zgbKNopnF2wCqJEoxywwwElATYvRsXdnOxA/OQeQoFZ3rFjVajhg==} + engines: {node: '>=4.0'} + + dayjs@1.11.23: + resolution: {integrity: sha512-QDTCU0M0MxR3hQfnlDJfwekQiaanm1ubOD231u73WBckQ/fsamwRLiE2GBz6D3a/xF1NgfiDLJjXBa1hYOYTtQ==} + + debug@2.6.9: + resolution: {integrity: sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + debug@3.2.7: + resolution: {integrity: sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + debug@4.3.1: + resolution: {integrity: sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + debug@4.4.3: + resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + dedent@1.7.2: + resolution: {integrity: sha512-WzMx3mW98SN+zn3hgemf4OzdmyNhhhKz5Ay0pUfQiMQ3e1g+xmTJWp/pKdwKVXhdSkAEGIIzqeuWrL3mV/AXbA==} + peerDependencies: + babel-plugin-macros: ^3.1.0 + peerDependenciesMeta: + babel-plugin-macros: + optional: true + + deep-is@0.1.4: + resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} + + deepmerge@4.3.1: + resolution: {integrity: sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==} + engines: {node: '>=0.10.0'} + + define-data-property@1.1.4: + resolution: {integrity: sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==} + engines: {node: '>= 0.4'} + + define-properties@1.2.1: + resolution: {integrity: sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==} + engines: {node: '>= 0.4'} + + degenerator@5.0.1: + resolution: {integrity: sha512-TllpMR/t0M5sqCXfj85i4XaAzxmS5tVA16dqvdkMwGmzI+dXLXnw3J+3Vdv7VKw+ThlTMboK6i9rnZ6Nntj5CQ==} + engines: {node: '>= 14'} + + degenerator@7.0.1: + resolution: {integrity: sha512-ABErK0IefDSyHjlPH7WUEenIAX2rPPnrDcDM+TS3z3+zu9TfyKKi07BQM+8rmxpdE2y1v5fjjdoAS/x4D2U60w==} + engines: {node: '>= 20'} + peerDependencies: + quickjs-wasi: ^2.2.0 + + delayed-stream@1.0.0: + resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} + engines: {node: '>=0.4.0'} + + denque@2.1.0: + resolution: {integrity: sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==} + engines: {node: '>=0.10'} + + depd@2.0.0: + resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==} + engines: {node: '>= 0.8'} + + detect-libc@2.1.2: + resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} + engines: {node: '>=8'} + + detect-newline@3.1.0: + resolution: {integrity: sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==} + engines: {node: '>=8'} + + devtools-protocol@0.0.1608973: + resolution: {integrity: sha512-Tpm17fxYzt+J7VrGdc1k8YdRqS3YV7se/M6KeemEqvUbq/n7At1rWVuXMxQgpWkdwSdIEKYbU//Bve+Shm4YNQ==} + + dezalgo@1.0.4: + resolution: {integrity: sha512-rXSP0bf+5n0Qonsb+SVVfNfIsimO4HEtmnIpPHY8Q1UCzKlQrDMfdobr8nJOOsRgWCyMRqeSBQzmWUMq7zvVig==} + + diff@9.0.0: + resolution: {integrity: sha512-svtcdpS8CgJyqAjEQIXdb3OjhFVVYjzGAPO8WGCmRbrml64SPw/jJD4GoE98aR7r25A0XcgrK3F02yw9R/vhQw==} + engines: {node: '>=0.3.1'} + + doctrine@2.1.0: + resolution: {integrity: sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==} + engines: {node: '>=0.10.0'} + + doctrine@3.0.0: + resolution: {integrity: sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==} + engines: {node: '>=6.0.0'} + + dom-serializer@1.4.1: + resolution: {integrity: sha512-VHwB3KfrcOOkelEG2ZOfxqLZdfkil8PtJi4P8N2MMXucZq2yLp75ClViUlOVwyoHEDjYU433Aq+5zWP61+RGag==} + + domelementtype@2.3.0: + resolution: {integrity: sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==} + + domhandler@4.3.1: + resolution: {integrity: sha512-GrwoxYN+uWlzO8uhUXRl0P+kHE4GtVPfYzVLcUxPL7KNdHKj66vvlhiweIHqYYXWlw+T8iLMp42Lm67ghw4WMQ==} + engines: {node: '>= 4'} + + domutils@2.8.0: + resolution: {integrity: sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A==} + + dotenv@17.4.2: + resolution: {integrity: sha512-nI4U3TottKAcAD9LLud4Cb7b2QztQMUEfHbvhTH09bqXTxnSie8WnjPALV/WMCrJZ6UV/qHJ6L03OqO3LcdYZw==} + engines: {node: '>=12'} + + dottie@2.0.7: + resolution: {integrity: sha512-7lAK2A0b3zZr3UC5aE69CPdCFR4RHW1o2Dr74TqFykxkUCBXSRJum/yPc7g8zRHJqWKomPLHwFLLoUnn8PXXRg==} + deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info. + + dunder-proto@1.0.1: + resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} + engines: {node: '>= 0.4'} + + duplexer2@0.1.4: + resolution: {integrity: sha512-asLFVfWWtJ90ZyOUHMqk7/S2w2guQKxUI2itj3d92ADHhxUSbCMGi1f1cBcJ7xM1To+pE/Khbwo1yuNbMEPKeA==} + + eastasianwidth@0.2.0: + resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} + + ecdsa-sig-formatter@1.0.11: + resolution: {integrity: sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==} + + editorconfig@1.0.7: + resolution: {integrity: sha512-e0GOtq/aTQhVdNyDU9e02+wz9oDDM+SIOQxWME2QRjzRX5yyLAuHDE+0aE8vHb9XRC8XD37eO2u57+F09JqFhw==} + engines: {node: '>=14'} + hasBin: true + + editorconfig@3.0.2: + resolution: {integrity: sha512-T0ix8GhtxyKVfUFEcvdNDt3YGqlwkFHbD4/5bgFUDgFmxhI/cSRAeJ87/Sz//Cq8Eam6JX/e23RkoFO71P7aAA==} + engines: {node: '>=20'} + hasBin: true + + ee-first@1.1.1: + resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==} + + ejs@3.1.10: + resolution: {integrity: sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA==} + engines: {node: '>=0.10.0'} + hasBin: true + + electron-to-chromium@1.5.422: + resolution: {integrity: sha512-UvA/32XqrLDdZSn7Jllo1AYNcWji/G0d5M0GTViE7KoGBiMunw3a34Sb2KO4ZZyrSEhqsxFoVhWWJshdyfKqJA==} + + elliptic@6.6.1: + resolution: {integrity: sha512-RaddvvMatK2LJHqFJ+YA4WysVN5Ita9E35botqIYspQ4TkRAlCicdzKOjlyv/1Za5RyTNn7di//eEV0uTAfe3g==} + + emittery@0.13.1: + resolution: {integrity: sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==} + engines: {node: '>=12'} + + emoji-regex@8.0.0: + resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} + + emoji-regex@9.2.2: + resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} + + encodeurl@2.0.0: + resolution: {integrity: sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==} + engines: {node: '>= 0.8'} + + end-of-stream@1.4.5: + resolution: {integrity: sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==} + + entities@2.2.0: + resolution: {integrity: sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==} + + entities@3.0.1: + resolution: {integrity: sha512-WiyBqoomrwMdFG1e0kqvASYfnlb0lp8M5o5Fw2OFq1hNZxxcNk8Ik0Xm7LxzBhuidnZB/UtBqVCgUz3kBOP51Q==} + engines: {node: '>=0.12'} + + env-paths@2.2.1: + resolution: {integrity: sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==} + engines: {node: '>=6'} + + error-ex@1.3.4: + resolution: {integrity: sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==} + + es-abstract-get@1.0.0: + resolution: {integrity: sha512-6PMWXpdhshVvFp+FoWYs1EvG1Nj0tvk0dZM+XcK0xMEM1czRVcP6ohqPWHy6qPagSpC8j4+p89WXlT+xXJs/fg==} + engines: {node: '>= 0.4'} + + es-abstract@1.24.2: + resolution: {integrity: sha512-2FpH9Q5i2RRwyEP1AylXe6nYLR5OhaJTZwmlcP0dL/+JCbgg7yyEo/sEK6HeGZRf3dFpWwThaRHVApXSkW3xeg==} + engines: {node: '>= 0.4'} + + es-array-method-boxes-properly@1.0.0: + resolution: {integrity: sha512-wd6JXUmyHmt8T5a2xreUwKcGPq6f1f+WwIJkijUqiGcJz1qqnZgP6XIK+QyIWU5lT7imeNxUll48bziG+TSYcA==} + + es-define-property@1.0.1: + resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==} + engines: {node: '>= 0.4'} + + es-errors@1.3.0: + resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} + engines: {node: '>= 0.4'} + + es-iterator-helpers@1.4.0: + resolution: {integrity: sha512-c/A0P0oxkACDc+cKWw8evLXK83oBKgn0qPOqCYT4x9uolpCIJAcYvJC9QYKNDRPsTeGyCrQ326jrvgZWdCdK5Q==} + engines: {node: '>= 0.4'} + + es-module-lexer@2.3.2: + resolution: {integrity: sha512-poHGpORABojJJucnV9KbOavETW8lBVnphkW77ER5/BQ5Fz7oXSoCNek7IH3vR5nRjdsEz926ibFYX8KtLQmdyw==} + + es-object-atoms@1.1.2: + resolution: {integrity: sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==} + engines: {node: '>= 0.4'} + + es-set-tostringtag@2.1.0: + resolution: {integrity: sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==} + engines: {node: '>= 0.4'} + + es-shim-unscopables@1.1.0: + resolution: {integrity: sha512-d9T8ucsEhh8Bi1woXCf+TIKDIROLG5WCkxg8geBCbvk22kzwC5G2OnXVMO6FUsvQlgUUXQ2itephWDLqDzbeCw==} + engines: {node: '>= 0.4'} + + es-to-primitive@1.3.4: + resolution: {integrity: sha512-yPDz7wqpg1/mmHLmS3tcfTfbw5f1eryXvyghYBffGdERwe+mV7ZcWzTR8LR17Kvqt3qfPurjlonmnq3MKXIOXw==} + engines: {node: '>= 0.4'} + + escalade@3.2.0: + resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} + engines: {node: '>=6'} + + escape-html@1.0.3: + resolution: {integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==} + + escape-string-regexp@2.0.0: + resolution: {integrity: sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==} + engines: {node: '>=8'} + + escape-string-regexp@4.0.0: + resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} + engines: {node: '>=10'} + + escodegen@2.1.0: + resolution: {integrity: sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w==} + engines: {node: '>=6.0'} + hasBin: true + + eslint-config-prettier@10.1.8: + resolution: {integrity: sha512-82GZUjRS0p/jganf6q1rEO25VSoHH0hKPCTrgillPjdI/3bgBhAE1QzHrHTizjpRvy6pGAvKjDJtk2pF9NDq8w==} + hasBin: true + peerDependencies: + eslint: '>=7.0.0' + + eslint-config-standard-jsx@11.0.0: + resolution: {integrity: sha512-+1EV/R0JxEK1L0NGolAr8Iktm3Rgotx3BKwgaX+eAuSX8D952LULKtjgZD3F+e6SvibONnhLwoTi9DPxN5LvvQ==} + peerDependencies: + eslint: ^8.8.0 + eslint-plugin-react: ^7.28.0 + + eslint-config-standard@17.1.0: + resolution: {integrity: sha512-IwHwmaBNtDK4zDHQukFDW5u/aTb8+meQWZvNFWkiGmbWjD6bqyuSSBxxXKkCftCUzc1zwCH2m/baCNDLGmuO5Q==} + engines: {node: '>=12.0.0'} + peerDependencies: + eslint: ^8.0.1 + eslint-plugin-import: ^2.25.2 + eslint-plugin-n: '^15.0.0 || ^16.0.0 ' + eslint-plugin-promise: ^6.0.0 + + eslint-import-resolver-node@0.3.10: + resolution: {integrity: sha512-tRrKqFyCaKict5hOd244sL6EQFNycnMQnBe+j8uqGNXYzsImGbGUU4ibtoaBmv5FLwJwcFJNeg1GeVjQfbMrDQ==} + + eslint-module-utils@2.14.0: + resolution: {integrity: sha512-W2WCRZ9Dqntd+2u8jJcVMV2PKulc6RdLgUUoh/yQr3uB6lo/ZOeGx11sv60/8S4QFFKNslAlWhr9u0Ef7ZW6Ig==} + engines: {node: '>=4'} + peerDependencies: + '@typescript-eslint/parser': '*' + eslint: '*' + eslint-import-resolver-node: '*' + eslint-import-resolver-typescript: '*' + eslint-import-resolver-webpack: '*' + peerDependenciesMeta: + '@typescript-eslint/parser': + optional: true + eslint: + optional: true + eslint-import-resolver-node: + optional: true + eslint-import-resolver-typescript: + optional: true + eslint-import-resolver-webpack: + optional: true + + eslint-plugin-es@4.1.0: + resolution: {integrity: sha512-GILhQTnjYE2WorX5Jyi5i4dz5ALWxBIdQECVQavL6s7cI76IZTDWleTHkxz/QT3kvcs2QlGHvKLYsSlPOlPXnQ==} + engines: {node: '>=8.10.0'} + peerDependencies: + eslint: '>=4.19.1' + + eslint-plugin-import@2.32.0: + resolution: {integrity: sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA==} + engines: {node: '>=4'} + peerDependencies: + '@typescript-eslint/parser': '*' + eslint: ^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8 || ^9 + peerDependenciesMeta: + '@typescript-eslint/parser': + optional: true + + eslint-plugin-n@15.7.0: + resolution: {integrity: sha512-jDex9s7D/Qial8AGVIHq4W7NswpUD5DPDL2RH8Lzd9EloWUuvUkHfv4FRLMipH5q2UtyurorBkPeNi1wVWNh3Q==} + engines: {node: '>=12.22.0'} + peerDependencies: + eslint: '>=7.0.0' + + eslint-plugin-prettier@5.5.6: + resolution: {integrity: sha512-ifetmTcxWfz+4qRW3pH/ujdTq2jQIj59AxJMIN26K5avYgU8dxycUETQonWiW+wPrYXA0j3Try0l1CnwVQtDqQ==} + engines: {node: ^14.18.0 || >=16.0.0} + peerDependencies: + '@types/eslint': '>=8.0.0' + eslint: '>=8.0.0' + eslint-config-prettier: '>= 7.0.0 <10.0.0 || >=10.1.0' + prettier: '>=3.0.0' + peerDependenciesMeta: + '@types/eslint': + optional: true + eslint-config-prettier: + optional: true + + eslint-plugin-promise@6.6.0: + resolution: {integrity: sha512-57Zzfw8G6+Gq7axm2Pdo3gW/Rx3h9Yywgn61uE/3elTCOePEHVrn2i5CdfBwA1BLK0Q0WqctICIUSqXZW/VprQ==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: ^7.0.0 || ^8.0.0 || ^9.0.0 + + eslint-plugin-react@7.37.5: + resolution: {integrity: sha512-Qteup0SqU15kdocexFNAJMvCJEfa2xUKNV4CC1xsVMrIIqEy3SQ/rqyxCWNzfrd3/ldy6HMlD2e0JDVpDg2qIA==} + engines: {node: '>=4'} + peerDependencies: + eslint: ^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9.7 + + eslint-scope@7.2.2: + resolution: {integrity: sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + eslint-scope@8.4.0: + resolution: {integrity: sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + eslint-utils@2.1.0: + resolution: {integrity: sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==} + engines: {node: '>=6'} + + eslint-utils@3.0.0: + resolution: {integrity: sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==} + engines: {node: ^10.0.0 || ^12.0.0 || >= 14.0.0} + peerDependencies: + eslint: '>=5' + + eslint-visitor-keys@1.3.0: + resolution: {integrity: sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==} + engines: {node: '>=4'} + + eslint-visitor-keys@2.1.0: + resolution: {integrity: sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==} + engines: {node: '>=10'} + + eslint-visitor-keys@3.4.3: + resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + eslint-visitor-keys@4.2.1: + resolution: {integrity: sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + eslint@8.57.1: + resolution: {integrity: sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + deprecated: This version is no longer supported. Please see https://eslint.org/version-support for other options. + hasBin: true + + eslint@9.39.5: + resolution: {integrity: sha512-DgZS62aPLXKlnxILS/AYCoRvHaZeXceIzlXPkkGGzJWSow1aEk0lbTlxUSlyjC8jcaKxAdOnTDz+o1JFSBsyjw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + deprecated: This version is no longer supported. Please see https://eslint.org/version-support for other options. + hasBin: true + peerDependencies: + jiti: '*' + peerDependenciesMeta: + jiti: + optional: true + + espree@10.4.0: + resolution: {integrity: sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + espree@9.6.1: + resolution: {integrity: sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + esprima@4.0.1: + resolution: {integrity: sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==} + engines: {node: '>=4'} + hasBin: true + + esquery@1.7.0: + resolution: {integrity: sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==} + engines: {node: '>=0.10'} + + esrecurse@4.3.0: + resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==} + engines: {node: '>=4.0'} + + estraverse@5.3.0: + resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} + engines: {node: '>=4.0'} + + esutils@2.0.3: + resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} + engines: {node: '>=0.10.0'} + + etag@1.8.1: + resolution: {integrity: sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==} + engines: {node: '>= 0.6'} + + events-universal@1.0.1: + resolution: {integrity: sha512-LUd5euvbMLpwOF8m6ivPCbhQeSiYVNb8Vs0fQ8QjXo0JTkEHpz8pxdQf0gStltaPpw0Cca8b39KxvK9cfKRiAw==} + + exceljs@4.4.0: + resolution: {integrity: sha512-XctvKaEMaj1Ii9oDOqbW/6e1gXknSY4g/aLCDicOXqBE4M0nRWkUu0PTp++UPNzoFY12BNHMfs/VadKIS6llvg==} + engines: {node: '>=8.3.0'} + + execa@5.1.1: + resolution: {integrity: sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==} + engines: {node: '>=10'} + + exifr@7.1.3: + resolution: {integrity: sha512-g/aje2noHivrRSLbAUtBPWFbxKdKhgj/xr1vATDdUXPOFYJlQ62Ft0oy+72V6XLIpDJfHs6gXLbBLAolqOXYRw==} + + exit-x@0.2.2: + resolution: {integrity: sha512-+I6B/IkJc1o/2tiURyz/ivu/O0nKNEArIUB5O7zBrlDVJr22SCLH3xTeEry428LvFhRzIA1g8izguxJ/gbNcVQ==} + engines: {node: '>= 0.8.0'} + + expect@30.5.1: + resolution: {integrity: sha512-m8YrYgvKe9+9gEnWEuKz+qCGfHqkrff7PPfyDnOFkjsfYRKqiYyOxDFMFieCGAuhtk/VNm63tvNgKZVzVy+Hvg==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + express-session@1.19.0: + resolution: {integrity: sha512-0csaMkGq+vaiZTmSMMGkfdCOabYv192VbytFypcvI0MANrp+4i/7yEkJ0sbAEhycQjntaKGzYfjfXQyVb7BHMA==} + engines: {node: '>= 0.8.0'} + + express@5.2.1: + resolution: {integrity: sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==} + engines: {node: '>= 18'} + + extract-zip@2.0.1: + resolution: {integrity: sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==} + engines: {node: '>= 10.17.0'} + hasBin: true + + fast-csv@4.3.6: + resolution: {integrity: sha512-2RNSpuwwsJGP0frGsOmTb9oUF+VkFSM4SyLTDgwf2ciHWTarN0lQTC+F2f/t5J9QjW+c65VFIAAu85GsvMIusw==} + engines: {node: '>=10.0.0'} + + fast-deep-equal@3.1.3: + resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} + + fast-diff@1.3.0: + resolution: {integrity: sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==} + + fast-fifo@1.3.2: + resolution: {integrity: sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==} + + fast-json-stable-stringify@2.1.0: + resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} + + fast-levenshtein@2.0.6: + resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} + + fast-safe-stringify@2.1.1: + resolution: {integrity: sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==} + + fastq@1.20.3: + resolution: {integrity: sha512-XKv5nnLs6nLF71NgiKJLIZFLkPyIEuOselLG7ujZnGrRfQK8HpvY+WqKhAJUAdLomwVHErVS4LfxFlPq0/FTAw==} + + fb-watchman@2.0.2: + resolution: {integrity: sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==} + + fd-slicer@1.1.0: + resolution: {integrity: sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==} + + fdir@6.5.0: + resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} + engines: {node: '>=12.0.0'} + peerDependencies: + picomatch: ^3 || ^4 + peerDependenciesMeta: + picomatch: + optional: true + + file-entry-cache@6.0.1: + resolution: {integrity: sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==} + engines: {node: ^10.12.0 || >=12.0.0} + + file-entry-cache@8.0.0: + resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==} + engines: {node: '>=16.0.0'} + + filelist@1.0.6: + resolution: {integrity: sha512-5giy2PkLYY1cP39p17Ech+2xlpTRL9HLspOfEgm0L6CwBXBTgsK5ou0JtzYuepxkaQ/tvhCFIJ5uXo0OrM2DxA==} + + fill-range@7.1.1: + resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} + engines: {node: '>=8'} + + finalhandler@2.1.1: + resolution: {integrity: sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==} + engines: {node: '>= 18.0.0'} + + find-cache-dir@2.1.0: + resolution: {integrity: sha512-Tq6PixE0w/VMFfCgbONnkiQIVol/JJL7nRMi20fqzA4NRs9AfeqMGeRdPi3wIhYkxjeBaWh2rxwapn5Tu3IqOQ==} + engines: {node: '>=6'} + + find-up@3.0.0: + resolution: {integrity: sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==} + engines: {node: '>=6'} + + find-up@4.1.0: + resolution: {integrity: sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==} + engines: {node: '>=8'} + + find-up@5.0.0: + resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} + engines: {node: '>=10'} + + flat-cache@3.2.0: + resolution: {integrity: sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==} + engines: {node: ^10.12.0 || >=12.0.0} + + flat-cache@4.0.1: + resolution: {integrity: sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==} + engines: {node: '>=16'} + + flatted@3.4.4: + resolution: {integrity: sha512-5+ybhBZANEJxaH3X5evAFatUxLfEHSr7n6kYJ+1Qd0mUqr4eu9gIf6GDbWHf8RJijHrjjO8G+la14SlL2SeS1Q==} + + follow-redirects@1.16.0: + resolution: {integrity: sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw==} + engines: {node: '>=4.0'} + peerDependencies: + debug: '*' + peerDependenciesMeta: + debug: + optional: true + + for-each@0.3.5: + resolution: {integrity: sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==} + engines: {node: '>= 0.4'} + + foreground-child@3.3.1: + resolution: {integrity: sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==} + engines: {node: '>=14'} + + form-data@4.0.6: + resolution: {integrity: sha512-vKatAh4SlVfgbv+YtmhiRjhEMJsYpsG1Y2rMQtR+SVSbytsSD1YGzDIcrAJmdFec88u/+VoGmxnl+80gL1tRCQ==} + engines: {node: '>= 6'} + + formidable@3.5.4: + resolution: {integrity: sha512-YikH+7CUTOtP44ZTnUhR7Ic2UASBPOqmaRkRKxRbywPTe5VxF7RRCck4af9wutiZ/QKM5nME9Bie2fFaPz5Gug==} + engines: {node: '>=14.0.0'} + + forwarded@0.2.0: + resolution: {integrity: sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==} + engines: {node: '>= 0.6'} + + fresh@2.0.0: + resolution: {integrity: sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==} + engines: {node: '>= 0.8'} + + fs-constants@1.0.0: + resolution: {integrity: sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==} + + fs-extra@8.1.0: + resolution: {integrity: sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==} + engines: {node: '>=6 <7 || >=8'} + + fs-extra@9.1.0: + resolution: {integrity: sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==} + engines: {node: '>=10'} + + fs-readdir-recursive@1.1.0: + resolution: {integrity: sha512-GNanXlVr2pf02+sPN40XN8HG+ePaNcvM0q5mZBd668Obwb0yD5GiUbZOFgwn8kGMY6I3mdyDJzieUy3PTYyTRA==} + + fs.realpath@1.0.0: + resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} + + fsevents@2.3.3: + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + + fstream@1.0.12: + resolution: {integrity: sha512-WvJ193OHa0GHPEL+AycEJgxvBEwyfRkN1vhjca23OaPVMCaLCXTd5qAu82AjTcgP1UJmytkOKb63Ypde7raDIg==} + engines: {node: '>=0.6'} + deprecated: This package is no longer supported. + + function-bind@1.1.2: + resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} + + function.prototype.name@1.2.0: + resolution: {integrity: sha512-jObKIik1P2QjPHP5nz5BaOtUlfgS0fWo8IUByNXkM+o+02sJOi94em77GwJKQSJ3gfPHdgzLNrHc1uokV4P/ew==} + engines: {node: '>= 0.4'} + + functions-have-names@1.2.3: + resolution: {integrity: sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==} + + generator-function@2.0.1: + resolution: {integrity: sha512-SFdFmIJi+ybC0vjlHN0ZGVGHc3lgE0DxPAT0djjVg+kjOnSqclqmj0KQ7ykTOLP6YxoqOvuAODGdcHJn+43q3g==} + engines: {node: '>= 0.4'} + + gensync@1.0.0-beta.2: + resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} + engines: {node: '>=6.9.0'} + + get-caller-file@2.0.5: + resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} + engines: {node: 6.* || 8.* || >= 10.*} + + get-intrinsic@1.3.0: + resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==} + engines: {node: '>= 0.4'} + + get-package-type@0.1.0: + resolution: {integrity: sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==} + engines: {node: '>=8.0.0'} + + get-proto@1.0.1: + resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==} + engines: {node: '>= 0.4'} + + get-stdin@8.0.0: + resolution: {integrity: sha512-sY22aA6xchAzprjyqmSEQv4UbAAzRN0L2dQB0NlN5acTTK9Don6nhoc3eAbUnpZiCANAMfd/+40kVdKfFygohg==} + engines: {node: '>=10'} + + get-stream@5.2.0: + resolution: {integrity: sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==} + engines: {node: '>=8'} + + get-stream@6.0.1: + resolution: {integrity: sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==} + engines: {node: '>=10'} + + get-symbol-description@1.1.0: + resolution: {integrity: sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==} + engines: {node: '>= 0.4'} + + get-uri@6.0.5: + resolution: {integrity: sha512-b1O07XYq8eRuVzBNgJLstU6FYc1tS6wnMtF1I1D9lE8LxZSOGZ7LhxN54yPP6mGw5f2CkXY2BQUL9Fx41qvcIg==} + engines: {node: '>= 14'} + + get-uri@8.0.1: + resolution: {integrity: sha512-/5N/P4Lrh0p/mDwlDRi7Y1+P2o/OyzZI3l6Iz1Ov6XXwwm1y3RlZLuo3gVgML99djrEDtV980bBxSuOeHLk8ww==} + engines: {node: '>= 20'} + + glob-parent@5.1.2: + resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} + engines: {node: '>= 6'} + + glob-parent@6.0.2: + resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} + engines: {node: '>=10.13.0'} + + glob@10.5.0: + resolution: {integrity: sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==} + deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me + hasBin: true + + glob@13.0.6: + resolution: {integrity: sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw==} + engines: {node: 18 || 20 || >=22} + + glob@7.2.3: + resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} + deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me + + globals@13.24.0: + resolution: {integrity: sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==} + engines: {node: '>=8'} + + globals@14.0.0: + resolution: {integrity: sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==} + engines: {node: '>=18'} + + globalthis@1.0.4: + resolution: {integrity: sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==} + engines: {node: '>= 0.4'} + + gopd@1.2.0: + resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} + engines: {node: '>= 0.4'} + + graceful-fs@4.2.11: + resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} + + graphemer@1.4.0: + resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==} + + has-bigints@1.1.0: + resolution: {integrity: sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==} + engines: {node: '>= 0.4'} + + has-flag@3.0.0: + resolution: {integrity: sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==} + engines: {node: '>=4'} + + has-flag@4.0.0: + resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} + engines: {node: '>=8'} + + has-property-descriptors@1.0.2: + resolution: {integrity: sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==} + + has-proto@1.2.0: + resolution: {integrity: sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ==} + engines: {node: '>= 0.4'} + + has-symbols@1.1.0: + resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==} + engines: {node: '>= 0.4'} + + has-tostringtag@1.0.2: + resolution: {integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==} + engines: {node: '>= 0.4'} + + hash.js@1.1.7: + resolution: {integrity: sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==} + + hasown@2.0.4: + resolution: {integrity: sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==} + engines: {node: '>= 0.4'} + + hmac-drbg@1.0.1: + resolution: {integrity: sha512-Tti3gMqLdZfhOQY1Mzf/AanLiqh1WTiJgEj26ZuYQ9fbkLomzGchCws4FyrSd4VkpBfiNhaE1On+lOz894jvXg==} + + homedir-polyfill@1.0.3: + resolution: {integrity: sha512-eSmmWE5bZTK2Nou4g0AI3zZ9rswp7GRKoKXS1BLUkvPviOqs4YTN1djQIqrXy9k5gEtdLPy86JjRwsNM9tnDcA==} + engines: {node: '>=0.10.0'} + + html-escaper@2.0.2: + resolution: {integrity: sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==} + + htmlparser2@7.2.0: + resolution: {integrity: sha512-H7MImA4MS6cw7nbyURtLPO1Tms7C5H602LRETv95z1MxO/7CP7rDVROehUYeYBUYEON94NXXDEPmZuq+hX4sog==} + + http-errors@2.0.1: + resolution: {integrity: sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==} + engines: {node: '>= 0.8'} + + http-proxy-agent@7.0.2: + resolution: {integrity: sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==} + engines: {node: '>= 14'} + + http-proxy-agent@9.1.0: + resolution: {integrity: sha512-2NxoveTT58mjYT4n3RPTEfCZGLMbidoO8XEieXfpSYxu+PQJ1qpx4ypwH6N+uF9twBPIvRRgvkvW5HUTYWENig==} + engines: {node: '>= 20'} + + https-proxy-agent@5.0.1: + resolution: {integrity: sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==} + engines: {node: '>= 6'} + + https-proxy-agent@7.0.6: + resolution: {integrity: sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==} + engines: {node: '>= 14'} + + https-proxy-agent@9.1.0: + resolution: {integrity: sha512-ag87y7cJJ9/3+GxFr8Oy4O5faDsGRGnBGsJj/YjOSsSx/5eadKLYTMPlzuR6obgoCDDm0abAAZitXXQkMOPSpA==} + engines: {node: '>= 20'} + + human-signals@2.1.0: + resolution: {integrity: sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==} + engines: {node: '>=10.17.0'} + + i@0.3.7: + resolution: {integrity: sha512-FYz4wlXgkQwIPqhzC5TdNMLSE5+GS1IIDJZY/1ZiEPCT2S3COUVZeT5OW4BmW4r5LHLQuOosSwsvnroG9GR59Q==} + engines: {node: '>=0.4'} + + iconv-lite@0.7.3: + resolution: {integrity: sha512-IKXpvIzjnC9XTAUbVBcMfGS0EPaIXtW6v+zr+RRp+hqULEpo0owZax6wyRwPOJbWbzjYspQwusTsfVr0ifh4uQ==} + engines: {node: '>=0.10.0'} + + ieee754@1.2.1: + resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} + + ignore-by-default@1.0.1: + resolution: {integrity: sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==} + + ignore@5.3.2: + resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} + engines: {node: '>= 4'} + + immediate@3.0.6: + resolution: {integrity: sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==} + + import-fresh@3.3.1: + resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==} + engines: {node: '>=6'} + + import-local@3.2.0: + resolution: {integrity: sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA==} + engines: {node: '>=8'} + hasBin: true + + imurmurhash@0.1.4: + resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} + engines: {node: '>=0.8.19'} + + inflection@1.13.4: + resolution: {integrity: sha512-6I/HUDeYFfuNCVS3td055BaXBwKYuzw7K3ExVMStBowKo9oOAMJIXIHvdyR3iboTCp1b+1i5DSkIZTcwIktuDw==} + engines: {'0': node >= 0.4.0} + + inflight@1.0.6: + resolution: {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. + + inherits@2.0.4: + resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} + + ini@1.3.8: + resolution: {integrity: sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==} + + internal-slot@1.1.0: + resolution: {integrity: sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==} + engines: {node: '>= 0.4'} + + ioredis@5.11.1: + resolution: {integrity: sha512-ehuGcf94bQXhfagULNXrJdfnWO38v070jxSx/qE87Kjzmu2fU7ro5EFAb+OPituLqgfyuQaym5DlrNydW2sJ9A==} + engines: {node: '>=12.22.0'} + + ioredis@6.0.0: + resolution: {integrity: sha512-f+Dtubxfpf6KYFq7WVXJoOLn0bk4TJrMrN9SzeE+jrWrCWj7XX3fA6vkryafhADX+GMymRxgDJDOI33COkJc0w==} + engines: {node: '>=20.0.0'} + + ip-address@10.7.0: + resolution: {integrity: sha512-BGFsyJd5mpXp3rK6jIdADLNgpJUK1jnjzvYF8lK+VyDab9JAmqN0YOKDdP17HlgKb2+ehPgDc8EtnRLbGCAMhA==} + engines: {node: '>= 12'} + + ip-regex@2.1.0: + resolution: {integrity: sha512-58yWmlHpp7VYfcdTwMTvwMmqx/Elfxjd9RXTDyMsbL7lLWmhMylLEqiYVLKuLzOZqVgiWXD9MfR62Vv89VRxkw==} + engines: {node: '>=4'} + + ipaddr.js@1.9.1: + resolution: {integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==} + engines: {node: '>= 0.10'} + + is-array-buffer@3.0.5: + resolution: {integrity: sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==} + engines: {node: '>= 0.4'} + + is-arrayish@0.2.1: + resolution: {integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==} + + is-async-function@2.1.1: + resolution: {integrity: sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ==} + engines: {node: '>= 0.4'} + + is-bigint@1.1.0: + resolution: {integrity: sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ==} + engines: {node: '>= 0.4'} + + is-binary-path@2.1.0: + resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==} + engines: {node: '>=8'} + + is-boolean-object@1.2.2: + resolution: {integrity: sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A==} + engines: {node: '>= 0.4'} + + is-callable@1.2.7: + resolution: {integrity: sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==} + engines: {node: '>= 0.4'} + + is-core-module@2.16.2: + resolution: {integrity: sha512-evOr8xfXKxE6qSR0hSXL2r3sd7ALj8+7jQEUvPYcm5sgZFdJ+AYzT6yNmJenvIYQBgIGwfwz08sL8zoL7yq2BA==} + engines: {node: '>= 0.4'} + + is-data-view@1.0.2: + resolution: {integrity: sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw==} + engines: {node: '>= 0.4'} + + is-date-object@1.1.0: + resolution: {integrity: sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==} + engines: {node: '>= 0.4'} + + is-document.all@1.0.0: + resolution: {integrity: sha512-+XSoyS05OdBbhFuELhgTCpFNHkpBOJqtsZfUFFpe5QTw+9Sjbh8zitxhQkYAo6wV7e1Vb8cAPvpCk9jGam/82g==} + engines: {node: '>= 0.4'} + + is-extglob@2.1.1: + resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} + engines: {node: '>=0.10.0'} + + is-finalizationregistry@1.1.1: + resolution: {integrity: sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg==} + engines: {node: '>= 0.4'} + + is-fullwidth-code-point@3.0.0: + resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} + engines: {node: '>=8'} + + is-generator-fn@2.1.0: + resolution: {integrity: sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==} + engines: {node: '>=6'} + + is-generator-function@1.1.2: + resolution: {integrity: sha512-upqt1SkGkODW9tsGNG5mtXTXtECizwtS2kA161M+gJPc1xdb/Ax629af6YrTwcOeQHbewrPNlE5Dx7kzvXTizA==} + engines: {node: '>= 0.4'} + + is-glob@4.0.3: + resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} + engines: {node: '>=0.10.0'} + + is-json@2.0.1: + resolution: {integrity: sha512-6BEnpVn1rcf3ngfmViLM6vjUjGErbdrL4rwlv+u1NO1XO8kqT4YGL8+19Q+Z/bas8tY90BTWMk2+fW1g6hQjbA==} + + is-map@2.0.3: + resolution: {integrity: sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==} + engines: {node: '>= 0.4'} + + is-negative-zero@2.0.3: + resolution: {integrity: sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==} + engines: {node: '>= 0.4'} + + is-number-object@1.1.1: + resolution: {integrity: sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw==} + engines: {node: '>= 0.4'} + + is-number@7.0.0: + resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} + engines: {node: '>=0.12.0'} + + is-path-inside@3.0.3: + resolution: {integrity: sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==} + engines: {node: '>=8'} + + is-plain-object@2.0.4: + resolution: {integrity: sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==} + engines: {node: '>=0.10.0'} + + is-promise@4.0.0: + resolution: {integrity: sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==} + + is-regex@1.2.1: + resolution: {integrity: sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==} + engines: {node: '>= 0.4'} + + is-set@2.0.3: + resolution: {integrity: sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==} + engines: {node: '>= 0.4'} + + is-shared-array-buffer@1.0.4: + resolution: {integrity: sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==} + engines: {node: '>= 0.4'} + + is-stream@2.0.1: + resolution: {integrity: sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==} + engines: {node: '>=8'} + + is-string@1.1.1: + resolution: {integrity: sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==} + engines: {node: '>= 0.4'} + + is-symbol@1.1.1: + resolution: {integrity: sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w==} + engines: {node: '>= 0.4'} + + is-typed-array@1.1.15: + resolution: {integrity: sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==} + engines: {node: '>= 0.4'} + + is-url@1.2.4: + resolution: {integrity: sha512-ITvGim8FhRiYe4IQ5uHSkj7pVaPDrCTkNd3yq3cV7iZAcJdHTUMPMEHcqSOy9xZ9qFenQCvi+2wjH9a1nXqHww==} + + is-weakmap@2.0.2: + resolution: {integrity: sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==} + engines: {node: '>= 0.4'} + + is-weakref@1.1.1: + resolution: {integrity: sha512-6i9mGWSlqzNMEqpCp93KwRS1uUOodk2OJ6b+sq7ZPDSy2WuI5NFIxp/254TytR8ftefexkWn5xNiHUNpPOfSew==} + engines: {node: '>= 0.4'} + + is-weakset@2.0.4: + resolution: {integrity: sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==} + engines: {node: '>= 0.4'} + + is2@2.0.1: + resolution: {integrity: sha512-+WaJvnaA7aJySz2q/8sLjMb2Mw14KTplHmSwcSpZ/fWJPkUmqw3YTzSWbPJ7OAwRvdYTWF2Wg+yYJ1AdP5Z8CA==} + engines: {node: '>=v0.10.0'} + + isarray@1.0.0: + resolution: {integrity: sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==} + + isarray@2.0.5: + resolution: {integrity: sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==} + + isexe@2.0.0: + resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + + isobject@3.0.1: + resolution: {integrity: sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==} + engines: {node: '>=0.10.0'} + + istanbul-lib-coverage@3.2.2: + resolution: {integrity: sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==} + engines: {node: '>=8'} + + istanbul-lib-instrument@6.0.3: + resolution: {integrity: sha512-Vtgk7L/R2JHyyGW07spoFlB8/lpjiOLTjMdms6AFMraYt3BaJauod/NGrfnVG/y4Ix1JEuMRPDPEj2ua+zz1/Q==} + engines: {node: '>=10'} + + istanbul-lib-report@3.0.1: + resolution: {integrity: sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==} + engines: {node: '>=10'} + + istanbul-lib-source-maps@5.0.6: + resolution: {integrity: sha512-yg2d+Em4KizZC5niWhQaIomgf5WlL4vOOjZ5xGCmF8SnPE/mDWWXgvRExdcpCgh9lLRRa1/fSYp2ymmbJ1pI+A==} + engines: {node: '>=10'} + + istanbul-reports@3.2.0: + resolution: {integrity: sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==} + engines: {node: '>=8'} + + iterator.prototype@1.1.5: + resolution: {integrity: sha512-H0dkQoCa3b2VEeKQBOxFph+JAbcrQdE7KC0UkqwpLmv2EC4P41QXP+rqo9wYodACiG5/WM5s9oDApTU8utwj9g==} + engines: {node: '>= 0.4'} + + jackspeak@3.4.3: + resolution: {integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==} + + jake@10.9.4: + resolution: {integrity: sha512-wpHYzhxiVQL+IV05BLE2Xn34zW1S223hvjtqk0+gsPrwd/8JNLXJgZZM/iPFsYc1xyphF+6M6EvdE5E9MBGkDA==} + engines: {node: '>=10'} + hasBin: true + + jest-changed-files@30.5.1: + resolution: {integrity: sha512-0+bvMM/ENhDI29Z8q1r4HxiDIi4G5tnBmSw4esfPQoj9q8Nik7KIyuxHkTVnnniJQf05SxpGaKDQ+4h28ynGPg==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + jest-circus@30.5.1: + resolution: {integrity: sha512-NgliezXQ6yznqR4W5Gqw++0cZSbBZlF0NknNIAE5VmSJKWOtmWJmWnBq3TSkUOVBTPrT7FGGhVdA8DLWnxo2Sw==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + jest-cli@30.5.1: + resolution: {integrity: sha512-uwNYepWgaNBCplm42fCIUZTf/tIEHIy5AYvx7eL1BrwIgyjPt+2poouR23UO2yopIP+kV8oZFHfv+l4pg/8prQ==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + hasBin: true + peerDependencies: + node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 + peerDependenciesMeta: + node-notifier: + optional: true + + jest-config@30.5.1: + resolution: {integrity: sha512-L8PKM2X/ngG8PxfLMqglKGZjylPgw84bVPUlMY9W/o76TnLqPWrmQgsaT0HrheGdRtpGE5FbmREA0Kvb+Qx5gQ==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + peerDependencies: + '@types/node': '*' + esbuild-register: '>=3.4.0' + ts-node: '>=9.0.0' + peerDependenciesMeta: + '@types/node': + optional: true + esbuild-register: + optional: true + ts-node: + optional: true + + jest-diff@30.5.1: + resolution: {integrity: sha512-e3cNNMpv8Kh20MjjphTXs+3Vz7DQyLM1nft7KJhnh46atFhjVJRa+0Hq0beywuwsACtMQUBihQkFl8zxb7gt1Q==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + jest-docblock@30.5.0: + resolution: {integrity: sha512-NwDqcxtoZi33RhuW+zJS/RVA3rmheQ8BnwpYZuc/Eruaz6seQb7+aoCeDZu/3X7W2XmD8DbSo9Pn72DbKsBFYw==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + jest-each@30.5.1: + resolution: {integrity: sha512-S1af0TU4v1EZ/AUlkFs/sxf/5KGsbAT9kRgdyoMX/x72y7C8ZEgETE5o1TPnbKRnrbprc5FR/moYLfdRmqEjsQ==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + jest-environment-node@30.5.1: + resolution: {integrity: sha512-LrPj3sPMjsQoOB3jrb8p/sa+XkSFNKo60TTsyc+EB2kxQJHgbwElpXnx1yX25fdFn5958FIObRtcLSHyV8VIAw==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + jest-haste-map@30.5.1: + resolution: {integrity: sha512-VIFgt67jW480YDxKfEv9IYQKrFpYt7bOCMn3VsnjK7AK9qo7p6acpnA1DHwsVOULE3dTaYew/6JaTD/d0VDHoQ==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + jest-junit@16.0.0: + resolution: {integrity: sha512-A94mmw6NfJab4Fg/BlvVOUXzXgF0XIH6EmTgJ5NDPp4xoKq0Kr7sErb+4Xs9nZvu58pJojz5RFGpqnZYJTrRfQ==} + engines: {node: '>=10.12.0'} + + jest-leak-detector@30.5.1: + resolution: {integrity: sha512-gt4GT2aWgEoCNTcBe4rqS74xTIUJxi+UD9SNSu9aOk5LmTEd6fS5KSTmAKAfitCCktQHNN+upUuL6EkBfFbMDQ==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + jest-matcher-utils@30.5.1: + resolution: {integrity: sha512-aroZVqwOz/wC2y6pC+obgFWKV9viaQWQTTSB6W5H55+egtUczIfXR/rxExTv92xD/ADYwpqaYfVWx1aqwKg7FA==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + jest-message-util@30.5.1: + resolution: {integrity: sha512-UdQlLdd9wL/Ys7xRErckqwD6wPlSZYueosSWuHc1r2ztGLwlgPvtSJq2+BPEgaEY13WLvfFbmhTj8pba0Sd1jg==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + jest-mock@30.5.1: + resolution: {integrity: sha512-9fVjc3leUpGID2/by/LU4Dvdcp7PFh9LlxS3QRWK3ABm+KtvEVsG/AEGeLY3gKOZsjkBxyfwGltoAVlW7dygHg==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + jest-regex-util@30.5.0: + resolution: {integrity: sha512-Mg0WK7A6xRHLSA1udJ8y9f3lM0uUhFTBnLKzwPmqB9AylvpleJ6BLemR8K9dK27DY+cesDryoA7yLZCAHsPG1A==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + jest-resolve-dependencies@30.5.1: + resolution: {integrity: sha512-JKpXGONDcTaunrNVn8KCl6qAnwl06jIkvv70lhq0Ze47lsPx9sH5HoeEUiXX6iFGnliHN/qpIbQ0l38wrVmXaw==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + jest-resolve@30.5.1: + resolution: {integrity: sha512-wprhLejRtwN6h8ZgaqC0eYjGJ1uMdGPT/+b3eFncbG4NWuuP9QL+vWwRinu9waw7hkOLcpMJGVJAMgBwsPssMQ==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + jest-runner@30.5.1: + resolution: {integrity: sha512-FPlQE4+mwnFxXmpPrSi836KV2ZzvK1g6/nPCT8o5BcoDUUNJQeHo1/Qdkoe/QeoL4M7OeJpbnGUhYKhC1VMdaQ==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + jest-runtime@30.5.1: + resolution: {integrity: sha512-UB88+NRkK2Tw/OqV7dofYcyiUGrVZtD41k/N0xQOs9fG//57XHK7JLWG52HD1UYpUAhjK4xm6DBvFX3yWudsMA==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + jest-snapshot@30.5.1: + resolution: {integrity: sha512-cNWFdSb5xuDGl8hKkAZJ3YtI/PzHpAPFV+HUXWIOG8rMhpDTLVbvAc2d2wRicoYw2wGsJGLaurJT6BLC97bLXQ==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + jest-util@30.5.1: + resolution: {integrity: sha512-yKuxmNy2rSbTXw+3SIPanJo+nV4/BS1p26v44IYBFMsswSQySfMMcPHErnOncda7i9HEz0q605rIhSTBVgrZTg==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + jest-validate@30.5.1: + resolution: {integrity: sha512-i/buJ56wTpxihE93hQYNMfdOThS87+HGvLZzZJmU4xggPcdTYQq051iwALLCHp3q+SkCGH+EFejQZz5EbBSkkg==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + jest-watcher@30.5.1: + resolution: {integrity: sha512-+FHJ7C+S7b3ySfhA1aFmoa6TztsnwZVv84ycPRn0tVN93np2EU4b8C/KadqA3l6igdjgLBTnUotab9ER0HLG8A==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + jest-worker@30.5.1: + resolution: {integrity: sha512-Cbxh5v7AoLuFRmFJSM4/aHdQ68rjXvUWr716EE0Dh3I7T+T/3FgFKhOERGXHcU2Meftq9+9zxPM3TSNyI9D+HA==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + jest@30.5.1: + resolution: {integrity: sha512-3qrR8+ZXFnn7y0H2yjWQNkGGBLBY4zTRoTMAq9zJcgwLLtlyonfsCLviIXK9xuE2KgIyM+M36AFcoK2DgFR36w==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + hasBin: true + peerDependencies: + node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 + peerDependenciesMeta: + node-notifier: + optional: true + + js-beautify@1.15.4: + resolution: {integrity: sha512-9/KXeZUKKJwqCXUdBxFJ3vPh467OCckSBmYDwSK/EtV090K+iMJ7zx2S3HLVDIWFQdqMIsZWbnaGiba18aWhaA==} + engines: {node: '>=14'} + hasBin: true + + js-beautify@2.0.3: + resolution: {integrity: sha512-cyFbh3tkPhknnTD/0bLf0T0yy2ZIbqL05mttzbt4y1Zfr7NxqXQZ62dkBLKs3oHH/lpjmDRAnciJiSUyOy8XwQ==} + engines: {node: '>=14'} + hasBin: true + + js-cookie@3.0.8: + resolution: {integrity: sha512-yeJd4aNAdYZQjaon2bpD/Gb0B/omw7HQOsynXXcOiWVCacbBcPlgn8S/d1X6blFSaHao7ozqtW7NZW19xpCtIw==} + + js-tokens@4.0.0: + resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} + + js-yaml@3.15.2: + resolution: {integrity: sha512-6EuL879VkRA+1Cz578mKMiKvjPNEuk6+r1JaFzoSWejZmtf7xWbIyw1e3KkxlkzTIt9Taw6JBhEppG7utc1P+w==} + hasBin: true + + js-yaml@4.3.2: + resolution: {integrity: sha512-SFNOvSJ+Dgf/9An904Yx+CgSlIPCkIpao4qo51lpee25TIRejdH3rhR4EZMGoNx3/TP3O+wzWuiTFl4sqbltzA==} + hasBin: true + + jsesc@3.1.0: + resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==} + engines: {node: '>=6'} + hasBin: true + + json-buffer@3.0.1: + resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} + + json-parse-better-errors@1.0.2: + resolution: {integrity: sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==} + + json-parse-even-better-errors@2.3.1: + resolution: {integrity: sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==} + + json-schema-traverse@0.4.1: + resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} + + json-stable-stringify-without-jsonify@1.0.1: + resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} + + json5@1.0.2: + resolution: {integrity: sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==} + hasBin: true + + json5@2.2.3: + resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==} + engines: {node: '>=6'} + hasBin: true + + jsonfile@4.0.0: + resolution: {integrity: sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==} + + jsonfile@6.2.1: + resolution: {integrity: sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q==} + + jsonwebtoken@9.0.3: + resolution: {integrity: sha512-MT/xP0CrubFRNLNKvxJ2BYfy53Zkm++5bX9dtuPbqAeQpTVe0MQTFhao8+Cp//EmJp244xt6Drw/GVEGCUj40g==} + engines: {node: '>=12', npm: '>=6'} + + jsx-ast-utils@3.3.5: + resolution: {integrity: sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==} + engines: {node: '>=4.0'} + + jszip@3.10.1: + resolution: {integrity: sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==} + + jwa@2.0.1: + resolution: {integrity: sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg==} + + jwk-to-pem@2.0.7: + resolution: {integrity: sha512-cSVphrmWr6reVchuKQZdfSs4U9c5Y4hwZggPoz6cbVnTpAVgGRpEuQng86IyqLeGZlhTh+c4MAreB6KbdQDKHQ==} + + jws@4.0.1: + resolution: {integrity: sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA==} + + kareem@3.3.0: + resolution: {integrity: sha512-kpSuLD3/7RenBnjnJdOHXCKC8dTd1JzeOiJhN0necWWci6cC+qX+VuwPnMVgb+a4+KNJSfgqahpnfWaeDXCimw==} + engines: {node: '>=18.0.0'} + + keycloak-connect@26.1.1: + resolution: {integrity: sha512-2wvNJXldB9Em+mp6liJ+AnftcJovFEvNhUgv3hblNDmVihBoBqn4zFlwLIN41lo0H8CicB2T86xZ5U2MiQ9FFA==} + engines: {node: '>=14'} + + keyv@4.5.4: + resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} + + kind-of@6.0.3: + resolution: {integrity: sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==} + engines: {node: '>=0.10.0'} + + lazystream@1.0.1: + resolution: {integrity: sha512-b94GiNHQNy6JNTrt5w6zNyffMrNkXZb3KTkCZJb2V1xaEGCk093vkZ2jk3tpaeP33/OiXC+WvK9AxUebnf5nbw==} + engines: {node: '>= 0.6.3'} + + leven@3.1.0: + resolution: {integrity: sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==} + engines: {node: '>=6'} + + levn@0.4.1: + resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} + engines: {node: '>= 0.8.0'} + + lie@3.3.0: + resolution: {integrity: sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==} + + lines-and-columns@1.2.4: + resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} + + listenercount@1.0.1: + resolution: {integrity: sha512-3mk/Zag0+IJxeDrxSgaDPy4zZ3w05PRZeJNnlWhzFz5OkX49J4krc+A8X2d2M69vGMBEX0uyl8M+W+8gH+kBqQ==} + + load-json-file@5.3.0: + resolution: {integrity: sha512-cJGP40Jc/VXUsp8/OrnyKyTZ1y6v/dphm3bioS+RrKXjK2BB6wHUd6JptZEFDGgGahMT+InnZO5i1Ei9mpC8Bw==} + engines: {node: '>=6'} + + locate-path@3.0.0: + resolution: {integrity: sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==} + engines: {node: '>=6'} + + locate-path@5.0.0: + resolution: {integrity: sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==} + engines: {node: '>=8'} + + locate-path@6.0.0: + resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} + engines: {node: '>=10'} + + lodash.debounce@4.0.8: + resolution: {integrity: sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==} + + lodash.defaults@4.2.0: + resolution: {integrity: sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ==} + + lodash.difference@4.5.0: + resolution: {integrity: sha512-dS2j+W26TQ7taQBGN8Lbbq04ssV3emRw4NY58WErlTO29pIqS0HmoT5aJ9+TUQ1N3G+JOZSji4eugsWwGp9yPA==} + + lodash.escaperegexp@4.1.2: + resolution: {integrity: sha512-TM9YBvyC84ZxE3rgfefxUWiQKLilstD6k7PTGt6wfbtXF8ixIJLOL3VYyV/z+ZiPLsVxAsKAFVwWlWeb2Y8Yyw==} + + lodash.flatten@4.4.0: + resolution: {integrity: sha512-C5N2Z3DgnnKr0LOpv/hKCgKdb7ZZwafIrsesve6lmzvZIRZRGaZ/l6Q8+2W7NaT+ZwO3fFlSCzCzrDCFdJfZ4g==} + + lodash.groupby@4.6.0: + resolution: {integrity: sha512-5dcWxm23+VAoz+awKmBaiBvzox8+RqMgFhi7UvX9DHZr2HdxHXM/Wrf8cfKpsW37RNrvtPn6hSwNqurSILbmJw==} + + lodash.includes@4.3.0: + resolution: {integrity: sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==} + + lodash.isboolean@3.0.3: + resolution: {integrity: sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==} + + lodash.isequal@4.5.0: + resolution: {integrity: sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==} + deprecated: This package is deprecated. Use require('node:util').isDeepStrictEqual instead. + + lodash.isfunction@3.0.9: + resolution: {integrity: sha512-AirXNj15uRIMMPihnkInB4i3NHeb4iBtNg9WRWuK2o31S+ePwwNmDPaTL3o7dTJ+VXNZim7rFs4rxN4YU1oUJw==} + + lodash.isinteger@4.0.4: + resolution: {integrity: sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==} + + lodash.isnil@4.0.0: + resolution: {integrity: sha512-up2Mzq3545mwVnMhTDMdfoG1OurpA/s5t88JmQX809eH3C8491iu2sfKhTfhQtKY78oPNhiaHJUpT/dUDAAtng==} + + lodash.isnumber@3.0.3: + resolution: {integrity: sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==} + + lodash.isplainobject@4.0.6: + resolution: {integrity: sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==} + + lodash.isstring@4.0.1: + resolution: {integrity: sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==} + + lodash.isundefined@3.0.1: + resolution: {integrity: sha512-MXB1is3s899/cD8jheYYE2V9qTHwKvt+npCwpD+1Sxm3Q3cECXCiYHjeHWXNwr6Q0SOBPrYUDxendrO6goVTEA==} + + lodash.merge@4.6.2: + resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} + + lodash.once@4.1.1: + resolution: {integrity: sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==} + + lodash.union@4.6.0: + resolution: {integrity: sha512-c4pB2CdGrGdjMKYLA+XiRDO7Y0PRQbm/Gzg8qMj+QH+pFVAoTp5sBpO0odL3FjoPCGjK96p6qsP+yQoiLoOBcw==} + + lodash.uniq@4.5.0: + resolution: {integrity: sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ==} + + lodash@4.18.1: + resolution: {integrity: sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==} + + log4js@6.9.1: + resolution: {integrity: sha512-1somDdy9sChrr9/f4UlzhdaGfDR2c/SaD2a4T7qEkG4jTS57/B3qmnjLYePwQ8cqWnUHZI0iAKxMBpCZICiZ2g==} + engines: {node: '>=8.0'} + + loose-envify@1.4.0: + resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} + hasBin: true + + lru-cache@10.4.3: + resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} + + lru-cache@11.5.2: + resolution: {integrity: sha512-4pfM1Ff0x50o0tQwb5ucw/RzNyD0/YJME6IVcStalZuMWxdt3sR3huStTtxz4PUmvZfRguvDejasvQ2kifR11g==} + engines: {node: 20 || >=22} + + lru-cache@5.1.1: + resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} + + lru-cache@7.18.3: + resolution: {integrity: sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==} + engines: {node: '>=12'} + + make-dir@2.1.0: + resolution: {integrity: sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==} + engines: {node: '>=6'} + + make-dir@4.0.0: + resolution: {integrity: sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==} + engines: {node: '>=10'} + + math-intrinsics@1.1.0: + resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} + engines: {node: '>= 0.4'} + + media-typer@0.3.0: + resolution: {integrity: sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==} + engines: {node: '>= 0.6'} + + media-typer@1.1.1: + resolution: {integrity: sha512-yz3xRaG20c6/BOzvYoDaGtPmGscs7YivItZEEqe6GbwNfHuxu9YNmvnEkMzKldAGY4/80pRcQRZSEnhquk9XuQ==} + engines: {node: '>= 0.8'} + + memory-pager@1.5.0: + resolution: {integrity: sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg==} + + merge-descriptors@2.0.0: + resolution: {integrity: sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==} + engines: {node: '>=18'} + + merge-stream@2.0.0: + resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==} + + methods@1.1.2: + resolution: {integrity: sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==} + engines: {node: '>= 0.6'} + + mime-db@1.52.0: + resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} + engines: {node: '>= 0.6'} + + mime-db@1.54.0: + resolution: {integrity: sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==} + engines: {node: '>= 0.6'} + + mime-types@2.1.35: + resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} + engines: {node: '>= 0.6'} + + mime-types@3.0.2: + resolution: {integrity: sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==} + engines: {node: '>=18'} + + mime@2.6.0: + resolution: {integrity: sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==} + engines: {node: '>=4.0.0'} + hasBin: true + + mimic-fn@2.1.0: + resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==} + engines: {node: '>=6'} + + minimalistic-assert@1.0.1: + resolution: {integrity: sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==} + + minimalistic-crypto-utils@1.0.1: + resolution: {integrity: sha512-JIYlbt6g8i5jKfJ3xz7rF0LXmv2TkDxBLUkiBeZ7bAx4GnnNMr8xFpGnOxn6GhTEHx3SjRrZEoU+j04prX1ktg==} + + minimatch@10.2.6: + resolution: {integrity: sha512-vpLQEs+VLCr1nU0BXS07maYoFwlDAH0gngQuuttxIwutDFEMHq2blX+8vpgxDdK3J1PwjCJiep77OitTZ4Ll1A==} + engines: {node: 18 || 20 || >=22} + + minimatch@3.1.5: + resolution: {integrity: sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==} + + minimatch@5.1.9: + resolution: {integrity: sha512-7o1wEA2RyMP7Iu7GNba9vc0RWWGACJOCZBJX2GJWip0ikV+wcOsgVuY9uE8CPiyQhkGFSlhuSkZPavN7u1c2Fw==} + engines: {node: '>=10'} + + minimatch@9.0.9: + resolution: {integrity: sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==} + engines: {node: '>=16 || 14 >=14.17'} + + minimist@1.2.8: + resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} + + minipass@7.1.3: + resolution: {integrity: sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==} + engines: {node: '>=16 || 14 >=14.17'} + + mitt@3.0.1: + resolution: {integrity: sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==} + + mkdirp@0.5.6: + resolution: {integrity: sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==} + hasBin: true + + mkdirp@1.0.4: + resolution: {integrity: sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==} + engines: {node: '>=10'} + hasBin: true + + moment-timezone@0.5.48: + resolution: {integrity: sha512-f22b8LV1gbTO2ms2j2z13MuPogNoh5UzxL3nzNAYKGraILnbGc9NEE6dyiiiLv46DGRb8A4kg8UKWLjPthxBHw==} + + moment@2.30.1: + resolution: {integrity: sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how==} + + mongodb-connection-string-url@3.0.2: + resolution: {integrity: sha512-rMO7CGo/9BFwyZABcKAWL8UJwH/Kc2x0g72uhDWzG48URRax5TCIcJ7Rc3RZqffZzO/Gwff/jyKwCU9TN8gehA==} + + mongodb-connection-string-url@7.0.2: + resolution: {integrity: sha512-ZoS07RoFqpKYQwAk59qmrx8+jJHNHU30UjlU96QktiGn1ltvDr+vCznLX5DiUBLEpMAHatHNWV1nM/74ul66kA==} + engines: {node: '>=20.19.0'} + + mongodb@6.21.0: + resolution: {integrity: sha512-URyb/VXMjJ4da46OeSXg+puO39XH9DeQpWCslifrRn9JWugy0D+DvvBvkm2WxmHe61O/H19JM66p1z7RHVkZ6A==} + engines: {node: '>=16.20.1'} + peerDependencies: + '@aws-sdk/credential-providers': ^3.188.0 + '@mongodb-js/zstd': ^1.1.0 || ^2.0.0 + gcp-metadata: ^5.2.0 + kerberos: ^2.0.1 + mongodb-client-encryption: '>=6.0.0 <7' + snappy: ^7.3.2 + socks: ^2.7.1 + peerDependenciesMeta: + '@aws-sdk/credential-providers': + optional: true + '@mongodb-js/zstd': + optional: true + gcp-metadata: + optional: true + kerberos: + optional: true + mongodb-client-encryption: + optional: true + snappy: + optional: true + socks: + optional: true + + mongodb@7.5.0: + resolution: {integrity: sha512-5FnrEDLnvp6ycUOGLNLLU33BfCx2qmp2mJjGPDwKLruYsVzXVSK5fsGpoDXvsXJwBfBsD7ebMRdawbDxC2814g==} + engines: {node: '>=20.19.0'} + peerDependencies: + '@aws-sdk/credential-providers': ^3.806.0 + '@mongodb-js/zstd': ^7.0.0 + gcp-metadata: ^7.0.1 + kerberos: ^7.0.0 + mongodb-client-encryption: ^7.2.0 + snappy: ^7.3.2 + socks: ^2.8.6 + peerDependenciesMeta: + '@aws-sdk/credential-providers': + optional: true + '@mongodb-js/zstd': + optional: true + gcp-metadata: + optional: true + kerberos: + optional: true + mongodb-client-encryption: + optional: true + snappy: + optional: true + socks: + optional: true + + mongoose@9.9.5: + resolution: {integrity: sha512-t/kUoeDjlHmav7yCaq//YKU3wiIgUPaTj4GrluiTHUw4jQ77/CXtdMDSor4f1u3SKLhIK6NgCF+AzzUqU02Aag==} + engines: {node: '>=20.19.0'} + + mpath@0.9.0: + resolution: {integrity: sha512-ikJRQTk8hw5DEoFVxHG1Gn9T/xcjtdnOKIU1JTmGjZZlg9LST2mBLmcX3/ICIbgJydT2GOc15RnNy5mHmzfSew==} + engines: {node: '>=4.0.0'} + + mquery@6.0.0: + resolution: {integrity: sha512-b2KQNsmgtkscfeDgkYMcWGn9vZI9YoXh802VDEwE6qc50zxBFQ0Oo8ROkawbPAsXCY1/Z1yp0MagqsZStPWJjw==} + engines: {node: '>=20.19.0'} + + ms@2.0.0: + resolution: {integrity: sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==} + + ms@2.1.2: + resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==} + + ms@2.1.3: + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + + multer@2.3.0: + resolution: {integrity: sha512-cjNbm3sttszgZeGfJR124D+jFEfkXCVAsoPBmFn9X7UxmDSFHWqE2CoEj0vrmSpuAFnqWR1Szcm9QTsiHr60Xw==} + engines: {node: '>= 10.16.0'} + + nanoid@5.1.16: + resolution: {integrity: sha512-kVrnsrJqMR8+oLJnGEmSWw9BivK5mt7H3FZatVRjrc5wGqFYuBxX1yG7+A7Gi5AefkX6t/oCkizcQgpu0cY1dQ==} + engines: {node: ^18 || >=20} + hasBin: true + + napi-postinstall@0.3.4: + resolution: {integrity: sha512-PHI5f1O0EP5xJ9gQmFGMS6IZcrVvTjpXjz7Na41gTE7eE2hK11lg04CECCYEEjdc17EV4DO+fkGEtt7TpTaTiQ==} + engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0} + hasBin: true + + natural-compare@1.4.0: + resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} + + negotiator@1.1.0: + resolution: {integrity: sha512-NMPBRMJgiQHjbd8phG3Vebdx4kZ1H121rbl5IkMqeOsahptB9BKo/d7oJ3zTXqTgagn2bWlNSXkh0QUGM31RYg==} + engines: {node: '>=18'} + + netmask@2.1.1: + resolution: {integrity: sha512-eonl3sLUha+S1GzTPxychyhnUzKyeQkZ7jLjKrBagJgPla13F+uQ71HgpFefyHgqrjEbCPkDArxYsjY8/+gLKA==} + engines: {node: '>= 0.4.0'} + + node-addon-api@7.1.1: + resolution: {integrity: sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==} + + node-addon-api@8.9.2: + resolution: {integrity: sha512-VijLXbi3UACN69I0JVXJsX4tjACjNoQDgv2gTF6sx2wWEi8tkSg2eX8p5gSIFi8z2+DL3oHmY6OyKce38SDolg==} + engines: {node: ^18 || ^20 || >= 21} + + node-cache@5.1.2: + resolution: {integrity: sha512-t1QzWwnk4sjLWaQAS8CHgOJ+RAfmHpxFWmc36IWTiWHQfs0w5JDMBS1b1ZxQteo0vVVuWJvIUKHDkkeK7vIGCg==} + engines: {node: '>= 8.0.0'} + + node-cron@4.6.0: + resolution: {integrity: sha512-Si/bzYiKRHOB8/a99T2+SDGN582ONDMSTlJr5oCkT6GtnqPjZ2s10eoQRYkW9ZHwjVxONL+W8Fb+qR0AHMQsdg==} + engines: {node: '>=20'} + + node-environment-flags@1.0.6: + resolution: {integrity: sha512-5Evy2epuL+6TM0lCQGpFIj6KwiEsGh1SrHUhTbNX+sLbBtjidPZFAnVK9y5yU1+h//RitLbRHTIMyxQPtxMdHw==} + + node-exports-info@1.6.2: + resolution: {integrity: sha512-kXs9Go0cah0qHVV2v389IXQLdLCeE1xfFtjOAF+iobu0OIoG1pje8At2vMHyaPMiPMnG/LWP50twML21eMcAag==} + engines: {node: '>= 0.4'} + + node-gyp-build@4.8.4: + resolution: {integrity: sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ==} + hasBin: true + + node-int64@0.4.0: + resolution: {integrity: sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==} + + node-readable-to-web-readable-stream@0.4.2: + resolution: {integrity: sha512-/cMZNI34v//jUTrI+UIo4ieHAB5EZRY/+7OmXZgBxaWBMcW2tGdceIw06RFxWxrKZ5Jp3sI2i5TsRo+CBhtVLQ==} + + node-releases@2.0.54: + resolution: {integrity: sha512-YHs7BmmcsdAI5Ozuf8JZo6PT0mv2GIWC9vMfvUC3dp65M8hn7Ux8CPL+2oBI7juNuj9d0ndhTcznq2ODBps9cQ==} + engines: {node: '>=18'} + + nodemailer@10.0.0: + resolution: {integrity: sha512-wdv+hXBg0iIPIOf108FaZyTxqjQpCvQeAJt9pHHTooPy/y3vQtI9eiImM9AIVKR3e/0FeMA1oueNfBjFJcaK+Q==} + engines: {node: '>=20.0.0'} + + nodemon@3.1.14: + resolution: {integrity: sha512-jakjZi93UtB3jHMWsXL68FXSAosbLfY0In5gtKq3niLSkrWznrVBzXFNOEMJUfc9+Ke7SHWoAZsiMkNP3vq6Jw==} + engines: {node: '>=10'} + hasBin: true + + nopt@10.0.1: + resolution: {integrity: sha512-df3sBr/6ax9hSGuC3CspvLlbnX8cP5L5nZwXF8cGN8l0zSWR6BvzmQ6jPUKjvo6+/xdpkNvEcucBNUdBeeV13g==} + engines: {node: ^22.22.2 || ^24.15.0 || >=26.0.0} + hasBin: true + + nopt@7.2.1: + resolution: {integrity: sha512-taM24ViiimT/XntxbPyJQzCG+p4EKOpgD3mxFwW38mGjVUrfERQOeY4EDHjdnptttfHuHQXFx+lTP08Q+mLa/w==} + engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + hasBin: true + + normalize-path@3.0.0: + resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} + engines: {node: '>=0.10.0'} + + npm-run-path@4.0.1: + resolution: {integrity: sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==} + engines: {node: '>=8'} + + object-assign@4.1.1: + resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} + engines: {node: '>=0.10.0'} + + object-inspect@1.13.4: + resolution: {integrity: sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==} + engines: {node: '>= 0.4'} + + object-keys@1.1.1: + resolution: {integrity: sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==} + engines: {node: '>= 0.4'} + + object.assign@4.1.7: + resolution: {integrity: sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==} + engines: {node: '>= 0.4'} + + object.entries@1.1.9: + resolution: {integrity: sha512-8u/hfXFRBD1O0hPUjioLhoWFHRmt6tKA4/vZPyckBr18l1KE9uHrFaFaUi8MDRTpi4uak2goyPTSNJLXX2k2Hw==} + engines: {node: '>= 0.4'} + + object.fromentries@2.0.8: + resolution: {integrity: sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ==} + engines: {node: '>= 0.4'} + + object.getownpropertydescriptors@2.1.9: + resolution: {integrity: sha512-mt8YM6XwsTTovI+kdZdHSxoyF2DI59up034orlC9NfweclcWOt7CVascNNLp6U+bjFVCVCIh9PwS76tDM/rH8g==} + engines: {node: '>= 0.4'} + + object.groupby@1.0.3: + resolution: {integrity: sha512-+Lhy3TQTuzXI5hevh8sBGqbmurHbbIjAi0Z4S63nthVLmLxfbj4T54a4CfZrXIrt9iP4mVAPYMo/v99taj3wjQ==} + engines: {node: '>= 0.4'} + + object.values@1.2.1: + resolution: {integrity: sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA==} + engines: {node: '>= 0.4'} + + on-finished@2.4.1: + resolution: {integrity: sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==} + engines: {node: '>= 0.8'} + + on-headers@1.1.0: + resolution: {integrity: sha512-737ZY3yNnXy37FHkQxPzt4UZ2UWPWiCZWLvFZ4fu5cueciegX0zGPnrlY6bwRg4FdQOe9YU8MkmJwGhoMybl8A==} + engines: {node: '>= 0.8'} + + once@1.4.0: + resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} + + onetime@5.1.2: + resolution: {integrity: sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==} + engines: {node: '>=6'} + + optionator@0.9.4: + resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} + engines: {node: '>= 0.8.0'} + + own-keys@1.0.2: + resolution: {integrity: sha512-19YVAg7T+WTrxggPukVq7DjTv6+PJ867TmhCvBsYwmbFCsZd344rq2Ld1p0wo8f8Qrrhgp82c6FJRqdXWtSEhg==} + engines: {node: '>= 0.4'} + + p-limit@2.3.0: + resolution: {integrity: sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==} + engines: {node: '>=6'} + + p-limit@3.1.0: + resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} + engines: {node: '>=10'} + + p-locate@3.0.0: + resolution: {integrity: sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==} + engines: {node: '>=6'} + + p-locate@4.1.0: + resolution: {integrity: sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==} + engines: {node: '>=8'} + + p-locate@5.0.0: + resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} + engines: {node: '>=10'} + + p-try@2.2.0: + resolution: {integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==} + engines: {node: '>=6'} + + pac-proxy-agent@7.2.0: + resolution: {integrity: sha512-TEB8ESquiLMc0lV8vcd5Ql/JAKAoyzHFXaStwjkzpOpC5Yv+pIzLfHvjTSdf3vpa2bMiUQrg9i6276yn8666aA==} + engines: {node: '>= 14'} + + pac-proxy-agent@9.1.0: + resolution: {integrity: sha512-1aU+1mpj3DrQPfo3gh+3Gap3G5x+axnMx1P/y0ZF2ch7kb2meyOCAH8K2k9d27ROsTE7TnAerzxqF9aon2jqnA==} + engines: {node: '>= 20'} + + pac-resolver@7.0.1: + resolution: {integrity: sha512-5NPgf87AT2STgwa2ntRMr45jTKrYBGkVU36yT0ig/n/GMAa3oPqhZfIQ2kMEimReg0+t9kZViDVZ83qfVUlckg==} + engines: {node: '>= 14'} + + pac-resolver@9.0.1: + resolution: {integrity: sha512-lJbS008tmkj08VhoM8Hzuv/VE5tK9MS0OIQ/7+s0lIF+BYhiQWFYzkSpML7lXs9iBu2jfmzBTLzhe9n6BX+dYw==} + engines: {node: '>= 20'} + peerDependencies: + quickjs-wasi: ^2.2.0 + + package-json-from-dist@1.0.1: + resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==} + + pako@1.0.11: + resolution: {integrity: sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==} + + parent-module@1.0.1: + resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} + engines: {node: '>=6'} + + parse-json@4.0.0: + resolution: {integrity: sha512-aOIos8bujGN93/8Ox/jPLh7RwVnPEysynVFE+fQZyg6jKELEHwzgKdLRFHUgXJL6kylijVSBC4BvN9OmsB48Rw==} + engines: {node: '>=4'} + + parse-json@5.2.0: + resolution: {integrity: sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==} + engines: {node: '>=8'} + + parse-passwd@1.0.0: + resolution: {integrity: sha512-1Y1A//QUXEZK7YKz+rD9WydcE1+EuPr6ZBgKecAB8tmoW6UFv0NREVJe1p+jRxtThkcbbKkfwIbWJe/IeE6m2Q==} + engines: {node: '>=0.10.0'} + + parseurl@1.3.3: + resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==} + engines: {node: '>= 0.8'} + + path-exists@3.0.0: + resolution: {integrity: sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==} + engines: {node: '>=4'} + + path-exists@4.0.0: + resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} + engines: {node: '>=8'} + + path-is-absolute@1.0.1: + resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==} + engines: {node: '>=0.10.0'} + + path-key@3.1.1: + resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} + engines: {node: '>=8'} + + path-parse@1.0.7: + resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} + + path-scurry@1.11.1: + resolution: {integrity: sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==} + engines: {node: '>=16 || 14 >=14.18'} + + path-scurry@2.0.2: + resolution: {integrity: sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg==} + engines: {node: 18 || 20 || >=22} + + path-to-regexp@8.4.2: + resolution: {integrity: sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA==} + + pdf-lib@1.17.1: + resolution: {integrity: sha512-V/mpyJAoTsN4cnP31vc0wfNA1+p20evqqnap0KLoRUN0Yk/p3wN52DOEsL4oBFcLdb76hlpKPtzJIgo67j/XLw==} + + pdf-to-img@6.3.0: + resolution: {integrity: sha512-tJXiWo2UyyyxcAnCPUI9KTi/7LrBLNheI0NYVHlufxhwXXwIxLju6Y02fjUutzPHaQtw3ekSaRbHLx/NPdtDSw==} + engines: {node: '>=20.19.0 || >=22.13.0 || >=24'} + hasBin: true + + pdfjs-dist@5.6.205: + resolution: {integrity: sha512-tlUj+2IDa7G1SbvBNN74UHRLJybZDWYom+k6p5KIZl7huBvsA4APi6mKL+zCxd3tLjN5hOOEE9Tv7VdzO88pfg==} + engines: {node: '>=20.19.0 || >=22.13.0 || >=24'} + + pend@1.2.0: + resolution: {integrity: sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==} + + pg-cloudflare@1.4.0: + resolution: {integrity: sha512-Vo7z/6rrQYxpNRylp4Tlob2elzbh+N/MOQbxFVWCxS7oEx6jF53GTJFxK2WWpKuBRkmiin4Mt+xofFDjx09R0A==} + + pg-connection-string@2.14.0: + resolution: {integrity: sha512-XwWDGcLRGCXAR8F/AM5bG7Q+A3Wm2s6QeEjlOKZLlH3UYcguiqCWKyWXVag5TLTIjR7oOJUY8kcADaZgWPyLeg==} + + pg-int8@1.0.1: + resolution: {integrity: sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==} + engines: {node: '>=4.0.0'} + + pg-pool@3.14.0: + resolution: {integrity: sha512-gKtPkFdQPU3DksooVLi9LsjZxrsBUZIpa+7aVx+LV5pNh0KzP4Zleud2po+ConrxbuXGBJ6Hfer6hdgpIBpBaw==} + peerDependencies: + pg: '>=8.0' + + pg-protocol@1.16.0: + resolution: {integrity: sha512-sILXutLVjCLjcDuOmvhX5e2Z4cS5qG/6Bu3VkpFwdf/633ElGLpEh9bgmuI5I4sqKqkifQiGyiCcx1HdtrK7tg==} + + pg-types@2.2.0: + resolution: {integrity: sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==} + engines: {node: '>=4'} + + pg@8.23.0: + resolution: {integrity: sha512-Ip2EQCngowJLGOfCwkFhPXU7/ljlhn6Rxlmy4XYfL2Y+vyRM59+8uR2xqRWKdYmbXmxCFOAmKxBuSUCdF34qLg==} + engines: {node: '>= 16.0.0'} + peerDependencies: + pg-native: '>=3.0.1' + peerDependenciesMeta: + pg-native: + optional: true + + pgpass@1.0.5: + resolution: {integrity: sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug==} + + picocolors@1.1.1: + resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} + + picomatch@2.3.2: + resolution: {integrity: sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==} + engines: {node: '>=8.6'} + + picomatch@4.0.7: + resolution: {integrity: sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==} + engines: {node: '>=12'} + + pify@4.0.1: + resolution: {integrity: sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==} + engines: {node: '>=6'} + + pirates@4.0.7: + resolution: {integrity: sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==} + engines: {node: '>= 6'} + + pkg-conf@3.1.0: + resolution: {integrity: sha512-m0OTbR/5VPNPqO1ph6Fqbj7Hv6QU7gR/tQW40ZqrL1rjgCU85W6C1bJn0BItuJqnR98PWzw7Z8hHeChD1WrgdQ==} + engines: {node: '>=6'} + + pkg-dir@3.0.0: + resolution: {integrity: sha512-/E57AYkoeQ25qkxMj5PBOVgF8Kiu/h7cYS30Z5+R7WaiCCBfLq58ZI/dSeaEKb9WVJV5n/03QwrN3IeWIFllvw==} + engines: {node: '>=6'} + + pkg-dir@4.2.0: + resolution: {integrity: sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==} + engines: {node: '>=8'} + + possible-typed-array-names@1.1.0: + resolution: {integrity: sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==} + engines: {node: '>= 0.4'} + + postgres-array@2.0.0: + resolution: {integrity: sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==} + engines: {node: '>=4'} + + postgres-bytea@1.0.1: + resolution: {integrity: sha512-5+5HqXnsZPE65IJZSMkZtURARZelel2oXUEO8rH83VS/hxH5vv1uHquPg5wZs8yMAfdv971IU+kcPUczi7NVBQ==} + engines: {node: '>=0.10.0'} + + postgres-date@1.0.7: + resolution: {integrity: sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==} + engines: {node: '>=0.10.0'} + + postgres-interval@1.2.0: + resolution: {integrity: sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==} + engines: {node: '>=0.10.0'} + + posthtml-parser@0.11.0: + resolution: {integrity: sha512-QecJtfLekJbWVo/dMAA+OSwY79wpRmbqS5TeXvXSX+f0c6pW4/SE6inzZ2qkU7oAMCPqIDkZDvd/bQsSFUnKyw==} + engines: {node: '>=12'} + + posthtml-render@3.0.0: + resolution: {integrity: sha512-z+16RoxK3fUPgwaIgH9NGnK1HKY9XIDpydky5eQGgAFVXTCSezalv9U2jQuNV+Z9qV1fDWNzldcw4eK0SSbqKA==} + engines: {node: '>=12'} + + posthtml@0.16.7: + resolution: {integrity: sha512-7Hc+IvlQ7hlaIfQFZnxlRl0jnpWq2qwibORBhQYIb0QbNtuicc5ZxvKkVT71HJ4Py1wSZ/3VR1r8LfkCtoCzhw==} + engines: {node: '>=12.0.0'} + + prelude-ls@1.2.1: + resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} + engines: {node: '>= 0.8.0'} + + prettier-linter-helpers@1.0.1: + resolution: {integrity: sha512-SxToR7P8Y2lWmv/kTzVLC1t/GDI2WGjMwNhLLE9qtH8Q13C+aEmuRlzDst4Up4s0Wc8sF2M+J57iB3cMLqftfg==} + engines: {node: '>=6.0.0'} + + prettier@3.9.6: + resolution: {integrity: sha512-OpN0zzVdiaiAhxpuuj5efpIS4sY9j7bY6uR5mnj5yPzGkdkjNKSJeUThPb60Jw29QuAZgA4o+/iB49kFiaBX6g==} + engines: {node: '>=14'} + hasBin: true + + pretty-format@30.5.1: + resolution: {integrity: sha512-byhRAPguVKMQIj4kjJwJ5lAskVhfuiSdiYl/aLTWpgkGEmic2jYhJh1yE9ih8Ox44Xg9ccCT11/S6QrzSJuNrg==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + process-nextick-args@2.0.1: + resolution: {integrity: sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==} + + progress@2.0.3: + resolution: {integrity: sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==} + engines: {node: '>=0.4.0'} + + prop-types@15.8.1: + resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==} + + proto-list@1.2.4: + resolution: {integrity: sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA==} + + proxy-addr@2.0.7: + resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==} + engines: {node: '>= 0.10'} + + proxy-agent-negotiate@1.1.0: + resolution: {integrity: sha512-N8IBcM3UgCVzz2L2Lqv8DVntDnnC8/hiV4nEDUPkqq72TPUgYWjQc+bdZlBPZK9LzPAvOY//gAt0S0DApoOXWQ==} + engines: {node: '>= 20'} + peerDependencies: + kerberos: ^2.0.0 + peerDependenciesMeta: + kerberos: + optional: true + + proxy-agent@6.5.0: + resolution: {integrity: sha512-TmatMXdr2KlRiA2CyDu8GqR8EjahTG3aY3nXjdzFyoZbmB8hrBsTyMezhULIXKnC0jpfjlmiZ3+EaCzoInSu/A==} + engines: {node: '>= 14'} + + proxy-agent@8.0.2: + resolution: {integrity: sha512-idLLRewuemWd7GH/BDJzGiB0dWGfT2SQs3jy6NtZtGWU9uPTTSdeC1/cdbqLwgzhfv027daGFuXX426e2Eg20A==} + engines: {node: '>= 20'} + + proxy-from-env@1.1.0: + resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==} + + proxy-from-env@2.1.0: + resolution: {integrity: sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA==} + engines: {node: '>=10'} + + pstree.remy@1.1.8: + resolution: {integrity: sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==} + + pump@3.0.4: + resolution: {integrity: sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA==} + + punycode@2.3.1: + resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} + engines: {node: '>=6'} + + puppeteer-core@24.43.1: + resolution: {integrity: sha512-T5ScUMAsmhdNbgDR41AGESYeS6V9MSgetkSnVhhW+gXvzC42VesKCn5ld87gAZDJ6vLHL9GkRvY9WtQWSnwFbw==} + engines: {node: '>=18'} + + puppeteer@24.43.1: + resolution: {integrity: sha512-/FSOViCrqRdb1HDocpsM9Z1giA71gTQPUt3SpHGVRALKAy/rJr1fLFYZW9F23qPxqVxTHQnbh/5B5opJST3kAw==} + engines: {node: '>=18'} + hasBin: true + + pure-rand@7.0.1: + resolution: {integrity: sha512-oTUZM/NAZS8p7ANR3SHh30kXB+zK2r2BPcEn/awJIbOvq82WoMN4p62AWWp3Hhw50G0xMsw1mhIBLqHw64EcNQ==} + + qs@6.16.0: + resolution: {integrity: sha512-h6fhOIaRrID2CbEY2fqs+7t+UXZo+MLAnU5gRIq85uFtdiUPCdsApMlHhXogKVM4HM2DVbIjGNTTYH2OcmP1vA==} + engines: {node: '>=0.6'} + + queue-microtask@1.2.3: + resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} + + quickjs-wasi@2.2.0: + resolution: {integrity: sha512-zQxXmQMrEoD3S+jQdYsloq4qAuaxKFHZj6hHqOYGwB2iQZH+q9e/lf5zQPXCKOk0WJuAjzRFbO4KwHIp2D05Iw==} + + random-bytes@1.0.0: + resolution: {integrity: sha512-iv7LhNVO047HzYR3InF6pUcUsPQiHTM1Qal51DcGSuZFBil1aBBWG5eHPNek7bvILMaYJ/8RU1e8w1AMdHmLQQ==} + engines: {node: '>= 0.8'} + + range-parser@1.3.0: + resolution: {integrity: sha512-hek2mFQpPuI4E1BBKrSto+BU3e3x4xuarsbiwr3+lf7p44juvFMV0XFWQAP3xUyqXA4RrXLIoaSUGbSt056ZMw==} + engines: {node: '>= 0.6'} + + raw-body@3.0.2: + resolution: {integrity: sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==} + engines: {node: '>= 0.10'} + + react-is@16.13.1: + resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==} + + react-is@18.3.1: + resolution: {integrity: sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==} + + react-is@19.2.8: + resolution: {integrity: sha512-s5un28nYxKJw5gvUHyW5PCC28CvBqLu9r3cWgzHT4Vo/5fqqkFcdRYsGcKf50WMPpjjFZS5d76fn3YCo2njKwQ==} + + readable-stream@2.3.8: + resolution: {integrity: sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==} + + readable-stream@3.6.2: + resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==} + engines: {node: '>= 6'} + + readdir-glob@1.1.3: + resolution: {integrity: sha512-v05I2k7xN8zXvPD9N+z/uhXPaj0sUFCe2rcWZIpBsqxfP7xXFQ0tipAd/wjj1YxWyWtUS5IDJpOG82JKt2EAVA==} + + readdirp@3.6.0: + resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} + engines: {node: '>=8.10.0'} + + redis-errors@1.2.0: + resolution: {integrity: sha512-1qny3OExCf0UvUV/5wpYKf2YwPcOqXzkwKKSmKHiE6ZMQs5heeE/c8eXK+PNllPvmjgAbfnsbpkGZWy8cBpn9w==} + engines: {node: '>=4'} + + redis-parser@3.0.0: + resolution: {integrity: sha512-DJnGAeenTdpMEH6uAJRK/uiyEIH9WVsUmoLwzudwGJUwZPp80PDBWPHXSAGNPwNvIXAbe7MSUB1zQFugFml66A==} + engines: {node: '>=4'} + + reflect.getprototypeof@1.0.10: + resolution: {integrity: sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==} + engines: {node: '>= 0.4'} + + regenerate-unicode-properties@10.2.2: + resolution: {integrity: sha512-m03P+zhBeQd1RGnYxrGyDAPpWX/epKirLrp8e3qevZdVkKtnCrjjWczIbYc8+xd6vcTStVlqfycTx1KR4LOr0g==} + engines: {node: '>=4'} + + regenerate@1.4.2: + resolution: {integrity: sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==} + + regenerator-runtime@0.14.1: + resolution: {integrity: sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==} + + regexp.prototype.flags@1.5.4: + resolution: {integrity: sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==} + engines: {node: '>= 0.4'} + + regexpp@3.2.0: + resolution: {integrity: sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==} + engines: {node: '>=8'} + + regexpu-core@6.4.0: + resolution: {integrity: sha512-0ghuzq67LI9bLXpOX/ISfve/Mq33a4aFRzoQYhnnok1JOFpmE/A2TBGkNVenOGEeSBCjIiWcc6MVOG5HEQv0sA==} + engines: {node: '>=4'} + + regjsgen@0.8.0: + resolution: {integrity: sha512-RvwtGe3d7LvWiDQXeQw8p5asZUmfU1G/l6WbUXeHta7Y2PEIvBTwH6E2EfmYUK8pxcxEdEmaomqyp0vZZ7C+3Q==} + + regjsparser@0.13.2: + resolution: {integrity: sha512-NgRBy2Nx/bE+9F27nVHnqcN5HjyLmecqsqx2PJHu3/IEtADD4WuxuXIVExD5PoSDFVrl78dOonfcOe5O+5nbzQ==} + hasBin: true + + require-directory@2.1.1: + resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} + engines: {node: '>=0.10.0'} + + resolve-cwd@3.0.0: + resolution: {integrity: sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==} + engines: {node: '>=8'} + + resolve-from@4.0.0: + resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} + engines: {node: '>=4'} + + resolve-from@5.0.0: + resolution: {integrity: sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==} + engines: {node: '>=8'} + + resolve@1.22.12: + resolution: {integrity: sha512-TyeJ1zif53BPfHootBGwPRYT1RUt6oGWsaQr8UyZW/eAm9bKoijtvruSDEmZHm92CwS9nj7/fWttqPCgzep8CA==} + engines: {node: '>= 0.4'} + hasBin: true + + resolve@2.0.0-next.7: + resolution: {integrity: sha512-tqt+NBWwyaMgw3zDsnygx4CByWjQEJHOPMdslYhppaQSJUtL/D4JO9CcBBlhPoI8lz9oJIDXkwXfhF4aWqP8xQ==} + engines: {node: '>= 0.4'} + hasBin: true + + retry-as-promised@7.1.1: + resolution: {integrity: sha512-hMD7odLOt3LkTjcif8aRZqi/hybjpLNgSk5oF5FCowfCjok6LukpN2bDX7R5wDmbgBQFn7YoBxSagmtXHaJYJw==} + + reusify@1.1.0: + resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==} + engines: {iojs: '>=1.0.0', node: '>=0.10.0'} + + rfdc@1.4.1: + resolution: {integrity: sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==} + + rimraf@2.7.1: + resolution: {integrity: sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==} + deprecated: Rimraf versions prior to v4 are no longer supported + hasBin: true + + rimraf@3.0.2: + resolution: {integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==} + deprecated: Rimraf versions prior to v4 are no longer supported + hasBin: true + + router@2.2.0: + resolution: {integrity: sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==} + engines: {node: '>= 18'} + + run-parallel@1.2.0: + resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} + + rxjs@7.8.2: + resolution: {integrity: sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==} + + safe-array-concat@1.1.4: + resolution: {integrity: sha512-wtZlHyOje6OZTGqAoaDKxFkgRtkF9CnHAVnCHKfuj200wAgL+bSJhdsCD2l0Qx/2ekEXjPWcyKkfGb5CPboslg==} + engines: {node: '>=0.4'} + + safe-buffer@5.1.2: + resolution: {integrity: sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==} + + safe-buffer@5.2.1: + resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} + + safe-push-apply@1.0.0: + resolution: {integrity: sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA==} + engines: {node: '>= 0.4'} + + safe-regex-test@1.1.0: + resolution: {integrity: sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==} + engines: {node: '>= 0.4'} + + safer-buffer@2.1.2: + resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} + + saxes@5.0.1: + resolution: {integrity: sha512-5LBh1Tls8c9xgGjw3QrMwETmTMVk0oFgvrFSvWx62llR2hcEInrKNZ2GZCCuuy2lvWrdl5jhbpeqc5hRYKFOcw==} + engines: {node: '>=10'} + + semver@5.7.2: + resolution: {integrity: sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==} + hasBin: true + + semver@6.3.1: + resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} + hasBin: true + + semver@7.8.5: + resolution: {integrity: sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==} + engines: {node: '>=10'} + hasBin: true + + send@1.2.1: + resolution: {integrity: sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==} + engines: {node: '>= 18'} + + sequelize-cli@6.6.5: + resolution: {integrity: sha512-DqyISCULOaEbTM+rRQH4YvcUWeOC1XDiSKcjsC6TfAnT7W837mNkChJhtB/Z4FdCFHRCojmiP7zsrA4pARmacA==} + engines: {node: '>=10.0.0'} + hasBin: true + + sequelize-pool@7.1.0: + resolution: {integrity: sha512-G9c0qlIWQSK29pR/5U2JF5dDQeqqHRragoyahj/Nx4KOOQ3CPPfzxnfqFPCSB7x5UgjOgnZ61nSxz+fjDpRlJg==} + engines: {node: '>= 10.0.0'} + + sequelize@6.37.8: + resolution: {integrity: sha512-HJ0IQFqcTsTiqbEgiuioYFMSD00TP6Cz7zoTti+zVVBwVe9fEhev9cH6WnM3XU31+ABS356durAb99ZuOthnKw==} + engines: {node: '>=10.0.0'} + peerDependencies: + ibm_db: '*' + mariadb: '*' + mysql2: '*' + oracledb: '*' + pg: '*' + pg-hstore: '*' + snowflake-sdk: '*' + sqlite3: '*' + tedious: '*' + peerDependenciesMeta: + ibm_db: + optional: true + mariadb: + optional: true + mysql2: + optional: true + oracledb: + optional: true + pg: + optional: true + pg-hstore: + optional: true + snowflake-sdk: + optional: true + sqlite3: + optional: true + tedious: + optional: true + + serve-static@2.2.1: + resolution: {integrity: sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==} + engines: {node: '>= 18'} + + set-function-length@1.2.2: + resolution: {integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==} + engines: {node: '>= 0.4'} + + set-function-name@2.0.2: + resolution: {integrity: sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==} + engines: {node: '>= 0.4'} + + set-proto@1.0.0: + resolution: {integrity: sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw==} + engines: {node: '>= 0.4'} + + setimmediate@1.0.5: + resolution: {integrity: sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==} + + setprototypeof@1.2.0: + resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==} + + shallow-clone@3.0.1: + resolution: {integrity: sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==} + engines: {node: '>=8'} + + sharp@0.34.5: + resolution: {integrity: sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + + shebang-command@2.0.0: + resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} + engines: {node: '>=8'} + + shebang-regex@3.0.0: + resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} + engines: {node: '>=8'} + + shell-quote@1.9.0: + resolution: {integrity: sha512-Iov+JwFv/2HcTpcwNMKd8+IWNb8tboQJNQTkAY/LLVK7gGH9jy+LGkVqPxfekHl+yMmiqXszdGWXgkfml7hjqA==} + engines: {node: '>= 0.4'} + + side-channel-list@1.0.1: + resolution: {integrity: sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==} + engines: {node: '>= 0.4'} + + side-channel-map@1.0.1: + resolution: {integrity: sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==} + engines: {node: '>= 0.4'} + + side-channel-weakmap@1.0.2: + resolution: {integrity: sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==} + engines: {node: '>= 0.4'} + + side-channel@1.1.1: + resolution: {integrity: sha512-6x6dK6zJdpTzF4sQeNYxwtvBzf6Eg4GtlesS94HOvTudUeyK2WXAaIfmDgsyslYrRBeFIlsi54AYsFGUuhmvrQ==} + engines: {node: '>= 0.4'} + + sift@17.1.3: + resolution: {integrity: sha512-Rtlj66/b0ICeFzYTuNvX/EF1igRbbnGSvEyT79McoZa/DeGhMyC5pWKOEsZKnpkqtSeovd5FL/bjHWC3CIIvCQ==} + + signal-exit@3.0.7: + resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==} + + signal-exit@4.1.0: + resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} + engines: {node: '>=14'} + + simple-update-notifier@2.0.0: + resolution: {integrity: sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==} + engines: {node: '>=10'} + + slash@2.0.0: + resolution: {integrity: sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==} + engines: {node: '>=6'} + + slash@3.0.0: + resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==} + engines: {node: '>=8'} + + smart-buffer@4.2.0: + resolution: {integrity: sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==} + engines: {node: '>= 6.0.0', npm: '>= 3.0.0'} + + socks-proxy-agent@10.1.0: + resolution: {integrity: sha512-WlMj/67cEJ6MDI1OcsnjuYKDNDoyPCCYZ249kuuXPiMDw9F8PXkVaQ7YWu3siTydfQ/4BEZcvGzu+aYvz7dDCQ==} + engines: {node: '>= 20'} + + socks-proxy-agent@8.0.5: + resolution: {integrity: sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw==} + engines: {node: '>= 14'} + + socks@2.8.10: + resolution: {integrity: sha512-e0VyvkVTwVYViNovRkZ9aodhxVlyoMn7eJhVUPxZ+eK9P/7CBkxvvsBOHqFPEH416726W8tLXXXjKwqgTErrCQ==} + engines: {node: '>= 10.0.0', npm: '>= 3.0.0'} + + source-map-support@0.5.21: + resolution: {integrity: sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==} + + source-map@0.6.1: + resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} + engines: {node: '>=0.10.0'} + + sparse-bitfield@3.0.3: + resolution: {integrity: sha512-kvzhi7vqKTfkh0PZU+2D2PIllw2ymqJKujUcyPMd9Y75Nv4nPbGJZXNhxsgdQab2BmlDct1YnfQCguEvHr7VsQ==} + + speedgoose@2.5.11: + resolution: {integrity: sha512-hiSHgk0LOpx/fxDqXMnofenqAGTHxaBUx49FyjJd/a2s4znDdu4UyDPgqwxh1z/6eatvlNEDI5KASwP8pp+PrA==} + engines: {node: '>=20.19.0'} + + split2@4.2.0: + resolution: {integrity: sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==} + engines: {node: '>= 10.x'} + + sprintf-js@1.0.3: + resolution: {integrity: sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==} + + stack-utils@2.0.6: + resolution: {integrity: sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==} + engines: {node: '>=10'} + + standard-as-callback@2.1.0: + resolution: {integrity: sha512-qoRRSyROncaz1z0mvYqIE4lCd9p2R90i6GxW3uZv5ucSu8tU7B5HXUP1gG8pVZsYNVaXjk8ClXHPttLyxAL48A==} + + standard-engine@15.1.0: + resolution: {integrity: sha512-VHysfoyxFu/ukT+9v49d4BRXIokFRZuH3z1VRxzFArZdjSCFpro6rEIU3ji7e4AoAtuSfKBkiOmsrDqKW5ZSRw==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + standard@17.1.2: + resolution: {integrity: sha512-WLm12WoXveKkvnPnPnaFUUHuOB2cUdAsJ4AiGHL2G0UNMrcRAWY2WriQaV8IQ3oRmYr0AWUbLNr94ekYFAHOrA==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + hasBin: true + + statuses@2.0.2: + resolution: {integrity: sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==} + engines: {node: '>= 0.8'} + + stop-iteration-iterator@1.1.0: + resolution: {integrity: sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==} + engines: {node: '>= 0.4'} + + streamroller@3.1.5: + resolution: {integrity: sha512-KFxaM7XT+irxvdqSP1LGLgNWbYN7ay5owZ3r/8t77p+EtSUAfUgtl7be3xtqtOmGUl9K9YPO2ca8133RlTjvKw==} + engines: {node: '>=8.0'} + + streamsearch@1.1.0: + resolution: {integrity: sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==} + engines: {node: '>=10.0.0'} + + streamx@2.28.1: + resolution: {integrity: sha512-zEzXb0s5Cds7tqMH6rhZ05lcJydCWiQPEwiNngVqzsxCc962vLY4Uw+mW7od8kDH258k2Uz/JrOkdIAAhSh9VA==} + + string-length@4.0.2: + resolution: {integrity: sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==} + engines: {node: '>=10'} + + string-width@4.2.3: + resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} + engines: {node: '>=8'} + + string-width@5.1.2: + resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==} + engines: {node: '>=12'} + + string.prototype.matchall@4.1.0: + resolution: {integrity: sha512-tHNHTxInrYLCga9O9YGxWA3G9/nnzQw8UGAyqGx3Ar1pSTTzIuM4woFSq4SowkXCjJIwq5sIiQvEfRI9tCH1qQ==} + engines: {node: '>= 0.4'} + + string.prototype.repeat@1.0.0: + resolution: {integrity: sha512-0u/TldDbKD8bFCQ/4f5+mNRrXwZ8hg2w7ZR8wa16e8z9XpePWl3eGEcUD0OXpEH/VJH/2G3gjUtR3ZOiBe2S/w==} + + string.prototype.trim@1.2.11: + resolution: {integrity: sha512-PwvK7BU+CMTJGYQCTZb5RWXIML92lftJLhQz1tBzgKiqGxJaMlBAa48POXaNAC2s4y8jr3EFqrkF9+44neS46w==} + engines: {node: '>= 0.4'} + + string.prototype.trimend@1.0.10: + resolution: {integrity: sha512-2+3aDAOmPTmuFwjDnmJG2ctEkQKVki7vOSqaxkv42Mowj1V6PnvuwFCRrR5lChUux1TBskPjfkeTOhqczDMxTw==} + engines: {node: '>= 0.4'} + + string.prototype.trimstart@1.0.8: + resolution: {integrity: sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==} + engines: {node: '>= 0.4'} + + string_decoder@1.1.1: + resolution: {integrity: sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==} + + string_decoder@1.3.0: + resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==} + + strip-ansi@6.0.1: + resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} + engines: {node: '>=8'} + + strip-ansi@7.2.0: + resolution: {integrity: sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==} + engines: {node: '>=12'} + + strip-bom@3.0.0: + resolution: {integrity: sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==} + engines: {node: '>=4'} + + strip-bom@4.0.0: + resolution: {integrity: sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==} + engines: {node: '>=8'} + + strip-final-newline@2.0.0: + resolution: {integrity: sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==} + engines: {node: '>=6'} + + strip-json-comments@3.1.1: + resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} + engines: {node: '>=8'} + + superagent@10.3.0: + resolution: {integrity: sha512-B+4Ik7ROgVKrQsXTV0Jwp2u+PXYLSlqtDAhYnkkD+zn3yg8s/zjA2MeGayPoY/KICrbitwneDHrjSotxKL+0XQ==} + engines: {node: '>=14.18.0'} + + supertest@7.2.2: + resolution: {integrity: sha512-oK8WG9diS3DlhdUkcFn4tkNIiIbBx9lI2ClF8K+b2/m8Eyv47LSawxUzZQSNKUrVb2KsqeTDCcjAAVPYaSLVTA==} + engines: {node: '>=14.18.0'} + + supports-color@5.5.0: + resolution: {integrity: sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==} + engines: {node: '>=4'} + + supports-color@7.2.0: + resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} + engines: {node: '>=8'} + + supports-color@8.1.1: + resolution: {integrity: sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==} + engines: {node: '>=10'} + + supports-preserve-symlinks-flag@1.0.0: + resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} + engines: {node: '>= 0.4'} + + synckit@0.11.13: + resolution: {integrity: sha512-eNRKgb3z66Yp3D2CixVujOUvXLFUTij/zVnV8KRyvFdQwpz7I5DS8UfRkTeLzb64u+dkzDSdelE24izu+zSSUg==} + engines: {node: ^14.18.0 || >=16.0.0} + + tar-fs@3.1.3: + resolution: {integrity: sha512-/hU4AXnIdZu+Gvl1pk0oI5f5HxWsCJRtY2aFaJdk9VvyL48DWU6iU5WAIPG+wIi1YvWA6eTJvIviP/tMAZZNwQ==} + + tar-stream@2.2.0: + resolution: {integrity: sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==} + engines: {node: '>=6'} + + tar-stream@3.2.1: + resolution: {integrity: sha512-nqsEO8zLZJvrOMdEwkA0QdCLFbetHMn95Zqu4fKwX+hkaTWJPZZOrxx/PwtxoK0MMGQmBQNRW3CPs8IFYQz4cQ==} + + tcp-port-used@1.0.3: + resolution: {integrity: sha512-4CEQ3qRJYo+mtEbJ+OoQu3dF4TDkwaO3RDVC4UzP5cpAOIUWwuwPjD7sdxDFFqsMUjsXVVYBMlg/boAaloThMA==} + + teex@1.0.1: + resolution: {integrity: sha512-eYE6iEI62Ni1H8oIa7KlDU6uQBtqr4Eajni3wX7rpfXD8ysFx8z0+dri+KWEPWpBsxXfxu58x/0jvTVT1ekOSg==} + + test-exclude@7.0.2: + resolution: {integrity: sha512-u9E6A+ZDYdp7a4WnarkXPZOx8Ilz46+kby6p1yZ8zsGTz9gYa6FIS7lj2oezzNKmtdyyJNNmmXDppga5GB7kSw==} + engines: {node: '>=18'} + + text-decoder@1.2.7: + resolution: {integrity: sha512-vlLytXkeP4xvEq2otHeJfSQIRyWxo/oZGEbXrtEEF9Hnmrdly59sUbzZ/QgyWuLYHctCHxFF4tRQZNQ9k60ExQ==} + + text-table@0.2.0: + resolution: {integrity: sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==} + + tmp@0.2.7: + resolution: {integrity: sha512-e0votIpp4Uo2AJYSzVHV6xCcawuiez3DzqDAbrTc3YxBkplN6e+dM13ZeIcZnDg/QpSuU2zfZ3rzwY8ukEnaXw==} + engines: {node: '>=14.14'} + + to-regex-range@5.0.1: + resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} + engines: {node: '>=8.0'} + + toidentifier@1.0.1: + resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==} + engines: {node: '>=0.6'} + + toposort-class@1.0.1: + resolution: {integrity: sha512-OsLcGGbYF3rMjPUf8oKktyvCiUxSbqMMS39m33MAjLTC1DVIH6x3WSt63/M77ihI09+Sdfk1AXvfhCEeUmC7mg==} + + touch@3.1.1: + resolution: {integrity: sha512-r0eojU4bI8MnHr8c5bNo7lJDdI2qXlWWJk6a9EAFG7vbhTjElYhBVS3/miuE0uOuoLdb8Mc/rVfsmm6eo5o9GA==} + hasBin: true + + tr46@5.1.1: + resolution: {integrity: sha512-hdF5ZgjTqgAntKkklYw0R03MG2x/bSzTtkxmIRw/sTNV8YXsCJ1tfLAX23lhxhHJlEf3CRCOCGGWw3vI3GaSPw==} + engines: {node: '>=18'} + + traverse@0.3.9: + resolution: {integrity: sha512-iawgk0hLP3SxGKDfnDJf8wTz4p2qImnyihM5Hh/sGvQ3K37dPi/w8sRhdNIxYA1TwFwc5mDhIJq+O0RsvXBKdQ==} + + tree-kill@1.2.2: + resolution: {integrity: sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==} + hasBin: true + + tsconfig-paths@3.15.0: + resolution: {integrity: sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==} + + tslib@1.14.1: + resolution: {integrity: sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==} + + tslib@2.8.1: + resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} + + tweetnacl@1.0.3: + resolution: {integrity: sha512-6rt+RN7aOi1nGMyC4Xa5DdYiukl2UWCbcJft7YhxReBGQD7OAM8Pbxw6YMo4r2diNEA8FEmu32YOn9rhaiE5yw==} + + type-check@0.4.0: + resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} + engines: {node: '>= 0.8.0'} + + type-detect@4.0.8: + resolution: {integrity: sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==} + engines: {node: '>=4'} + + type-fest@0.20.2: + resolution: {integrity: sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==} + engines: {node: '>=10'} + + type-fest@0.21.3: + resolution: {integrity: sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==} + engines: {node: '>=10'} + + type-fest@0.3.1: + resolution: {integrity: sha512-cUGJnCdr4STbePCgqNFbpVNCepa+kAVohJs1sLhxzdH+gnEoOd8VhbYa7pD3zZYGiURWM2xzEII3fQcRizDkYQ==} + engines: {node: '>=6'} + + type-is@1.6.18: + resolution: {integrity: sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==} + engines: {node: '>= 0.6'} + + type-is@2.1.0: + resolution: {integrity: sha512-faYHw0anBbc/kWF3zFTEnxSFOAGUX9GFbOBthvDdLsIlEoWOFOtS0zgCiQYwIskL9iGXZL3kAXD8OoZ4GmMATA==} + engines: {node: '>= 18'} + + typed-array-buffer@1.0.3: + resolution: {integrity: sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==} + engines: {node: '>= 0.4'} + + typed-array-byte-length@1.0.3: + resolution: {integrity: sha512-BaXgOuIxz8n8pIq3e7Atg/7s+DpiYrxn4vdot3w9KbnBhcRQq6o3xemQdIfynqSeXeDrF32x+WvfzmOjPiY9lg==} + engines: {node: '>= 0.4'} + + typed-array-byte-offset@1.0.4: + resolution: {integrity: sha512-bTlAFB/FBYMcuX81gbL4OcpH5PmlFHqlCCpAl8AlEzMz5k53oNDvN8p1PNOWLEmI2x4orp3raOFB51tv9X+MFQ==} + engines: {node: '>= 0.4'} + + typed-array-length@1.0.8: + resolution: {integrity: sha512-phPGCwqr2+Qo0fwniCE8e4pKnGu/yFb5nD5Y8bf0EEeiI5GklnACYA9GFy/DrAeRrKHXvHn+1SUsOWgJp6RO+g==} + engines: {node: '>= 0.4'} + + typed-query-selector@2.12.2: + resolution: {integrity: sha512-EOPFbyIub4ngnEdqi2yOcNeDLaX/0jcE1JoAXQDDMIthap7FoN795lc/SHfIq2d416VufXpM8z/lD+WRm2gfOQ==} + + typedarray@0.0.6: + resolution: {integrity: sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==} + + typedi@0.10.0: + resolution: {integrity: sha512-v3UJF8xm68BBj6AF4oQML3ikrfK2c9EmZUyLOfShpJuItAqVBHWP/KtpGinkSsIiP6EZyyb6Z3NXyW9dgS9X1w==} + + uid-safe@2.1.5: + resolution: {integrity: sha512-KPHm4VL5dDXKz01UuEd88Df+KzynaohSL9fBh096KWAxSKZQDI2uBrVqtvRM4rwrIrRRKsdLNML/lnaaVSRioA==} + engines: {node: '>= 0.8'} + + umzug@2.3.0: + resolution: {integrity: sha512-Z274K+e8goZK8QJxmbRPhl89HPO1K+ORFtm6rySPhFKfKc5GHhqdzD0SGhSWHkzoXasqJuItdhorSvY7/Cgflw==} + engines: {node: '>=6.0.0'} + + unbox-primitive@1.1.0: + resolution: {integrity: sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==} + engines: {node: '>= 0.4'} + + undefsafe@2.0.5: + resolution: {integrity: sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==} + + undici-types@8.3.0: + resolution: {integrity: sha512-j375ScV60dom+YkPFIfTLcOiPxkN/buHz5GobjLhixFuANaNs3C9l4GmrWqejgXWJ7BbJcFYpTEUkS1Ge8bpZQ==} + + unicode-canonical-property-names-ecmascript@2.0.1: + resolution: {integrity: sha512-dA8WbNeb2a6oQzAQ55YlT5vQAWGV9WXOsi3SskE3bcCdM0P4SDd+24zS/OCacdRq5BkdsRj9q3Pg6YyQoxIGqg==} + engines: {node: '>=4'} + + unicode-match-property-ecmascript@2.0.0: + resolution: {integrity: sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q==} + engines: {node: '>=4'} + + unicode-match-property-value-ecmascript@2.2.1: + resolution: {integrity: sha512-JQ84qTuMg4nVkx8ga4A16a1epI9H6uTXAknqxkGF/aFfRLw1xC/Bp24HNLaZhHSkWd3+84t8iXnp1J0kYcZHhg==} + engines: {node: '>=4'} + + unicode-property-aliases-ecmascript@2.2.0: + resolution: {integrity: sha512-hpbDzxUY9BFwX+UeBnxv3Sh1q7HFxj48DTmXchNgRa46lO8uj3/1iEn3MiNUYTg1g9ctIqXCCERn8gYZhHC5lQ==} + engines: {node: '>=4'} + + universalify@0.1.2: + resolution: {integrity: sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==} + engines: {node: '>= 4.0.0'} + + universalify@2.0.1: + resolution: {integrity: sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==} + engines: {node: '>= 10.0.0'} + + unpipe@1.0.0: + resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==} + engines: {node: '>= 0.8'} + + unrs-resolver@1.12.2: + resolution: {integrity: sha512-dmlRxBJJayXjqTwC+JtF1HhJmgf3ftQ3YejFcZrf4+KKtJv0qDsK1pjqaaVjG7wJ5NJ6UVP1OqRMQ71Z4C3rxQ==} + + unzipper@0.10.14: + resolution: {integrity: sha512-ti4wZj+0bQTiX2KmKWuwj7lhV+2n//uXEotUmGuQqrbVZSEGFMbI68+c6JCQ8aAmUWYvtHEz2A8K6wXvueR/6g==} + + update-browserslist-db@1.3.2: + resolution: {integrity: sha512-UQ+MSxlhRm1bzjhU+DcuXfjFO1FzNtqhK5+9Yvlp90ItDLk5vT932A0rFu619nf7RVS+Y/VeaUW1jaRDqZ8VJw==} + hasBin: true + peerDependencies: + browserslist: '>= 4.21.0' + + uri-js@4.4.1: + resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} + + util-deprecate@1.0.2: + resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} + + uuid@8.3.2: + resolution: {integrity: sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==} + deprecated: uuid@10 and below is no longer supported. For ESM codebases, update to uuid@latest. For CommonJS codebases, use uuid@11 (but be aware this version will likely be deprecated in 2028). + hasBin: true + + v8-to-istanbul@9.3.0: + resolution: {integrity: sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA==} + engines: {node: '>=10.12.0'} + + v8flags@3.2.0: + resolution: {integrity: sha512-mH8etigqMfiGWdeXpaaqGfs6BndypxusHHcv2qSHyZkGEznCd/qAXCWWRzeowtL54147cktFOC4P5y+kl8d8Jg==} + engines: {node: '>= 0.10'} + + validator@13.15.35: + resolution: {integrity: sha512-TQ5pAGhd5whStmqWvYF4OjQROlmv9SMFVt37qoCBdqRffuuklWYQlCNnEs2ZaIBD1kZRNnikiZOS1eqgkar0iw==} + engines: {node: '>= 0.10'} + + vary@1.1.2: + resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==} + engines: {node: '>= 0.8'} + + version-guard@1.1.3: + resolution: {integrity: sha512-JwPr6erhX53EWH/HCSzfy1tTFrtPXUe927wdM1jqBBeYp1OM+qPHjWbsvv6pIBduqdgxxS+ScfG7S28pzyr2DQ==} + engines: {node: '>=0.10.48'} + + webdriver-bidi-protocol@0.4.1: + resolution: {integrity: sha512-ARrjNjtWRRs2w4Tk7nqrf2gBI0QXWuOmMCx2hU+1jUt6d00MjMxURrhxhGbrsoiZKJrhTSTzbIrc554iKI10qw==} + + webidl-conversions@7.0.0: + resolution: {integrity: sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==} + engines: {node: '>=12'} + + whatwg-url@14.2.0: + resolution: {integrity: sha512-De72GdQZzNTUBBChsXueQUnPKDkg/5A5zp7pFDuQAj5UFoENpiACU0wlCvzpAGnTkj++ihpKwKyYewn/XNUbKw==} + engines: {node: '>=18'} + + which-boxed-primitive@1.1.1: + resolution: {integrity: sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==} + engines: {node: '>= 0.4'} + + which-builtin-type@1.2.1: + resolution: {integrity: sha512-6iBczoX+kDQ7a3+YJBnh3T+KZRxM/iYNPXicqk66/Qfm1b93iu+yOImkg0zHbj5LNOcNv1TEADiZ0xa34B4q6Q==} + engines: {node: '>= 0.4'} + + which-collection@1.0.2: + resolution: {integrity: sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==} + engines: {node: '>= 0.4'} + + which-typed-array@1.1.22: + resolution: {integrity: sha512-fvO4ExWMFsqyhG3AiPAObMuY1lxaqgYcxbc49CNdWDDECOJNgQyvsOWVwbZc+qf3rzRtxojBK+CMEv0Ld5CYpw==} + engines: {node: '>= 0.4'} + + which@2.0.2: + resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} + engines: {node: '>= 8'} + hasBin: true + + wkx@0.5.0: + resolution: {integrity: sha512-Xng/d4Ichh8uN4l0FToV/258EjMGU9MGcA0HV2d9B/ZpZB3lqQm7nkOdZdm5GhKtLLhAE7PiVQwN4eN+2YJJUg==} + + word-wrap@1.2.5: + resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} + engines: {node: '>=0.10.0'} + + wrap-ansi@7.0.0: + resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} + engines: {node: '>=10'} + + wrap-ansi@8.1.0: + resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==} + engines: {node: '>=12'} + + wrappy@1.0.2: + resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} + + write-file-atomic@5.0.1: + resolution: {integrity: sha512-+QU2zd6OTD8XWIJCbffaiQeH9U73qIqafo1x6V1snCWYGJf6cVE0cDR4D8xRzcEnfI21IFrUPzPGtcPf8AC+Rw==} + engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + + ws@8.21.3: + resolution: {integrity: sha512-201TZ/kPWxoPr/OKWjquZR1SWKXcvxdH+e1xrx89b3YbmzLMFCLfnaG1HFIgWzJOEWZ7MvpK++odZufgYR50Rw==} + engines: {node: '>=10.0.0'} + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: '>=5.0.2' + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + + xdg-basedir@4.0.0: + resolution: {integrity: sha512-PSNhEJDejZYV7h50BohL09Er9VaIefr2LMAf3OEmpCkjOi34eYyQYAXUTjEQtZJTKcF0E2UKTh+osDLsgNim9Q==} + engines: {node: '>=8'} + + xml@1.0.1: + resolution: {integrity: sha512-huCv9IH9Tcf95zuYCsQraZtWnJvBtLVE0QHMOs8bWyZAFZNDcYjsPq1nEx8jKA9y+Beo9v+7OBPRisQTjinQMw==} + + xmlchars@2.2.0: + resolution: {integrity: sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==} + + xtend@4.0.2: + resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==} + engines: {node: '>=0.4'} + + y18n@5.0.8: + resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} + engines: {node: '>=10'} + + yallist@3.1.1: + resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} + + yargs-parser@20.2.9: + resolution: {integrity: sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==} + engines: {node: '>=10'} + + yargs-parser@21.1.1: + resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==} + engines: {node: '>=12'} + + yargs@16.2.2: + resolution: {integrity: sha512-Nt9ZJjXTv5R8MHbqby/wXQ6Gi0Bb3TcYZkR1bzuL4yB2OxWPkXknz513gEF0GoA6tn00UpbPvERW8rzCuWCA6w==} + engines: {node: '>=10'} + + yargs@17.7.2: + resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==} + engines: {node: '>=12'} + + yargs@17.7.3: + resolution: {integrity: sha512-GZtjxm/J/4TSxuL3FNYjCmLktBTnIw/rVmKSIyKeYAZpmJB2ig9VauCC5xsa82GNKVKDAqpOn3KVzNt0zmrU0g==} + engines: {node: '>=12'} + + yauzl@2.10.0: + resolution: {integrity: sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==} + + yocto-queue@0.1.0: + resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} + engines: {node: '>=10'} + + zip-stream@4.1.1: + resolution: {integrity: sha512-9qv4rlDiopXg4E69k+vMHjNN63YFMe9sZMrdlvKnCjlCRWeCBswPPMPUfx+ipsAWq1LXHe70RcbaHdJJpS6hyQ==} + engines: {node: '>= 10'} + + zod@3.25.76: + resolution: {integrity: sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==} + +snapshots: + + '@aws-sdk/checksums@3.1000.29': + dependencies: + '@aws-sdk/core': 3.977.9 + '@aws-sdk/types': 3.974.5 + '@smithy/core': 3.33.3 + '@smithy/types': 4.18.0 + tslib: 2.8.1 + + '@aws-sdk/client-s3@3.1127.0': + dependencies: + '@aws-sdk/checksums': 3.1000.29 + '@aws-sdk/core': 3.977.9 + '@aws-sdk/credential-provider-node': 3.972.82 + '@aws-sdk/middleware-sdk-s3': 3.972.75 + '@aws-sdk/signature-v4-multi-region': 3.996.46 + '@aws-sdk/types': 3.974.5 + '@smithy/core': 3.33.3 + '@smithy/fetch-http-handler': 5.8.0 + '@smithy/node-http-handler': 4.12.1 + '@smithy/types': 4.18.0 + tslib: 2.8.1 + + '@aws-sdk/core@3.977.9': + dependencies: + '@aws-sdk/types': 3.974.5 + '@aws-sdk/xml-builder': 3.972.40 + '@aws/lambda-invoke-store': 0.3.0 + '@smithy/core': 3.33.3 + '@smithy/signature-v4': 5.7.3 + '@smithy/types': 4.18.0 + bowser: 2.14.1 + tslib: 2.8.1 + + '@aws-sdk/credential-provider-env@3.972.70': + dependencies: + '@aws-sdk/core': 3.977.9 + '@aws-sdk/types': 3.974.5 + '@smithy/core': 3.33.3 + '@smithy/types': 4.18.0 + tslib: 2.8.1 + + '@aws-sdk/credential-provider-http@3.972.72': + dependencies: + '@aws-sdk/core': 3.977.9 + '@aws-sdk/types': 3.974.5 + '@smithy/core': 3.33.3 + '@smithy/fetch-http-handler': 5.8.0 + '@smithy/node-http-handler': 4.12.1 + '@smithy/types': 4.18.0 + tslib: 2.8.1 + + '@aws-sdk/credential-provider-ini@3.973.15': + dependencies: + '@aws-sdk/core': 3.977.9 + '@aws-sdk/credential-provider-env': 3.972.70 + '@aws-sdk/credential-provider-http': 3.972.72 + '@aws-sdk/credential-provider-login': 3.972.77 + '@aws-sdk/credential-provider-process': 3.972.70 + '@aws-sdk/credential-provider-sso': 3.973.14 + '@aws-sdk/credential-provider-web-identity': 3.972.76 + '@aws-sdk/nested-clients': 3.997.44 + '@aws-sdk/types': 3.974.5 + '@smithy/core': 3.33.3 + '@smithy/credential-provider-imds': 4.5.2 + '@smithy/types': 4.18.0 + tslib: 2.8.1 + + '@aws-sdk/credential-provider-login@3.972.77': + dependencies: + '@aws-sdk/core': 3.977.9 + '@aws-sdk/nested-clients': 3.997.44 + '@aws-sdk/types': 3.974.5 + '@smithy/core': 3.33.3 + '@smithy/types': 4.18.0 + tslib: 2.8.1 + + '@aws-sdk/credential-provider-node@3.972.82': + dependencies: + '@aws-sdk/credential-provider-env': 3.972.70 + '@aws-sdk/credential-provider-http': 3.972.72 + '@aws-sdk/credential-provider-ini': 3.973.15 + '@aws-sdk/credential-provider-process': 3.972.70 + '@aws-sdk/credential-provider-sso': 3.973.14 + '@aws-sdk/credential-provider-web-identity': 3.972.76 + '@aws-sdk/types': 3.974.5 + '@smithy/core': 3.33.3 + '@smithy/credential-provider-imds': 4.5.2 + '@smithy/types': 4.18.0 + tslib: 2.8.1 + + '@aws-sdk/credential-provider-process@3.972.70': + dependencies: + '@aws-sdk/core': 3.977.9 + '@aws-sdk/types': 3.974.5 + '@smithy/core': 3.33.3 + '@smithy/types': 4.18.0 + tslib: 2.8.1 + + '@aws-sdk/credential-provider-sso@3.973.14': + dependencies: + '@aws-sdk/core': 3.977.9 + '@aws-sdk/nested-clients': 3.997.44 + '@aws-sdk/token-providers': 3.1116.0 + '@aws-sdk/types': 3.974.5 + '@smithy/core': 3.33.3 + '@smithy/types': 4.18.0 + tslib: 2.8.1 + + '@aws-sdk/credential-provider-web-identity@3.972.76': + dependencies: + '@aws-sdk/core': 3.977.9 + '@aws-sdk/nested-clients': 3.997.44 + '@aws-sdk/types': 3.974.5 + '@smithy/core': 3.33.3 + '@smithy/types': 4.18.0 + tslib: 2.8.1 + + '@aws-sdk/middleware-sdk-s3@3.972.75': + dependencies: + '@aws-sdk/core': 3.977.9 + '@aws-sdk/signature-v4-multi-region': 3.996.46 + '@aws-sdk/types': 3.974.5 + '@smithy/core': 3.33.3 + '@smithy/types': 4.18.0 + tslib: 2.8.1 + + '@aws-sdk/nested-clients@3.997.44': + dependencies: + '@aws-sdk/core': 3.977.9 + '@aws-sdk/signature-v4-multi-region': 3.996.46 + '@aws-sdk/types': 3.974.5 + '@smithy/core': 3.33.3 + '@smithy/fetch-http-handler': 5.8.0 + '@smithy/node-http-handler': 4.12.1 + '@smithy/types': 4.18.0 + tslib: 2.8.1 + + '@aws-sdk/s3-request-presigner@3.1127.0': + dependencies: + '@aws-sdk/core': 3.977.9 + '@aws-sdk/signature-v4-multi-region': 3.996.46 + '@aws-sdk/types': 3.974.5 + '@smithy/core': 3.33.3 + '@smithy/types': 4.18.0 + tslib: 2.8.1 + + '@aws-sdk/signature-v4-multi-region@3.996.46': + dependencies: + '@aws-sdk/types': 3.974.5 + '@smithy/signature-v4': 5.7.3 + '@smithy/types': 4.18.0 + tslib: 2.8.1 + + '@aws-sdk/token-providers@3.1116.0': + dependencies: + '@aws-sdk/core': 3.977.9 + '@aws-sdk/nested-clients': 3.997.44 + '@aws-sdk/types': 3.974.5 + '@smithy/core': 3.33.3 + '@smithy/types': 4.18.0 + tslib: 2.8.1 + + '@aws-sdk/types@3.974.5': + dependencies: + '@smithy/types': 4.18.0 + tslib: 2.8.1 + + '@aws-sdk/xml-builder@3.972.40': + dependencies: + '@smithy/types': 4.18.0 + tslib: 2.8.1 + + '@aws/lambda-invoke-store@0.3.0': {} + + '@babel/cli@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@jridgewell/trace-mapping': 0.3.31 + commander: 6.2.1 + convert-source-map: 2.0.0 + fs-readdir-recursive: 1.1.0 + glob: 7.2.3 + make-dir: 2.1.0 + slash: 2.0.0 + optionalDependencies: + '@nicolo-ribaudo/chokidar-2': 2.1.8-no-fsevents.3 + chokidar: 3.6.0 + + '@babel/code-frame@7.29.7': + dependencies: + '@babel/helper-validator-identifier': 7.29.7 + js-tokens: 4.0.0 + picocolors: 1.1.1 + + '@babel/compat-data@7.29.7': {} + + '@babel/core@7.29.7': + dependencies: + '@babel/code-frame': 7.29.7 + '@babel/generator': 7.29.8 + '@babel/helper-compilation-targets': 7.29.7 + '@babel/helper-module-transforms': 7.29.7(@babel/core@7.29.7) + '@babel/helpers': 7.29.7 + '@babel/parser': 7.29.8 + '@babel/template': 7.29.7 + '@babel/traverse': 7.29.8 + '@babel/types': 7.29.8 + '@jridgewell/remapping': 2.3.5 + convert-source-map: 2.0.0 + debug: 4.4.3(supports-color@5.5.0) + gensync: 1.0.0-beta.2 + json5: 2.2.3 + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + + '@babel/generator@7.29.8': + dependencies: + '@babel/parser': 7.29.8 + '@babel/types': 7.29.8 + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 + jsesc: 3.1.0 + + '@babel/helper-annotate-as-pure@7.29.7': + dependencies: + '@babel/types': 7.29.8 + + '@babel/helper-compilation-targets@7.29.7': + dependencies: + '@babel/compat-data': 7.29.7 + '@babel/helper-validator-option': 7.29.7 + browserslist: 4.28.9 + lru-cache: 5.1.1 + semver: 6.3.1 + + '@babel/helper-create-class-features-plugin@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-annotate-as-pure': 7.29.7 + '@babel/helper-member-expression-to-functions': 7.29.7 + '@babel/helper-optimise-call-expression': 7.29.7 + '@babel/helper-replace-supers': 7.29.7(@babel/core@7.29.7) + '@babel/helper-skip-transparent-expression-wrappers': 7.29.7 + '@babel/traverse': 7.29.8 + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + + '@babel/helper-create-regexp-features-plugin@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-annotate-as-pure': 7.29.7 + regexpu-core: 6.4.0 + semver: 6.3.1 + + '@babel/helper-define-polyfill-provider@0.6.8(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-compilation-targets': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + debug: 4.4.3(supports-color@5.5.0) + lodash.debounce: 4.0.8 + resolve: 1.22.12 + transitivePeerDependencies: + - supports-color + + '@babel/helper-globals@7.29.7': {} + + '@babel/helper-member-expression-to-functions@7.29.7': + dependencies: + '@babel/traverse': 7.29.8 + '@babel/types': 7.29.8 + transitivePeerDependencies: + - supports-color + + '@babel/helper-module-imports@7.29.7': + dependencies: + '@babel/traverse': 7.29.8 + '@babel/types': 7.29.8 + transitivePeerDependencies: + - supports-color + + '@babel/helper-module-transforms@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-module-imports': 7.29.7 + '@babel/helper-validator-identifier': 7.29.7 + '@babel/traverse': 7.29.8 + transitivePeerDependencies: + - supports-color + + '@babel/helper-optimise-call-expression@7.29.7': + dependencies: + '@babel/types': 7.29.8 + + '@babel/helper-plugin-utils@7.29.7': {} + + '@babel/helper-remap-async-to-generator@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-annotate-as-pure': 7.29.7 + '@babel/helper-wrap-function': 7.29.7 + '@babel/traverse': 7.29.8 + transitivePeerDependencies: + - supports-color + + '@babel/helper-replace-supers@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-member-expression-to-functions': 7.29.7 + '@babel/helper-optimise-call-expression': 7.29.7 + '@babel/traverse': 7.29.8 + transitivePeerDependencies: + - supports-color + + '@babel/helper-skip-transparent-expression-wrappers@7.29.7': + dependencies: + '@babel/traverse': 7.29.8 + '@babel/types': 7.29.8 + transitivePeerDependencies: + - supports-color + + '@babel/helper-string-parser@7.29.7': {} + + '@babel/helper-validator-identifier@7.29.7': {} + + '@babel/helper-validator-option@7.29.7': {} + + '@babel/helper-wrap-function@7.29.7': + dependencies: + '@babel/template': 7.29.7 + '@babel/traverse': 7.29.8 + '@babel/types': 7.29.8 + transitivePeerDependencies: + - supports-color + + '@babel/helpers@7.29.7': + dependencies: + '@babel/template': 7.29.7 + '@babel/types': 7.29.8 + + '@babel/node@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/register': 7.29.7(@babel/core@7.29.7) + commander: 6.2.1 + core-js: 3.50.0 + node-environment-flags: 1.0.6 + regenerator-runtime: 0.14.1 + v8flags: 3.2.0 + + '@babel/parser@7.29.8': + dependencies: + '@babel/types': 7.29.8 + + '@babel/plugin-bugfix-firefox-class-in-computed-class-key@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + '@babel/traverse': 7.29.8 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-bugfix-safari-class-field-initializer-scope@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/plugin-bugfix-safari-rest-destructuring-rhs-array@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + '@babel/helper-skip-transparent-expression-wrappers': 7.29.7 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + '@babel/helper-skip-transparent-expression-wrappers': 7.29.7 + '@babel/plugin-transform-optional-chaining': 7.29.7(@babel/core@7.29.7) + transitivePeerDependencies: + - supports-color + + '@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + '@babel/traverse': 7.29.8 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-proposal-class-properties@7.18.6(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-create-class-features-plugin': 7.29.7(@babel/core@7.29.7) + '@babel/helper-plugin-utils': 7.29.7 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-proposal-object-rest-spread@7.20.7(@babel/core@7.29.7)': + dependencies: + '@babel/compat-data': 7.29.7 + '@babel/core': 7.29.7 + '@babel/helper-compilation-targets': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + '@babel/plugin-syntax-object-rest-spread': 7.8.3(@babel/core@7.29.7) + '@babel/plugin-transform-parameters': 7.29.7(@babel/core@7.29.7) + + '@babel/plugin-proposal-private-property-in-object@7.21.0-placeholder-for-preset-env.2(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + + '@babel/plugin-syntax-async-generators@7.8.4(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/plugin-syntax-bigint@7.8.3(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/plugin-syntax-class-properties@7.12.13(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/plugin-syntax-class-static-block@7.14.5(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/plugin-syntax-import-assertions@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/plugin-syntax-import-attributes@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/plugin-syntax-import-meta@7.10.4(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/plugin-syntax-json-strings@7.8.3(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/plugin-syntax-jsx@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/plugin-syntax-logical-assignment-operators@7.10.4(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/plugin-syntax-nullish-coalescing-operator@7.8.3(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/plugin-syntax-numeric-separator@7.10.4(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/plugin-syntax-object-rest-spread@7.8.3(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/plugin-syntax-optional-catch-binding@7.8.3(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/plugin-syntax-optional-chaining@7.8.3(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/plugin-syntax-private-property-in-object@7.14.5(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/plugin-syntax-top-level-await@7.14.5(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/plugin-syntax-typescript@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/plugin-syntax-unicode-sets-regex@7.18.6(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-create-regexp-features-plugin': 7.29.7(@babel/core@7.29.7) + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/plugin-transform-arrow-functions@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/plugin-transform-async-generator-functions@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + '@babel/helper-remap-async-to-generator': 7.29.7(@babel/core@7.29.7) + '@babel/traverse': 7.29.8 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-async-to-generator@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-module-imports': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + '@babel/helper-remap-async-to-generator': 7.29.7(@babel/core@7.29.7) + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-block-scoped-functions@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/plugin-transform-block-scoping@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/plugin-transform-class-properties@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-create-class-features-plugin': 7.29.7(@babel/core@7.29.7) + '@babel/helper-plugin-utils': 7.29.7 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-class-static-block@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-create-class-features-plugin': 7.29.7(@babel/core@7.29.7) + '@babel/helper-plugin-utils': 7.29.7 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-classes@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-annotate-as-pure': 7.29.7 + '@babel/helper-compilation-targets': 7.29.7 + '@babel/helper-globals': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + '@babel/helper-replace-supers': 7.29.7(@babel/core@7.29.7) + '@babel/traverse': 7.29.8 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-computed-properties@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + '@babel/template': 7.29.7 + + '@babel/plugin-transform-destructuring@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + '@babel/traverse': 7.29.8 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-dotall-regex@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-create-regexp-features-plugin': 7.29.7(@babel/core@7.29.7) + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/plugin-transform-duplicate-keys@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/plugin-transform-duplicate-named-capturing-groups-regex@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-create-regexp-features-plugin': 7.29.7(@babel/core@7.29.7) + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/plugin-transform-dynamic-import@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/plugin-transform-explicit-resource-management@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + '@babel/plugin-transform-destructuring': 7.29.7(@babel/core@7.29.7) + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-exponentiation-operator@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/plugin-transform-export-namespace-from@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/plugin-transform-for-of@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + '@babel/helper-skip-transparent-expression-wrappers': 7.29.7 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-function-name@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-compilation-targets': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + '@babel/traverse': 7.29.8 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-json-strings@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/plugin-transform-literals@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/plugin-transform-logical-assignment-operators@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/plugin-transform-member-expression-literals@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/plugin-transform-modules-amd@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-module-transforms': 7.29.7(@babel/core@7.29.7) + '@babel/helper-plugin-utils': 7.29.7 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-modules-commonjs@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-module-transforms': 7.29.7(@babel/core@7.29.7) + '@babel/helper-plugin-utils': 7.29.7 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-modules-systemjs@7.29.8(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-module-transforms': 7.29.7(@babel/core@7.29.7) + '@babel/helper-plugin-utils': 7.29.7 + '@babel/helper-validator-identifier': 7.29.7 + '@babel/traverse': 7.29.8 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-modules-umd@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-module-transforms': 7.29.7(@babel/core@7.29.7) + '@babel/helper-plugin-utils': 7.29.7 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-named-capturing-groups-regex@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-create-regexp-features-plugin': 7.29.7(@babel/core@7.29.7) + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/plugin-transform-new-target@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/plugin-transform-nullish-coalescing-operator@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/plugin-transform-numeric-separator@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/plugin-transform-object-rest-spread@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-compilation-targets': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + '@babel/plugin-transform-destructuring': 7.29.7(@babel/core@7.29.7) + '@babel/plugin-transform-parameters': 7.29.7(@babel/core@7.29.7) + '@babel/traverse': 7.29.8 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-object-super@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + '@babel/helper-replace-supers': 7.29.7(@babel/core@7.29.7) + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-optional-catch-binding@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/plugin-transform-optional-chaining@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + '@babel/helper-skip-transparent-expression-wrappers': 7.29.7 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-parameters@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/plugin-transform-private-methods@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-create-class-features-plugin': 7.29.7(@babel/core@7.29.7) + '@babel/helper-plugin-utils': 7.29.7 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-private-property-in-object@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-annotate-as-pure': 7.29.7 + '@babel/helper-create-class-features-plugin': 7.29.7(@babel/core@7.29.7) + '@babel/helper-plugin-utils': 7.29.7 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-property-literals@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/plugin-transform-regenerator@7.29.8(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/plugin-transform-regexp-modifiers@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-create-regexp-features-plugin': 7.29.7(@babel/core@7.29.7) + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/plugin-transform-reserved-words@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/plugin-transform-shorthand-properties@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/plugin-transform-spread@7.29.8(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + '@babel/helper-skip-transparent-expression-wrappers': 7.29.7 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-sticky-regex@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/plugin-transform-template-literals@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/plugin-transform-typeof-symbol@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/plugin-transform-unicode-escapes@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/plugin-transform-unicode-property-regex@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-create-regexp-features-plugin': 7.29.7(@babel/core@7.29.7) + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/plugin-transform-unicode-regex@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-create-regexp-features-plugin': 7.29.7(@babel/core@7.29.7) + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/plugin-transform-unicode-sets-regex@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-create-regexp-features-plugin': 7.29.7(@babel/core@7.29.7) + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/preset-env@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/compat-data': 7.29.7 + '@babel/core': 7.29.7 + '@babel/helper-compilation-targets': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + '@babel/helper-validator-option': 7.29.7 + '@babel/plugin-bugfix-firefox-class-in-computed-class-key': 7.29.7(@babel/core@7.29.7) + '@babel/plugin-bugfix-safari-class-field-initializer-scope': 7.29.7(@babel/core@7.29.7) + '@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression': 7.29.7(@babel/core@7.29.7) + '@babel/plugin-bugfix-safari-rest-destructuring-rhs-array': 7.29.7(@babel/core@7.29.7) + '@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining': 7.29.7(@babel/core@7.29.7) + '@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly': 7.29.7(@babel/core@7.29.7) + '@babel/plugin-proposal-private-property-in-object': 7.21.0-placeholder-for-preset-env.2(@babel/core@7.29.7) + '@babel/plugin-syntax-import-assertions': 7.29.7(@babel/core@7.29.7) + '@babel/plugin-syntax-import-attributes': 7.29.7(@babel/core@7.29.7) + '@babel/plugin-syntax-unicode-sets-regex': 7.18.6(@babel/core@7.29.7) + '@babel/plugin-transform-arrow-functions': 7.29.7(@babel/core@7.29.7) + '@babel/plugin-transform-async-generator-functions': 7.29.7(@babel/core@7.29.7) + '@babel/plugin-transform-async-to-generator': 7.29.7(@babel/core@7.29.7) + '@babel/plugin-transform-block-scoped-functions': 7.29.7(@babel/core@7.29.7) + '@babel/plugin-transform-block-scoping': 7.29.7(@babel/core@7.29.7) + '@babel/plugin-transform-class-properties': 7.29.7(@babel/core@7.29.7) + '@babel/plugin-transform-class-static-block': 7.29.7(@babel/core@7.29.7) + '@babel/plugin-transform-classes': 7.29.7(@babel/core@7.29.7) + '@babel/plugin-transform-computed-properties': 7.29.7(@babel/core@7.29.7) + '@babel/plugin-transform-destructuring': 7.29.7(@babel/core@7.29.7) + '@babel/plugin-transform-dotall-regex': 7.29.7(@babel/core@7.29.7) + '@babel/plugin-transform-duplicate-keys': 7.29.7(@babel/core@7.29.7) + '@babel/plugin-transform-duplicate-named-capturing-groups-regex': 7.29.7(@babel/core@7.29.7) + '@babel/plugin-transform-dynamic-import': 7.29.7(@babel/core@7.29.7) + '@babel/plugin-transform-explicit-resource-management': 7.29.7(@babel/core@7.29.7) + '@babel/plugin-transform-exponentiation-operator': 7.29.7(@babel/core@7.29.7) + '@babel/plugin-transform-export-namespace-from': 7.29.7(@babel/core@7.29.7) + '@babel/plugin-transform-for-of': 7.29.7(@babel/core@7.29.7) + '@babel/plugin-transform-function-name': 7.29.7(@babel/core@7.29.7) + '@babel/plugin-transform-json-strings': 7.29.7(@babel/core@7.29.7) + '@babel/plugin-transform-literals': 7.29.7(@babel/core@7.29.7) + '@babel/plugin-transform-logical-assignment-operators': 7.29.7(@babel/core@7.29.7) + '@babel/plugin-transform-member-expression-literals': 7.29.7(@babel/core@7.29.7) + '@babel/plugin-transform-modules-amd': 7.29.7(@babel/core@7.29.7) + '@babel/plugin-transform-modules-commonjs': 7.29.7(@babel/core@7.29.7) + '@babel/plugin-transform-modules-systemjs': 7.29.8(@babel/core@7.29.7) + '@babel/plugin-transform-modules-umd': 7.29.7(@babel/core@7.29.7) + '@babel/plugin-transform-named-capturing-groups-regex': 7.29.7(@babel/core@7.29.7) + '@babel/plugin-transform-new-target': 7.29.7(@babel/core@7.29.7) + '@babel/plugin-transform-nullish-coalescing-operator': 7.29.7(@babel/core@7.29.7) + '@babel/plugin-transform-numeric-separator': 7.29.7(@babel/core@7.29.7) + '@babel/plugin-transform-object-rest-spread': 7.29.7(@babel/core@7.29.7) + '@babel/plugin-transform-object-super': 7.29.7(@babel/core@7.29.7) + '@babel/plugin-transform-optional-catch-binding': 7.29.7(@babel/core@7.29.7) + '@babel/plugin-transform-optional-chaining': 7.29.7(@babel/core@7.29.7) + '@babel/plugin-transform-parameters': 7.29.7(@babel/core@7.29.7) + '@babel/plugin-transform-private-methods': 7.29.7(@babel/core@7.29.7) + '@babel/plugin-transform-private-property-in-object': 7.29.7(@babel/core@7.29.7) + '@babel/plugin-transform-property-literals': 7.29.7(@babel/core@7.29.7) + '@babel/plugin-transform-regenerator': 7.29.8(@babel/core@7.29.7) + '@babel/plugin-transform-regexp-modifiers': 7.29.7(@babel/core@7.29.7) + '@babel/plugin-transform-reserved-words': 7.29.7(@babel/core@7.29.7) + '@babel/plugin-transform-shorthand-properties': 7.29.7(@babel/core@7.29.7) + '@babel/plugin-transform-spread': 7.29.8(@babel/core@7.29.7) + '@babel/plugin-transform-sticky-regex': 7.29.7(@babel/core@7.29.7) + '@babel/plugin-transform-template-literals': 7.29.7(@babel/core@7.29.7) + '@babel/plugin-transform-typeof-symbol': 7.29.7(@babel/core@7.29.7) + '@babel/plugin-transform-unicode-escapes': 7.29.7(@babel/core@7.29.7) + '@babel/plugin-transform-unicode-property-regex': 7.29.7(@babel/core@7.29.7) + '@babel/plugin-transform-unicode-regex': 7.29.7(@babel/core@7.29.7) + '@babel/plugin-transform-unicode-sets-regex': 7.29.7(@babel/core@7.29.7) + '@babel/preset-modules': 0.1.6-no-external-plugins(@babel/core@7.29.7) + babel-plugin-polyfill-corejs2: 0.4.17(@babel/core@7.29.7) + babel-plugin-polyfill-corejs3: 0.14.2(@babel/core@7.29.7) + babel-plugin-polyfill-regenerator: 0.6.8(@babel/core@7.29.7) + core-js-compat: 3.50.0 + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + + '@babel/preset-modules@0.1.6-no-external-plugins(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + '@babel/types': 7.29.8 + esutils: 2.0.3 + + '@babel/register@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + clone-deep: 4.0.1 + find-cache-dir: 2.1.0 + make-dir: 2.1.0 + pirates: 4.0.7 + source-map-support: 0.5.21 + + '@babel/template@7.29.7': + dependencies: + '@babel/code-frame': 7.29.7 + '@babel/parser': 7.29.8 + '@babel/types': 7.29.8 + + '@babel/traverse@7.29.8': + dependencies: + '@babel/code-frame': 7.29.7 + '@babel/generator': 7.29.8 + '@babel/helper-globals': 7.29.7 + '@babel/parser': 7.29.8 + '@babel/template': 7.29.7 + '@babel/types': 7.29.8 + debug: 4.4.3(supports-color@5.5.0) + transitivePeerDependencies: + - supports-color + + '@babel/types@7.29.8': + dependencies: + '@babel/helper-string-parser': 7.29.7 + '@babel/helper-validator-identifier': 7.29.7 + + '@bcoe/v8-coverage@0.2.3': {} + + '@emnapi/core@1.10.0': + dependencies: + '@emnapi/wasi-threads': 1.2.1 + tslib: 2.8.1 + optional: true + + '@emnapi/runtime@1.10.0': + dependencies: + tslib: 2.8.1 + optional: true + + '@emnapi/runtime@1.11.3': + dependencies: + tslib: 2.8.1 + optional: true + + '@emnapi/wasi-threads@1.2.1': + dependencies: + tslib: 2.8.1 + optional: true + + '@eslint-community/eslint-utils@4.10.1(eslint@8.57.1)': + dependencies: + eslint: 8.57.1 + eslint-visitor-keys: 3.4.3 + + '@eslint-community/eslint-utils@4.10.1(eslint@9.39.5)': + dependencies: + eslint: 9.39.5 + eslint-visitor-keys: 3.4.3 + + '@eslint-community/regexpp@4.12.2': {} + + '@eslint/config-array@0.21.2': + dependencies: + '@eslint/object-schema': 2.1.7 + debug: 4.4.3(supports-color@5.5.0) + minimatch: 3.1.5 + transitivePeerDependencies: + - supports-color + + '@eslint/config-helpers@0.4.2': + dependencies: + '@eslint/core': 0.17.0 + + '@eslint/core@0.17.0': + dependencies: + '@types/json-schema': 7.0.15 + + '@eslint/eslintrc@2.1.4': + dependencies: + ajv: 6.15.0 + debug: 4.4.3(supports-color@5.5.0) + espree: 9.6.1 + globals: 13.24.0 + ignore: 5.3.2 + import-fresh: 3.3.1 + js-yaml: 4.3.2 + minimatch: 3.1.5 + strip-json-comments: 3.1.1 + transitivePeerDependencies: + - supports-color + + '@eslint/eslintrc@3.3.7': + dependencies: + ajv: 6.15.0 + debug: 4.4.3(supports-color@5.5.0) + espree: 10.4.0 + globals: 14.0.0 + ignore: 5.3.2 + import-fresh: 3.3.1 + js-yaml: 4.3.2 + minimatch: 3.1.5 + strip-json-comments: 3.1.1 + transitivePeerDependencies: + - supports-color + + '@eslint/js@8.57.1': {} + + '@eslint/js@9.39.5': {} + + '@eslint/object-schema@2.1.7': {} + + '@eslint/plugin-kit@0.4.1': + dependencies: + '@eslint/core': 0.17.0 + levn: 0.4.1 + + '@fast-csv/format@4.3.5': + dependencies: + '@types/node': 14.18.63 + lodash.escaperegexp: 4.1.2 + lodash.isboolean: 3.0.3 + lodash.isequal: 4.5.0 + lodash.isfunction: 3.0.9 + lodash.isnil: 4.0.0 + + '@fast-csv/parse@4.3.6': + dependencies: + '@types/node': 14.18.63 + lodash.escaperegexp: 4.1.2 + lodash.groupby: 4.6.0 + lodash.isfunction: 3.0.9 + lodash.isnil: 4.0.0 + lodash.isundefined: 3.0.1 + lodash.uniq: 4.5.0 + + '@humanfs/core@0.19.2': + dependencies: + '@humanfs/types': 0.15.0 + + '@humanfs/node@0.16.8': + dependencies: + '@humanfs/core': 0.19.2 + '@humanfs/types': 0.15.0 + '@humanwhocodes/retry': 0.4.3 + + '@humanfs/types@0.15.0': {} + + '@humanwhocodes/config-array@0.13.0': + dependencies: + '@humanwhocodes/object-schema': 2.0.3 + debug: 4.4.3(supports-color@5.5.0) + minimatch: 3.1.5 + transitivePeerDependencies: + - supports-color + + '@humanwhocodes/module-importer@1.0.1': {} + + '@humanwhocodes/object-schema@2.0.3': {} + + '@humanwhocodes/retry@0.4.3': {} + + '@img/colour@1.1.0': {} + + '@img/sharp-darwin-arm64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-darwin-arm64': 1.2.4 + optional: true + + '@img/sharp-darwin-x64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-darwin-x64': 1.2.4 + optional: true + + '@img/sharp-libvips-darwin-arm64@1.2.4': + optional: true + + '@img/sharp-libvips-darwin-x64@1.2.4': + optional: true + + '@img/sharp-libvips-linux-arm64@1.2.4': + optional: true + + '@img/sharp-libvips-linux-arm@1.2.4': + optional: true + + '@img/sharp-libvips-linux-ppc64@1.2.4': + optional: true + + '@img/sharp-libvips-linux-riscv64@1.2.4': + optional: true + + '@img/sharp-libvips-linux-s390x@1.2.4': + optional: true + + '@img/sharp-libvips-linux-x64@1.2.4': + optional: true + + '@img/sharp-libvips-linuxmusl-arm64@1.2.4': + optional: true + + '@img/sharp-libvips-linuxmusl-x64@1.2.4': + optional: true + + '@img/sharp-linux-arm64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linux-arm64': 1.2.4 + optional: true + + '@img/sharp-linux-arm@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linux-arm': 1.2.4 + optional: true + + '@img/sharp-linux-ppc64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linux-ppc64': 1.2.4 + optional: true + + '@img/sharp-linux-riscv64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linux-riscv64': 1.2.4 + optional: true + + '@img/sharp-linux-s390x@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linux-s390x': 1.2.4 + optional: true + + '@img/sharp-linux-x64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linux-x64': 1.2.4 + optional: true + + '@img/sharp-linuxmusl-arm64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linuxmusl-arm64': 1.2.4 + optional: true + + '@img/sharp-linuxmusl-x64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linuxmusl-x64': 1.2.4 + optional: true + + '@img/sharp-wasm32@0.34.5': + dependencies: + '@emnapi/runtime': 1.11.3 + optional: true + + '@img/sharp-win32-arm64@0.34.5': + optional: true + + '@img/sharp-win32-ia32@0.34.5': + optional: true + + '@img/sharp-win32-x64@0.34.5': + optional: true + + '@ioredis/commands@1.10.0': {} + + '@ioredis/commands@2.0.0': {} + + '@isaacs/cliui@8.0.2': + dependencies: + string-width: 5.1.2 + string-width-cjs: string-width@4.2.3 + strip-ansi: 7.2.0 + strip-ansi-cjs: strip-ansi@6.0.1 + wrap-ansi: 8.1.0 + wrap-ansi-cjs: wrap-ansi@7.0.0 + + '@istanbuljs/load-nyc-config@1.1.0': + dependencies: + camelcase: 5.3.1 + find-up: 4.1.0 + get-package-type: 0.1.0 + js-yaml: 3.15.2 + resolve-from: 5.0.0 + + '@istanbuljs/schema@0.1.6': {} + + '@jest/console@30.5.1': + dependencies: + '@jest/types': 30.5.1 + '@types/node': 26.4.1 + chalk: 4.1.2 + jest-message-util: 30.5.1 + jest-util: 30.5.1 + slash: 3.0.0 + + '@jest/core@30.5.1': + dependencies: + '@jest/console': 30.5.1 + '@jest/pattern': 30.5.0 + '@jest/reporters': 30.5.1 + '@jest/test-result': 30.5.1 + '@jest/transform': 30.5.1 + '@jest/types': 30.5.1 + '@types/node': 26.4.1 + ansi-escapes: 4.3.2 + chalk: 4.1.2 + ci-info: 4.4.0 + exit-x: 0.2.2 + fast-json-stable-stringify: 2.1.0 + graceful-fs: 4.2.11 + jest-changed-files: 30.5.1 + jest-config: 30.5.1(@types/node@26.4.1) + jest-haste-map: 30.5.1 + jest-message-util: 30.5.1 + jest-regex-util: 30.5.0 + jest-resolve: 30.5.1 + jest-resolve-dependencies: 30.5.1 + jest-runner: 30.5.1 + jest-runtime: 30.5.1 + jest-snapshot: 30.5.1 + jest-util: 30.5.1 + jest-validate: 30.5.1 + jest-watcher: 30.5.1 + pretty-format: 30.5.1 + slash: 3.0.0 + transitivePeerDependencies: + - babel-plugin-macros + - esbuild-register + - supports-color + - ts-node + + '@jest/diff-sequences@30.5.0': {} + + '@jest/environment@30.5.1': + dependencies: + '@jest/fake-timers': 30.5.1 + '@jest/types': 30.5.1 + '@types/node': 26.4.1 + jest-mock: 30.5.1 + + '@jest/expect-utils@30.5.1': + dependencies: + '@jest/get-type': 30.5.0 + + '@jest/expect@30.5.1': + dependencies: + expect: 30.5.1 + jest-snapshot: 30.5.1 + transitivePeerDependencies: + - supports-color + + '@jest/fake-timers@30.5.1': + dependencies: + '@jest/types': 30.5.1 + '@sinonjs/fake-timers': 15.4.0 + '@types/node': 26.4.1 + jest-message-util: 30.5.1 + jest-mock: 30.5.1 + jest-util: 30.5.1 + + '@jest/get-type@30.5.0': {} + + '@jest/globals@30.5.1': + dependencies: + '@jest/environment': 30.5.1 + '@jest/expect': 30.5.1 + '@jest/types': 30.5.1 + jest-mock: 30.5.1 + transitivePeerDependencies: + - supports-color + + '@jest/pattern@30.5.0': + dependencies: + '@types/node': 26.4.1 + jest-regex-util: 30.5.0 + + '@jest/reporters@30.5.1': + dependencies: + '@bcoe/v8-coverage': 0.2.3 + '@jest/console': 30.5.1 + '@jest/test-result': 30.5.1 + '@jest/transform': 30.5.1 + '@jest/types': 30.5.1 + '@jridgewell/trace-mapping': 0.3.31 + '@types/node': 26.4.1 + chalk: 4.1.2 + collect-v8-coverage: 1.0.3 + exit-x: 0.2.2 + glob: 13.0.6 + graceful-fs: 4.2.11 + istanbul-lib-coverage: 3.2.2 + istanbul-lib-instrument: 6.0.3 + istanbul-lib-report: 3.0.1 + istanbul-lib-source-maps: 5.0.6 + istanbul-reports: 3.2.0 + jest-message-util: 30.5.1 + jest-util: 30.5.1 + jest-worker: 30.5.1 + slash: 3.0.0 + string-length: 4.0.2 + v8-to-istanbul: 9.3.0 + transitivePeerDependencies: + - supports-color + + '@jest/schemas@30.5.0': + dependencies: + '@sinclair/typebox': 0.34.52 + + '@jest/snapshot-utils@30.5.1': + dependencies: + '@jest/types': 30.5.1 + chalk: 4.1.2 + graceful-fs: 4.2.11 + natural-compare: 1.4.0 + + '@jest/source-map@30.5.0': + dependencies: + '@jridgewell/trace-mapping': 0.3.31 + callsites: 3.1.0 + convert-source-map: 2.0.0 + graceful-fs: 4.2.11 + + '@jest/test-result@30.5.1': + dependencies: + '@jest/console': 30.5.1 + '@jest/types': 30.5.1 + '@types/istanbul-lib-coverage': 2.0.6 + collect-v8-coverage: 1.0.3 + + '@jest/test-sequencer@30.5.1': + dependencies: + '@jest/test-result': 30.5.1 + graceful-fs: 4.2.11 + jest-haste-map: 30.5.1 + slash: 3.0.0 + + '@jest/transform@30.5.1': + dependencies: + '@babel/core': 7.29.7 + '@jest/types': 30.5.1 + '@jridgewell/trace-mapping': 0.3.31 + babel-plugin-istanbul: 8.0.0 + chalk: 4.1.2 + convert-source-map: 2.0.0 + fast-json-stable-stringify: 2.1.0 + graceful-fs: 4.2.11 + jest-haste-map: 30.5.1 + jest-regex-util: 30.5.0 + jest-util: 30.5.1 + pirates: 4.0.7 + slash: 3.0.0 + write-file-atomic: 5.0.1 + transitivePeerDependencies: + - supports-color + + '@jest/types@30.5.1': + dependencies: + '@jest/pattern': 30.5.0 + '@jest/schemas': 30.5.0 + '@types/istanbul-lib-coverage': 2.0.6 + '@types/istanbul-reports': 3.0.4 + '@types/node': 26.4.1 + '@types/yargs': 17.0.35 + chalk: 4.1.2 + + '@jridgewell/gen-mapping@0.3.13': + dependencies: + '@jridgewell/sourcemap-codec': 1.6.0 + '@jridgewell/trace-mapping': 0.3.31 + + '@jridgewell/remapping@2.3.5': + dependencies: + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 + + '@jridgewell/resolve-uri@3.1.2': {} + + '@jridgewell/sourcemap-codec@1.6.0': {} + + '@jridgewell/trace-mapping@0.3.31': + dependencies: + '@jridgewell/resolve-uri': 3.1.2 + '@jridgewell/sourcemap-codec': 1.6.0 + + '@mongodb-js/saslprep@1.5.0': + dependencies: + sparse-bitfield: 3.0.3 + + '@napi-rs/canvas-android-arm64@0.1.100': + optional: true + + '@napi-rs/canvas-darwin-arm64@0.1.100': + optional: true + + '@napi-rs/canvas-darwin-x64@0.1.100': + optional: true + + '@napi-rs/canvas-linux-arm-gnueabihf@0.1.100': + optional: true + + '@napi-rs/canvas-linux-arm64-gnu@0.1.100': + optional: true + + '@napi-rs/canvas-linux-arm64-musl@0.1.100': + optional: true + + '@napi-rs/canvas-linux-riscv64-gnu@0.1.100': + optional: true + + '@napi-rs/canvas-linux-x64-gnu@0.1.100': + optional: true + + '@napi-rs/canvas-linux-x64-musl@0.1.100': + optional: true + + '@napi-rs/canvas-win32-arm64-msvc@0.1.100': + optional: true + + '@napi-rs/canvas-win32-x64-msvc@0.1.100': + optional: true + + '@napi-rs/canvas@0.1.100': + optionalDependencies: + '@napi-rs/canvas-android-arm64': 0.1.100 + '@napi-rs/canvas-darwin-arm64': 0.1.100 + '@napi-rs/canvas-darwin-x64': 0.1.100 + '@napi-rs/canvas-linux-arm-gnueabihf': 0.1.100 + '@napi-rs/canvas-linux-arm64-gnu': 0.1.100 + '@napi-rs/canvas-linux-arm64-musl': 0.1.100 + '@napi-rs/canvas-linux-riscv64-gnu': 0.1.100 + '@napi-rs/canvas-linux-x64-gnu': 0.1.100 + '@napi-rs/canvas-linux-x64-musl': 0.1.100 + '@napi-rs/canvas-win32-arm64-msvc': 0.1.100 + '@napi-rs/canvas-win32-x64-msvc': 0.1.100 + optional: true + + '@napi-rs/wasm-runtime@1.2.3(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)': + dependencies: + '@emnapi/core': 1.10.0 + '@emnapi/runtime': 1.10.0 + '@tybys/wasm-util': 0.10.3 + optional: true + + '@nats-io/nats-core@3.4.0': + dependencies: + '@nats-io/nkeys': 2.0.3 + '@nats-io/nuid': 3.0.0 + + '@nats-io/nkeys@2.0.3': + dependencies: + tweetnacl: 1.0.3 + + '@nats-io/nuid@3.0.0': {} + + '@nats-io/transport-node@3.4.0': + dependencies: + '@nats-io/nats-core': 3.4.0 + '@nats-io/nkeys': 2.0.3 + '@nats-io/nuid': 3.0.0 + + '@nicolo-ribaudo/chokidar-2@2.1.8-no-fsevents.3': + optional: true + + '@noble/hashes@1.8.0': {} + + '@nodelib/fs.scandir@2.1.5': + dependencies: + '@nodelib/fs.stat': 2.0.5 + run-parallel: 1.2.0 + + '@nodelib/fs.stat@2.0.5': {} + + '@nodelib/fs.walk@1.2.8': + dependencies: + '@nodelib/fs.scandir': 2.1.5 + fastq: 1.20.3 + + '@one-ini/wasm@0.1.1': {} + + '@one-ini/wasm@0.2.1': {} + + '@paralleldrive/cuid2@2.3.1': + dependencies: + '@noble/hashes': 1.8.0 + + '@parcel/watcher-android-arm64@2.6.0': + optional: true + + '@parcel/watcher-darwin-arm64@2.6.0': + optional: true + + '@parcel/watcher-darwin-x64@2.6.0': + optional: true + + '@parcel/watcher-freebsd-x64@2.6.0': + optional: true + + '@parcel/watcher-linux-arm-glibc@2.6.0': + optional: true + + '@parcel/watcher-linux-arm-musl@2.6.0': + optional: true + + '@parcel/watcher-linux-arm64-glibc@2.6.0': + optional: true + + '@parcel/watcher-linux-arm64-musl@2.6.0': + optional: true + + '@parcel/watcher-linux-x64-glibc@2.6.0': + optional: true + + '@parcel/watcher-linux-x64-musl@2.6.0': + optional: true + + '@parcel/watcher-win32-arm64@2.6.0': + optional: true + + '@parcel/watcher-win32-x64@2.6.0': + optional: true + + '@parcel/watcher@2.6.0': + dependencies: + detect-libc: 2.1.2 + is-glob: 4.0.3 + node-addon-api: 7.1.1 + picomatch: 4.0.7 + optionalDependencies: + '@parcel/watcher-android-arm64': 2.6.0 + '@parcel/watcher-darwin-arm64': 2.6.0 + '@parcel/watcher-darwin-x64': 2.6.0 + '@parcel/watcher-freebsd-x64': 2.6.0 + '@parcel/watcher-linux-arm-glibc': 2.6.0 + '@parcel/watcher-linux-arm-musl': 2.6.0 + '@parcel/watcher-linux-arm64-glibc': 2.6.0 + '@parcel/watcher-linux-arm64-musl': 2.6.0 + '@parcel/watcher-linux-x64-glibc': 2.6.0 + '@parcel/watcher-linux-x64-musl': 2.6.0 + '@parcel/watcher-win32-arm64': 2.6.0 + '@parcel/watcher-win32-x64': 2.6.0 + + '@pdf-lib/standard-fonts@1.0.0': + dependencies: + pako: 1.0.11 + + '@pdf-lib/upng@1.0.1': + dependencies: + pako: 1.0.11 + + '@pkgjs/parseargs@0.11.0': + optional: true + + '@pkgr/core@0.3.6': {} + + '@puppeteer/browsers@2.13.2': + dependencies: + debug: 4.4.3(supports-color@5.5.0) + extract-zip: 2.0.1 + progress: 2.0.3 + proxy-agent: 6.5.0 + semver: 7.8.5 + tar-fs: 3.1.3 + yargs: 17.7.3 + transitivePeerDependencies: + - bare-abort-controller + - bare-buffer + - react-native-b4a + - supports-color + + '@rtsao/scc@1.1.0': {} + + '@sinclair/typebox@0.34.52': {} + + '@sinonjs/commons@3.0.1': + dependencies: + type-detect: 4.0.8 + + '@sinonjs/fake-timers@15.4.0': + dependencies: + '@sinonjs/commons': 3.0.1 + + '@smithy/core@3.33.3': + dependencies: + '@smithy/types': 4.18.0 + tslib: 2.8.1 + + '@smithy/credential-provider-imds@4.5.2': + dependencies: + '@smithy/core': 3.33.3 + '@smithy/types': 4.18.0 + tslib: 2.8.1 + + '@smithy/fetch-http-handler@5.8.0': + dependencies: + '@smithy/core': 3.33.3 + '@smithy/types': 4.18.0 + tslib: 2.8.1 + + '@smithy/node-http-handler@4.12.1': + dependencies: + '@smithy/core': 3.33.3 + '@smithy/types': 4.18.0 + tslib: 2.8.1 + + '@smithy/signature-v4@5.7.3': + dependencies: + '@smithy/core': 3.33.3 + '@smithy/types': 4.18.0 + tslib: 2.8.1 + + '@smithy/types@4.18.0': + dependencies: + tslib: 2.8.1 + + '@standard-schema/spec@1.1.0': {} + + '@testim/chrome-version@1.1.4': + optional: true + + '@tootallnate/quickjs-emscripten@0.23.0': {} + + '@tybys/wasm-util@0.10.3': + dependencies: + tslib: 2.8.1 + optional: true + + '@types/babel__core@7.20.5': + dependencies: + '@babel/parser': 7.29.8 + '@babel/types': 7.29.8 + '@types/babel__generator': 7.27.0 + '@types/babel__template': 7.4.4 + '@types/babel__traverse': 7.28.0 + + '@types/babel__generator@7.27.0': + dependencies: + '@babel/types': 7.29.8 + + '@types/babel__template@7.4.4': + dependencies: + '@babel/parser': 7.29.8 + '@babel/types': 7.29.8 + + '@types/babel__traverse@7.28.0': + dependencies: + '@babel/types': 7.29.8 + + '@types/debug@4.1.13': + dependencies: + '@types/ms': 2.1.0 + + '@types/estree@1.0.9': {} + + '@types/istanbul-lib-coverage@2.0.6': {} + + '@types/istanbul-lib-report@3.0.3': + dependencies: + '@types/istanbul-lib-coverage': 2.0.6 + + '@types/istanbul-reports@3.0.4': + dependencies: + '@types/istanbul-lib-report': 3.0.3 + + '@types/json-schema@7.0.15': {} + + '@types/json5@0.0.29': {} + + '@types/ms@2.1.0': {} + + '@types/node@14.18.63': {} + + '@types/node@26.4.1': + dependencies: + undici-types: 8.3.0 + + '@types/stack-utils@2.0.3': {} + + '@types/validator@13.15.10': {} + + '@types/webidl-conversions@7.0.3': {} + + '@types/whatwg-url@11.0.5': + dependencies: + '@types/webidl-conversions': 7.0.3 + + '@types/whatwg-url@13.0.0': + dependencies: + '@types/webidl-conversions': 7.0.3 + + '@types/yargs-parser@21.0.3': {} + + '@types/yargs@17.0.35': + dependencies: + '@types/yargs-parser': 21.0.3 + + '@types/yauzl@2.10.3': + dependencies: + '@types/node': 26.4.1 + optional: true + + '@ungap/structured-clone@1.4.0': {} + + '@unrs/resolver-binding-android-arm-eabi@1.12.2': + optional: true + + '@unrs/resolver-binding-android-arm64@1.12.2': + optional: true + + '@unrs/resolver-binding-darwin-arm64@1.12.2': + optional: true + + '@unrs/resolver-binding-darwin-x64@1.12.2': + optional: true + + '@unrs/resolver-binding-freebsd-x64@1.12.2': + optional: true + + '@unrs/resolver-binding-linux-arm-gnueabihf@1.12.2': + optional: true + + '@unrs/resolver-binding-linux-arm-musleabihf@1.12.2': + optional: true + + '@unrs/resolver-binding-linux-arm64-gnu@1.12.2': + optional: true + + '@unrs/resolver-binding-linux-arm64-musl@1.12.2': + optional: true + + '@unrs/resolver-binding-linux-loong64-gnu@1.12.2': + optional: true + + '@unrs/resolver-binding-linux-loong64-musl@1.12.2': + optional: true + + '@unrs/resolver-binding-linux-ppc64-gnu@1.12.2': + optional: true + + '@unrs/resolver-binding-linux-riscv64-gnu@1.12.2': + optional: true + + '@unrs/resolver-binding-linux-riscv64-musl@1.12.2': + optional: true + + '@unrs/resolver-binding-linux-s390x-gnu@1.12.2': + optional: true + + '@unrs/resolver-binding-linux-x64-gnu@1.12.2': + optional: true + + '@unrs/resolver-binding-linux-x64-musl@1.12.2': + optional: true + + '@unrs/resolver-binding-openharmony-arm64@1.12.2': + optional: true + + '@unrs/resolver-binding-wasm32-wasi@1.12.2': + dependencies: + '@emnapi/core': 1.10.0 + '@emnapi/runtime': 1.10.0 + '@napi-rs/wasm-runtime': 1.2.3(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0) + optional: true + + '@unrs/resolver-binding-win32-arm64-msvc@1.12.2': + optional: true + + '@unrs/resolver-binding-win32-ia32-msvc@1.12.2': + optional: true + + '@unrs/resolver-binding-win32-x64-msvc@1.12.2': + optional: true + + abbrev@2.0.0: {} + + abbrev@5.0.0: {} + + accepts@2.0.0: + dependencies: + mime-types: 3.0.2 + negotiator: 1.1.0 + + acorn-jsx@5.3.2(acorn@8.18.0): + dependencies: + acorn: 8.18.0 + + acorn@8.18.0: {} + + adm-zip@0.6.0: + optional: true + + agent-base@6.0.2: + dependencies: + debug: 4.4.3(supports-color@5.5.0) + transitivePeerDependencies: + - supports-color + + agent-base@7.1.4: {} + + agent-base@9.0.0: + optional: true + + ajv@6.15.0: + dependencies: + fast-deep-equal: 3.1.3 + fast-json-stable-stringify: 2.1.0 + json-schema-traverse: 0.4.1 + uri-js: 4.4.1 + + ansi-escapes@4.3.2: + dependencies: + type-fest: 0.21.3 + + ansi-regex@5.0.1: {} + + ansi-regex@6.3.0: {} + + ansi-styles@4.3.0: + dependencies: + color-convert: 2.0.1 + + ansi-styles@5.2.0: {} + + ansi-styles@6.2.3: {} + + anymatch@3.1.3: + dependencies: + normalize-path: 3.0.0 + picomatch: 2.3.2 + + append-field@1.0.0: {} + + archiver-utils@2.1.0: + dependencies: + glob: 7.2.3 + graceful-fs: 4.2.11 + lazystream: 1.0.1 + lodash.defaults: 4.2.0 + lodash.difference: 4.5.0 + lodash.flatten: 4.4.0 + lodash.isplainobject: 4.0.6 + lodash.union: 4.6.0 + normalize-path: 3.0.0 + readable-stream: 2.3.8 + + archiver-utils@3.0.4: + dependencies: + glob: 7.2.3 + graceful-fs: 4.2.11 + lazystream: 1.0.1 + lodash.defaults: 4.2.0 + lodash.difference: 4.5.0 + lodash.flatten: 4.4.0 + lodash.isplainobject: 4.0.6 + lodash.union: 4.6.0 + normalize-path: 3.0.0 + readable-stream: 3.6.2 + + archiver@5.3.2: + dependencies: + archiver-utils: 2.1.0 + async: 3.2.6 + buffer-crc32: 0.2.13 + readable-stream: 3.6.2 + readdir-glob: 1.1.3 + tar-stream: 2.2.0 + zip-stream: 4.1.1 + + argparse@1.0.10: + dependencies: + sprintf-js: 1.0.3 + + argparse@2.0.1: {} + + array-buffer-byte-length@1.0.2: + dependencies: + call-bound: 1.0.4 + is-array-buffer: 3.0.5 + + array-includes@3.1.9: + dependencies: + call-bind: 1.0.9 + call-bound: 1.0.4 + define-properties: 1.2.1 + es-abstract: 1.24.2 + es-object-atoms: 1.1.2 + get-intrinsic: 1.3.0 + is-string: 1.1.1 + math-intrinsics: 1.1.0 + + array.prototype.findlast@1.2.5: + dependencies: + call-bind: 1.0.9 + define-properties: 1.2.1 + es-abstract: 1.24.2 + es-errors: 1.3.0 + es-object-atoms: 1.1.2 + es-shim-unscopables: 1.1.0 + + array.prototype.findlastindex@1.2.6: + dependencies: + call-bind: 1.0.9 + call-bound: 1.0.4 + define-properties: 1.2.1 + es-abstract: 1.24.2 + es-errors: 1.3.0 + es-object-atoms: 1.1.2 + es-shim-unscopables: 1.1.0 + + array.prototype.flat@1.3.3: + dependencies: + call-bind: 1.0.9 + define-properties: 1.2.1 + es-abstract: 1.24.2 + es-shim-unscopables: 1.1.0 + + array.prototype.flatmap@1.3.3: + dependencies: + call-bind: 1.0.9 + define-properties: 1.2.1 + es-abstract: 1.24.2 + es-shim-unscopables: 1.1.0 + + array.prototype.reduce@1.0.8: + dependencies: + call-bind: 1.0.9 + call-bound: 1.0.4 + define-properties: 1.2.1 + es-abstract: 1.24.2 + es-array-method-boxes-properly: 1.0.0 + es-errors: 1.3.0 + es-object-atoms: 1.1.2 + is-string: 1.1.1 + + array.prototype.tosorted@1.1.4: + dependencies: + call-bind: 1.0.9 + define-properties: 1.2.1 + es-abstract: 1.24.2 + es-errors: 1.3.0 + es-shim-unscopables: 1.1.0 + + arraybuffer.prototype.slice@1.0.4: + dependencies: + array-buffer-byte-length: 1.0.2 + call-bind: 1.0.9 + define-properties: 1.2.1 + es-abstract: 1.24.2 + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + is-array-buffer: 3.0.5 + + asap@2.0.6: {} + + asn1.js@5.4.1: + dependencies: + bn.js: 4.12.5 + inherits: 2.0.4 + minimalistic-assert: 1.0.1 + safer-buffer: 2.1.2 + + ast-types@0.13.4: + dependencies: + tslib: 2.8.1 + + async-function@1.0.0: {} + + async@3.2.6: {} + + asynckit@0.4.0: {} + + at-least-node@1.0.0: {} + + available-typed-arrays@1.0.7: + dependencies: + possible-typed-array-names: 1.1.0 + + axios@1.20.0: + dependencies: + follow-redirects: 1.16.0 + form-data: 4.0.6 + https-proxy-agent: 5.0.1 + proxy-from-env: 2.1.0 + transitivePeerDependencies: + - debug + - supports-color + + b4a@1.8.1: {} + + babel-jest@30.5.1(@babel/core@7.29.7): + dependencies: + '@babel/core': 7.29.7 + '@jest/transform': 30.5.1 + '@types/babel__core': 7.20.5 + babel-plugin-istanbul: 8.0.0 + babel-preset-jest: 30.5.0(@babel/core@7.29.7) + chalk: 4.1.2 + graceful-fs: 4.2.11 + slash: 3.0.0 + transitivePeerDependencies: + - supports-color + + babel-plugin-istanbul@8.0.0: + dependencies: + '@babel/helper-plugin-utils': 7.29.7 + '@istanbuljs/load-nyc-config': 1.1.0 + '@istanbuljs/schema': 0.1.6 + istanbul-lib-instrument: 6.0.3 + test-exclude: 7.0.2 + transitivePeerDependencies: + - supports-color + + babel-plugin-jest-hoist@30.5.0: + dependencies: + '@types/babel__core': 7.20.5 + + babel-plugin-polyfill-corejs2@0.4.17(@babel/core@7.29.7): + dependencies: + '@babel/compat-data': 7.29.7 + '@babel/core': 7.29.7 + '@babel/helper-define-polyfill-provider': 0.6.8(@babel/core@7.29.7) + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + + babel-plugin-polyfill-corejs3@0.14.2(@babel/core@7.29.7): + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-define-polyfill-provider': 0.6.8(@babel/core@7.29.7) + core-js-compat: 3.50.0 + transitivePeerDependencies: + - supports-color + + babel-plugin-polyfill-regenerator@0.6.8(@babel/core@7.29.7): + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-define-polyfill-provider': 0.6.8(@babel/core@7.29.7) + transitivePeerDependencies: + - supports-color + + babel-plugin-transform-import-meta@2.3.3(@babel/core@7.29.7): + dependencies: + '@babel/core': 7.29.7 + '@babel/template': 7.29.7 + tslib: 2.8.1 + + babel-preset-current-node-syntax@1.2.0(@babel/core@7.29.7): + dependencies: + '@babel/core': 7.29.7 + '@babel/plugin-syntax-async-generators': 7.8.4(@babel/core@7.29.7) + '@babel/plugin-syntax-bigint': 7.8.3(@babel/core@7.29.7) + '@babel/plugin-syntax-class-properties': 7.12.13(@babel/core@7.29.7) + '@babel/plugin-syntax-class-static-block': 7.14.5(@babel/core@7.29.7) + '@babel/plugin-syntax-import-attributes': 7.29.7(@babel/core@7.29.7) + '@babel/plugin-syntax-import-meta': 7.10.4(@babel/core@7.29.7) + '@babel/plugin-syntax-json-strings': 7.8.3(@babel/core@7.29.7) + '@babel/plugin-syntax-logical-assignment-operators': 7.10.4(@babel/core@7.29.7) + '@babel/plugin-syntax-nullish-coalescing-operator': 7.8.3(@babel/core@7.29.7) + '@babel/plugin-syntax-numeric-separator': 7.10.4(@babel/core@7.29.7) + '@babel/plugin-syntax-object-rest-spread': 7.8.3(@babel/core@7.29.7) + '@babel/plugin-syntax-optional-catch-binding': 7.8.3(@babel/core@7.29.7) + '@babel/plugin-syntax-optional-chaining': 7.8.3(@babel/core@7.29.7) + '@babel/plugin-syntax-private-property-in-object': 7.14.5(@babel/core@7.29.7) + '@babel/plugin-syntax-top-level-await': 7.14.5(@babel/core@7.29.7) + + babel-preset-jest@30.5.0(@babel/core@7.29.7): + dependencies: + '@babel/core': 7.29.7 + babel-plugin-jest-hoist: 30.5.0 + babel-preset-current-node-syntax: 1.2.0(@babel/core@7.29.7) + + balanced-match@1.0.2: {} + + balanced-match@4.0.4: {} + + bare-events@2.9.2: {} + + bare-fs@4.8.1: + dependencies: + bare-events: 2.9.2 + bare-path: 3.1.2 + bare-stream: 2.13.4(bare-events@2.9.2) + bare-url: 2.5.4 + fast-fifo: 1.3.2 + transitivePeerDependencies: + - bare-abort-controller + - react-native-b4a + + bare-path@3.1.2: {} + + bare-stream@2.13.4(bare-events@2.9.2): + dependencies: + b4a: 1.8.1 + streamx: 2.28.1 + teex: 1.0.1 + optionalDependencies: + bare-events: 2.9.2 + transitivePeerDependencies: + - react-native-b4a + + bare-url@2.5.4: + dependencies: + bare-path: 3.1.2 + + base64-js@1.5.1: {} + + baseline-browser-mapping@2.11.21: {} + + basic-ftp@5.3.1: {} + + bcrypt@6.0.0: + dependencies: + node-addon-api: 8.9.2 + node-gyp-build: 4.8.4 + + big-integer@1.6.52: {} + + binary-extensions@2.3.0: {} + + binary@0.3.0: + dependencies: + buffers: 0.1.1 + chainsaw: 0.1.0 + + bl@4.1.0: + dependencies: + buffer: 5.7.1 + inherits: 2.0.4 + readable-stream: 3.6.2 + + bluebird@3.4.7: {} + + bluebird@3.7.2: {} + + blurhash@2.0.5: {} + + bn.js@4.12.5: {} + + body-parser@2.3.0: + dependencies: + bytes: 3.1.2 + content-type: 2.1.0 + debug: 4.4.3(supports-color@5.5.0) + http-errors: 2.0.1 + iconv-lite: 0.7.3 + on-finished: 2.4.1 + qs: 6.16.0 + raw-body: 3.0.2 + type-is: 2.1.0 + transitivePeerDependencies: + - supports-color + + bowser@2.14.1: {} + + brace-expansion@1.1.18: + dependencies: + balanced-match: 1.0.2 + concat-map: 0.0.1 + + brace-expansion@2.1.4: + dependencies: + balanced-match: 1.0.2 + + brace-expansion@5.0.9: + dependencies: + balanced-match: 4.0.4 + + braces@3.0.3: + dependencies: + fill-range: 7.1.1 + + brorand@1.1.0: {} + + browserslist@4.28.9: + dependencies: + baseline-browser-mapping: 2.11.21 + caniuse-lite: 1.0.30001810 + electron-to-chromium: 1.5.422 + node-releases: 2.0.54 + update-browserslist-db: 1.3.2(browserslist@4.28.9) + + bser@2.1.1: + dependencies: + node-int64: 0.4.0 + + bson@6.10.4: {} + + bson@7.3.2: {} + + buffer-crc32@0.2.13: {} + + buffer-equal-constant-time@1.0.1: {} + + buffer-from@1.1.2: {} + + buffer-indexof-polyfill@1.0.2: {} + + buffer@5.7.1: + dependencies: + base64-js: 1.5.1 + ieee754: 1.2.1 + + buffers@0.1.1: {} + + builtins@5.1.0: + dependencies: + semver: 7.8.5 + + busboy@1.6.0: + dependencies: + streamsearch: 1.1.0 + + bytes@3.1.2: {} + + call-bind-apply-helpers@1.0.2: + dependencies: + es-errors: 1.3.0 + function-bind: 1.1.2 + + call-bind@1.0.9: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-define-property: 1.0.1 + get-intrinsic: 1.3.0 + set-function-length: 1.2.2 + + call-bound@1.0.4: + dependencies: + call-bind-apply-helpers: 1.0.2 + get-intrinsic: 1.3.0 + + callsites@3.1.0: {} + + camelcase@5.3.1: {} + + camelcase@6.3.0: {} + + caniuse-lite@1.0.30001810: {} + + canonical-json@0.2.0: {} + + chainsaw@0.1.0: + dependencies: + traverse: 0.3.9 + + chalk@4.1.2: + dependencies: + ansi-styles: 4.3.0 + supports-color: 7.2.0 + + char-regex@1.0.2: {} + + chokidar@3.6.0: + dependencies: + anymatch: 3.1.3 + braces: 3.0.3 + glob-parent: 5.1.2 + is-binary-path: 2.1.0 + is-glob: 4.0.3 + normalize-path: 3.0.0 + readdirp: 3.6.0 + optionalDependencies: + fsevents: 2.3.3 + + chromedriver@152.0.3: + dependencies: + '@testim/chrome-version': 1.1.4 + adm-zip: 0.6.0 + axios: 1.20.0 + compare-versions: 6.1.1 + proxy-agent: 8.0.2 + proxy-from-env: 2.1.0 + tcp-port-used: 1.0.3 + transitivePeerDependencies: + - debug + - kerberos + - supports-color + optional: true + + chromium-bidi@14.0.0(devtools-protocol@0.0.1608973): + dependencies: + devtools-protocol: 0.0.1608973 + mitt: 3.0.1 + zod: 3.25.76 + + ci-info@4.4.0: {} + + cjs-module-lexer@2.2.1: {} + + cliui@7.0.4: + dependencies: + string-width: 4.2.3 + strip-ansi: 6.0.1 + wrap-ansi: 7.0.0 + + cliui@8.0.1: + dependencies: + string-width: 4.2.3 + strip-ansi: 6.0.1 + wrap-ansi: 7.0.0 + + clone-deep@4.0.1: + dependencies: + is-plain-object: 2.0.4 + kind-of: 6.0.3 + shallow-clone: 3.0.1 + + clone@2.1.2: {} + + cluster-key-slot@1.1.1: {} + + co@4.6.0: {} + + collect-v8-coverage@1.0.3: {} + + color-convert@2.0.1: + dependencies: + color-name: 1.1.4 + + color-name@1.1.4: {} + + combined-stream@1.0.8: + dependencies: + delayed-stream: 1.0.0 + + commander@10.0.1: {} + + commander@14.0.3: {} + + commander@6.2.1: {} + + commondir@1.0.1: {} + + compare-versions@6.1.1: + optional: true + + component-emitter@1.3.1: {} + + compress-commons@4.1.2: + dependencies: + buffer-crc32: 0.2.13 + crc32-stream: 4.0.3 + normalize-path: 3.0.0 + readable-stream: 3.6.2 + + concat-map@0.0.1: {} + + concat-stream@2.0.0: + dependencies: + buffer-from: 1.1.2 + inherits: 2.0.4 + readable-stream: 3.6.2 + typedarray: 0.0.6 + + concurrently@9.2.4: + dependencies: + chalk: 4.1.2 + rxjs: 7.8.2 + shell-quote: 1.9.0 + supports-color: 8.1.1 + tree-kill: 1.2.2 + yargs: 17.7.2 + + config-chain@1.1.13: + dependencies: + ini: 1.3.8 + proto-list: 1.2.4 + + content-disposition@1.1.0: {} + + content-type@1.0.5: {} + + content-type@2.1.0: {} + + convert-source-map@2.0.0: {} + + cookie-signature@1.0.7: {} + + cookie-signature@1.2.2: {} + + cookie@0.7.2: {} + + cookiejar@2.1.4: {} + + core-js-compat@3.50.0: + dependencies: + browserslist: 4.28.9 + + core-js@3.50.0: {} + + core-util-is@1.0.3: {} + + cors@2.8.6: + dependencies: + object-assign: 4.1.1 + vary: 1.1.2 + + cosmiconfig@9.0.2: + dependencies: + env-paths: 2.2.1 + import-fresh: 3.3.1 + js-yaml: 4.3.2 + parse-json: 5.2.0 + + crc-32@1.2.2: {} + + crc32-stream@4.0.3: + dependencies: + crc-32: 1.2.2 + readable-stream: 3.6.2 + + cross-spawn@7.0.6: + dependencies: + path-key: 3.1.1 + shebang-command: 2.0.0 + which: 2.0.2 + + data-uri-to-buffer@6.0.2: {} + + data-uri-to-buffer@8.0.0: + optional: true + + data-view-buffer@1.0.2: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + is-data-view: 1.0.2 + + data-view-byte-length@1.0.2: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + is-data-view: 1.0.2 + + data-view-byte-offset@1.0.1: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + is-data-view: 1.0.2 + + date-format@4.0.14: {} + + dayjs@1.11.23: {} + + debug@2.6.9: + dependencies: + ms: 2.0.0 + + debug@3.2.7: + dependencies: + ms: 2.1.3 + + debug@4.3.1: + dependencies: + ms: 2.1.2 + optional: true + + debug@4.4.3(supports-color@5.5.0): + dependencies: + ms: 2.1.3 + optionalDependencies: + supports-color: 5.5.0 + + dedent@1.7.2: {} + + deep-is@0.1.4: {} + + deepmerge@4.3.1: {} + + define-data-property@1.1.4: + dependencies: + es-define-property: 1.0.1 + es-errors: 1.3.0 + gopd: 1.2.0 + + define-properties@1.2.1: + dependencies: + define-data-property: 1.1.4 + has-property-descriptors: 1.0.2 + object-keys: 1.1.1 + + degenerator@5.0.1: + dependencies: + ast-types: 0.13.4 + escodegen: 2.1.0 + esprima: 4.0.1 + + degenerator@7.0.1(quickjs-wasi@2.2.0): + dependencies: + ast-types: 0.13.4 + escodegen: 2.1.0 + esprima: 4.0.1 + quickjs-wasi: 2.2.0 + optional: true + + delayed-stream@1.0.0: {} + + denque@2.1.0: {} + + depd@2.0.0: {} + + detect-libc@2.1.2: {} + + detect-newline@3.1.0: {} + + devtools-protocol@0.0.1608973: {} + + dezalgo@1.0.4: + dependencies: + asap: 2.0.6 + wrappy: 1.0.2 + + diff@9.0.0: {} + + doctrine@2.1.0: + dependencies: + esutils: 2.0.3 + + doctrine@3.0.0: + dependencies: + esutils: 2.0.3 + + dom-serializer@1.4.1: + dependencies: + domelementtype: 2.3.0 + domhandler: 4.3.1 + entities: 2.2.0 + + domelementtype@2.3.0: {} + + domhandler@4.3.1: + dependencies: + domelementtype: 2.3.0 + + domutils@2.8.0: + dependencies: + dom-serializer: 1.4.1 + domelementtype: 2.3.0 + domhandler: 4.3.1 + + dotenv@17.4.2: {} + + dottie@2.0.7: {} + + dunder-proto@1.0.1: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-errors: 1.3.0 + gopd: 1.2.0 + + duplexer2@0.1.4: + dependencies: + readable-stream: 2.3.8 + + eastasianwidth@0.2.0: {} + + ecdsa-sig-formatter@1.0.11: + dependencies: + safe-buffer: 5.2.1 + + editorconfig@1.0.7: + dependencies: + '@one-ini/wasm': 0.1.1 + commander: 10.0.1 + minimatch: 9.0.9 + semver: 7.8.5 + + editorconfig@3.0.2: + dependencies: + '@one-ini/wasm': 0.2.1 + commander: 14.0.3 + minimatch: 10.2.6 + semver: 7.8.5 + + ee-first@1.1.1: {} + + ejs@3.1.10: + dependencies: + jake: 10.9.4 + + electron-to-chromium@1.5.422: {} + + elliptic@6.6.1: + dependencies: + bn.js: 4.12.5 + brorand: 1.1.0 + hash.js: 1.1.7 + hmac-drbg: 1.0.1 + inherits: 2.0.4 + minimalistic-assert: 1.0.1 + minimalistic-crypto-utils: 1.0.1 + + emittery@0.13.1: {} + + emoji-regex@8.0.0: {} + + emoji-regex@9.2.2: {} + + encodeurl@2.0.0: {} + + end-of-stream@1.4.5: + dependencies: + once: 1.4.0 + + entities@2.2.0: {} + + entities@3.0.1: {} + + env-paths@2.2.1: {} + + error-ex@1.3.4: + dependencies: + is-arrayish: 0.2.1 + + es-abstract-get@1.0.0: + dependencies: + es-errors: 1.3.0 + es-object-atoms: 1.1.2 + is-callable: 1.2.7 + object-inspect: 1.13.4 + + es-abstract@1.24.2: + dependencies: + array-buffer-byte-length: 1.0.2 + arraybuffer.prototype.slice: 1.0.4 + available-typed-arrays: 1.0.7 + call-bind: 1.0.9 + call-bound: 1.0.4 + data-view-buffer: 1.0.2 + data-view-byte-length: 1.0.2 + data-view-byte-offset: 1.0.1 + es-define-property: 1.0.1 + es-errors: 1.3.0 + es-object-atoms: 1.1.2 + es-set-tostringtag: 2.1.0 + es-to-primitive: 1.3.4 + function.prototype.name: 1.2.0 + get-intrinsic: 1.3.0 + get-proto: 1.0.1 + get-symbol-description: 1.1.0 + globalthis: 1.0.4 + gopd: 1.2.0 + has-property-descriptors: 1.0.2 + has-proto: 1.2.0 + has-symbols: 1.1.0 + hasown: 2.0.4 + internal-slot: 1.1.0 + is-array-buffer: 3.0.5 + is-callable: 1.2.7 + is-data-view: 1.0.2 + is-negative-zero: 2.0.3 + is-regex: 1.2.1 + is-set: 2.0.3 + is-shared-array-buffer: 1.0.4 + is-string: 1.1.1 + is-typed-array: 1.1.15 + is-weakref: 1.1.1 + math-intrinsics: 1.1.0 + object-inspect: 1.13.4 + object-keys: 1.1.1 + object.assign: 4.1.7 + own-keys: 1.0.2 + regexp.prototype.flags: 1.5.4 + safe-array-concat: 1.1.4 + safe-push-apply: 1.0.0 + safe-regex-test: 1.1.0 + set-proto: 1.0.0 + stop-iteration-iterator: 1.1.0 + string.prototype.trim: 1.2.11 + string.prototype.trimend: 1.0.10 + string.prototype.trimstart: 1.0.8 + typed-array-buffer: 1.0.3 + typed-array-byte-length: 1.0.3 + typed-array-byte-offset: 1.0.4 + typed-array-length: 1.0.8 + unbox-primitive: 1.1.0 + which-typed-array: 1.1.22 + + es-array-method-boxes-properly@1.0.0: {} + + es-define-property@1.0.1: {} + + es-errors@1.3.0: {} + + es-iterator-helpers@1.4.0: + dependencies: + call-bind: 1.0.9 + call-bound: 1.0.4 + define-properties: 1.2.1 + es-abstract: 1.24.2 + es-errors: 1.3.0 + es-set-tostringtag: 2.1.0 + function-bind: 1.1.2 + get-intrinsic: 1.3.0 + globalthis: 1.0.4 + gopd: 1.2.0 + has-property-descriptors: 1.0.2 + has-proto: 1.2.0 + has-symbols: 1.1.0 + internal-slot: 1.1.0 + iterator.prototype: 1.1.5 + math-intrinsics: 1.1.0 + + es-module-lexer@2.3.2: {} + + es-object-atoms@1.1.2: + dependencies: + es-errors: 1.3.0 + + es-set-tostringtag@2.1.0: + dependencies: + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + has-tostringtag: 1.0.2 + hasown: 2.0.4 + + es-shim-unscopables@1.1.0: + dependencies: + hasown: 2.0.4 + + es-to-primitive@1.3.4: + dependencies: + es-abstract-get: 1.0.0 + es-define-property: 1.0.1 + es-errors: 1.3.0 + is-callable: 1.2.7 + is-date-object: 1.1.0 + is-symbol: 1.1.1 + + escalade@3.2.0: {} + + escape-html@1.0.3: {} + + escape-string-regexp@2.0.0: {} + + escape-string-regexp@4.0.0: {} + + escodegen@2.1.0: + dependencies: + esprima: 4.0.1 + estraverse: 5.3.0 + esutils: 2.0.3 + optionalDependencies: + source-map: 0.6.1 + + eslint-config-prettier@10.1.8(eslint@9.39.5): + dependencies: + eslint: 9.39.5 + + eslint-config-standard-jsx@11.0.0(eslint-plugin-react@7.37.5(eslint@8.57.1))(eslint@8.57.1): + dependencies: + eslint: 8.57.1 + eslint-plugin-react: 7.37.5(eslint@8.57.1) + + eslint-config-standard@17.1.0(eslint-plugin-import@2.32.0(eslint@8.57.1))(eslint-plugin-n@15.7.0(eslint@8.57.1))(eslint-plugin-promise@6.6.0(eslint@8.57.1))(eslint@8.57.1): + dependencies: + eslint: 8.57.1 + eslint-plugin-import: 2.32.0(eslint@8.57.1) + eslint-plugin-n: 15.7.0(eslint@8.57.1) + eslint-plugin-promise: 6.6.0(eslint@8.57.1) + + eslint-import-resolver-node@0.3.10: + dependencies: + debug: 3.2.7 + is-core-module: 2.16.2 + resolve: 2.0.0-next.7 + transitivePeerDependencies: + - supports-color + + eslint-module-utils@2.14.0(eslint-import-resolver-node@0.3.10)(eslint@8.57.1): + dependencies: + debug: 3.2.7 + optionalDependencies: + eslint: 8.57.1 + eslint-import-resolver-node: 0.3.10 + transitivePeerDependencies: + - supports-color + + eslint-plugin-es@4.1.0(eslint@8.57.1): + dependencies: + eslint: 8.57.1 + eslint-utils: 2.1.0 + regexpp: 3.2.0 + + eslint-plugin-import@2.32.0(eslint@8.57.1): + dependencies: + '@rtsao/scc': 1.1.0 + array-includes: 3.1.9 + array.prototype.findlastindex: 1.2.6 + array.prototype.flat: 1.3.3 + array.prototype.flatmap: 1.3.3 + debug: 3.2.7 + doctrine: 2.1.0 + eslint: 8.57.1 + eslint-import-resolver-node: 0.3.10 + eslint-module-utils: 2.14.0(eslint-import-resolver-node@0.3.10)(eslint@8.57.1) + hasown: 2.0.4 + is-core-module: 2.16.2 + is-glob: 4.0.3 + minimatch: 3.1.5 + object.fromentries: 2.0.8 + object.groupby: 1.0.3 + object.values: 1.2.1 + semver: 6.3.1 + string.prototype.trimend: 1.0.10 + tsconfig-paths: 3.15.0 + transitivePeerDependencies: + - eslint-import-resolver-typescript + - eslint-import-resolver-webpack + - supports-color + + eslint-plugin-n@15.7.0(eslint@8.57.1): + dependencies: + builtins: 5.1.0 + eslint: 8.57.1 + eslint-plugin-es: 4.1.0(eslint@8.57.1) + eslint-utils: 3.0.0(eslint@8.57.1) + ignore: 5.3.2 + is-core-module: 2.16.2 + minimatch: 3.1.5 + resolve: 1.22.12 + semver: 7.8.5 + + eslint-plugin-prettier@5.5.6(eslint-config-prettier@10.1.8(eslint@9.39.5))(eslint@9.39.5)(prettier@3.9.6): + dependencies: + eslint: 9.39.5 + prettier: 3.9.6 + prettier-linter-helpers: 1.0.1 + synckit: 0.11.13 + optionalDependencies: + eslint-config-prettier: 10.1.8(eslint@9.39.5) + + eslint-plugin-promise@6.6.0(eslint@8.57.1): + dependencies: + eslint: 8.57.1 + + eslint-plugin-react@7.37.5(eslint@8.57.1): + dependencies: + array-includes: 3.1.9 + array.prototype.findlast: 1.2.5 + array.prototype.flatmap: 1.3.3 + array.prototype.tosorted: 1.1.4 + doctrine: 2.1.0 + es-iterator-helpers: 1.4.0 + eslint: 8.57.1 + estraverse: 5.3.0 + hasown: 2.0.4 + jsx-ast-utils: 3.3.5 + minimatch: 3.1.5 + object.entries: 1.1.9 + object.fromentries: 2.0.8 + object.values: 1.2.1 + prop-types: 15.8.1 + resolve: 2.0.0-next.7 + semver: 6.3.1 + string.prototype.matchall: 4.1.0 + string.prototype.repeat: 1.0.0 + + eslint-scope@7.2.2: + dependencies: + esrecurse: 4.3.0 + estraverse: 5.3.0 + + eslint-scope@8.4.0: + dependencies: + esrecurse: 4.3.0 + estraverse: 5.3.0 + + eslint-utils@2.1.0: + dependencies: + eslint-visitor-keys: 1.3.0 + + eslint-utils@3.0.0(eslint@8.57.1): + dependencies: + eslint: 8.57.1 + eslint-visitor-keys: 2.1.0 + + eslint-visitor-keys@1.3.0: {} + + eslint-visitor-keys@2.1.0: {} + + eslint-visitor-keys@3.4.3: {} + + eslint-visitor-keys@4.2.1: {} + + eslint@8.57.1: + dependencies: + '@eslint-community/eslint-utils': 4.10.1(eslint@8.57.1) + '@eslint-community/regexpp': 4.12.2 + '@eslint/eslintrc': 2.1.4 + '@eslint/js': 8.57.1 + '@humanwhocodes/config-array': 0.13.0 + '@humanwhocodes/module-importer': 1.0.1 + '@nodelib/fs.walk': 1.2.8 + '@ungap/structured-clone': 1.4.0 + ajv: 6.15.0 + chalk: 4.1.2 + cross-spawn: 7.0.6 + debug: 4.4.3(supports-color@5.5.0) + doctrine: 3.0.0 + escape-string-regexp: 4.0.0 + eslint-scope: 7.2.2 + eslint-visitor-keys: 3.4.3 + espree: 9.6.1 + esquery: 1.7.0 + esutils: 2.0.3 + fast-deep-equal: 3.1.3 + file-entry-cache: 6.0.1 + find-up: 5.0.0 + glob-parent: 6.0.2 + globals: 13.24.0 + graphemer: 1.4.0 + ignore: 5.3.2 + imurmurhash: 0.1.4 + is-glob: 4.0.3 + is-path-inside: 3.0.3 + js-yaml: 4.3.2 + json-stable-stringify-without-jsonify: 1.0.1 + levn: 0.4.1 + lodash.merge: 4.6.2 + minimatch: 3.1.5 + natural-compare: 1.4.0 + optionator: 0.9.4 + strip-ansi: 6.0.1 + text-table: 0.2.0 + transitivePeerDependencies: + - supports-color + + eslint@9.39.5: + dependencies: + '@eslint-community/eslint-utils': 4.10.1(eslint@9.39.5) + '@eslint-community/regexpp': 4.12.2 + '@eslint/config-array': 0.21.2 + '@eslint/config-helpers': 0.4.2 + '@eslint/core': 0.17.0 + '@eslint/eslintrc': 3.3.7 + '@eslint/js': 9.39.5 + '@eslint/plugin-kit': 0.4.1 + '@humanfs/node': 0.16.8 + '@humanwhocodes/module-importer': 1.0.1 + '@humanwhocodes/retry': 0.4.3 + '@types/estree': 1.0.9 + ajv: 6.15.0 + chalk: 4.1.2 + cross-spawn: 7.0.6 + debug: 4.4.3(supports-color@5.5.0) + escape-string-regexp: 4.0.0 + eslint-scope: 8.4.0 + eslint-visitor-keys: 4.2.1 + espree: 10.4.0 + esquery: 1.7.0 + esutils: 2.0.3 + fast-deep-equal: 3.1.3 + file-entry-cache: 8.0.0 + find-up: 5.0.0 + glob-parent: 6.0.2 + ignore: 5.3.2 + imurmurhash: 0.1.4 + is-glob: 4.0.3 + json-stable-stringify-without-jsonify: 1.0.1 + lodash.merge: 4.6.2 + minimatch: 3.1.5 + natural-compare: 1.4.0 + optionator: 0.9.4 + transitivePeerDependencies: + - supports-color + + espree@10.4.0: + dependencies: + acorn: 8.18.0 + acorn-jsx: 5.3.2(acorn@8.18.0) + eslint-visitor-keys: 4.2.1 + + espree@9.6.1: + dependencies: + acorn: 8.18.0 + acorn-jsx: 5.3.2(acorn@8.18.0) + eslint-visitor-keys: 3.4.3 + + esprima@4.0.1: {} + + esquery@1.7.0: + dependencies: + estraverse: 5.3.0 + + esrecurse@4.3.0: + dependencies: + estraverse: 5.3.0 + + estraverse@5.3.0: {} + + esutils@2.0.3: {} + + etag@1.8.1: {} + + events-universal@1.0.1: + dependencies: + bare-events: 2.9.2 + transitivePeerDependencies: + - bare-abort-controller + + exceljs@4.4.0: + dependencies: + archiver: 5.3.2 + dayjs: 1.11.23 + fast-csv: 4.3.6 + jszip: 3.10.1 + readable-stream: 3.6.2 + saxes: 5.0.1 + tmp: 0.2.7 + unzipper: 0.10.14 + uuid: 8.3.2 + + execa@5.1.1: + dependencies: + cross-spawn: 7.0.6 + get-stream: 6.0.1 + human-signals: 2.1.0 + is-stream: 2.0.1 + merge-stream: 2.0.0 + npm-run-path: 4.0.1 + onetime: 5.1.2 + signal-exit: 3.0.7 + strip-final-newline: 2.0.0 + + exifr@7.1.3: {} + + exit-x@0.2.2: {} + + expect@30.5.1: + dependencies: + '@jest/expect-utils': 30.5.1 + '@jest/get-type': 30.5.0 + jest-matcher-utils: 30.5.1 + jest-message-util: 30.5.1 + jest-mock: 30.5.1 + jest-util: 30.5.1 + + express-session@1.19.0: + dependencies: + cookie: 0.7.2 + cookie-signature: 1.0.7 + debug: 2.6.9 + depd: 2.0.0 + on-headers: 1.1.0 + parseurl: 1.3.3 + safe-buffer: 5.2.1 + uid-safe: 2.1.5 + transitivePeerDependencies: + - supports-color + + express@5.2.1: + dependencies: + accepts: 2.0.0 + body-parser: 2.3.0 + content-disposition: 1.1.0 + content-type: 1.0.5 + cookie: 0.7.2 + cookie-signature: 1.2.2 + debug: 4.4.3(supports-color@5.5.0) + depd: 2.0.0 + encodeurl: 2.0.0 + escape-html: 1.0.3 + etag: 1.8.1 + finalhandler: 2.1.1 + fresh: 2.0.0 + http-errors: 2.0.1 + merge-descriptors: 2.0.0 + mime-types: 3.0.2 + on-finished: 2.4.1 + once: 1.4.0 + parseurl: 1.3.3 + proxy-addr: 2.0.7 + qs: 6.16.0 + range-parser: 1.3.0 + router: 2.2.0 + send: 1.2.1 + serve-static: 2.2.1 + statuses: 2.0.2 + type-is: 2.1.0 + vary: 1.1.2 + transitivePeerDependencies: + - supports-color + + extract-zip@2.0.1: + dependencies: + debug: 4.4.3(supports-color@5.5.0) + get-stream: 5.2.0 + yauzl: 2.10.0 + optionalDependencies: + '@types/yauzl': 2.10.3 + transitivePeerDependencies: + - supports-color + + fast-csv@4.3.6: + dependencies: + '@fast-csv/format': 4.3.5 + '@fast-csv/parse': 4.3.6 + + fast-deep-equal@3.1.3: {} + + fast-diff@1.3.0: {} + + fast-fifo@1.3.2: {} + + fast-json-stable-stringify@2.1.0: {} + + fast-levenshtein@2.0.6: {} + + fast-safe-stringify@2.1.1: {} + + fastq@1.20.3: + dependencies: + reusify: 1.1.0 + + fb-watchman@2.0.2: + dependencies: + bser: 2.1.1 + + fd-slicer@1.1.0: + dependencies: + pend: 1.2.0 + + fdir@6.5.0(picomatch@4.0.7): + optionalDependencies: + picomatch: 4.0.7 + + file-entry-cache@6.0.1: + dependencies: + flat-cache: 3.2.0 + + file-entry-cache@8.0.0: + dependencies: + flat-cache: 4.0.1 + + filelist@1.0.6: + dependencies: + minimatch: 5.1.9 + + fill-range@7.1.1: + dependencies: + to-regex-range: 5.0.1 + + finalhandler@2.1.1: + dependencies: + debug: 4.4.3(supports-color@5.5.0) + encodeurl: 2.0.0 + escape-html: 1.0.3 + on-finished: 2.4.1 + parseurl: 1.3.3 + statuses: 2.0.2 + transitivePeerDependencies: + - supports-color + + find-cache-dir@2.1.0: + dependencies: + commondir: 1.0.1 + make-dir: 2.1.0 + pkg-dir: 3.0.0 + + find-up@3.0.0: + dependencies: + locate-path: 3.0.0 + + find-up@4.1.0: + dependencies: + locate-path: 5.0.0 + path-exists: 4.0.0 + + find-up@5.0.0: + dependencies: + locate-path: 6.0.0 + path-exists: 4.0.0 + + flat-cache@3.2.0: + dependencies: + flatted: 3.4.4 + keyv: 4.5.4 + rimraf: 3.0.2 + + flat-cache@4.0.1: + dependencies: + flatted: 3.4.4 + keyv: 4.5.4 + + flatted@3.4.4: {} + + follow-redirects@1.16.0: {} + + for-each@0.3.5: + dependencies: + is-callable: 1.2.7 + + foreground-child@3.3.1: + dependencies: + cross-spawn: 7.0.6 + signal-exit: 4.1.0 + + form-data@4.0.6: + dependencies: + asynckit: 0.4.0 + combined-stream: 1.0.8 + es-set-tostringtag: 2.1.0 + hasown: 2.0.4 + mime-types: 2.1.35 + + formidable@3.5.4: + dependencies: + '@paralleldrive/cuid2': 2.3.1 + dezalgo: 1.0.4 + once: 1.4.0 + + forwarded@0.2.0: {} + + fresh@2.0.0: {} + + fs-constants@1.0.0: {} + + fs-extra@8.1.0: + dependencies: + graceful-fs: 4.2.11 + jsonfile: 4.0.0 + universalify: 0.1.2 + + fs-extra@9.1.0: + dependencies: + at-least-node: 1.0.0 + graceful-fs: 4.2.11 + jsonfile: 6.2.1 + universalify: 2.0.1 + + fs-readdir-recursive@1.1.0: {} + + fs.realpath@1.0.0: {} + + fsevents@2.3.3: + optional: true + + fstream@1.0.12: + dependencies: + graceful-fs: 4.2.11 + inherits: 2.0.4 + mkdirp: 0.5.6 + rimraf: 2.7.1 + + function-bind@1.1.2: {} + + function.prototype.name@1.2.0: + dependencies: + call-bind: 1.0.9 + call-bound: 1.0.4 + es-define-property: 1.0.1 + es-errors: 1.3.0 + functions-have-names: 1.2.3 + has-property-descriptors: 1.0.2 + hasown: 2.0.4 + is-callable: 1.2.7 + is-document.all: 1.0.0 + + functions-have-names@1.2.3: {} + + generator-function@2.0.1: {} + + gensync@1.0.0-beta.2: {} + + get-caller-file@2.0.5: {} + + get-intrinsic@1.3.0: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-define-property: 1.0.1 + es-errors: 1.3.0 + es-object-atoms: 1.1.2 + function-bind: 1.1.2 + get-proto: 1.0.1 + gopd: 1.2.0 + has-symbols: 1.1.0 + hasown: 2.0.4 + math-intrinsics: 1.1.0 + + get-package-type@0.1.0: {} + + get-proto@1.0.1: + dependencies: + dunder-proto: 1.0.1 + es-object-atoms: 1.1.2 + + get-stdin@8.0.0: {} + + get-stream@5.2.0: + dependencies: + pump: 3.0.4 + + get-stream@6.0.1: {} + + get-symbol-description@1.1.0: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + + get-uri@6.0.5: + dependencies: + basic-ftp: 5.3.1 + data-uri-to-buffer: 6.0.2 + debug: 4.4.3(supports-color@5.5.0) + transitivePeerDependencies: + - supports-color + + get-uri@8.0.1: + dependencies: + basic-ftp: 5.3.1 + data-uri-to-buffer: 8.0.0 + debug: 4.4.3(supports-color@5.5.0) + transitivePeerDependencies: + - supports-color + optional: true + + glob-parent@5.1.2: + dependencies: + is-glob: 4.0.3 + + glob-parent@6.0.2: + dependencies: + is-glob: 4.0.3 + + glob@10.5.0: + dependencies: + foreground-child: 3.3.1 + jackspeak: 3.4.3 + minimatch: 9.0.9 + minipass: 7.1.3 + package-json-from-dist: 1.0.1 + path-scurry: 1.11.1 + + glob@13.0.6: + dependencies: + minimatch: 10.2.6 + minipass: 7.1.3 + path-scurry: 2.0.2 + + glob@7.2.3: + dependencies: + fs.realpath: 1.0.0 + inflight: 1.0.6 + inherits: 2.0.4 + minimatch: 3.1.5 + once: 1.4.0 + path-is-absolute: 1.0.1 + + globals@13.24.0: + dependencies: + type-fest: 0.20.2 + + globals@14.0.0: {} + + globalthis@1.0.4: + dependencies: + define-properties: 1.2.1 + gopd: 1.2.0 + + gopd@1.2.0: {} + + graceful-fs@4.2.11: {} + + graphemer@1.4.0: {} + + has-bigints@1.1.0: {} + + has-flag@3.0.0: {} + + has-flag@4.0.0: {} + + has-property-descriptors@1.0.2: + dependencies: + es-define-property: 1.0.1 + + has-proto@1.2.0: + dependencies: + dunder-proto: 1.0.1 + + has-symbols@1.1.0: {} + + has-tostringtag@1.0.2: + dependencies: + has-symbols: 1.1.0 + + hash.js@1.1.7: + dependencies: + inherits: 2.0.4 + minimalistic-assert: 1.0.1 + + hasown@2.0.4: + dependencies: + function-bind: 1.1.2 + + hmac-drbg@1.0.1: + dependencies: + hash.js: 1.1.7 + minimalistic-assert: 1.0.1 + minimalistic-crypto-utils: 1.0.1 + + homedir-polyfill@1.0.3: + dependencies: + parse-passwd: 1.0.0 + + html-escaper@2.0.2: {} + + htmlparser2@7.2.0: + dependencies: + domelementtype: 2.3.0 + domhandler: 4.3.1 + domutils: 2.8.0 + entities: 3.0.1 + + http-errors@2.0.1: + dependencies: + depd: 2.0.0 + inherits: 2.0.4 + setprototypeof: 1.2.0 + statuses: 2.0.2 + toidentifier: 1.0.1 + + http-proxy-agent@7.0.2: + dependencies: + agent-base: 7.1.4 + debug: 4.4.3(supports-color@5.5.0) + transitivePeerDependencies: + - supports-color + + http-proxy-agent@9.1.0: + dependencies: + agent-base: 9.0.0 + debug: 4.4.3(supports-color@5.5.0) + proxy-agent-negotiate: 1.1.0 + transitivePeerDependencies: + - kerberos + - supports-color + optional: true + + https-proxy-agent@5.0.1: + dependencies: + agent-base: 6.0.2 + debug: 4.4.3(supports-color@5.5.0) + transitivePeerDependencies: + - supports-color + + https-proxy-agent@7.0.6: + dependencies: + agent-base: 7.1.4 + debug: 4.4.3(supports-color@5.5.0) + transitivePeerDependencies: + - supports-color + + https-proxy-agent@9.1.0: + dependencies: + agent-base: 9.0.0 + debug: 4.4.3(supports-color@5.5.0) + proxy-agent-negotiate: 1.1.0 + transitivePeerDependencies: + - kerberos + - supports-color + optional: true + + human-signals@2.1.0: {} + + i@0.3.7: {} + + iconv-lite@0.7.3: + dependencies: + safer-buffer: 2.1.2 + + ieee754@1.2.1: {} + + ignore-by-default@1.0.1: {} + + ignore@5.3.2: {} + + immediate@3.0.6: {} + + import-fresh@3.3.1: + dependencies: + parent-module: 1.0.1 + resolve-from: 4.0.0 + + import-local@3.2.0: + dependencies: + pkg-dir: 4.2.0 + resolve-cwd: 3.0.0 + + imurmurhash@0.1.4: {} + + inflection@1.13.4: {} + + inflight@1.0.6: + dependencies: + once: 1.4.0 + wrappy: 1.0.2 + + inherits@2.0.4: {} + + ini@1.3.8: {} + + internal-slot@1.1.0: + dependencies: + es-errors: 1.3.0 + hasown: 2.0.4 + side-channel: 1.1.1 + + ioredis@5.11.1: + dependencies: + '@ioredis/commands': 1.10.0 + cluster-key-slot: 1.1.1 + debug: 4.4.3(supports-color@5.5.0) + denque: 2.1.0 + redis-errors: 1.2.0 + redis-parser: 3.0.0 + standard-as-callback: 2.1.0 + transitivePeerDependencies: + - supports-color + + ioredis@6.0.0: + dependencies: + '@ioredis/commands': 2.0.0 + cluster-key-slot: 1.1.1 + debug: 4.4.3(supports-color@5.5.0) + denque: 2.1.0 + redis-errors: 1.2.0 + standard-as-callback: 2.1.0 + transitivePeerDependencies: + - supports-color + + ip-address@10.7.0: {} + + ip-regex@2.1.0: + optional: true + + ipaddr.js@1.9.1: {} + + is-array-buffer@3.0.5: + dependencies: + call-bind: 1.0.9 + call-bound: 1.0.4 + get-intrinsic: 1.3.0 + + is-arrayish@0.2.1: {} + + is-async-function@2.1.1: + dependencies: + async-function: 1.0.0 + call-bound: 1.0.4 + get-proto: 1.0.1 + has-tostringtag: 1.0.2 + safe-regex-test: 1.1.0 + + is-bigint@1.1.0: + dependencies: + has-bigints: 1.1.0 + + is-binary-path@2.1.0: + dependencies: + binary-extensions: 2.3.0 + + is-boolean-object@1.2.2: + dependencies: + call-bound: 1.0.4 + has-tostringtag: 1.0.2 + + is-callable@1.2.7: {} + + is-core-module@2.16.2: + dependencies: + hasown: 2.0.4 + + is-data-view@1.0.2: + dependencies: + call-bound: 1.0.4 + get-intrinsic: 1.3.0 + is-typed-array: 1.1.15 + + is-date-object@1.1.0: + dependencies: + call-bound: 1.0.4 + has-tostringtag: 1.0.2 + + is-document.all@1.0.0: + dependencies: + call-bound: 1.0.4 + + is-extglob@2.1.1: {} + + is-finalizationregistry@1.1.1: + dependencies: + call-bound: 1.0.4 + + is-fullwidth-code-point@3.0.0: {} + + is-generator-fn@2.1.0: {} + + is-generator-function@1.1.2: + dependencies: + call-bound: 1.0.4 + generator-function: 2.0.1 + get-proto: 1.0.1 + has-tostringtag: 1.0.2 + safe-regex-test: 1.1.0 + + is-glob@4.0.3: + dependencies: + is-extglob: 2.1.1 + + is-json@2.0.1: {} + + is-map@2.0.3: {} + + is-negative-zero@2.0.3: {} + + is-number-object@1.1.1: + dependencies: + call-bound: 1.0.4 + has-tostringtag: 1.0.2 + + is-number@7.0.0: {} + + is-path-inside@3.0.3: {} + + is-plain-object@2.0.4: + dependencies: + isobject: 3.0.1 + + is-promise@4.0.0: {} + + is-regex@1.2.1: + dependencies: + call-bound: 1.0.4 + gopd: 1.2.0 + has-tostringtag: 1.0.2 + hasown: 2.0.4 + + is-set@2.0.3: {} + + is-shared-array-buffer@1.0.4: + dependencies: + call-bound: 1.0.4 + + is-stream@2.0.1: {} + + is-string@1.1.1: + dependencies: + call-bound: 1.0.4 + has-tostringtag: 1.0.2 + + is-symbol@1.1.1: + dependencies: + call-bound: 1.0.4 + has-symbols: 1.1.0 + safe-regex-test: 1.1.0 + + is-typed-array@1.1.15: + dependencies: + which-typed-array: 1.1.22 + + is-url@1.2.4: + optional: true + + is-weakmap@2.0.2: {} + + is-weakref@1.1.1: + dependencies: + call-bound: 1.0.4 + + is-weakset@2.0.4: + dependencies: + call-bound: 1.0.4 + get-intrinsic: 1.3.0 + + is2@2.0.1: + dependencies: + deep-is: 0.1.4 + ip-regex: 2.1.0 + is-url: 1.2.4 + optional: true + + isarray@1.0.0: {} + + isarray@2.0.5: {} + + isexe@2.0.0: {} + + isobject@3.0.1: {} + + istanbul-lib-coverage@3.2.2: {} + + istanbul-lib-instrument@6.0.3: + dependencies: + '@babel/core': 7.29.7 + '@babel/parser': 7.29.8 + '@istanbuljs/schema': 0.1.6 + istanbul-lib-coverage: 3.2.2 + semver: 7.8.5 + transitivePeerDependencies: + - supports-color + + istanbul-lib-report@3.0.1: + dependencies: + istanbul-lib-coverage: 3.2.2 + make-dir: 4.0.0 + supports-color: 7.2.0 + + istanbul-lib-source-maps@5.0.6: + dependencies: + '@jridgewell/trace-mapping': 0.3.31 + debug: 4.4.3(supports-color@5.5.0) + istanbul-lib-coverage: 3.2.2 + transitivePeerDependencies: + - supports-color + + istanbul-reports@3.2.0: + dependencies: + html-escaper: 2.0.2 + istanbul-lib-report: 3.0.1 + + iterator.prototype@1.1.5: + dependencies: + define-data-property: 1.1.4 + es-object-atoms: 1.1.2 + get-intrinsic: 1.3.0 + get-proto: 1.0.1 + has-symbols: 1.1.0 + set-function-name: 2.0.2 + + jackspeak@3.4.3: + dependencies: + '@isaacs/cliui': 8.0.2 + optionalDependencies: + '@pkgjs/parseargs': 0.11.0 + + jake@10.9.4: + dependencies: + async: 3.2.6 + filelist: 1.0.6 + picocolors: 1.1.1 + + jest-changed-files@30.5.1: + dependencies: + execa: 5.1.1 + jest-util: 30.5.1 + p-limit: 3.1.0 + + jest-circus@30.5.1: + dependencies: + '@jest/environment': 30.5.1 + '@jest/expect': 30.5.1 + '@jest/test-result': 30.5.1 + '@jest/types': 30.5.1 + '@types/node': 26.4.1 + chalk: 4.1.2 + co: 4.6.0 + dedent: 1.7.2 + is-generator-fn: 2.1.0 + jest-each: 30.5.1 + jest-matcher-utils: 30.5.1 + jest-message-util: 30.5.1 + jest-runtime: 30.5.1 + jest-snapshot: 30.5.1 + jest-util: 30.5.1 + p-limit: 3.1.0 + pretty-format: 30.5.1 + pure-rand: 7.0.1 + slash: 3.0.0 + stack-utils: 2.0.6 + transitivePeerDependencies: + - babel-plugin-macros + - supports-color + + jest-cli@30.5.1(@types/node@26.4.1): + dependencies: + '@jest/core': 30.5.1 + '@jest/test-result': 30.5.1 + '@jest/types': 30.5.1 + chalk: 4.1.2 + exit-x: 0.2.2 + import-local: 3.2.0 + jest-config: 30.5.1(@types/node@26.4.1) + jest-util: 30.5.1 + jest-validate: 30.5.1 + yargs: 17.7.3 + transitivePeerDependencies: + - '@types/node' + - babel-plugin-macros + - esbuild-register + - supports-color + - ts-node + + jest-config@30.5.1(@types/node@26.4.1): + dependencies: + '@babel/core': 7.29.7 + '@jest/get-type': 30.5.0 + '@jest/pattern': 30.5.0 + '@jest/test-sequencer': 30.5.1 + '@jest/types': 30.5.1 + babel-jest: 30.5.1(@babel/core@7.29.7) + chalk: 4.1.2 + ci-info: 4.4.0 + deepmerge: 4.3.1 + glob: 13.0.6 + graceful-fs: 4.2.11 + jest-circus: 30.5.1 + jest-docblock: 30.5.0 + jest-environment-node: 30.5.1 + jest-regex-util: 30.5.0 + jest-resolve: 30.5.1 + jest-runner: 30.5.1 + jest-util: 30.5.1 + jest-validate: 30.5.1 + parse-json: 5.2.0 + pretty-format: 30.5.1 + slash: 3.0.0 + strip-json-comments: 3.1.1 + optionalDependencies: + '@types/node': 26.4.1 + transitivePeerDependencies: + - babel-plugin-macros + - supports-color + + jest-diff@30.5.1: + dependencies: + '@jest/diff-sequences': 30.5.0 + '@jest/get-type': 30.5.0 + chalk: 4.1.2 + pretty-format: 30.5.1 + + jest-docblock@30.5.0: + dependencies: + detect-newline: 3.1.0 + + jest-each@30.5.1: + dependencies: + '@jest/get-type': 30.5.0 + '@jest/types': 30.5.1 + chalk: 4.1.2 + jest-util: 30.5.1 + pretty-format: 30.5.1 + + jest-environment-node@30.5.1: + dependencies: + '@jest/environment': 30.5.1 + '@jest/fake-timers': 30.5.1 + '@jest/types': 30.5.1 + '@types/node': 26.4.1 + jest-mock: 30.5.1 + jest-util: 30.5.1 + jest-validate: 30.5.1 + + jest-haste-map@30.5.1: + dependencies: + '@jest/types': 30.5.1 + '@parcel/watcher': 2.6.0 + '@types/node': 26.4.1 + anymatch: 3.1.3 + fb-watchman: 2.0.2 + fdir: 6.5.0(picomatch@4.0.7) + graceful-fs: 4.2.11 + jest-regex-util: 30.5.0 + jest-util: 30.5.1 + jest-worker: 30.5.1 + picomatch: 4.0.7 + + jest-junit@16.0.0: + dependencies: + mkdirp: 1.0.4 + strip-ansi: 6.0.1 + uuid: 8.3.2 + xml: 1.0.1 + + jest-leak-detector@30.5.1: + dependencies: + '@jest/get-type': 30.5.0 + pretty-format: 30.5.1 + + jest-matcher-utils@30.5.1: + dependencies: + '@jest/get-type': 30.5.0 + chalk: 4.1.2 + jest-diff: 30.5.1 + pretty-format: 30.5.1 + + jest-message-util@30.5.1: + dependencies: + '@babel/code-frame': 7.29.7 + '@jest/types': 30.5.1 + '@types/stack-utils': 2.0.3 + chalk: 4.1.2 + graceful-fs: 4.2.11 + jest-util: 30.5.1 + picomatch: 4.0.7 + pretty-format: 30.5.1 + slash: 3.0.0 + stack-utils: 2.0.6 + + jest-mock@30.5.1: + dependencies: + '@jest/expect-utils': 30.5.1 + '@jest/types': 30.5.1 + '@types/node': 26.4.1 + jest-util: 30.5.1 + + jest-regex-util@30.5.0: {} + + jest-resolve-dependencies@30.5.1: + dependencies: + jest-regex-util: 30.5.0 + jest-snapshot: 30.5.1 + transitivePeerDependencies: + - supports-color + + jest-resolve@30.5.1: + dependencies: + chalk: 4.1.2 + graceful-fs: 4.2.11 + jest-haste-map: 30.5.1 + jest-util: 30.5.1 + jest-validate: 30.5.1 + slash: 3.0.0 + unrs-resolver: 1.12.2 + + jest-runner@30.5.1: + dependencies: + '@jest/console': 30.5.1 + '@jest/environment': 30.5.1 + '@jest/source-map': 30.5.0 + '@jest/test-result': 30.5.1 + '@jest/transform': 30.5.1 + '@jest/types': 30.5.1 + '@types/node': 26.4.1 + chalk: 4.1.2 + emittery: 0.13.1 + exit-x: 0.2.2 + graceful-fs: 4.2.11 + jest-docblock: 30.5.0 + jest-environment-node: 30.5.1 + jest-haste-map: 30.5.1 + jest-leak-detector: 30.5.1 + jest-message-util: 30.5.1 + jest-resolve: 30.5.1 + jest-runtime: 30.5.1 + jest-util: 30.5.1 + jest-watcher: 30.5.1 + jest-worker: 30.5.1 + p-limit: 3.1.0 + transitivePeerDependencies: + - supports-color + + jest-runtime@30.5.1: + dependencies: + '@jest/environment': 30.5.1 + '@jest/fake-timers': 30.5.1 + '@jest/globals': 30.5.1 + '@jest/source-map': 30.5.0 + '@jest/test-result': 30.5.1 + '@jest/transform': 30.5.1 + '@jest/types': 30.5.1 + '@types/node': 26.4.1 + chalk: 4.1.2 + cjs-module-lexer: 2.2.1 + collect-v8-coverage: 1.0.3 + es-module-lexer: 2.3.2 + glob: 13.0.6 + graceful-fs: 4.2.11 + jest-haste-map: 30.5.1 + jest-message-util: 30.5.1 + jest-mock: 30.5.1 + jest-regex-util: 30.5.0 + jest-resolve: 30.5.1 + jest-snapshot: 30.5.1 + jest-util: 30.5.1 + slash: 3.0.0 + strip-bom: 4.0.0 + transitivePeerDependencies: + - supports-color + + jest-snapshot@30.5.1: + dependencies: + '@babel/core': 7.29.7 + '@babel/generator': 7.29.8 + '@babel/plugin-syntax-jsx': 7.29.7(@babel/core@7.29.7) + '@babel/plugin-syntax-typescript': 7.29.7(@babel/core@7.29.7) + '@babel/types': 7.29.8 + '@jest/expect-utils': 30.5.1 + '@jest/get-type': 30.5.0 + '@jest/snapshot-utils': 30.5.1 + '@jest/transform': 30.5.1 + '@jest/types': 30.5.1 + babel-preset-current-node-syntax: 1.2.0(@babel/core@7.29.7) + chalk: 4.1.2 + expect: 30.5.1 + graceful-fs: 4.2.11 + jest-diff: 30.5.1 + jest-matcher-utils: 30.5.1 + jest-message-util: 30.5.1 + jest-util: 30.5.1 + pretty-format: 30.5.1 + semver: 7.8.5 + synckit: 0.11.13 + transitivePeerDependencies: + - supports-color + + jest-util@30.5.1: + dependencies: + '@jest/types': 30.5.1 + '@types/node': 26.4.1 + chalk: 4.1.2 + ci-info: 4.4.0 + graceful-fs: 4.2.11 + picomatch: 4.0.7 + + jest-validate@30.5.1: + dependencies: + '@jest/get-type': 30.5.0 + '@jest/types': 30.5.1 + camelcase: 6.3.0 + chalk: 4.1.2 + leven: 3.1.0 + pretty-format: 30.5.1 + + jest-watcher@30.5.1: + dependencies: + '@jest/test-result': 30.5.1 + '@jest/types': 30.5.1 + '@types/node': 26.4.1 + ansi-escapes: 4.3.2 + chalk: 4.1.2 + emittery: 0.13.1 + jest-util: 30.5.1 + string-length: 4.0.2 + + jest-worker@30.5.1: + dependencies: + '@types/node': 26.4.1 + '@ungap/structured-clone': 1.4.0 + jest-util: 30.5.1 + merge-stream: 2.0.0 + supports-color: 8.1.1 + + jest@30.5.1(@types/node@26.4.1): + dependencies: + '@jest/core': 30.5.1 + '@jest/types': 30.5.1 + import-local: 3.2.0 + jest-cli: 30.5.1(@types/node@26.4.1) + transitivePeerDependencies: + - '@types/node' + - babel-plugin-macros + - esbuild-register + - supports-color + - ts-node + + js-beautify@1.15.4: + dependencies: + config-chain: 1.1.13 + editorconfig: 1.0.7 + glob: 10.5.0 + js-cookie: 3.0.8 + nopt: 7.2.1 + + js-beautify@2.0.3: + dependencies: + config-chain: 1.1.13 + editorconfig: 3.0.2 + glob: 13.0.6 + js-cookie: 3.0.8 + nopt: 10.0.1 + + js-cookie@3.0.8: {} + + js-tokens@4.0.0: {} + + js-yaml@3.15.2: + dependencies: + argparse: 1.0.10 + esprima: 4.0.1 + + js-yaml@4.3.2: + dependencies: + argparse: 2.0.1 + + jsesc@3.1.0: {} + + json-buffer@3.0.1: {} + + json-parse-better-errors@1.0.2: {} + + json-parse-even-better-errors@2.3.1: {} + + json-schema-traverse@0.4.1: {} + + json-stable-stringify-without-jsonify@1.0.1: {} + + json5@1.0.2: + dependencies: + minimist: 1.2.8 + + json5@2.2.3: {} + + jsonfile@4.0.0: + optionalDependencies: + graceful-fs: 4.2.11 + + jsonfile@6.2.1: + dependencies: + universalify: 2.0.1 + optionalDependencies: + graceful-fs: 4.2.11 + + jsonwebtoken@9.0.3: + dependencies: + jws: 4.0.1 + lodash.includes: 4.3.0 + lodash.isboolean: 3.0.3 + lodash.isinteger: 4.0.4 + lodash.isnumber: 3.0.3 + lodash.isplainobject: 4.0.6 + lodash.isstring: 4.0.1 + lodash.once: 4.1.1 + ms: 2.1.3 + semver: 7.8.5 + + jsx-ast-utils@3.3.5: + dependencies: + array-includes: 3.1.9 + array.prototype.flat: 1.3.3 + object.assign: 4.1.7 + object.values: 1.2.1 + + jszip@3.10.1: + dependencies: + lie: 3.3.0 + pako: 1.0.11 + readable-stream: 2.3.8 + setimmediate: 1.0.5 + + jwa@2.0.1: + dependencies: + buffer-equal-constant-time: 1.0.1 + ecdsa-sig-formatter: 1.0.11 + safe-buffer: 5.2.1 + + jwk-to-pem@2.0.7: + dependencies: + asn1.js: 5.4.1 + elliptic: 6.6.1 + safe-buffer: 5.2.1 + + jws@4.0.1: + dependencies: + jwa: 2.0.1 + safe-buffer: 5.2.1 + + kareem@3.3.0: {} + + keycloak-connect@26.1.1: + dependencies: + jwk-to-pem: 2.0.7 + optionalDependencies: + chromedriver: 152.0.3 + transitivePeerDependencies: + - debug + - kerberos + - supports-color + + keyv@4.5.4: + dependencies: + json-buffer: 3.0.1 + + kind-of@6.0.3: {} + + lazystream@1.0.1: + dependencies: + readable-stream: 2.3.8 + + leven@3.1.0: {} + + levn@0.4.1: + dependencies: + prelude-ls: 1.2.1 + type-check: 0.4.0 + + lie@3.3.0: + dependencies: + immediate: 3.0.6 + + lines-and-columns@1.2.4: {} + + listenercount@1.0.1: {} + + load-json-file@5.3.0: + dependencies: + graceful-fs: 4.2.11 + parse-json: 4.0.0 + pify: 4.0.1 + strip-bom: 3.0.0 + type-fest: 0.3.1 + + locate-path@3.0.0: + dependencies: + p-locate: 3.0.0 + path-exists: 3.0.0 + + locate-path@5.0.0: + dependencies: + p-locate: 4.1.0 + + locate-path@6.0.0: + dependencies: + p-locate: 5.0.0 + + lodash.debounce@4.0.8: {} + + lodash.defaults@4.2.0: {} + + lodash.difference@4.5.0: {} + + lodash.escaperegexp@4.1.2: {} + + lodash.flatten@4.4.0: {} + + lodash.groupby@4.6.0: {} + + lodash.includes@4.3.0: {} + + lodash.isboolean@3.0.3: {} + + lodash.isequal@4.5.0: {} + + lodash.isfunction@3.0.9: {} + + lodash.isinteger@4.0.4: {} + + lodash.isnil@4.0.0: {} + + lodash.isnumber@3.0.3: {} + + lodash.isplainobject@4.0.6: {} + + lodash.isstring@4.0.1: {} + + lodash.isundefined@3.0.1: {} + + lodash.merge@4.6.2: {} + + lodash.once@4.1.1: {} + + lodash.union@4.6.0: {} + + lodash.uniq@4.5.0: {} + + lodash@4.18.1: {} + + log4js@6.9.1: + dependencies: + date-format: 4.0.14 + debug: 4.4.3(supports-color@5.5.0) + flatted: 3.4.4 + rfdc: 1.4.1 + streamroller: 3.1.5 + transitivePeerDependencies: + - supports-color + + loose-envify@1.4.0: + dependencies: + js-tokens: 4.0.0 + + lru-cache@10.4.3: {} + + lru-cache@11.5.2: {} + + lru-cache@5.1.1: + dependencies: + yallist: 3.1.1 + + lru-cache@7.18.3: {} + + make-dir@2.1.0: + dependencies: + pify: 4.0.1 + semver: 5.7.2 + + make-dir@4.0.0: + dependencies: + semver: 7.8.5 + + math-intrinsics@1.1.0: {} + + media-typer@0.3.0: {} + + media-typer@1.1.1: {} + + memory-pager@1.5.0: {} + + merge-descriptors@2.0.0: {} + + merge-stream@2.0.0: {} + + methods@1.1.2: {} + + mime-db@1.52.0: {} + + mime-db@1.54.0: {} + + mime-types@2.1.35: + dependencies: + mime-db: 1.52.0 + + mime-types@3.0.2: + dependencies: + mime-db: 1.54.0 + + mime@2.6.0: {} + + mimic-fn@2.1.0: {} + + minimalistic-assert@1.0.1: {} + + minimalistic-crypto-utils@1.0.1: {} + + minimatch@10.2.6: + dependencies: + brace-expansion: 5.0.9 + + minimatch@3.1.5: + dependencies: + brace-expansion: 1.1.18 + + minimatch@5.1.9: + dependencies: + brace-expansion: 2.1.4 + + minimatch@9.0.9: + dependencies: + brace-expansion: 2.1.4 + + minimist@1.2.8: {} + + minipass@7.1.3: {} + + mitt@3.0.1: {} + + mkdirp@0.5.6: + dependencies: + minimist: 1.2.8 + + mkdirp@1.0.4: {} + + moment-timezone@0.5.48: + dependencies: + moment: 2.30.1 + + moment@2.30.1: {} + + mongodb-connection-string-url@3.0.2: + dependencies: + '@types/whatwg-url': 11.0.5 + whatwg-url: 14.2.0 + + mongodb-connection-string-url@7.0.2: + dependencies: + '@types/whatwg-url': 13.0.0 + whatwg-url: 14.2.0 + + mongodb@6.21.0(socks@2.8.10): + dependencies: + '@mongodb-js/saslprep': 1.5.0 + bson: 6.10.4 + mongodb-connection-string-url: 3.0.2 + optionalDependencies: + socks: 2.8.10 + + mongodb@7.5.0(socks@2.8.10): + dependencies: + '@mongodb-js/saslprep': 1.5.0 + bson: 7.3.2 + mongodb-connection-string-url: 7.0.2 + optionalDependencies: + socks: 2.8.10 + + mongoose@9.9.5(socks@2.8.10): + dependencies: + '@standard-schema/spec': 1.1.0 + kareem: 3.3.0 + mongodb: 7.5.0(socks@2.8.10) + mpath: 0.9.0 + mquery: 6.0.0 + ms: 2.1.3 + sift: 17.1.3 + transitivePeerDependencies: + - '@aws-sdk/credential-providers' + - '@mongodb-js/zstd' + - gcp-metadata + - kerberos + - mongodb-client-encryption + - snappy + - socks + + mpath@0.9.0: {} + + mquery@6.0.0: {} + + ms@2.0.0: {} + + ms@2.1.2: + optional: true + + ms@2.1.3: {} + + multer@2.3.0: + dependencies: + append-field: 1.0.0 + busboy: 1.6.0 + concat-stream: 2.0.0 + type-is: 1.6.18 + + nanoid@5.1.16: {} + + napi-postinstall@0.3.4: {} + + natural-compare@1.4.0: {} + + negotiator@1.1.0: + dependencies: + content-type: 2.1.0 + + netmask@2.1.1: {} + + node-addon-api@7.1.1: {} + + node-addon-api@8.9.2: {} + + node-cache@5.1.2: + dependencies: + clone: 2.1.2 + + node-cron@4.6.0: {} + + node-environment-flags@1.0.6: + dependencies: + object.getownpropertydescriptors: 2.1.9 + semver: 5.7.2 + + node-exports-info@1.6.2: + dependencies: + array.prototype.flatmap: 1.3.3 + es-errors: 1.3.0 + object.entries: 1.1.9 + semver: 6.3.1 + + node-gyp-build@4.8.4: {} + + node-int64@0.4.0: {} + + node-readable-to-web-readable-stream@0.4.2: + optional: true + + node-releases@2.0.54: {} + + nodemailer@10.0.0: {} + + nodemon@3.1.14: + dependencies: + chokidar: 3.6.0 + debug: 4.4.3(supports-color@5.5.0) + ignore-by-default: 1.0.1 + minimatch: 10.2.6 + pstree.remy: 1.1.8 + semver: 7.8.5 + simple-update-notifier: 2.0.0 + supports-color: 5.5.0 + touch: 3.1.1 + undefsafe: 2.0.5 + + nopt@10.0.1: + dependencies: + abbrev: 5.0.0 + + nopt@7.2.1: + dependencies: + abbrev: 2.0.0 + + normalize-path@3.0.0: {} + + npm-run-path@4.0.1: + dependencies: + path-key: 3.1.1 + + object-assign@4.1.1: {} + + object-inspect@1.13.4: {} + + object-keys@1.1.1: {} + + object.assign@4.1.7: + dependencies: + call-bind: 1.0.9 + call-bound: 1.0.4 + define-properties: 1.2.1 + es-object-atoms: 1.1.2 + has-symbols: 1.1.0 + object-keys: 1.1.1 + + object.entries@1.1.9: + dependencies: + call-bind: 1.0.9 + call-bound: 1.0.4 + define-properties: 1.2.1 + es-object-atoms: 1.1.2 + + object.fromentries@2.0.8: + dependencies: + call-bind: 1.0.9 + define-properties: 1.2.1 + es-abstract: 1.24.2 + es-object-atoms: 1.1.2 + + object.getownpropertydescriptors@2.1.9: + dependencies: + array.prototype.reduce: 1.0.8 + call-bind: 1.0.9 + define-properties: 1.2.1 + es-abstract: 1.24.2 + es-object-atoms: 1.1.2 + gopd: 1.2.0 + safe-array-concat: 1.1.4 + + object.groupby@1.0.3: + dependencies: + call-bind: 1.0.9 + define-properties: 1.2.1 + es-abstract: 1.24.2 + + object.values@1.2.1: + dependencies: + call-bind: 1.0.9 + call-bound: 1.0.4 + define-properties: 1.2.1 + es-object-atoms: 1.1.2 + + on-finished@2.4.1: + dependencies: + ee-first: 1.1.1 + + on-headers@1.1.0: {} + + once@1.4.0: + dependencies: + wrappy: 1.0.2 + + onetime@5.1.2: + dependencies: + mimic-fn: 2.1.0 + + optionator@0.9.4: + dependencies: + deep-is: 0.1.4 + fast-levenshtein: 2.0.6 + levn: 0.4.1 + prelude-ls: 1.2.1 + type-check: 0.4.0 + word-wrap: 1.2.5 + + own-keys@1.0.2: + dependencies: + call-bound: 1.0.4 + get-intrinsic: 1.3.0 + object-keys: 1.1.1 + safe-push-apply: 1.0.0 + + p-limit@2.3.0: + dependencies: + p-try: 2.2.0 + + p-limit@3.1.0: + dependencies: + yocto-queue: 0.1.0 + + p-locate@3.0.0: + dependencies: + p-limit: 2.3.0 + + p-locate@4.1.0: + dependencies: + p-limit: 2.3.0 + + p-locate@5.0.0: + dependencies: + p-limit: 3.1.0 + + p-try@2.2.0: {} + + pac-proxy-agent@7.2.0: + dependencies: + '@tootallnate/quickjs-emscripten': 0.23.0 + agent-base: 7.1.4 + debug: 4.4.3(supports-color@5.5.0) + get-uri: 6.0.5 + http-proxy-agent: 7.0.2 + https-proxy-agent: 7.0.6 + pac-resolver: 7.0.1 + socks-proxy-agent: 8.0.5 + transitivePeerDependencies: + - supports-color + + pac-proxy-agent@9.1.0: + dependencies: + agent-base: 9.0.0 + debug: 4.4.3(supports-color@5.5.0) + get-uri: 8.0.1 + http-proxy-agent: 9.1.0 + https-proxy-agent: 9.1.0 + pac-resolver: 9.0.1(quickjs-wasi@2.2.0) + quickjs-wasi: 2.2.0 + socks-proxy-agent: 10.1.0 + transitivePeerDependencies: + - kerberos + - supports-color + optional: true + + pac-resolver@7.0.1: + dependencies: + degenerator: 5.0.1 + netmask: 2.1.1 + + pac-resolver@9.0.1(quickjs-wasi@2.2.0): + dependencies: + degenerator: 7.0.1(quickjs-wasi@2.2.0) + netmask: 2.1.1 + quickjs-wasi: 2.2.0 + optional: true + + package-json-from-dist@1.0.1: {} + + pako@1.0.11: {} + + parent-module@1.0.1: + dependencies: + callsites: 3.1.0 + + parse-json@4.0.0: + dependencies: + error-ex: 1.3.4 + json-parse-better-errors: 1.0.2 + + parse-json@5.2.0: + dependencies: + '@babel/code-frame': 7.29.7 + error-ex: 1.3.4 + json-parse-even-better-errors: 2.3.1 + lines-and-columns: 1.2.4 + + parse-passwd@1.0.0: {} + + parseurl@1.3.3: {} + + path-exists@3.0.0: {} + + path-exists@4.0.0: {} + + path-is-absolute@1.0.1: {} + + path-key@3.1.1: {} + + path-parse@1.0.7: {} + + path-scurry@1.11.1: + dependencies: + lru-cache: 10.4.3 + minipass: 7.1.3 + + path-scurry@2.0.2: + dependencies: + lru-cache: 11.5.2 + minipass: 7.1.3 + + path-to-regexp@8.4.2: {} + + pdf-lib@1.17.1: + dependencies: + '@pdf-lib/standard-fonts': 1.0.0 + '@pdf-lib/upng': 1.0.1 + pako: 1.0.11 + tslib: 1.14.1 + + pdf-to-img@6.3.0: + dependencies: + pdfjs-dist: 5.6.205 + + pdfjs-dist@5.6.205: + optionalDependencies: + '@napi-rs/canvas': 0.1.100 + node-readable-to-web-readable-stream: 0.4.2 + + pend@1.2.0: {} + + pg-cloudflare@1.4.0: + optional: true + + pg-connection-string@2.14.0: {} + + pg-int8@1.0.1: {} + + pg-pool@3.14.0(pg@8.23.0): + dependencies: + pg: 8.23.0 + + pg-protocol@1.16.0: {} + + pg-types@2.2.0: + dependencies: + pg-int8: 1.0.1 + postgres-array: 2.0.0 + postgres-bytea: 1.0.1 + postgres-date: 1.0.7 + postgres-interval: 1.2.0 + + pg@8.23.0: + dependencies: + pg-connection-string: 2.14.0 + pg-pool: 3.14.0(pg@8.23.0) + pg-protocol: 1.16.0 + pg-types: 2.2.0 + pgpass: 1.0.5 + optionalDependencies: + pg-cloudflare: 1.4.0 + + pgpass@1.0.5: + dependencies: + split2: 4.2.0 + + picocolors@1.1.1: {} + + picomatch@2.3.2: {} + + picomatch@4.0.7: {} + + pify@4.0.1: {} + + pirates@4.0.7: {} + + pkg-conf@3.1.0: + dependencies: + find-up: 3.0.0 + load-json-file: 5.3.0 + + pkg-dir@3.0.0: + dependencies: + find-up: 3.0.0 + + pkg-dir@4.2.0: + dependencies: + find-up: 4.1.0 + + possible-typed-array-names@1.1.0: {} + + postgres-array@2.0.0: {} + + postgres-bytea@1.0.1: {} + + postgres-date@1.0.7: {} + + postgres-interval@1.2.0: + dependencies: + xtend: 4.0.2 + + posthtml-parser@0.11.0: + dependencies: + htmlparser2: 7.2.0 + + posthtml-render@3.0.0: + dependencies: + is-json: 2.0.1 + + posthtml@0.16.7: + dependencies: + posthtml-parser: 0.11.0 + posthtml-render: 3.0.0 + + prelude-ls@1.2.1: {} + + prettier-linter-helpers@1.0.1: + dependencies: + fast-diff: 1.3.0 + + prettier@3.9.6: {} + + pretty-format@30.5.1: + dependencies: + '@jest/react-is-18': react-is@18.3.1 + '@jest/react-is-19': react-is@19.2.8 + '@jest/schemas': 30.5.0 + ansi-styles: 5.2.0 + + process-nextick-args@2.0.1: {} + + progress@2.0.3: {} + + prop-types@15.8.1: + dependencies: + loose-envify: 1.4.0 + object-assign: 4.1.1 + react-is: 16.13.1 + + proto-list@1.2.4: {} + + proxy-addr@2.0.7: + dependencies: + forwarded: 0.2.0 + ipaddr.js: 1.9.1 + + proxy-agent-negotiate@1.1.0: + optional: true + + proxy-agent@6.5.0: + dependencies: + agent-base: 7.1.4 + debug: 4.4.3(supports-color@5.5.0) + http-proxy-agent: 7.0.2 + https-proxy-agent: 7.0.6 + lru-cache: 7.18.3 + pac-proxy-agent: 7.2.0 + proxy-from-env: 1.1.0 + socks-proxy-agent: 8.0.5 + transitivePeerDependencies: + - supports-color + + proxy-agent@8.0.2: + dependencies: + agent-base: 9.0.0 + debug: 4.4.3(supports-color@5.5.0) + http-proxy-agent: 9.1.0 + https-proxy-agent: 9.1.0 + lru-cache: 7.18.3 + pac-proxy-agent: 9.1.0 + proxy-from-env: 2.1.0 + socks-proxy-agent: 10.1.0 + transitivePeerDependencies: + - kerberos + - supports-color + optional: true + + proxy-from-env@1.1.0: {} + + proxy-from-env@2.1.0: {} + + pstree.remy@1.1.8: {} + + pump@3.0.4: + dependencies: + end-of-stream: 1.4.5 + once: 1.4.0 + + punycode@2.3.1: {} + + puppeteer-core@24.43.1: + dependencies: + '@puppeteer/browsers': 2.13.2 + chromium-bidi: 14.0.0(devtools-protocol@0.0.1608973) + debug: 4.4.3(supports-color@5.5.0) + devtools-protocol: 0.0.1608973 + typed-query-selector: 2.12.2 + webdriver-bidi-protocol: 0.4.1 + ws: 8.21.3 + transitivePeerDependencies: + - bare-abort-controller + - bare-buffer + - bufferutil + - react-native-b4a + - supports-color + - utf-8-validate + + puppeteer@24.43.1: + dependencies: + '@puppeteer/browsers': 2.13.2 + chromium-bidi: 14.0.0(devtools-protocol@0.0.1608973) + cosmiconfig: 9.0.2 + devtools-protocol: 0.0.1608973 + puppeteer-core: 24.43.1 + typed-query-selector: 2.12.2 + transitivePeerDependencies: + - bare-abort-controller + - bare-buffer + - bufferutil + - react-native-b4a + - supports-color + - typescript + - utf-8-validate + + pure-rand@7.0.1: {} + + qs@6.16.0: + dependencies: + es-define-property: 1.0.1 + side-channel: 1.1.1 + + queue-microtask@1.2.3: {} + + quickjs-wasi@2.2.0: + optional: true + + random-bytes@1.0.0: {} + + range-parser@1.3.0: {} + + raw-body@3.0.2: + dependencies: + bytes: 3.1.2 + http-errors: 2.0.1 + iconv-lite: 0.7.3 + unpipe: 1.0.0 + + react-is@16.13.1: {} + + react-is@18.3.1: {} + + react-is@19.2.8: {} + + readable-stream@2.3.8: + dependencies: + core-util-is: 1.0.3 + inherits: 2.0.4 + isarray: 1.0.0 + process-nextick-args: 2.0.1 + safe-buffer: 5.1.2 + string_decoder: 1.1.1 + util-deprecate: 1.0.2 + + readable-stream@3.6.2: + dependencies: + inherits: 2.0.4 + string_decoder: 1.3.0 + util-deprecate: 1.0.2 + + readdir-glob@1.1.3: + dependencies: + minimatch: 5.1.9 + + readdirp@3.6.0: + dependencies: + picomatch: 2.3.2 + + redis-errors@1.2.0: {} + + redis-parser@3.0.0: + dependencies: + redis-errors: 1.2.0 + + reflect.getprototypeof@1.0.10: + dependencies: + call-bind: 1.0.9 + define-properties: 1.2.1 + es-abstract: 1.24.2 + es-errors: 1.3.0 + es-object-atoms: 1.1.2 + get-intrinsic: 1.3.0 + get-proto: 1.0.1 + which-builtin-type: 1.2.1 + + regenerate-unicode-properties@10.2.2: + dependencies: + regenerate: 1.4.2 + + regenerate@1.4.2: {} + + regenerator-runtime@0.14.1: {} + + regexp.prototype.flags@1.5.4: + dependencies: + call-bind: 1.0.9 + define-properties: 1.2.1 + es-errors: 1.3.0 + get-proto: 1.0.1 + gopd: 1.2.0 + set-function-name: 2.0.2 + + regexpp@3.2.0: {} + + regexpu-core@6.4.0: + dependencies: + regenerate: 1.4.2 + regenerate-unicode-properties: 10.2.2 + regjsgen: 0.8.0 + regjsparser: 0.13.2 + unicode-match-property-ecmascript: 2.0.0 + unicode-match-property-value-ecmascript: 2.2.1 + + regjsgen@0.8.0: {} + + regjsparser@0.13.2: + dependencies: + jsesc: 3.1.0 + + require-directory@2.1.1: {} + + resolve-cwd@3.0.0: + dependencies: + resolve-from: 5.0.0 + + resolve-from@4.0.0: {} + + resolve-from@5.0.0: {} + + resolve@1.22.12: + dependencies: + es-errors: 1.3.0 + is-core-module: 2.16.2 + path-parse: 1.0.7 + supports-preserve-symlinks-flag: 1.0.0 + + resolve@2.0.0-next.7: + dependencies: + es-errors: 1.3.0 + is-core-module: 2.16.2 + node-exports-info: 1.6.2 + object-keys: 1.1.1 + path-parse: 1.0.7 + supports-preserve-symlinks-flag: 1.0.0 + + retry-as-promised@7.1.1: {} + + reusify@1.1.0: {} + + rfdc@1.4.1: {} + + rimraf@2.7.1: + dependencies: + glob: 7.2.3 + + rimraf@3.0.2: + dependencies: + glob: 7.2.3 + + router@2.2.0: + dependencies: + debug: 4.4.3(supports-color@5.5.0) + depd: 2.0.0 + is-promise: 4.0.0 + parseurl: 1.3.3 + path-to-regexp: 8.4.2 + transitivePeerDependencies: + - supports-color + + run-parallel@1.2.0: + dependencies: + queue-microtask: 1.2.3 + + rxjs@7.8.2: + dependencies: + tslib: 2.8.1 + + safe-array-concat@1.1.4: + dependencies: + call-bind: 1.0.9 + call-bound: 1.0.4 + get-intrinsic: 1.3.0 + has-symbols: 1.1.0 + isarray: 2.0.5 + + safe-buffer@5.1.2: {} + + safe-buffer@5.2.1: {} + + safe-push-apply@1.0.0: + dependencies: + es-errors: 1.3.0 + isarray: 2.0.5 + + safe-regex-test@1.1.0: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + is-regex: 1.2.1 + + safer-buffer@2.1.2: {} + + saxes@5.0.1: + dependencies: + xmlchars: 2.2.0 + + semver@5.7.2: {} + + semver@6.3.1: {} + + semver@7.8.5: {} + + send@1.2.1: + dependencies: + debug: 4.4.3(supports-color@5.5.0) + encodeurl: 2.0.0 + escape-html: 1.0.3 + etag: 1.8.1 + fresh: 2.0.0 + http-errors: 2.0.1 + mime-types: 3.0.2 + ms: 2.1.3 + on-finished: 2.4.1 + range-parser: 1.3.0 + statuses: 2.0.2 + transitivePeerDependencies: + - supports-color + + sequelize-cli@6.6.5: + dependencies: + fs-extra: 9.1.0 + js-beautify: 1.15.4 + lodash: 4.18.1 + picocolors: 1.1.1 + resolve: 1.22.12 + umzug: 2.3.0 + yargs: 16.2.2 + + sequelize-pool@7.1.0: {} + + sequelize@6.37.8(pg@8.23.0): + dependencies: + '@types/debug': 4.1.13 + '@types/validator': 13.15.10 + debug: 4.4.3(supports-color@5.5.0) + dottie: 2.0.7 + inflection: 1.13.4 + lodash: 4.18.1 + moment: 2.30.1 + moment-timezone: 0.5.48 + pg-connection-string: 2.14.0 + retry-as-promised: 7.1.1 + semver: 7.8.5 + sequelize-pool: 7.1.0 + toposort-class: 1.0.1 + uuid: 8.3.2 + validator: 13.15.35 + wkx: 0.5.0 + optionalDependencies: + pg: 8.23.0 + transitivePeerDependencies: + - supports-color + + serve-static@2.2.1: + dependencies: + encodeurl: 2.0.0 + escape-html: 1.0.3 + parseurl: 1.3.3 + send: 1.2.1 + transitivePeerDependencies: + - supports-color + + set-function-length@1.2.2: + dependencies: + define-data-property: 1.1.4 + es-errors: 1.3.0 + function-bind: 1.1.2 + get-intrinsic: 1.3.0 + gopd: 1.2.0 + has-property-descriptors: 1.0.2 + + set-function-name@2.0.2: + dependencies: + define-data-property: 1.1.4 + es-errors: 1.3.0 + functions-have-names: 1.2.3 + has-property-descriptors: 1.0.2 + + set-proto@1.0.0: + dependencies: + dunder-proto: 1.0.1 + es-errors: 1.3.0 + es-object-atoms: 1.1.2 + + setimmediate@1.0.5: {} + + setprototypeof@1.2.0: {} + + shallow-clone@3.0.1: + dependencies: + kind-of: 6.0.3 + + sharp@0.34.5: + dependencies: + '@img/colour': 1.1.0 + detect-libc: 2.1.2 + semver: 7.8.5 + optionalDependencies: + '@img/sharp-darwin-arm64': 0.34.5 + '@img/sharp-darwin-x64': 0.34.5 + '@img/sharp-libvips-darwin-arm64': 1.2.4 + '@img/sharp-libvips-darwin-x64': 1.2.4 + '@img/sharp-libvips-linux-arm': 1.2.4 + '@img/sharp-libvips-linux-arm64': 1.2.4 + '@img/sharp-libvips-linux-ppc64': 1.2.4 + '@img/sharp-libvips-linux-riscv64': 1.2.4 + '@img/sharp-libvips-linux-s390x': 1.2.4 + '@img/sharp-libvips-linux-x64': 1.2.4 + '@img/sharp-libvips-linuxmusl-arm64': 1.2.4 + '@img/sharp-libvips-linuxmusl-x64': 1.2.4 + '@img/sharp-linux-arm': 0.34.5 + '@img/sharp-linux-arm64': 0.34.5 + '@img/sharp-linux-ppc64': 0.34.5 + '@img/sharp-linux-riscv64': 0.34.5 + '@img/sharp-linux-s390x': 0.34.5 + '@img/sharp-linux-x64': 0.34.5 + '@img/sharp-linuxmusl-arm64': 0.34.5 + '@img/sharp-linuxmusl-x64': 0.34.5 + '@img/sharp-wasm32': 0.34.5 + '@img/sharp-win32-arm64': 0.34.5 + '@img/sharp-win32-ia32': 0.34.5 + '@img/sharp-win32-x64': 0.34.5 + + shebang-command@2.0.0: + dependencies: + shebang-regex: 3.0.0 + + shebang-regex@3.0.0: {} + + shell-quote@1.9.0: {} + + side-channel-list@1.0.1: + dependencies: + es-errors: 1.3.0 + object-inspect: 1.13.4 + + side-channel-map@1.0.1: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + object-inspect: 1.13.4 + + side-channel-weakmap@1.0.2: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + object-inspect: 1.13.4 + side-channel-map: 1.0.1 + + side-channel@1.1.1: + dependencies: + es-errors: 1.3.0 + object-inspect: 1.13.4 + side-channel-list: 1.0.1 + side-channel-map: 1.0.1 + side-channel-weakmap: 1.0.2 + + sift@17.1.3: {} + + signal-exit@3.0.7: {} + + signal-exit@4.1.0: {} + + simple-update-notifier@2.0.0: + dependencies: + semver: 7.8.5 + + slash@2.0.0: {} + + slash@3.0.0: {} + + smart-buffer@4.2.0: {} + + socks-proxy-agent@10.1.0: + dependencies: + agent-base: 9.0.0 + debug: 4.4.3(supports-color@5.5.0) + socks: 2.8.10 + transitivePeerDependencies: + - supports-color + optional: true + + socks-proxy-agent@8.0.5: + dependencies: + agent-base: 7.1.4 + debug: 4.4.3(supports-color@5.5.0) + socks: 2.8.10 + transitivePeerDependencies: + - supports-color + + socks@2.8.10: + dependencies: + ip-address: 10.7.0 + smart-buffer: 4.2.0 + + source-map-support@0.5.21: + dependencies: + buffer-from: 1.1.2 + source-map: 0.6.1 + + source-map@0.6.1: {} + + sparse-bitfield@3.0.3: + dependencies: + memory-pager: 1.5.0 + + speedgoose@2.5.11(socks@2.8.10): + dependencies: + debug: 4.4.3(supports-color@5.5.0) + fastq: 1.20.3 + ioredis: 5.11.1 + keyv: 4.5.4 + mongoose: 9.9.5(socks@2.8.10) + mpath: 0.9.0 + typedi: 0.10.0 + transitivePeerDependencies: + - '@aws-sdk/credential-providers' + - '@mongodb-js/zstd' + - gcp-metadata + - kerberos + - mongodb-client-encryption + - snappy + - socks + - supports-color + + split2@4.2.0: {} + + sprintf-js@1.0.3: {} + + stack-utils@2.0.6: + dependencies: + escape-string-regexp: 2.0.0 + + standard-as-callback@2.1.0: {} + + standard-engine@15.1.0: + dependencies: + get-stdin: 8.0.0 + minimist: 1.2.8 + pkg-conf: 3.1.0 + xdg-basedir: 4.0.0 + + standard@17.1.2: + dependencies: + eslint: 8.57.1 + eslint-config-standard: 17.1.0(eslint-plugin-import@2.32.0(eslint@8.57.1))(eslint-plugin-n@15.7.0(eslint@8.57.1))(eslint-plugin-promise@6.6.0(eslint@8.57.1))(eslint@8.57.1) + eslint-config-standard-jsx: 11.0.0(eslint-plugin-react@7.37.5(eslint@8.57.1))(eslint@8.57.1) + eslint-plugin-import: 2.32.0(eslint@8.57.1) + eslint-plugin-n: 15.7.0(eslint@8.57.1) + eslint-plugin-promise: 6.6.0(eslint@8.57.1) + eslint-plugin-react: 7.37.5(eslint@8.57.1) + standard-engine: 15.1.0 + version-guard: 1.1.3 + transitivePeerDependencies: + - '@typescript-eslint/parser' + - eslint-import-resolver-typescript + - eslint-import-resolver-webpack + - supports-color + + statuses@2.0.2: {} + + stop-iteration-iterator@1.1.0: + dependencies: + es-errors: 1.3.0 + internal-slot: 1.1.0 + + streamroller@3.1.5: + dependencies: + date-format: 4.0.14 + debug: 4.4.3(supports-color@5.5.0) + fs-extra: 8.1.0 + transitivePeerDependencies: + - supports-color + + streamsearch@1.1.0: {} + + streamx@2.28.1: + dependencies: + events-universal: 1.0.1 + fast-fifo: 1.3.2 + text-decoder: 1.2.7 + transitivePeerDependencies: + - bare-abort-controller + - react-native-b4a + + string-length@4.0.2: + dependencies: + char-regex: 1.0.2 + strip-ansi: 6.0.1 + + string-width@4.2.3: + dependencies: + emoji-regex: 8.0.0 + is-fullwidth-code-point: 3.0.0 + strip-ansi: 6.0.1 + + string-width@5.1.2: + dependencies: + eastasianwidth: 0.2.0 + emoji-regex: 9.2.2 + strip-ansi: 7.2.0 + + string.prototype.matchall@4.1.0: + dependencies: + call-bind: 1.0.9 + call-bound: 1.0.4 + define-properties: 1.2.1 + es-abstract: 1.24.2 + es-errors: 1.3.0 + es-object-atoms: 1.1.2 + get-intrinsic: 1.3.0 + gopd: 1.2.0 + has-symbols: 1.1.0 + internal-slot: 1.1.0 + regexp.prototype.flags: 1.5.4 + set-function-name: 2.0.2 + side-channel: 1.1.1 + + string.prototype.repeat@1.0.0: + dependencies: + define-properties: 1.2.1 + es-abstract: 1.24.2 + + string.prototype.trim@1.2.11: + dependencies: + call-bind: 1.0.9 + call-bound: 1.0.4 + define-data-property: 1.1.4 + define-properties: 1.2.1 + es-abstract: 1.24.2 + es-object-atoms: 1.1.2 + has-property-descriptors: 1.0.2 + safe-regex-test: 1.1.0 + + string.prototype.trimend@1.0.10: + dependencies: + call-bind: 1.0.9 + call-bound: 1.0.4 + define-properties: 1.2.1 + es-object-atoms: 1.1.2 + + string.prototype.trimstart@1.0.8: + dependencies: + call-bind: 1.0.9 + define-properties: 1.2.1 + es-object-atoms: 1.1.2 + + string_decoder@1.1.1: + dependencies: + safe-buffer: 5.1.2 + + string_decoder@1.3.0: + dependencies: + safe-buffer: 5.2.1 + + strip-ansi@6.0.1: + dependencies: + ansi-regex: 5.0.1 + + strip-ansi@7.2.0: + dependencies: + ansi-regex: 6.3.0 + + strip-bom@3.0.0: {} + + strip-bom@4.0.0: {} + + strip-final-newline@2.0.0: {} + + strip-json-comments@3.1.1: {} + + superagent@10.3.0: + dependencies: + component-emitter: 1.3.1 + cookiejar: 2.1.4 + debug: 4.4.3(supports-color@5.5.0) + fast-safe-stringify: 2.1.1 + form-data: 4.0.6 + formidable: 3.5.4 + methods: 1.1.2 + mime: 2.6.0 + qs: 6.16.0 + transitivePeerDependencies: + - supports-color + + supertest@7.2.2: + dependencies: + cookie-signature: 1.2.2 + methods: 1.1.2 + superagent: 10.3.0 + transitivePeerDependencies: + - supports-color + + supports-color@5.5.0: + dependencies: + has-flag: 3.0.0 + + supports-color@7.2.0: + dependencies: + has-flag: 4.0.0 + + supports-color@8.1.1: + dependencies: + has-flag: 4.0.0 + + supports-preserve-symlinks-flag@1.0.0: {} + + synckit@0.11.13: + dependencies: + '@pkgr/core': 0.3.6 + + tar-fs@3.1.3: + dependencies: + pump: 3.0.4 + tar-stream: 3.2.1 + optionalDependencies: + bare-fs: 4.8.1 + bare-path: 3.1.2 + transitivePeerDependencies: + - bare-abort-controller + - bare-buffer + - react-native-b4a + + tar-stream@2.2.0: + dependencies: + bl: 4.1.0 + end-of-stream: 1.4.5 + fs-constants: 1.0.0 + inherits: 2.0.4 + readable-stream: 3.6.2 + + tar-stream@3.2.1: + dependencies: + b4a: 1.8.1 + bare-fs: 4.8.1 + fast-fifo: 1.3.2 + streamx: 2.28.1 + transitivePeerDependencies: + - bare-abort-controller + - bare-buffer + - react-native-b4a + + tcp-port-used@1.0.3: + dependencies: + debug: 4.3.1 + is2: 2.0.1 + transitivePeerDependencies: + - supports-color + optional: true + + teex@1.0.1: + dependencies: + streamx: 2.28.1 + transitivePeerDependencies: + - bare-abort-controller + - react-native-b4a + + test-exclude@7.0.2: + dependencies: + '@istanbuljs/schema': 0.1.6 + glob: 10.5.0 + minimatch: 10.2.6 + + text-decoder@1.2.7: + dependencies: + b4a: 1.8.1 + transitivePeerDependencies: + - react-native-b4a + + text-table@0.2.0: {} + + tmp@0.2.7: {} + + to-regex-range@5.0.1: + dependencies: + is-number: 7.0.0 + + toidentifier@1.0.1: {} + + toposort-class@1.0.1: {} + + touch@3.1.1: {} + + tr46@5.1.1: + dependencies: + punycode: 2.3.1 + + traverse@0.3.9: {} + + tree-kill@1.2.2: {} + + tsconfig-paths@3.15.0: + dependencies: + '@types/json5': 0.0.29 + json5: 1.0.2 + minimist: 1.2.8 + strip-bom: 3.0.0 + + tslib@1.14.1: {} + + tslib@2.8.1: {} + + tweetnacl@1.0.3: {} + + type-check@0.4.0: + dependencies: + prelude-ls: 1.2.1 + + type-detect@4.0.8: {} + + type-fest@0.20.2: {} + + type-fest@0.21.3: {} + + type-fest@0.3.1: {} + + type-is@1.6.18: + dependencies: + media-typer: 0.3.0 + mime-types: 2.1.35 + + type-is@2.1.0: + dependencies: + content-type: 2.1.0 + media-typer: 1.1.1 + mime-types: 3.0.2 + + typed-array-buffer@1.0.3: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + is-typed-array: 1.1.15 + + typed-array-byte-length@1.0.3: + dependencies: + call-bind: 1.0.9 + for-each: 0.3.5 + gopd: 1.2.0 + has-proto: 1.2.0 + is-typed-array: 1.1.15 + + typed-array-byte-offset@1.0.4: + dependencies: + available-typed-arrays: 1.0.7 + call-bind: 1.0.9 + for-each: 0.3.5 + gopd: 1.2.0 + has-proto: 1.2.0 + is-typed-array: 1.1.15 + reflect.getprototypeof: 1.0.10 + + typed-array-length@1.0.8: + dependencies: + call-bind: 1.0.9 + for-each: 0.3.5 + gopd: 1.2.0 + is-typed-array: 1.1.15 + possible-typed-array-names: 1.1.0 + reflect.getprototypeof: 1.0.10 + + typed-query-selector@2.12.2: {} + + typedarray@0.0.6: {} + + typedi@0.10.0: {} + + uid-safe@2.1.5: + dependencies: + random-bytes: 1.0.0 + + umzug@2.3.0: + dependencies: + bluebird: 3.7.2 + + unbox-primitive@1.1.0: + dependencies: + call-bound: 1.0.4 + has-bigints: 1.1.0 + has-symbols: 1.1.0 + which-boxed-primitive: 1.1.1 + + undefsafe@2.0.5: {} + + undici-types@8.3.0: {} + + unicode-canonical-property-names-ecmascript@2.0.1: {} + + unicode-match-property-ecmascript@2.0.0: + dependencies: + unicode-canonical-property-names-ecmascript: 2.0.1 + unicode-property-aliases-ecmascript: 2.2.0 + + unicode-match-property-value-ecmascript@2.2.1: {} + + unicode-property-aliases-ecmascript@2.2.0: {} + + universalify@0.1.2: {} + + universalify@2.0.1: {} + + unpipe@1.0.0: {} + + unrs-resolver@1.12.2: + dependencies: + napi-postinstall: 0.3.4 + optionalDependencies: + '@unrs/resolver-binding-android-arm-eabi': 1.12.2 + '@unrs/resolver-binding-android-arm64': 1.12.2 + '@unrs/resolver-binding-darwin-arm64': 1.12.2 + '@unrs/resolver-binding-darwin-x64': 1.12.2 + '@unrs/resolver-binding-freebsd-x64': 1.12.2 + '@unrs/resolver-binding-linux-arm-gnueabihf': 1.12.2 + '@unrs/resolver-binding-linux-arm-musleabihf': 1.12.2 + '@unrs/resolver-binding-linux-arm64-gnu': 1.12.2 + '@unrs/resolver-binding-linux-arm64-musl': 1.12.2 + '@unrs/resolver-binding-linux-loong64-gnu': 1.12.2 + '@unrs/resolver-binding-linux-loong64-musl': 1.12.2 + '@unrs/resolver-binding-linux-ppc64-gnu': 1.12.2 + '@unrs/resolver-binding-linux-riscv64-gnu': 1.12.2 + '@unrs/resolver-binding-linux-riscv64-musl': 1.12.2 + '@unrs/resolver-binding-linux-s390x-gnu': 1.12.2 + '@unrs/resolver-binding-linux-x64-gnu': 1.12.2 + '@unrs/resolver-binding-linux-x64-musl': 1.12.2 + '@unrs/resolver-binding-openharmony-arm64': 1.12.2 + '@unrs/resolver-binding-wasm32-wasi': 1.12.2 + '@unrs/resolver-binding-win32-arm64-msvc': 1.12.2 + '@unrs/resolver-binding-win32-ia32-msvc': 1.12.2 + '@unrs/resolver-binding-win32-x64-msvc': 1.12.2 + + unzipper@0.10.14: + dependencies: + big-integer: 1.6.52 + binary: 0.3.0 + bluebird: 3.4.7 + buffer-indexof-polyfill: 1.0.2 + duplexer2: 0.1.4 + fstream: 1.0.12 + graceful-fs: 4.2.11 + listenercount: 1.0.1 + readable-stream: 2.3.8 + setimmediate: 1.0.5 + + update-browserslist-db@1.3.2(browserslist@4.28.9): + dependencies: + browserslist: 4.28.9 + escalade: 3.2.0 + picocolors: 1.1.1 + + uri-js@4.4.1: + dependencies: + punycode: 2.3.1 + + util-deprecate@1.0.2: {} + + uuid@8.3.2: {} + + v8-to-istanbul@9.3.0: + dependencies: + '@jridgewell/trace-mapping': 0.3.31 + '@types/istanbul-lib-coverage': 2.0.6 + convert-source-map: 2.0.0 + + v8flags@3.2.0: + dependencies: + homedir-polyfill: 1.0.3 + + validator@13.15.35: {} + + vary@1.1.2: {} + + version-guard@1.1.3: {} + + webdriver-bidi-protocol@0.4.1: {} + + webidl-conversions@7.0.0: {} + + whatwg-url@14.2.0: + dependencies: + tr46: 5.1.1 + webidl-conversions: 7.0.0 + + which-boxed-primitive@1.1.1: + dependencies: + is-bigint: 1.1.0 + is-boolean-object: 1.2.2 + is-number-object: 1.1.1 + is-string: 1.1.1 + is-symbol: 1.1.1 + + which-builtin-type@1.2.1: + dependencies: + call-bound: 1.0.4 + function.prototype.name: 1.2.0 + has-tostringtag: 1.0.2 + is-async-function: 2.1.1 + is-date-object: 1.1.0 + is-finalizationregistry: 1.1.1 + is-generator-function: 1.1.2 + is-regex: 1.2.1 + is-weakref: 1.1.1 + isarray: 2.0.5 + which-boxed-primitive: 1.1.1 + which-collection: 1.0.2 + which-typed-array: 1.1.22 + + which-collection@1.0.2: + dependencies: + is-map: 2.0.3 + is-set: 2.0.3 + is-weakmap: 2.0.2 + is-weakset: 2.0.4 + + which-typed-array@1.1.22: + dependencies: + available-typed-arrays: 1.0.7 + call-bind: 1.0.9 + call-bound: 1.0.4 + for-each: 0.3.5 + get-proto: 1.0.1 + gopd: 1.2.0 + has-tostringtag: 1.0.2 + + which@2.0.2: + dependencies: + isexe: 2.0.0 + + wkx@0.5.0: + dependencies: + '@types/node': 26.4.1 + + word-wrap@1.2.5: {} + + wrap-ansi@7.0.0: + dependencies: + ansi-styles: 4.3.0 + string-width: 4.2.3 + strip-ansi: 6.0.1 + + wrap-ansi@8.1.0: + dependencies: + ansi-styles: 6.2.3 + string-width: 5.1.2 + strip-ansi: 7.2.0 + + wrappy@1.0.2: {} + + write-file-atomic@5.0.1: + dependencies: + imurmurhash: 0.1.4 + signal-exit: 4.1.0 + + ws@8.21.3: {} + + xdg-basedir@4.0.0: {} + + xml@1.0.1: {} + + xmlchars@2.2.0: {} + + xtend@4.0.2: {} + + y18n@5.0.8: {} + + yallist@3.1.1: {} + + yargs-parser@20.2.9: {} + + yargs-parser@21.1.1: {} + + yargs@16.2.2: + dependencies: + cliui: 7.0.4 + escalade: 3.2.0 + get-caller-file: 2.0.5 + require-directory: 2.1.1 + string-width: 4.2.3 + y18n: 5.0.8 + yargs-parser: 20.2.9 + + yargs@17.7.2: + dependencies: + cliui: 8.0.1 + escalade: 3.2.0 + get-caller-file: 2.0.5 + require-directory: 2.1.1 + string-width: 4.2.3 + y18n: 5.0.8 + yargs-parser: 21.1.1 + + yargs@17.7.3: + dependencies: + cliui: 8.0.1 + escalade: 3.2.0 + get-caller-file: 2.0.5 + require-directory: 2.1.1 + string-width: 4.2.3 + y18n: 5.0.8 + yargs-parser: 21.1.1 + + yauzl@2.10.0: + dependencies: + buffer-crc32: 0.2.13 + fd-slicer: 1.1.0 + + yocto-queue@0.1.0: {} + + zip-stream@4.1.1: + dependencies: + archiver-utils: 3.0.4 + compress-commons: 4.1.2 + readable-stream: 3.6.2 + + zod@3.25.76: {} diff --git a/src/config.js b/src/config.js new file mode 100644 index 0000000..7449540 --- /dev/null +++ b/src/config.js @@ -0,0 +1,55 @@ +// config.js - Configuration handling +import fs from 'fs'; +import path from 'path'; +import { fileURLToPath } from 'url'; +import dotenv from 'dotenv'; + +// Load environment variables from .env file +dotenv.config(); + +// Configure paths relative to this file +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); +const CONFIG_PATH = path.resolve(__dirname, '../config.json'); + +// Load config file +export function loadConfig() { + const env = process.env.NODE_ENV || 'development'; + try { + if (!fs.existsSync(CONFIG_PATH)) { + throw new Error(`Configuration file not found at ${CONFIG_PATH}`); + } + + const configData = fs.readFileSync(CONFIG_PATH, 'utf8'); + const config = JSON.parse(configData); + + if (!config[env]) { + throw new Error( + `Configuration for environment '${env}' not found in config.json` + ); + } + + const envConfig = config[env]; + + // Override keycloak client secret with environment variable if available + if (process.env.KEYCLOAK_CLIENT_SECRET) { + if (!envConfig.auth) { + envConfig.auth = {}; + } + if (!envConfig.auth.keycloak) { + envConfig.auth.keycloak = {}; + } + envConfig.auth.keycloak.clientSecret = process.env.KEYCLOAK_CLIENT_SECRET; + } + + return envConfig; + } catch (err) { + console.error('Error loading config:', err); + throw err; + } +} + +// Get current environment +export function getEnvironment() { + return process.env.NODE_ENV || 'development'; +} diff --git a/src/database/database.js b/src/database/database.js new file mode 100644 index 0000000..7361d55 --- /dev/null +++ b/src/database/database.js @@ -0,0 +1,675 @@ +import _ from "lodash"; +import { + deleteAuditLog, + expandObjectIds, + editAuditLog, + editNotification, + distributeUpdate, + newAuditLog, + distributeNew, + distributeStats, +} from "./utils.js"; +import log4js from "log4js"; +import { loadConfig } from "../config.js"; +import { formatTraceData, getQueryToCacheKey } from "../utils.js"; +import { redisServer } from "./redis.js"; +import { auditLogModel } from "./schemas/management/auditlog.schema.js"; + +const config = loadConfig(); + +const logger = log4js.getLogger("Database"); +const cacheLogger = log4js.getLogger("Local Cache"); +logger.level = config.server.logLevel; +cacheLogger.level = config.server.logLevel; + +const mergeObjectUpdates = (target, source) => + _.mergeWith(target, source, (objValue, srcValue, key) => { + if (Array.isArray(objValue) || Array.isArray(srcValue)) { + return srcValue; + } + if (key === "permissions" && srcValue !== undefined) { + return srcValue; + } + }); + +// Default cache TTL in seconds (similar to previous in-memory cache) +const CACHE_TTL_SECONDS = config.database?.redis?.ttlSeconds || 5; + +export const retrieveObjectCache = async ({ model, id, populate = [] }) => { + const cacheKey = getQueryToCacheKey({ model: model.modelName, id, populate }); + + cacheLogger.trace("Retrieving:", cacheKey); + + try { + const cachedObject = await redisServer.getKey(cacheKey); + + if (cachedObject == null) { + cacheLogger.trace("Miss:", cacheKey); + return undefined; + } + + cacheLogger.trace("Hit:", cacheKey); + + return cachedObject; + } catch (err) { + cacheLogger.error("Error retrieving object from Redis cache:", err); + return undefined; + } +}; + +export const updateObjectCache = async ({ + model, + id, + object, + populate = [], +}) => { + const cacheKeyFilter = `${model.modelName}:${id?.toString()}*`; + const cacheKey = getQueryToCacheKey({ model: model.modelName, id, populate }); + + cacheLogger.trace("Updating:", cacheKeyFilter); + + try { + // Get all keys matching the filter pattern + const matchingKeys = await redisServer.getKeysByPattern(cacheKeyFilter); + + logger.trace(`Matching keys: ${formatTraceData(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 = mergeObjectUpdates(cachedObject, object); + await redisServer.setKey(key, mergedObject, CACHE_TTL_SECONDS); + mergedObjects.push(mergedObject); + } + + const cacheObject = (await redisServer.getKey(cacheKey)) || {}; + const mergedObject = mergeObjectUpdates(cacheObject, object); + await redisServer.setKey(cacheKey, mergedObject, CACHE_TTL_SECONDS); + + 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); + // Fallback to returning the provided object if cache fails + return object; + } +}; + +export const deleteObjectCache = async ({ model, id }) => { + const cacheKeyFilter = `${model.modelName}:${id?.toString()}*`; + + cacheLogger.trace("Deleting:", cacheKeyFilter); + + try { + // 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:", { + filter: cacheKeyFilter, + keysDeleted: matchingKeys.length, + }); + } catch (err) { + cacheLogger.error("Error deleting object from Redis cache:", err); + } +}; + +// Reusable function to list objects with aggregation, filtering, search, sorting, and pagination +export const listObjects = async ({ + model, + populate = [], + filter = {}, + sort = "", + order = "ascend", + project = {}, // optional: override default projection + cached = false, +}) => { + try { + logger.trace( + `Listing objects: ${formatTraceData({ + model, + populate, + filter, + sort, + order, + project, + cached, + })}`, + ); + + // Fix: descend should be -1, ascend should be 1 + const sortOrder = order === "descend" ? -1 : 1; + + if (!sort || sort === "") { + sort = "createdAt"; + } + // Translate parent._id to parent for Mongoose + if (filter["parent._id"]) { + filter.parent = filter["parent._id"]; + delete filter["parent._id"]; + } + + // Translate owner._id to owner for Mongoose + if (filter["owner._id"]) { + filter.owner = filter["owner._id"]; + delete filter["owner._id"]; + } + + // Use find with population and filter + let query = model.find(filter).sort({ [sort]: sortOrder }); + + // Handle populate (array or single value) + if (populate.length > 0) { + if (Array.isArray(populate)) { + for (const pop of populate) { + query = query.populate(pop); + } + } else if (typeof populate === "string" || typeof populate === "object") { + query = query.populate(populate); + } + } + + // Handle select (projection) + if (project && Object.keys(project).length > 0) { + query = query.select(project); + } + + query = query.lean(); + + const queryResult = await query; + + const finalResult = expandObjectIds(queryResult); + + logger.trace( + `Retreived from database: ${formatTraceData({ + model, + populate, + filter, + sort, + order, + project, + cached, + length: finalResult.length, + })}`, + ); + return finalResult; + } catch (error) { + logger.error("Object list error:", error); + return { error: error, code: 500 }; + } +}; + +// Reusable function to get a single object by ID +export const getObject = async ({ + model, + id, + populate = [], + cached = false, +}) => { + try { + logger.trace( + `Getting object: ${formatTraceData({ + model, + id, + populate, + })}`, + ); + + if (cached == true) { + const cachedObject = await retrieveObjectCache({ model, id, populate }); + if (cachedObject != undefined) { + return cachedObject; + } + } + + let query = model.findById(id).lean(); + + // Handle populate (array or single value) + if (populate) { + if (Array.isArray(populate)) { + for (const pop of populate) { + query = query.populate(pop); + } + } else if (typeof populate === "string" || typeof populate === "object") { + query = query.populate(populate); + } + } + const finalResult = await query; + + if (!finalResult) { + logger.warn("Object not found in database:", { + model, + id, + populate, + }); + return undefined; + } + + logger.trace( + `Retreived object from database: ${formatTraceData({ + model, + id, + populate, + })}`, + ); + + logger.trace(formatTraceData(finalResult)); + + await updateObjectCache({ + model: model, + id: finalResult._id.toString(), + populate, + object: finalResult, + }); + + return finalResult; + } catch (error) { + logger.error("An error retreiving object:", error.message); + throw error; + return undefined; + } +}; + +// 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; + }, {}); +}; + +// Snapshot absolute rollup values at each point in time by reconstructing +// object state from the current documents plus audit logs. +export const aggregateRollupsHistory = async ({ + model, + baseFilter = {}, + rollupConfigs = [], + startDate, + endDate, +}) => { + if (!rollupConfigs.length) { + return []; + } + + const end = endDate ? new Date(endDate) : new Date(); + const start = startDate + ? new Date(startDate) + : new Date(end.getTime() - 24 * 60 * 60 * 1000); + const parentType = model.modelName ? model.modelName : "unknown"; + + const matchesFilter = (obj, filter) => { + if (!filter || Object.keys(filter).length === 0) return true; + + for (const [key, expectedValue] of Object.entries(filter)) { + const actualValue = _.get(obj, key); + if (actualValue != expectedValue) { + return false; + } + } + return true; + }; + + const existedAt = (obj, bucketDate) => { + if (!obj?.createdAt) return true; + return new Date(obj.createdAt) <= bucketDate; + }; + + const snapshotRollups = (objects, bucketDate) => { + const bucketResult = { + date: bucketDate.toISOString(), + }; + + rollupConfigs.forEach((config) => { + const matchingObjects = objects.filter( + (obj) => + existedAt(obj, bucketDate) && + matchesFilter(obj, baseFilter) && + matchesFilter(obj, config.filter), + ); + + (config.rollups || []).forEach((rollup) => { + let value = 0; + if (rollup.operation === "count") { + value = matchingObjects.length; + } else if (rollup.operation === "sum") { + value = _.sumBy( + matchingObjects, + (obj) => _.get(obj, rollup.property) || 0, + ); + } else if (rollup.operation === "avg") { + const sum = _.sumBy( + matchingObjects, + (obj) => _.get(obj, rollup.property) || 0, + ); + value = matchingObjects.length ? sum / matchingObjects.length : 0; + } + + bucketResult[rollup.name] = { [rollup.operation]: value }; + }); + }); + + return bucketResult; + }; + + const auditLogs = await auditLogModel + .find({ + parentType, + createdAt: { $gte: start }, + }) + .sort({ createdAt: -1 }) + .lean(); + + const currentObjects = await model.find(baseFilter).lean(); + const objectMap = new Map(); + currentObjects.forEach((obj) => { + objectMap.set(obj._id.toString(), expandObjectIds(obj)); + }); + + const extraIds = [ + ...new Set( + auditLogs + .map((log) => log.parent?.toString()) + .filter((id) => id && !objectMap.has(id)), + ), + ]; + if (extraIds.length) { + const extraObjects = await model.find({ _id: { $in: extraIds } }).lean(); + extraObjects.forEach((obj) => { + objectMap.set(obj._id.toString(), expandObjectIds(obj)); + }); + } + + if (objectMap.size === 0 && auditLogs.length === 0) { + return []; + } + + const buckets = []; + let currentTime = new Date(end); + currentTime.setSeconds(0, 0); + + while (currentTime >= start) { + buckets.push(new Date(currentTime)); + currentTime = new Date(currentTime.getTime() - 60000); + } + + if (!buckets.length) { + return []; + } + + const workingObjects = new Map(); + objectMap.forEach((val, key) => workingObjects.set(key, _.cloneDeep(val))); + + const results = []; + let logIndex = 0; + + for (const bucketDate of buckets) { + while (logIndex < auditLogs.length) { + const log = auditLogs[logIndex]; + const logDate = new Date(log.createdAt); + + if (logDate <= bucketDate) { + break; + } + + const objectId = log.parent.toString(); + const object = workingObjects.get(objectId); + + if (log.operation === "new") { + workingObjects.delete(objectId); + } else if (log.operation === "delete" && log.changes?.old) { + if (!workingObjects.has(objectId)) { + workingObjects.set( + objectId, + expandObjectIds({ ...log.changes.old, _id: log.parent }), + ); + } + } else if (object && log.changes?.old) { + mergeObjectUpdates(object, log.changes.old); + } + + logIndex++; + } + + results.push( + snapshotRollups(Array.from(workingObjects.values()), bucketDate), + ); + } + + return results.reverse(); +}; + +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, + id, + updateData, + owner = undefined, + ownerType = undefined, + populate = [], + auditLog = true, + notify = true, + recalculate = true, +}) => { + try { + // Determine parentType from model name + const parentType = model.modelName ? model.modelName : "unknown"; + // Fetch the and update object + var query = model.findByIdAndUpdate(id, updateData).lean(); + var newQuery = model.findById(id).lean(); + + if (populate) { + if (Array.isArray(populate)) { + for (const pop of populate) { + query = query.populate(pop); + newQuery = newQuery.populate(pop); + } + } else if (typeof populate === "string" || typeof populate === "object") { + query = query.populate(populate); + newQuery = newQuery.populate(populate); + } + } + + const previousObject = await query; + const newObject = await newQuery; + if (!previousObject || !newObject) { + return { error: `${parentType} not found.`, code: 404 }; + } + const previousExpandedObject = expandObjectIds(previousObject); + const newExpandedObject = expandObjectIds(newObject); + + if (auditLog == true && owner != undefined && ownerType != undefined) { + // Audit log before update + await editAuditLog( + previousExpandedObject, + newExpandedObject, + id, + parentType, + owner, + ownerType, + ); + } + + if ( + notify == true && + owner != undefined && + ownerType != undefined && + parentType !== "notification" && + parentType !== "auditLog" && + parentType !== "userNotifier" && + parentType !== "objectView" + ) { + await editNotification( + previousExpandedObject, + newExpandedObject, + id, + parentType, + owner, + ownerType, + ); + } + + // Distribute update + await distributeUpdate(updateData, id, parentType); + + await updateObjectCache({ + model: model, + id: id.toString(), + object: { ...previousExpandedObject, ...updateData }, + populate, + }); + + if (model.recalculate && recalculate == true) { + 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); + return { error: error.message, code: 500 }; + } +}; + +// Reusable function to create a new object +export const newObject = async ({ + model, + newData, + owner = null, + ownerType = undefined, +}) => { + try { + const parentType = model.modelName ? model.modelName : "unknown"; + + const result = await model.create(newData); + if (!result || result.length === 0) { + return { error: "No object created.", code: 500 }; + } + const created = result; + + if (owner != undefined && ownerType != undefined) { + await newAuditLog(newData, created._id, parentType, owner, ownerType); + } + + await distributeNew(created, parentType); + + await updateObjectCache({ + model: model, + id: created._id.toString(), + object: { _id: created._id, ...newData }, + populate: [], + }); + + return created; + } catch (error) { + logger.error("newObject error:", error); + return { error: error.message, code: 500 }; + } +}; + +// Reusable function to delete an object by ID, with audit logging and distribution +export const deleteObject = async ({ + model, + id, + owner = null, + ownerType = undefined, +}) => { + try { + const parentType = model.modelName ? model.modelName : "unknown"; + // Delete the object + const result = await model.findByIdAndDelete(id); + + if (!result) { + return { error: `${parentType} not found.`, code: 404 }; + } + + if (owner != undefined && ownerType != undefined) { + // Audit log the deletion + await deleteAuditLog(result, id, parentType, owner, ownerType); + } + + deleteObjectCache({ model: model, id: id.toString() }); + + // Distribute the deletion event + await distributeUpdate({ deleted: true }, id, parentType); + + return { deleted: true, id: id.toString() }; + } catch (error) { + logger.error("deleteObject error:", error); + return { error: error.message, code: 500 }; + } +}; diff --git a/src/database/filter.js b/src/database/filter.js new file mode 100644 index 0000000..9143c4d --- /dev/null +++ b/src/database/filter.js @@ -0,0 +1,1371 @@ +import mongoose from "mongoose"; +import log4js from "log4js"; +import { loadConfig } from "../config.js"; + +const config = loadConfig(); +const logger = log4js.getLogger("Filter"); +logger.level = config.server.logLevel; + +let modelsCache = null; + +async function getModels() { + if (!modelsCache) { + modelsCache = (await import("./schemas/models.js")).models; + } + return modelsCache; +} + +async function getObjectTypeModel(objectType) { + const models = await getModels(); + const entry = Object.values(models).find( + (item) => item.type === objectType || item.model?.modelName === objectType, + ); + return entry?.model ?? null; +} + +// --------------------------------------------------------------------------- +// Filter expression parsing (Microsoft Dynamics NAV / Business Central style) +// +// Supported syntax (all compiled down to MongoDB query operators): +// = equal to 377 -> { field: 377 } +// <> not equal to <>0 -> { field: { $ne: 0 } } +// > greater than >1200 -> { field: { $gt: 1200 } } +// >= greater than or equal >=1200 -> { field: { $gte: 1200 } } +// < less than <1200 -> { field: { $lt: 1200 } } +// <= less than or equal <=1200 -> { field: { $lte: 1200 } } +// .. interval 1100..2100 -> { field: { $gte: 1100, $lte: 2100 } } +// open-ended intervals ..2500 / 23.. +// | either / or 1200|1300 -> { field: { $in: [1200, 1300] } } +// & and <2000&>1000 -> { field: { $gt: 1000, $lt: 2000 } } +// ( ) grouping / precedence 30|(>=10&<=20) +// * any number of characters Co* / *Co / *Co* +// ? a single character Hans?n +// @ ignore case @location (text matching is case-insensitive) +// '' empty (null or missing) '' / ='' / <>'' +// +// Date-typed fields (schema Date) interpret bare values as calendar +// days/datetimes, e.g. "22" => the whole of day 22 of the current month/year, +// "22..23" => start of 22 through end of 23. +// +// Relative date operands: +// TODAY current calendar day +// TODAY.. from start of today onwards +// -7D.. from 7 days ago onwards (last 7 days) +// -30D.. from 30 days ago onwards +// -1W previous calendar week (Monday–Sunday) +// -1M previous calendar month (as a whole day-range) +// -1M..-CM previous calendar month (from its start up to current month) +// -1Y previous calendar year +// -nD / +nD n days before / after today +// -nW / +nW n calendar weeks before / after the current week +// -nM / +nM n calendar months before / after the current month +// -nY / +nY n calendar years before / after the current year +// -nH / +nH n hours before / after the current hour +// -nm / +nm n minutes before / after the current minute (lowercase m) +// CW / CM / CY / CH / Cm current week / month / year / hour / minute +// -CW / -CM / -CY / -CH / -Cm +// current-unit boundary (start of this unit / end of last unit) +// +// Unit letters: D day, W week, M month, Y year, H hour, m minute. +// M (month) and m (minute) are case-sensitive; other units are not. +// +// An invalid date operand (anything other than a date, relative date, or +// filter operator) omits that field from the query instead of sending it to +// MongoDB. +// +// Note: BC's space-delimited date shorthand is locale dependent; here numeric +// date operands are read day-first ("D M Y H Min S") with missing components +// filled from the current date and the interval boundary. +// --------------------------------------------------------------------------- + +function buildWildcardRegexPattern(input) { + // Escape all regex special chars except * and ? (which we treat as wildcards) + const escaped = String(input).replace(/[.+^${}()|[\]\\]/g, "\\$&"); + // * matches any run of characters, ? matches exactly one character + const withWildcards = escaped.replace(/\*/g, ".*").replace(/\?/g, "."); + // Anchor so that, without wildcards, this is an exact match + return `^${withWildcards}$`; +} + +const NO_MATCH_CONDITION = { op: { $in: [] } }; +const EMPTY_OPERAND = "''"; + +function isEmptyOperand(value) { + return String(value).trim() === EMPTY_OPERAND; +} + +// MongoDB null equality matches both null and missing (undefined) fields. +function emptyCondition(operator = "eq") { + if (operator === "ne") return { op: { $ne: null } }; + return { value: null }; +} + +function isBooleanField(property, model = null) { + const schema = model?.schema; + if (schema) { + const path = schema.path(property); + return path?.instance === "Boolean"; + } + + let found = false; + for (const entry of Object.values(models)) { + const path = entry.model?.schema?.path(property); + if (!path) continue; + found = true; + if (path.instance !== "Boolean") return false; + } + return found; +} + +function isNumberSchemaPath(path) { + if (!path) return false; + if (path.instance === "Number") return true; + if (path.instance === "Array") { + return getEmbeddedSchemaType(path)?.instance === "Number"; + } + return false; +} + +function isNumberField(property, model = null) { + if (model?.schema) { + return isNumberSchemaPath(model.schema.path(property)); + } + + for (const entry of Object.values(models)) { + if (isNumberSchemaPath(entry.model?.schema?.path(property))) return true; + } + return false; +} + +function isDateTimeSchemaPath(path) { + if (!path) return false; + + if (path.instance === "Date") return true; + + if (path.instance === "Array") { + return getEmbeddedSchemaType(path)?.instance === "Date"; + } + + return false; +} + +function isDateTimeField(property, model = null) { + if (model?.schema) { + return isDateTimeSchemaPath(model.schema.path(property)); + } + + for (const entry of Object.values(models)) { + if (isDateTimeSchemaPath(entry.model?.schema?.path(property))) return true; + } + + return false; +} + +function buildNumberRegexCondition(property, pattern, negated = false) { + const expr = { + $regexMatch: { + input: { $toString: `$${property}` }, + regex: pattern, + options: "i", + }, + }; + return { query: { $expr: negated ? { $not: expr } : expr } }; +} + +function isObjectIdPath(property) { + return property === "_id" || property.endsWith("._id"); +} + +function getBaseProperty(property) { + if (property === "_id") return "_id"; + if (property.endsWith("._id")) return property.slice(0, -4); + return property; +} + +function getEmbeddedSchemaType(path) { + if (!path) return null; + return ( + path.embeddedSchemaType ?? path.$embeddedSchemaType ?? path.caster ?? null + ); +} + +function getObjectIdSchemaTypeFromPath(path) { + if (!path) return null; + const schemaType = + path.instance === "Array" ? getEmbeddedSchemaType(path) : path; + if (!schemaType) return null; + if ( + schemaType.instance === "ObjectId" || + schemaType.instance === "ObjectID" + ) { + return schemaType; + } + return null; +} + +function getSchemaPathFromModels(property, model = null) { + if (model?.schema?.path(property)) return model.schema.path(property); + for (const entry of Object.values(models)) { + const path = entry.model?.schema?.path(property); + if (path) return path; + } + return null; +} + +function getFilterFieldKind(property, model = null) { + const baseProperty = getBaseProperty(property); + + const inspectPath = (path) => { + if (!path) return null; + + const objectIdType = getObjectIdSchemaTypeFromPath(path); + if (isObjectIdPath(property) || objectIdType) { + const ref = objectIdType?.options?.ref ?? path.options?.ref; + if (property.endsWith("._id") && property !== "_id" && ref) { + return { kind: "objectId", property: baseProperty }; + } + if (ref) { + return { kind: "objectRef", property: baseProperty, ref }; + } + return { kind: "objectId", property: baseProperty }; + } + + return { kind: "default", property: baseProperty }; + }; + + if (model) { + return ( + inspectPath(model.schema?.path(baseProperty)) || { + kind: "default", + property: baseProperty, + } + ); + } + + const path = getSchemaPathFromModels(baseProperty); + const kind = inspectPath(path); + if (kind) return kind; + + return { kind: "default", property: baseProperty }; +} + +function parsePrefixedValue(value) { + const trimmed = String(value).trim(); + if (trimmed.length > 4 && trimmed.charAt(3) === ":") { + return { + prefix: trimmed.slice(0, 3).toUpperCase(), + suffix: trimmed.slice(4).trim(), + hadPrefix: true, + }; + } + return { prefix: null, suffix: trimmed, hadPrefix: false }; +} + +const FILTER_OPERATOR_PREFIXES = ["<>", ">=", "<=", ">", "<", "="]; + +function splitLeadingOperator(str) { + const text = String(str).trim(); + for (const op of FILTER_OPERATOR_PREFIXES) { + if (text.startsWith(op)) { + return { operator: op, operand: text.slice(op.length).trim() }; + } + } + return { operator: "", operand: text }; +} + +// Strips a model prefix (e.g. GCF:) from an expression, including after a leading operator. +function stripModelPrefixFromExpression(expression) { + const { operator, operand } = splitLeadingOperator(expression); + const { suffix, hadPrefix } = parsePrefixedValue(operand); + if (!hadPrefix) return expression; + return operator + suffix; +} + +function buildRegexOp(pattern, useOptions = true) { + const op = { $regex: pattern }; + if (useOptions) op.$options = "i"; + return op; +} + +function getSchemaRefName(property, model = null) { + const baseProperty = getBaseProperty(property); + const path = + model?.schema?.path(baseProperty) ?? getSchemaPathFromModels(baseProperty); + const embeddedType = getEmbeddedSchemaType(path); + return path?.options?.ref ?? embeddedType?.options?.ref ?? null; +} + +function getRefModelEntryFromSchemaRef(refName) { + if (!refName) return null; + + let refModel; + try { + refModel = mongoose.model(refName); + } catch { + refModel = getModelByName(refName)?.model ?? null; + } + + if (!refModel) return null; + + const registryEntry = getModelByName(refName); + return { + model: refModel, + idField: registryEntry?.idField ?? "_id", + referenceField: registryEntry?.referenceField ?? "_reference", + }; +} + +function getRefModelEntryFromPrefix(prefix) { + const entry = getModelByPrefix(prefix); + if (!entry?.model) return null; + return { + model: entry.model, + idField: entry.idField ?? "_id", + referenceField: entry.referenceField ?? "_reference", + }; +} + +function stripPrefixFromOperand(operand) { + return stripModelPrefixFromExpression(operand); +} + +function getRefModelEntryForToken(token, fallbackRefName) { + const { operand } = splitLeadingOperator(token); + const { prefix, hadPrefix } = parsePrefixedValue(operand); + if (hadPrefix) { + const entry = getRefModelEntryFromPrefix(prefix); + if (entry) return entry; + } + return getRefModelEntryFromSchemaRef(fallbackRefName); +} + +function parseBooleanOperand(value) { + const lower = String(value).trim().toLowerCase(); + if (lower === "yes") return true; + if (lower === "no") return false; + if (lower === "true") return true; + if (lower === "false") return false; + if (lower === "1") return true; + if (lower === "0") return false; + if (lower === "on") return true; + if (lower === "off") return false; + if (lower === "y") return true; + if (lower === "n") return false; + if (typeof value === "boolean") return value; + return undefined; +} + +function buildBooleanEquality(value) { + const bool = parseBooleanOperand(value); + if (bool === undefined) return NO_MATCH_CONDITION; + return { value: bool }; +} + +function buildBooleanComparison(name, value) { + if (name === "eq") return buildBooleanEquality(value); + + if (name === "ne") { + const bool = parseBooleanOperand(value); + if (bool === undefined) return NO_MATCH_CONDITION; + return { op: { $ne: bool } }; + } + + return NO_MATCH_CONDITION; +} + +function isObjectIdString(value) { + return /^[a-f\d]{24}$/i.test(value); +} + +function isNumeric(value) { + return value.trim() !== "" && !isNaN(value); +} + +function startOfDay(date) { + const d = new Date(date); + d.setHours(0, 0, 0, 0); + return d; +} + +function endOfDay(date) { + const d = new Date(date); + d.setHours(23, 59, 59, 999); + return d; +} + +function startOfMonth(date) { + return new Date(date.getFullYear(), date.getMonth(), 1, 0, 0, 0, 0); +} + +function endOfMonth(date) { + return new Date(date.getFullYear(), date.getMonth() + 1, 0, 23, 59, 59, 999); +} + +function shiftCalendarMonths(date, n) { + return new Date(date.getFullYear(), date.getMonth() + n, 1); +} + +function startOfWeek(date) { + const d = startOfDay(date); + const day = d.getDay(); + const mondayOffset = day === 0 ? -6 : 1 - day; + d.setDate(d.getDate() + mondayOffset); + return d; +} + +function endOfWeek(date) { + const start = startOfWeek(date); + const end = new Date(start); + end.setDate(end.getDate() + 6); + return endOfDay(end); +} + +function shiftWeeks(date, n) { + const d = new Date(date); + d.setDate(d.getDate() + n * 7); + return d; +} + +function startOfYear(date) { + return new Date(date.getFullYear(), 0, 1, 0, 0, 0, 0); +} + +function endOfYear(date) { + return new Date(date.getFullYear(), 11, 31, 23, 59, 59, 999); +} + +function shiftYears(date, n) { + return new Date(date.getFullYear() + n, 0, 1); +} + +function startOfHour(date) { + const d = new Date(date); + d.setMinutes(0, 0, 0); + return d; +} + +function endOfHour(date) { + const d = new Date(date); + d.setMinutes(59, 59, 999); + return d; +} + +function startOfMinute(date) { + const d = new Date(date); + d.setSeconds(0, 0); + return d; +} + +function endOfMinute(date) { + const d = new Date(date); + d.setSeconds(59, 999); + return d; +} + +function normalizeYear(year) { + if (year >= 100) return year; + return year < 70 ? 2000 + year : 1900 + year; +} + +const RELATIVE_OFFSET_RE = /^([+-])?(\d+)([DdWwYyHhMm])$/; +const RELATIVE_CURRENT_RE = /^([+-])?C([DdWwYyHhMm])$/; + +function normalizeRelativeUnit(letter) { + if (letter === "M" || letter === "m") return letter; + return letter.toUpperCase(); +} + +function resolveRelativeUnit( + now, + unit, + amount, + boundary, + isCurrent, + minusCurrent, +) { + const end = boundary === "end"; + + if (unit === "D") { + if (isCurrent) return end ? endOfDay(now) : startOfDay(now); + const date = new Date(now); + date.setDate(date.getDate() + amount); + return end ? endOfDay(date) : startOfDay(date); + } + + if (unit === "W") { + if (isCurrent && minusCurrent) { + return end ? endOfWeek(shiftWeeks(now, -1)) : startOfWeek(now); + } + const target = isCurrent ? now : shiftWeeks(now, amount); + return end ? endOfWeek(target) : startOfWeek(target); + } + + if (unit === "M") { + if (isCurrent) { + if (minusCurrent) { + return end + ? endOfMonth(shiftCalendarMonths(now, -1)) + : startOfMonth(now); + } + return end ? endOfMonth(now) : startOfMonth(now); + } + const month = shiftCalendarMonths(now, amount); + return end ? endOfMonth(month) : startOfMonth(month); + } + + if (unit === "Y") { + if (isCurrent) { + if (minusCurrent) { + return end ? endOfYear(shiftYears(now, -1)) : startOfYear(now); + } + return end ? endOfYear(now) : startOfYear(now); + } + const year = shiftYears(now, amount); + return end ? endOfYear(year) : startOfYear(year); + } + + if (unit === "H") { + if (isCurrent) { + if (minusCurrent) { + const prev = new Date(now); + prev.setHours(prev.getHours() - 1); + return end ? endOfHour(prev) : startOfHour(now); + } + return end ? endOfHour(now) : startOfHour(now); + } + const date = new Date(now); + date.setHours(date.getHours() + amount); + return end ? endOfHour(date) : startOfHour(date); + } + + if (unit === "m") { + if (isCurrent) { + if (minusCurrent) { + const prev = new Date(now); + prev.setMinutes(prev.getMinutes() - 1); + return end ? endOfMinute(prev) : startOfMinute(now); + } + return end ? endOfMinute(now) : startOfMinute(now); + } + const date = new Date(now); + date.setMinutes(date.getMinutes() + amount); + return end ? endOfMinute(date) : startOfMinute(date); + } + + return null; +} + +function parseRelativeDateOperand(text, boundary = "start") { + const raw = String(text).trim(); + if (!raw) return null; + + if (/^TODAY$/i.test(raw)) { + const now = new Date(); + return boundary === "end" ? endOfDay(now) : startOfDay(now); + } + + const offset = raw.match(RELATIVE_OFFSET_RE); + if (offset) { + const amount = (offset[1] === "-" ? -1 : 1) * Number(offset[2]); + return resolveRelativeUnit( + new Date(), + normalizeRelativeUnit(offset[3]), + amount, + boundary, + false, + false, + ); + } + + const current = raw.match(RELATIVE_CURRENT_RE); + if (current) { + return resolveRelativeUnit( + new Date(), + normalizeRelativeUnit(current[2]), + 0, + boundary, + true, + current[1] === "-", + ); + } + + return null; +} + +function dateComponentsMatch(date, year, month, day, hour, minute, second) { + return ( + date.getFullYear() === year && + date.getMonth() === month - 1 && + date.getDate() === day && + date.getHours() === hour && + date.getMinutes() === minute && + date.getSeconds() === second + ); +} + +// Parses a single date operand to a Date, filling missing parts from the +// current date and the supplied interval boundary ('start' | 'end'). +function parseDateOperand(value, boundary = "start") { + const text = String(value).trim(); + if (!text) return null; + const end = boundary === "end"; + + const relative = parseRelativeDateOperand(text, boundary); + if (relative) return relative; + + // Values with explicit separators (ISO and similar) are parsed directly. + if (/[-/T]/.test(text) || /\d:\d/.test(text)) { + const parsed = new Date(text); + if (isNaN(parsed.getTime())) return null; + if (!/[T:]/.test(text)) { + return end ? endOfDay(parsed) : startOfDay(parsed); + } + return parsed; + } + + // Numeric, day-first form: "D [M] [Y] [H] [Min] [S]". + const parts = text.split(/\s+/); + if (!parts.every((part) => /^\d+$/.test(part))) return null; + const nums = parts.map(Number); + const now = new Date(); + const day = nums[0]; + const month = nums.length >= 2 ? nums[1] : now.getMonth() + 1; + const year = nums.length >= 3 ? normalizeYear(nums[2]) : now.getFullYear(); + const hour = nums.length >= 4 ? nums[3] : end ? 23 : 0; + const minute = nums.length >= 5 ? nums[4] : end ? 59 : 0; + const second = nums.length >= 6 ? nums[5] : end ? 59 : 0; + const ms = end ? 999 : 0; + const date = new Date(year, month - 1, day, hour, minute, second, ms); + if (isNaN(date.getTime())) return null; + if (!dateComponentsMatch(date, year, month, day, hour, minute, second)) + return null; + return date; +} + +function isValidDateOperand(value) { + return parseDateOperand(value, "start") != null; +} + +function isValidDateFilterLeaf(rawToken) { + const token = rawToken.trim(); + if (token === "") return true; + if (isEmptyOperand(token)) return true; + + const rangeIdx = token.indexOf(".."); + if (rangeIdx !== -1) { + const lo = stripIgnoreCase(token.slice(0, rangeIdx).trim()); + const hi = stripIgnoreCase(token.slice(rangeIdx + 2).trim()); + if (lo !== "" && !isValidDateOperand(lo)) return false; + if (hi !== "" && !isValidDateOperand(hi)) return false; + return true; + } + + const operators = ["<>", ">=", "<=", ">", "<", "="]; + for (const symbol of operators) { + if (token.startsWith(symbol)) { + const operand = stripIgnoreCase(token.slice(symbol.length).trim()); + if ((symbol === "=" || symbol === "<>") && isEmptyOperand(operand)) + return true; + return operand !== "" && isValidDateOperand(operand); + } + } + + return isValidDateOperand(stripIgnoreCase(token)); +} + +function isValidDateFilterNode(node) { + if (node.type === "leaf") return isValidDateFilterLeaf(node.token); + return node.items.every(isValidDateFilterNode); +} + +function isValidDateFilterExpression(expression) { + return isValidDateFilterNode(parseExpression(String(expression))); +} + +// Coerces a literal to its strongest matching scalar type. +function coerceScalar(value) { + const lower = value.toLowerCase(); + if (lower === "true") return true; + if (lower === "false") return false; + if (isObjectIdString(value)) return new mongoose.Types.ObjectId(value); + if (isNumeric(value)) return Number(value); + return value; +} + +// Coerces an interval/comparison boundary, preferring a date for date fields. +function coerceBoundary(value, isDateField, boundary) { + if (isDateField) { + return parseDateOperand(value, boundary); + } + return coerceScalar(value); +} + +// Splits on a separator while respecting parentheses depth. +function splitTopLevel(str, separator) { + const parts = []; + let depth = 0; + let current = ""; + for (const ch of str) { + if (ch === "(") depth++; + else if (ch === ")") depth = Math.max(0, depth - 1); + + if (ch === separator && depth === 0) { + parts.push(current); + current = ""; + } else { + current += ch; + } + } + parts.push(current); + return parts; +} + +// True when a single outer pair of parentheses wraps the whole token. +function isWrappedInParens(str) { + if (!str.startsWith("(") || !str.endsWith(")")) return false; + let depth = 0; + for (let i = 0; i < str.length; i++) { + if (str[i] === "(") depth++; + else if (str[i] === ")") { + depth--; + if (depth === 0 && i < str.length - 1) return false; + } + } + return depth === 0; +} + +// Parses a filter expression into an AST of or / and / leaf nodes. +function parseExpression(str) { + const orParts = splitTopLevel(str, "|"); + if (orParts.length > 1) { + return { type: "or", items: orParts.map(parseExpression) }; + } + const andParts = splitTopLevel(str, "&"); + if (andParts.length > 1) { + return { type: "and", items: andParts.map(parseExpression) }; + } + const trimmed = str.trim(); + if (isWrappedInParens(trimmed)) { + return parseExpression(trimmed.slice(1, -1)); + } + return { type: "leaf", token: trimmed }; +} + +// Strips a leading @ (ignore-case marker); text matching is already case-insensitive. +function stripIgnoreCase(str) { + return str.startsWith("@") ? str.slice(1) : str; +} + +// Builds an equality condition (value, date range, or wildcard regex). +function buildEquality( + value, + isDateField, + isBooleanField = false, + isObjectIdField = false, + isNumberField = false, + property = "", +) { + if (isEmptyOperand(value)) return emptyCondition("eq"); + if (isBooleanField) return buildBooleanEquality(value); + if (isObjectIdField) { + const { suffix } = parsePrefixedValue(value); + if (/[*?]/.test(suffix)) return NO_MATCH_CONDITION; + if (isObjectIdString(suffix)) + return { value: new mongoose.Types.ObjectId(suffix) }; + return NO_MATCH_CONDITION; + } + if (isDateField) { + const start = parseDateOperand(value, "start"); + const end = parseDateOperand(value, "end"); + if (start && end) return { op: { $gte: start, $lte: end } }; + return NO_MATCH_CONDITION; + } + const lower = value.toLowerCase(); + if (lower === "true") return { value: true }; + if (lower === "false") return { value: false }; + if (isObjectIdString(value)) + return { value: new mongoose.Types.ObjectId(value) }; + if (isNumeric(value)) return { value: Number(value) }; + if (isNumberField) { + if (/[*?]/.test(value)) { + return buildNumberRegexCondition( + property, + buildWildcardRegexPattern(value), + ); + } + return NO_MATCH_CONDITION; + } + return { op: buildRegexOp(buildWildcardRegexPattern(value)) }; +} + +// Builds a comparison condition for a single operator. +function buildComparison( + name, + value, + isDateField, + isBooleanField = false, + isObjectIdField = false, + isNumberField = false, + property = "", +) { + if (isEmptyOperand(value)) { + if (name === "eq" || name === "ne") return emptyCondition(name); + return NO_MATCH_CONDITION; + } + if (isBooleanField) return buildBooleanComparison(name, value); + if (isObjectIdField) { + const { suffix } = parsePrefixedValue(value); + if (/[*?]/.test(suffix)) return NO_MATCH_CONDITION; + if (name === "eq") return buildEquality(suffix, false, false, true); + if (name === "ne" && isObjectIdString(suffix)) { + return { op: { $ne: new mongoose.Types.ObjectId(suffix) } }; + } + return NO_MATCH_CONDITION; + } + if (name === "eq") { + return buildEquality( + value, + isDateField, + false, + false, + isNumberField, + property, + ); + } + + if (name === "ne") { + if (isDateField) { + const start = parseDateOperand(value, "start"); + const end = parseDateOperand(value, "end"); + if (start && end) return { op: { $not: { $gte: start, $lte: end } } }; + return NO_MATCH_CONDITION; + } + if (/[*?]/.test(value)) { + if (isNumberField) { + return buildNumberRegexCondition( + property, + buildWildcardRegexPattern(value), + true, + ); + } + return { op: { $not: buildRegexOp(buildWildcardRegexPattern(value)) } }; + } + if (isNumberField && !isNumeric(value)) return NO_MATCH_CONDITION; + return { op: { $ne: coerceScalar(value) } }; + } + + if (isNumberField && !isNumeric(value)) return NO_MATCH_CONDITION; + + // For dates, < and >= align to the start of the day, > and <= to the end. + const boundary = name === "gt" || name === "lte" ? "end" : "start"; + const bound = coerceBoundary(value, isDateField, boundary); + if (isDateField && !bound) return NO_MATCH_CONDITION; + return { op: { [`$${name}`]: bound } }; +} + +function parseLeafComparisonOperators( + token, + isDateField, + isBooleanField, + isObjectIdField, + isNumberField, + property, +) { + const operators = [ + ["<>", "ne"], + [">=", "gte"], + ["<=", "lte"], + [">", "gt"], + ["<", "lt"], + ["=", "eq"], + ]; + for (const [symbol, name] of operators) { + if (token.startsWith(symbol)) { + return buildComparison( + name, + stripIgnoreCase(token.slice(symbol.length).trim()), + isDateField, + isBooleanField, + isObjectIdField, + isNumberField, + property, + ); + } + } + return null; +} + +// Parses a leaf token (range, comparison, or plain value) into a condition. +function parseLeafCondition( + rawToken, + isDateField, + isBooleanField = false, + isObjectIdField = false, + isNumberField = false, + property = "", +) { + const token = rawToken.trim(); + if (token === "") { + if (isBooleanField || isObjectIdField || isNumberField) + return NO_MATCH_CONDITION; + return { op: buildRegexOp("^$") }; + } + if (isEmptyOperand(token)) return emptyCondition("eq"); + + // Interval: a..b, ..b, a.. + const rangeIdx = token.indexOf(".."); + if (rangeIdx !== -1) { + if (isBooleanField || isObjectIdField) return NO_MATCH_CONDITION; + const lo = stripIgnoreCase(token.slice(0, rangeIdx).trim()); + const hi = stripIgnoreCase(token.slice(rangeIdx + 2).trim()); + if (isNumberField) { + if ((lo !== "" && !isNumeric(lo)) || (hi !== "" && !isNumeric(hi))) { + return NO_MATCH_CONDITION; + } + } + if (isDateField) { + if (lo !== "" && !parseDateOperand(lo, "start")) + return NO_MATCH_CONDITION; + if (hi !== "" && !parseDateOperand(hi, "end")) return NO_MATCH_CONDITION; + } + const op = {}; + if (lo !== "") op.$gte = coerceBoundary(lo, isDateField, "start"); + if (hi !== "") op.$lte = coerceBoundary(hi, isDateField, "end"); + // A bare ".." places no constraint on the field. + if (Object.keys(op).length === 0) return { query: {} }; + return { op }; + } + + const comparison = parseLeafComparisonOperators( + token, + isDateField, + isBooleanField, + isObjectIdField, + isNumberField, + property, + ); + if (comparison) return comparison; + + return buildEquality( + stripIgnoreCase(token), + isDateField, + isBooleanField, + isObjectIdField, + isNumberField, + property, + ); +} + +// Converts a condition descriptor into a MongoDB query object for `property`. +function conditionToQuery(cond, property) { + if (cond.query) return cond.query; + if (cond.op) return { [property]: cond.op }; + return { [property]: cond.value }; +} + +function isOnlyRegex(op) { + return Object.keys(op).every((key) => key === "$regex" || key === "$options"); +} + +// Combines AND-ed conditions, merging operator objects on the same field where possible. +function combineAnd(children, property) { + if (children.some((child) => child.query)) { + return { + query: { + $and: children.map((child) => conditionToQuery(child, property)), + }, + }; + } + const merged = {}; + for (const child of children) { + if (child.value !== undefined) { + merged.$eq = child.value; + } else if (child.op) { + Object.assign(merged, child.op); + } + } + return { op: merged }; +} + +// Combines OR-ed conditions, collapsing to $in or a single regex when possible. +function combineOr(children, property, isObjectIdField = false) { + if (children.every((child) => child.value !== undefined)) { + return { op: { $in: children.map((child) => child.value) } }; + } + if ( + !isObjectIdField && + children.every((child) => child.op && isOnlyRegex(child.op)) + ) { + const pattern = children.map((child) => child.op.$regex).join("|"); + const op = { $regex: pattern }; + if (children[0].op.$options) op.$options = children[0].op.$options; + return { op }; + } + return { + query: { $or: children.map((child) => conditionToQuery(child, property)) }, + }; +} + +function buildCondition( + node, + property, + isDateField, + isBooleanField = false, + isObjectIdField = false, + isNumberField = false, +) { + if (node.type === "leaf") { + return parseLeafCondition( + node.token, + isDateField, + isBooleanField, + isObjectIdField, + isNumberField, + property, + ); + } + const children = node.items.map((item) => + buildCondition( + item, + property, + isDateField, + isBooleanField, + isObjectIdField, + isNumberField, + ), + ); + return node.type === "and" + ? combineAnd(children, property) + : combineOr(children, property, isObjectIdField); +} + +function idsToCondition(ids) { + if (!ids || ids.length === 0) return NO_MATCH_CONDITION; + if (ids.length === 1) return { value: ids[0] }; + return { op: { $in: ids } }; +} + +function extractObjectIdFromOperand(operand) { + const { suffix } = parsePrefixedValue( + stripIgnoreCase(String(operand).trim()), + ); + if (isObjectIdString(suffix)) { + return new mongoose.Types.ObjectId(suffix); + } + return null; +} + +const REF_OPERATOR_SYMBOLS = { + eq: "", + ne: "<>", + gt: ">", + gte: ">=", + lt: "<", + lte: "<=", +}; + +function buildRefListExpression(operator, operand) { + const text = String(operand).trim(); + if (operator === "eq") return text; + return `${REF_OPERATOR_SYMBOLS[operator]}${text}`; +} + +// Pre-list the referenced model, matching the expression against name and/or _reference via parseFilter. +async function listRefModelIds(refModelEntry, expression) { + const refModel = refModelEntry.model; + const orClauses = []; + + if (refModel.schema.path("name")) { + orClauses.push(await parseFilter("name", expression, refModel)); + } + if (refModel.schema.path("_reference")) { + orClauses.push(await parseFilter("_reference", expression, refModel)); + } + + if (orClauses.length === 0) return []; + + const query = orClauses.length === 1 ? orClauses[0] : { $or: orClauses }; + const docs = await refModel.find(query).select("_id").lean(); + return docs.map((doc) => doc._id); +} + +function conditionToIds(cond) { + if (cond.query) return null; + if (cond.value !== undefined) return [cond.value]; + if (cond.op?.$in) return cond.op.$in; + return null; +} + +function combineOrRefIds(children) { + const ids = new Set(); + for (const child of children) { + if (child.query) return child; + const childIds = conditionToIds(child); + if (!childIds) return child; + childIds.forEach((id) => ids.add(String(id))); + } + return idsToCondition([...ids].map((id) => new mongoose.Types.ObjectId(id))); +} + +function combineAndRefIds(children) { + let ids = null; + for (const child of children) { + if (child.query) return child; + const childIds = conditionToIds(child); + if (!childIds || childIds.length === 0) return NO_MATCH_CONDITION; + const set = new Set(childIds.map(String)); + if (ids === null) { + ids = set; + } else { + ids = new Set([...ids].filter((id) => set.has(id))); + } + if (ids.size === 0) return NO_MATCH_CONDITION; + } + return idsToCondition([...ids].map((id) => new mongoose.Types.ObjectId(id))); +} + +async function resolveRefOperand(operand, refModelEntry, operator = "eq") { + const lookupOperand = String(operand).trim(); + if (isEmptyOperand(lookupOperand)) { + if (operator === "eq" || operator === "ne") return emptyCondition(operator); + return NO_MATCH_CONDITION; + } + const { prefix, suffix, hadPrefix } = parsePrefixedValue(lookupOperand); + if (hadPrefix) { + const prefixEntry = getRefModelEntryFromPrefix(prefix); + if (prefixEntry) refModelEntry = prefixEntry; + } + + if (!refModelEntry?.model) return NO_MATCH_CONDITION; + + const objectId = extractObjectIdFromOperand(lookupOperand); + + if (objectId) { + if (operator === "ne") return { op: { $ne: objectId } }; + if (operator === "eq") return { value: objectId }; + return NO_MATCH_CONDITION; + } + + const expressionOperand = hadPrefix ? suffix : lookupOperand; + + if (operator === "ne") { + const ids = await listRefModelIds(refModelEntry, expressionOperand); + if (ids.length === 0) return { query: {} }; + return { op: { $nin: ids } }; + } + + const expression = buildRefListExpression(operator, expressionOperand); + const ids = await listRefModelIds(refModelEntry, expression); + return idsToCondition(ids); +} + +async function resolveRefLeaf(rawToken, fallbackRefName) { + const token = rawToken.trim(); + if (token === "") return NO_MATCH_CONDITION; + if (isEmptyOperand(token)) return emptyCondition("eq"); + + const refModelEntry = getRefModelEntryForToken(token, fallbackRefName); + const lookupToken = stripPrefixFromOperand(token); + + if (!refModelEntry?.model) { + const objectId = extractObjectIdFromOperand(lookupToken); + if (objectId) return { value: objectId }; + return NO_MATCH_CONDITION; + } + + const rangeIdx = lookupToken.indexOf(".."); + if (rangeIdx !== -1) { + const ids = await listRefModelIds(refModelEntry, lookupToken); + return idsToCondition(ids); + } + + const operators = [ + ["<>", "ne"], + [">=", "gte"], + ["<=", "lte"], + [">", "gt"], + ["<", "lt"], + ["=", "eq"], + ]; + for (const [symbol, name] of operators) { + if (lookupToken.startsWith(symbol)) { + return resolveRefOperand( + lookupToken.slice(symbol.length), + refModelEntry, + name, + ); + } + } + const objectId = extractObjectIdFromOperand(lookupToken); + if (objectId) return { value: objectId }; + + const ids = await listRefModelIds(refModelEntry, lookupToken); + return idsToCondition(ids); +} + +async function resolveRefCondition(node, fallbackRefName) { + if (node.type === "leaf") { + return resolveRefLeaf(node.token, fallbackRefName); + } + + const children = await Promise.all( + node.items.map((item) => resolveRefCondition(item, fallbackRefName)), + ); + + return node.type === "and" + ? combineAndRefIds(children) + : combineOrRefIds(children); +} + +async function resolveObjectRefFilter( + filterProperty, + expression, + fallbackRefName, +) { + const tree = parseExpression(expression); + const condition = await resolveRefCondition(tree, fallbackRefName); + return conditionToQuery(condition, filterProperty); +} + +async function parseFilter(property, value, model = null) { + // Normalize state filter to state.type for schemas with state: { type } + if (property === "state") { + property = "state.type"; + } + + const fieldKind = getFilterFieldKind(property, model); + const filterProperty = fieldKind.property; + const isBoolField = isBooleanField(property, model); + + if (value?._id !== undefined && value?._id !== null) { + return { [filterProperty]: new mongoose.Types.ObjectId(value._id) }; + } + + // Non-string values pass through; boolean fields only accept actual booleans. + if (typeof value !== "string") { + if (isBoolField && typeof value !== "boolean") { + return { [filterProperty]: { $in: [] } }; + } + return { [filterProperty]: value }; + } + + const trimmed = value.trim(); + + if ( + (fieldKind.kind === "objectId" || fieldKind.kind === "objectRef") && + property != "_id" + ) { + const refName = fieldKind.ref ?? getSchemaRefName(property, model); + return resolveObjectRefFilter(filterProperty, trimmed, refName); + } + + let expression = trimmed; + if (fieldKind.kind === "default") { + expression = stripModelPrefixFromExpression(expression); + } + + const isDateField = isDateTimeField(property, model); + const isObjectIdField = fieldKind.kind === "objectId"; + const isNumField = isNumberField(filterProperty, model); + const tree = parseExpression(expression); + if (isDateField && !isValidDateFilterNode(tree)) { + return {}; + } + const condition = buildCondition( + tree, + filterProperty, + isDateField, + isBoolField, + isObjectIdField, + isNumField, + ); + return conditionToQuery(condition, filterProperty); +} + +// Merges per-field filter clauses, falling back to $and when clauses use +// logical operators or target the same field (so nothing is silently lost). +function mergeFilterClauses(clauses) { + const valid = clauses.filter( + (clause) => clause && Object.keys(clause).length > 0, + ); + if (valid.length === 0) return {}; + if (valid.length === 1) return valid[0]; + + const seen = new Set(); + let needsAnd = false; + for (const clause of valid) { + for (const key of Object.keys(clause)) { + if (key === "$or" || key === "$and" || seen.has(key)) { + needsAnd = true; + } + seen.add(key); + } + } + + return needsAnd ? { $and: valid } : Object.assign({}, ...valid); +} + +// Returns a validated sort field based on allowed sorters +function getSort(sort, allowedSorters, defaultSort = "createdAt") { + if (sort && allowedSorters.includes(sort)) { + return sort; + } + return defaultSort; +} + +function normalizeFilterQuery(query = {}) { + const queryClean = { ...query }; + + // qs turns `order._id=` into `{ order: { _id } }`. Lift it so it can be parsed + // as the order object filter instead of a nested query object. + if ( + queryClean.order && + typeof queryClean.order === "object" && + !Array.isArray(queryClean.order) + ) { + if (queryClean.order._id !== undefined) { + queryClean["order._id"] = queryClean.order._id; + } + delete queryClean.order; + } + + return queryClean; +} + +// Returns a filter object based on allowed filters and req.query +async function getFilter( + query, + allowedFilters = ["*"], + parse = true, + model = null, +) { + const clauses = []; + const queryClean = normalizeFilterQuery(query); + for (const key of ["sortProperty", "sortOrder", "page", "limit"]) { + if (key in queryClean) delete queryClean[key]; + } + if ( + !parse && + queryClean["order._id"] !== undefined && + (allowedFilters.includes("order") || allowedFilters.includes("*")) + ) { + queryClean.order = queryClean["order._id"]; + delete queryClean["order._id"]; + } + for (const [key, value] of Object.entries(queryClean)) { + if (allowedFilters.includes(key) || allowedFilters.includes("*")) { + if (parse) { + const clause = await parseFilter(key, value, model); + // Drop date filters that contain anything other than operators and dates. + if (clause && Object.keys(clause).length > 0) { + clauses.push(clause); + } + } else { + clauses.push({ [key]: value }); + } + } + } + return mergeFilterClauses(clauses); +} + +export { parseFilter, getFilter, getObjectTypeModel }; diff --git a/src/database/mongo.js b/src/database/mongo.js new file mode 100644 index 0000000..1d00e5a --- /dev/null +++ b/src/database/mongo.js @@ -0,0 +1,59 @@ +import mongoose from 'mongoose'; +import { loadConfig } from '../config.js'; +import log4js from 'log4js'; + +const config = loadConfig(); +const logger = log4js.getLogger('Mongo DB'); +logger.level = config.server.logLevel; + +class MongoServer { + constructor() { + this.connected = false; + this.connecting = false; + this.connectionPromise = null; + this.url = config.database.mongo.url; + } + + connect() { + if (this.connected) return mongoose.connection; + if (this.connecting) return this.connectionPromise; + this.connecting = true; + logger.info('Connecting to MongoDB...'); + logger.debug('Connection URL:', this.url); + this.connectionPromise = mongoose + .connect(this.url, {}) + .then(conn => { + this.connected = true; + logger.info('Database connected.'); + return conn.connection; + }) + .catch(err => { + this.connected = false; + logger.error('MongoDB connection error:', err); + throw err; + }) + .finally(() => { + this.connecting = false; + }); + return this.connectionPromise; + } + + async getConnection() { + if (!this.connected) { + await this.connect(); + } + 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(); + +export { MongoServer, mongoServer }; diff --git a/src/database/nats.js b/src/database/nats.js new file mode 100644 index 0000000..8c4d3ee --- /dev/null +++ b/src/database/nats.js @@ -0,0 +1,310 @@ +import { connect } from '@nats-io/transport-node'; +import log4js from 'log4js'; +import { loadConfig } from '../config.js'; +import { formatTraceData } from '../utils.js'; + +const config = loadConfig(); +const logger = log4js.getLogger('Nats'); +logger.level = config.server.logLevel; + +class NatsServer { + constructor() { + this.client = null; + this.subscriptions = new Map(); // subject → { subscription, callbacks } + this.requestHandlers = new Map(); // subject → { handler, callbacks } + this.queuedSubscriptions = new Map(); // subject → { subscription, callbacks, queue } + + const natsConfig = config.database?.nats || config.database; // fallback for production config + const host = natsConfig.host || 'localhost'; + const port = natsConfig.port || 4222; + this.servers = [`nats://${host}:${port}`]; + this.textEncoder = new TextEncoder(); + this.textDecoder = new TextDecoder(); + + logger.trace(`NatsServer: servers set to ${JSON.stringify(this.servers)}`); + } + + async connect() { + if (!this.client) { + logger.info('Connecting to NATS...'); + logger.trace( + `Creating NATS client with servers ${JSON.stringify(this.servers)}` + ); + + try { + this.client = await connect({ + servers: this.servers, + reconnect: true, + maxReconnectAttempts: -1, // unlimited reconnects + reconnectTimeWait: 1000, + timeout: 20000 + }); + + // Test connection by checking if client is connected + try { + if (this.client.isClosed()) { + throw new Error('NATS client connection failed'); + } + logger.trace('NATS client connected successfully.'); + } catch (error) { + throw error; + } + } catch (error) { + logger.error('Failed to connect to NATS:', error); + throw error; + } + } else { + logger.trace('NATS 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 publish(subject, data) { + const client = await this.getClient(); + const payload = typeof data === 'string' ? data : JSON.stringify(data); + + try { + client.publish(subject, this.textEncoder.encode(payload)); + logger.trace(`Published to subject: ${subject}, data: ${formatTraceData(payload)}`); + return { success: true }; + } catch (error) { + logger.error(`Failed to publish to subject ${subject}:`, error); + throw error; + } + } + + async request(subject, data, timeout = 30000) { + const client = await this.getClient(); + const payload = typeof data === 'string' ? data : JSON.stringify(data); + + try { + const response = await client.request( + subject, + this.textEncoder.encode(payload), + { + timeout: timeout + } + ); + + const responseData = this.textDecoder.decode(response.data); + logger.trace( + `Request to subject: ${subject}, response: ${formatTraceData(responseData)}` + ); + + // Try to parse as JSON, fallback to string + try { + return JSON.parse(responseData); + } catch { + return responseData; + } + } catch (error) { + if (error.code === 'TIMEOUT') { + logger.trace(`Request timeout for subject: ${subject}`); + return null; + } + throw error; + } + } + + async subscribe(subject, owner, callback) { + const client = await this.getClient(); + const subscriptionKey = subject; + + if (this.subscriptions.has(subscriptionKey)) { + this.subscriptions.get(subscriptionKey).callbacks.set(owner, callback); + logger.trace( + `Added subscription callback for owner=${owner} on subject=${subject}` + ); + return { success: true }; + } + + logger.trace(`Creating new subscription for subject: ${subject}`); + const subscription = client.subscribe(subject); + const callbacks = new Map(); + callbacks.set(owner, callback); + + (async () => { + for await (const msg of subscription) { + logger.trace(`Message received on subject: ${subject}`); + const data = this.textDecoder.decode(msg.data); + let parsedData; + + try { + parsedData = JSON.parse(data); + } catch { + parsedData = data; + } + + for (const [ownerId, cb] of callbacks) { + try { + cb(subject, parsedData, msg); + } catch (err) { + logger.error( + `Error in subscription callback for owner=${ownerId}, subject=${subject}:`, + err + ); + } + } + } + })().catch(err => { + logger.error(`Subscription error for subject ${subject}:`, err); + }); + + this.subscriptions.set(subscriptionKey, { subscription, callbacks }); + return { success: true }; + } + + async setRequestHandler(subject, owner, handler) { + const client = await this.getClient(); + const handlerKey = subject; + + if (this.requestHandlers.has(handlerKey)) { + this.requestHandlers.get(handlerKey).callbacks.set(owner, handler); + logger.trace( + `Added request handler for owner=${owner} on subject=${subject}` + ); + return { success: true }; + } + + logger.trace(`Creating new request handler for subject: ${subject}`); + const subscription = client.subscribe(subject); + const callbacks = new Map(); + callbacks.set(owner, handler); + + (async () => { + for await (const msg of subscription) { + logger.trace(`Request received on subject: ${subject}`); + const data = this.textDecoder.decode(msg.data); + let parsedData; + + try { + parsedData = JSON.parse(data); + } catch { + parsedData = data; + } + + for (const [ownerId, cb] of callbacks) { + try { + const response = await cb(subject, parsedData, msg); + const responsePayload = + typeof response === 'string' + ? response + : JSON.stringify(response); + msg.respond(this.textEncoder.encode(responsePayload)); + } catch (err) { + logger.error( + `Error in request handler for owner=${ownerId}, subject=${subject}:`, + err + ); + // Send error response + msg.respond( + this.textEncoder.encode(JSON.stringify({ error: err.message })) + ); + } + } + } + })().catch(err => { + logger.error(`Request handler error for subject ${subject}:`, err); + }); + + this.requestHandlers.set(handlerKey, { subscription, callbacks }); + return { success: true }; + } + + async removeSubscription(subject, owner) { + const entry = this.subscriptions.get(subject); + + if (!entry) { + logger.trace(`Subscription not found for subject: ${subject}`); + return false; + } + + if (entry.callbacks.delete(owner)) { + logger.trace( + `Removed subscription callback for owner: ${owner} on subject: ${subject}` + ); + } else { + logger.trace( + `No subscription callback found for owner: ${owner} on subject: ${subject}` + ); + } + + if (entry.callbacks.size === 0) { + logger.trace(`No callbacks left, stopping subscription for ${subject}`); + entry.subscription.unsubscribe(); + this.subscriptions.delete(subject); + } + + return true; + } + + async removeRequestHandler(subject, owner) { + const entry = this.requestHandlers.get(subject); + + if (!entry) { + logger.trace(`Request handler not found for subject: ${subject}`); + return false; + } + + if (entry.callbacks.delete(owner)) { + logger.trace( + `Removed request handler for owner: ${owner} on subject: ${subject}` + ); + } else { + logger.trace( + `No request handler found for owner: ${owner} on subject: ${subject}` + ); + } + + if (entry.callbacks.size === 0) { + logger.trace(`No handlers left, stopping request handler for ${subject}`); + entry.subscription.unsubscribe(); + this.requestHandlers.delete(subject); + } + + return true; + } + + async disconnect() { + logger.info('Disconnecting from NATS...'); + + // Stop all subscriptions + for (const [subject, entry] of this.subscriptions) { + logger.trace(`Stopping subscription: ${subject}`); + entry.subscription.unsubscribe(); + } + this.subscriptions.clear(); + + // Stop all queued subscriptions + for (const [key, entry] of this.queuedSubscriptions) { + logger.trace(`Stopping queued subscription: ${key}`); + entry.subscription.unsubscribe(); + } + this.queuedSubscriptions.clear(); + + // Stop all request handlers + for (const [subject, entry] of this.requestHandlers) { + logger.trace(`Stopping request handler: ${subject}`); + entry.subscription.unsubscribe(); + } + this.requestHandlers.clear(); + + if (this.client) { + await this.client.close(); + this.client = null; + logger.info('Disconnected from NATS'); + } + } +} + +const natsServer = new NatsServer(); + +export { NatsServer, natsServer }; diff --git a/src/database/permissions.js b/src/database/permissions.js new file mode 100644 index 0000000..f0fa291 --- /dev/null +++ b/src/database/permissions.js @@ -0,0 +1,78 @@ +import mongoose from "mongoose"; +import { redisServer } from "./redis.js"; + +export const getUserPermissionsCacheKey = (userId) => `permissions:${userId}`; + +export const saveUserPermissionsToRedis = async (userId, permissions = {}) => { + if (!userId) { + return; + } + + await redisServer.setKey( + getUserPermissionsCacheKey(userId), + permissions || {}, + ); +}; + +export const applyPermissionSettingsList = (settingsList = []) => { + const permissions = {}; + + for (const setting of settingsList) { + const matrix = setting?.permissions; + if (!matrix || typeof matrix !== "object" || Array.isArray(matrix)) { + continue; + } + + for (const [modelName, actions] of Object.entries(matrix)) { + if (!actions || typeof actions !== "object" || Array.isArray(actions)) { + continue; + } + if (!permissions[modelName]) { + permissions[modelName] = {}; + } + for (const [actionName, value] of Object.entries(actions)) { + if (value === true || value === false) { + permissions[modelName][actionName] = value; + } + } + if (Object.keys(permissions[modelName]).length === 0) { + delete permissions[modelName]; + } + } + } + + return permissions; +}; + +export const getPermissionSettingsId = (value) => { + if (!value) return null; + if (typeof value === "string") return value; + if (value._id) { + return value._id._id || value._id; + } + return value; +}; + +export const excludeIdFromList = (items = [], id) => + (items || []).filter( + (item) => String(getPermissionSettingsId(item)) !== String(id), + ); + +export const resolveReferencedDocs = async (modelName, items = []) => { + const ids = (items || []).map(getPermissionSettingsId).filter(Boolean); + if (ids.length === 0) { + return []; + } + + const docs = await mongoose + .model(modelName) + .find({ _id: { $in: ids } }) + .lean(); + const docsById = new Map(docs.map((doc) => [String(doc._id), doc])); + + return ids.map((id) => docsById.get(String(id))).filter(Boolean); +}; + +export const resolvePermissionSettings = async (owner) => { + return resolveReferencedDocs("permissionSetting", owner?.permissionSettings); +}; diff --git a/src/database/redis.js b/src/database/redis.js new file mode 100644 index 0000000..2f46086 --- /dev/null +++ b/src/database/redis.js @@ -0,0 +1,110 @@ +import Redis from "ioredis"; +import log4js from "log4js"; +import { loadConfig } from "../config.js"; + +const config = loadConfig(); +const logger = log4js.getLogger("Redis"); +logger.level = config.server.logLevel; + +class RedisServer { + constructor() { + const url = + config.database.redis.url || + `redis://${config.database.redis.host}:${config.database.redis.port}`; + const password = config.database.redis.password || undefined; + + this.client = new Redis(url, { + password, + lazyConnect: true, + }); + + this.client.on("error", (err) => { + logger.error("Redis Client Error", err); + }); + + this.connected = false; + } + + 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() { + await this.client.disconnect(); + 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); + if (ttlSeconds) { + await this.client.set(key, payload, "EX", ttlSeconds); + } else { + await this.client.set(key, payload); + } + } + + async getKey(key) { + await this.connect(); + const value = await this.client.get(key); + if (value == null) return null; + try { + return JSON.parse(value); + } catch { + return value; + } + } + + async deleteKey(key) { + await this.connect(); + await this.client.del(key); + } + + async getAndDeleteKey(key) { + await this.connect(); + const value = await this.client.getdel(key); + if (value == null) return null; + try { + return JSON.parse(value); + } catch { + return value; + } + } + + async eval(script, keys = [], args = []) { + await this.connect(); + return this.client.eval( + script, + keys.length, + ...keys, + ...args.map((arg) => String(arg)), + ); + } + + async getKeysByPattern(pattern) { + await this.connect(); + const keys = []; + let cursor = "0"; + do { + const [nextCursor, foundKeys] = await this.client.scan( + cursor, + "MATCH", + pattern, + "COUNT", + 100, + ); + cursor = nextCursor; + keys.push(...foundKeys); + } while (cursor !== "0"); + return keys; + } +} + +const redisServer = new RedisServer(); + +export { RedisServer, redisServer }; diff --git a/src/database/schemas/__tests__/stockQuantity.recalculate.test.js b/src/database/schemas/__tests__/stockQuantity.recalculate.test.js new file mode 100644 index 0000000..9f808c4 --- /dev/null +++ b/src/database/schemas/__tests__/stockQuantity.recalculate.test.js @@ -0,0 +1,416 @@ +import { beforeEach, describe, expect, it, jest } from '@jest/globals'; +import mongoose from 'mongoose'; + +jest.unstable_mockModule('../../database.js', () => ({ + searchObjects: jest.fn(), + getPropertyValues: jest.fn(), + listObjects: jest.fn(), + getObject: jest.fn(), + editObject: jest.fn(), + editObjects: jest.fn(), + newObject: jest.fn(), + deleteObject: jest.fn(), + listObjectsByProperties: jest.fn(), + getModelStats: jest.fn(), + getModelHistory: jest.fn(), + aggregateRollups: jest.fn(), + aggregateRollupsHistory: jest.fn(), + checkStates: jest.fn(), + getObjectNeighbors: jest.fn(), +})); + +jest.unstable_mockModule('../../utils.js', () => ({ + generateId: jest.fn(() => () => 'test-id'), +})); + +jest.unstable_mockModule('../inventory/stockaudit.schema.js', () => ({ + updateDraftStockAuditCurrents: jest.fn().mockResolvedValue(), +})); + +const { aggregateRollups, editObject, newObject, deleteObject } = await import('../../database.js'); +const { listingModel } = await import('../sales/listing.schema.js'); +const { listingVarientModel } = await import('../sales/listingvarient.schema.js'); +const { productSkuModel } = await import('../management/productsku.schema.js'); +const { productStockModel } = await import('../inventory/productstock.schema.js'); + +const listingId = new mongoose.Types.ObjectId(); +const productId = new mongoose.Types.ObjectId(); +const productSkuId = new mongoose.Types.ObjectId(); +const stockLocationId = new mongoose.Types.ObjectId(); +const varientId = new mongoose.Types.ObjectId(); + +const mockFind = (docs) => ({ + sort: () => ({ lean: async () => docs }), +}); + +describe('listing.recalculate', () => { + beforeEach(() => { + editObject.mockReset(); + newObject.mockReset(); + deleteObject.mockReset(); + jest.restoreAllMocks(); + }); + + it('calls recalculate on each listing varient', async () => { + const recalculate = jest.spyOn(listingVarientModel, 'recalculate').mockResolvedValue(); + jest.spyOn(listingVarientModel, 'find').mockReturnValue( + mockFind([{ _id: varientId, listing: listingId }]) + ); + + const listing = { _id: listingId, stockLocation: stockLocationId }; + await listingModel.recalculate(listing, 'user-1'); + + expect(listingVarientModel.find).toHaveBeenCalledWith({ listing: listingId }); + expect(recalculate).toHaveBeenCalledWith({ _id: varientId, listing: listingId }, 'user-1'); + expect(newObject).not.toHaveBeenCalled(); + expect(deleteObject).not.toHaveBeenCalled(); + }); + + it('creates listing varients from product skus when none exist', async () => { + const skuBId = new mongoose.Types.ObjectId(); + const createdVarientId = new mongoose.Types.ObjectId(); + const productSkus = [{ _id: productSkuId }, { _id: skuBId }]; + const syncedVarients = [ + { _id: varientId, listing: listingId, product: productId, productSku: productSkuId }, + { _id: createdVarientId, listing: listingId, product: productId, productSku: skuBId }, + ]; + + const recalculate = jest.spyOn(listingVarientModel, 'recalculate').mockResolvedValue(); + jest + .spyOn(listingVarientModel, 'find') + .mockReturnValueOnce(mockFind([])) + .mockReturnValueOnce(mockFind(syncedVarients)); + jest.spyOn(productSkuModel, 'find').mockReturnValue(mockFind(productSkus)); + newObject.mockResolvedValue({ _id: createdVarientId }); + + await listingModel.recalculate({ _id: listingId, product: productId }, 'user-1'); + + expect(productSkuModel.find).toHaveBeenCalledWith({ product: productId }); + expect(editObject).not.toHaveBeenCalled(); + expect(deleteObject).not.toHaveBeenCalled(); + expect(newObject).toHaveBeenCalledTimes(2); + expect(newObject).toHaveBeenCalledWith({ + model: listingVarientModel, + newData: expect.objectContaining({ + listing: listingId, + product: productId, + productSku: productSkuId, + state: { type: 'draft' }, + }), + user: 'user-1', + recalculate: false, + }); + expect(recalculate).toHaveBeenCalledTimes(2); + }); + + it('creates missing listing varients when one already matches a sku', async () => { + const skuBId = new mongoose.Types.ObjectId(); + const skuCId = new mongoose.Types.ObjectId(); + const existingVarient = { + _id: varientId, + listing: listingId, + product: productId, + productSku: productSkuId, + }; + + const recalculate = jest.spyOn(listingVarientModel, 'recalculate').mockResolvedValue(); + jest + .spyOn(listingVarientModel, 'find') + .mockReturnValueOnce(mockFind([existingVarient])) + .mockReturnValueOnce( + mockFind([ + existingVarient, + { _id: new mongoose.Types.ObjectId(), listing: listingId, product: productId, productSku: skuBId }, + { _id: new mongoose.Types.ObjectId(), listing: listingId, product: productId, productSku: skuCId }, + ]) + ); + jest + .spyOn(productSkuModel, 'find') + .mockReturnValue(mockFind([{ _id: productSkuId }, { _id: skuBId }, { _id: skuCId }])); + newObject.mockResolvedValue({ _id: new mongoose.Types.ObjectId() }); + + await listingModel.recalculate({ _id: listingId, product: productId }, 'user-1'); + + expect(editObject).not.toHaveBeenCalled(); + expect(newObject).toHaveBeenCalledTimes(2); + expect(newObject).toHaveBeenCalledWith({ + model: listingVarientModel, + newData: expect.objectContaining({ + listing: listingId, + product: productId, + productSku: skuBId, + state: { type: 'draft' }, + }), + user: 'user-1', + recalculate: false, + }); + expect(recalculate).toHaveBeenCalledTimes(3); + }); + + it('rebuilds listing varients when an existing varient is missing a product sku', async () => { + const skuBId = new mongoose.Types.ObjectId(); + jest.spyOn(listingVarientModel, 'recalculate').mockResolvedValue(); + jest + .spyOn(listingVarientModel, 'find') + .mockReturnValueOnce( + mockFind([{ _id: varientId, listing: listingId, product: productId }]) + ) + .mockReturnValueOnce( + mockFind([ + { _id: varientId, listing: listingId, product: productId, productSku: productSkuId }, + { + _id: new mongoose.Types.ObjectId(), + listing: listingId, + product: productId, + productSku: skuBId, + }, + ]) + ); + jest + .spyOn(productSkuModel, 'find') + .mockReturnValue(mockFind([{ _id: productSkuId }, { _id: skuBId }])); + editObject.mockResolvedValue({}); + newObject.mockResolvedValue({ _id: new mongoose.Types.ObjectId() }); + + await listingModel.recalculate({ _id: listingId, product: productId }, 'user-1'); + + expect(editObject).toHaveBeenCalledWith({ + model: listingVarientModel, + id: varientId, + updateData: expect.objectContaining({ + product: productId, + productSku: productSkuId, + }), + user: 'user-1', + recalculate: false, + }); + expect(newObject).toHaveBeenCalledTimes(1); + }); + + it('does not rebuild varients when they already match the product skus', async () => { + const existingVarient = { + _id: varientId, + listing: listingId, + product: productId, + productSku: productSkuId, + }; + const recalculate = jest.spyOn(listingVarientModel, 'recalculate').mockResolvedValue(); + jest.spyOn(listingVarientModel, 'find').mockReturnValue(mockFind([existingVarient])); + jest.spyOn(productSkuModel, 'find').mockReturnValue(mockFind([{ _id: productSkuId }])); + + await listingModel.recalculate( + { _id: listingId, product: productId, stockLocation: stockLocationId }, + 'user-1' + ); + + expect(newObject).not.toHaveBeenCalled(); + expect(deleteObject).not.toHaveBeenCalled(); + expect(editObject).not.toHaveBeenCalled(); + expect(recalculate).toHaveBeenCalledTimes(1); + }); + + it('creates and updates listing varients to match product skus when a product differs', async () => { + const otherProductId = new mongoose.Types.ObjectId(); + const skuBId = new mongoose.Types.ObjectId(); + const skuCId = new mongoose.Types.ObjectId(); + const createdVarientId = new mongoose.Types.ObjectId(); + const existingVarients = [{ _id: varientId, listing: listingId, product: otherProductId }]; + const productSkus = [{ _id: productSkuId }, { _id: skuBId }, { _id: skuCId }]; + const syncedVarients = [ + { _id: varientId, listing: listingId, product: productId, productSku: productSkuId }, + { _id: createdVarientId, listing: listingId, product: productId, productSku: skuBId }, + { _id: new mongoose.Types.ObjectId(), listing: listingId, product: productId, productSku: skuCId }, + ]; + + const recalculate = jest.spyOn(listingVarientModel, 'recalculate').mockResolvedValue(); + jest + .spyOn(listingVarientModel, 'find') + .mockReturnValueOnce(mockFind(existingVarients)) + .mockReturnValueOnce(mockFind(syncedVarients)); + jest.spyOn(productSkuModel, 'find').mockReturnValue(mockFind(productSkus)); + editObject.mockResolvedValue({}); + newObject.mockResolvedValue({ _id: createdVarientId }); + + await listingModel.recalculate({ _id: listingId, product: productId }, 'user-1'); + + expect(editObject).toHaveBeenCalledWith({ + model: listingVarientModel, + id: varientId, + updateData: expect.objectContaining({ + product: productId, + productSku: productSkuId, + }), + user: 'user-1', + recalculate: false, + }); + expect(newObject).toHaveBeenCalledTimes(2); + expect(newObject).toHaveBeenCalledWith({ + model: listingVarientModel, + newData: expect.objectContaining({ + listing: listingId, + product: productId, + productSku: skuBId, + state: { type: 'draft' }, + }), + user: 'user-1', + recalculate: false, + }); + expect(deleteObject).not.toHaveBeenCalled(); + expect(recalculate).toHaveBeenCalledTimes(3); + }); + + it('deletes extra listing varients when the product has fewer skus', async () => { + const otherProductId = new mongoose.Types.ObjectId(); + const extraVarientId = new mongoose.Types.ObjectId(); + const existingVarients = [ + { _id: varientId, listing: listingId, product: otherProductId }, + { _id: extraVarientId, listing: listingId, product: otherProductId }, + ]; + + jest.spyOn(listingVarientModel, 'recalculate').mockResolvedValue(); + jest + .spyOn(listingVarientModel, 'find') + .mockReturnValueOnce(mockFind(existingVarients)) + .mockReturnValueOnce( + mockFind([{ _id: varientId, listing: listingId, product: productId, productSku: productSkuId }]) + ); + jest.spyOn(productSkuModel, 'find').mockReturnValue(mockFind([{ _id: productSkuId }])); + editObject.mockResolvedValue({}); + deleteObject.mockResolvedValue({}); + + await listingModel.recalculate({ _id: listingId, product: productId }, 'user-1'); + + expect(editObject).toHaveBeenCalledTimes(1); + expect(newObject).not.toHaveBeenCalled(); + expect(deleteObject).toHaveBeenCalledWith({ + model: listingVarientModel, + id: extraVarientId, + user: 'user-1', + }); + }); +}); + +describe('listingVarient.recalculate', () => { + beforeEach(() => { + aggregateRollups.mockReset(); + editObject.mockReset(); + jest.restoreAllMocks(); + }); + + it('sums sibling listing varient stock quantities onto the listing', async () => { + aggregateRollups.mockResolvedValue({ stockQuantity: { sum: 12 } }); + editObject.mockResolvedValue({}); + + await listingVarientModel.recalculate({ listing: listingId, stockQuantity: 4 }, 'user-1'); + + expect(aggregateRollups).toHaveBeenCalledWith( + expect.objectContaining({ + model: listingVarientModel, + baseFilter: { listing: listingId }, + }) + ); + expect(editObject).toHaveBeenCalledWith({ + model: listingModel, + id: listingId, + updateData: { stockQuantity: 12 }, + user: 'user-1', + recalculate: false, + }); + }); + + it('writes the product sku stock total onto the listing varient before rolling up', async () => { + jest.spyOn(listingVarientModel, 'exists').mockResolvedValue({ _id: varientId }); + aggregateRollups.mockImplementation(async ({ model }) => { + if (model === productStockModel) { + return { stockQuantity: { sum: 9 } }; + } + return { stockQuantity: { sum: 12 } }; + }); + editObject.mockResolvedValue({}); + + await listingVarientModel.recalculate( + { + _id: varientId, + listing: { _id: listingId, stockLocation: stockLocationId, product: productId }, + product: productId, + productSku: productSkuId, + stockQuantity: 0, + }, + 'user-1' + ); + + expect(aggregateRollups).toHaveBeenCalledWith( + expect.objectContaining({ + model: productStockModel, + baseFilter: { + productSku: productSkuId, + stockLocation: stockLocationId, + }, + }) + ); + expect(editObject).toHaveBeenCalledWith({ + model: listingVarientModel, + id: varientId, + updateData: { stockQuantity: 9 }, + user: 'user-1', + recalculate: false, + }); + expect(editObject).toHaveBeenCalledWith({ + model: listingModel, + id: listingId, + updateData: { stockQuantity: 12 }, + user: 'user-1', + recalculate: false, + }); + }); +}); + +describe('productStock.recalculate', () => { + beforeEach(() => { + aggregateRollups.mockReset(); + editObject.mockReset(); + jest.restoreAllMocks(); + }); + + it('writes the sku/location total onto matching listing varients', async () => { + aggregateRollups.mockResolvedValue({ stockQuantity: { sum: 9 } }); + editObject.mockResolvedValue({}); + jest.spyOn(productSkuModel, 'findById').mockReturnValue({ + select: () => ({ lean: async () => ({ product: productId }) }), + }); + jest.spyOn(listingVarientModel, 'find').mockReturnValue({ + populate: () => ({ + lean: async () => [ + { + _id: varientId, + product: productId, + productSku: productSkuId, + stockQuantity: 0, + listing: { product: productId, stockLocation: stockLocationId }, + }, + ], + }), + }); + + await productStockModel.recalculate( + { productSku: productSkuId, stockLocation: stockLocationId, currentQuantity: 9 }, + 'user-1' + ); + + expect(aggregateRollups).toHaveBeenCalledWith( + expect.objectContaining({ + model: productStockModel, + baseFilter: { + productSku: productSkuId, + stockLocation: stockLocationId, + }, + }) + ); + expect(editObject).toHaveBeenCalledWith({ + model: listingVarientModel, + id: varientId, + updateData: { stockQuantity: 9 }, + user: 'user-1', + }); + }); +}); diff --git a/src/database/schemas/finance/invoice.schema.js b/src/database/schemas/finance/invoice.schema.js new file mode 100644 index 0000000..c09bbb7 --- /dev/null +++ b/src/database/schemas/finance/invoice.schema.js @@ -0,0 +1,251 @@ +import mongoose from 'mongoose'; +import { generateId } from '../../utils.js'; +const { Schema } = mongoose; +import { + aggregateRollups, + aggregateRollupsHistory, + editObject, + getObject, +} from '../../database.js'; +import { taxRateModel } from '../management/taxrate.schema.js'; +import { amountWithTax, resolveTaxRate } from '../../tax.js'; + +const invoiceOrderItemSchema = new Schema( + { + orderItem: { type: Schema.Types.ObjectId, ref: 'orderItem', required: true }, + taxRate: { type: Schema.Types.ObjectId, ref: 'taxRate', required: false }, + invoiceAmountWithTax: { type: Number, required: true, default: 0 }, + invoiceAmount: { type: Number, required: true, default: 0 }, + invoiceQuantity: { type: Number, required: true, default: 0 }, + }, + { timestamps: true } +); + +const invoiceShipmentSchema = new Schema( + { + shipment: { type: Schema.Types.ObjectId, ref: 'shipment', required: true }, + taxRate: { type: Schema.Types.ObjectId, ref: 'taxRate', required: false }, + invoiceAmountWithTax: { type: Number, required: true, default: 0 }, + invoiceAmount: { type: Number, required: true, default: 0 }, + }, + { timestamps: true } +); + +const invoiceSchema = new Schema( + { + _reference: { type: String, default: () => generateId()() }, + totalAmount: { type: Number, required: true, default: 0 }, + totalAmountWithTax: { type: Number, required: true, default: 0 }, + shippingAmount: { type: Number, required: true, default: 0 }, + shippingAmountWithTax: { type: Number, required: true, default: 0 }, + grandTotalAmount: { type: Number, required: true, default: 0 }, + totalTaxAmount: { type: Number, required: true, default: 0 }, + from: { type: Schema.Types.ObjectId, refPath: 'fromType', required: false }, + fromType: { type: String, required: false }, + to: { type: Schema.Types.ObjectId, refPath: 'toType', required: false }, + toType: { type: String, required: false }, + state: { + type: { type: String, required: true, default: 'draft' }, + }, + orderType: { type: String, required: true }, + order: { type: Schema.Types.ObjectId, refPath: 'orderType', required: true }, + issuedAt: { type: Date, required: false }, + dueAt: { type: Date, required: false }, + postedAt: { type: Date, required: false }, + acknowledgedAt: { type: Date, required: false }, + paidAt: { type: Date, required: false }, + cancelledAt: { type: Date, required: false }, + invoiceOrderItems: [invoiceOrderItemSchema], + invoiceShipments: [invoiceShipmentSchema], + }, + { timestamps: true } +); + +invoiceSchema.index({ orderType: 'text', fromType: 'text', toType: 'text' }); + +const rollupConfigs = [ + { + name: 'draft', + filter: { 'state.type': 'draft' }, + rollups: [ + { name: 'draftCount', property: 'state.type', operation: 'count' }, + { name: 'draftGrandTotalAmount', property: 'grandTotalAmount', operation: 'sum' }, + ], + }, + { + name: 'sent', + filter: { 'state.type': 'sent' }, + rollups: [ + { name: 'sentCount', property: 'state.type', operation: 'count' }, + { name: 'sentGrandTotalAmount', property: 'grandTotalAmount', operation: 'sum' }, + ], + }, + { + name: 'acknowledged', + filter: { 'state.type': 'acknowledged' }, + rollups: [ + { name: 'acknowledgedCount', property: 'state.type', operation: 'count' }, + { name: 'acknowledgedGrandTotalAmount', property: 'grandTotalAmount', operation: 'sum' }, + ], + }, + { + name: 'partiallyPaid', + filter: { 'state.type': 'partiallyPaid' }, + rollups: [ + { name: 'partiallyPaidCount', property: 'state.type', operation: 'count' }, + { name: 'partiallyPaidGrandTotalAmount', property: 'grandTotalAmount', operation: 'sum' }, + ], + }, + { + name: 'paid', + filter: { 'state.type': 'paid' }, + rollups: [ + { name: 'paidCount', property: 'state.type', operation: 'count' }, + { name: 'paidGrandTotalAmount', property: 'grandTotalAmount', operation: 'sum' }, + ], + }, + { + name: 'overdue', + filter: { 'state.type': 'overdue' }, + rollups: [ + { name: 'overdueCount', property: 'state.type', operation: 'count' }, + { name: 'overdueGrandTotalAmount', property: 'grandTotalAmount', operation: 'sum' }, + ], + }, + { + name: 'cancelled', + filter: { 'state.type': 'cancelled' }, + rollups: [{ name: 'cancelledCount', property: 'state.type', operation: 'count' }], + }, +]; + +invoiceSchema.statics.stats = async function () { + const results = await aggregateRollups({ + model: this, + rollupConfigs: rollupConfigs, + }); + + // Transform the results to match the expected format + return results; +}; + +invoiceSchema.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; +}; + +invoiceSchema.statics.scheduledProperties = [ + { + name: 'dueAt', + filter: { state: 'due|sent|acknowledged' }, + type: 'dateTime', + }, +]; + +invoiceSchema.statics.onSchedulerEvent = async function (invoice, property) { + const invoiceId = invoice._id || invoice; + if (!invoiceId) { + return; + } + + if (property === 'dueAt' && invoice.dueAt && invoice.dueAt < new Date()) { + await editObject({ + model: this, + id: invoiceId, + updateData: { + state: { type: 'overdue' }, + }, + user: 'system', + recalculate: false, + }); + } +}; + +invoiceSchema.statics.recalculate = async function (invoice, user) { + const invoiceId = invoice._id || invoice; + if (!invoiceId) { + return; + } + + const invoiceOrderItems = []; + for (const item of invoice.invoiceOrderItems || []) { + const taxRate = await resolveTaxRate(item.taxRate, getObject, taxRateModel); + invoiceOrderItems.push({ + ...item, + invoiceAmountWithTax: amountWithTax(item.invoiceAmount, taxRate), + }); + } + + const invoiceShipments = []; + for (const item of invoice.invoiceShipments || []) { + const taxRate = await resolveTaxRate(item.taxRate, getObject, taxRateModel); + invoiceShipments.push({ + ...item, + invoiceAmountWithTax: amountWithTax(item.invoiceAmount, taxRate), + }); + } + + // Calculate totals from invoiceOrderItems + let totalAmount = 0; + for (const item of invoiceOrderItems) { + totalAmount += Number.parseFloat(item.invoiceAmount) || 0; + } + let totalAmountWithTax = 0; + for (const item of invoiceOrderItems) { + totalAmountWithTax += Number.parseFloat(item.invoiceAmountWithTax) || 0; + } + + // Calculate shipping totals from invoiceShipments + let shippingAmount = 0; + for (const item of invoiceShipments) { + shippingAmount += Number.parseFloat(item.invoiceAmount) || 0; + } + let shippingAmountWithTax = 0; + for (const item of invoiceShipments) { + shippingAmountWithTax += Number.parseFloat(item.invoiceAmountWithTax) || 0; + } + + // Calculate grand total and tax amount + const grandTotalAmount = parseFloat(totalAmountWithTax) + parseFloat(shippingAmountWithTax); + const totalTaxAmount = + parseFloat(totalAmountWithTax) - + parseFloat(totalAmount) + + (parseFloat(shippingAmountWithTax) - parseFloat(shippingAmount)); + + const updateData = { + invoiceOrderItems, + invoiceShipments, + totalAmount: parseFloat(totalAmount).toFixed(2), + totalAmountWithTax: parseFloat(totalAmountWithTax).toFixed(2), + shippingAmount: parseFloat(shippingAmount).toFixed(2), + shippingAmountWithTax: parseFloat(shippingAmountWithTax).toFixed(2), + grandTotalAmount: parseFloat(grandTotalAmount).toFixed(2), + totalTaxAmount: parseFloat(totalTaxAmount).toFixed(2), + }; + + await editObject({ + model: this, + id: invoiceId, + updateData, + user, + recalculate: false, + }); +}; + +// Add virtual id getter +invoiceSchema.virtual('id').get(function () { + return this._id; +}); + +// Configure JSON serialization to include virtuals +invoiceSchema.set('toJSON', { virtuals: true }); + +// Create and export the model +export const invoiceModel = mongoose.model('invoice', invoiceSchema); diff --git a/src/database/schemas/finance/payment.schema.js b/src/database/schemas/finance/payment.schema.js new file mode 100644 index 0000000..92fb1ca --- /dev/null +++ b/src/database/schemas/finance/payment.schema.js @@ -0,0 +1,124 @@ +import mongoose from 'mongoose'; +import { generateId } from '../../utils.js'; +const { Schema } = mongoose; +import { aggregateRollups, aggregateRollupsHistory, editObject } from '../../database.js'; + +const paymentSchema = new Schema( + { + _reference: { type: String, default: () => generateId()() }, + amount: { type: Number, required: true, default: 0 }, + vendor: { type: Schema.Types.ObjectId, ref: 'vendor', required: false }, + client: { type: Schema.Types.ObjectId, ref: 'client', required: false }, + invoice: { type: Schema.Types.ObjectId, ref: 'invoice', required: true }, + state: { + type: { type: String, required: true, default: 'draft' }, + }, + postedAt: { type: Date, required: false }, + authorisedAt: { type: Date, required: false }, + declinedAt: { type: Date, required: false }, + cancelledAt: { type: Date, required: false }, + paymentMethod: { type: String, required: false }, + notes: { type: String, required: false }, + }, + { timestamps: true } +); + +paymentSchema.index({ paymentMethod: 'text', notes: 'text' }); + +const rollupConfigs = [ + { + name: 'draft', + filter: { 'state.type': 'draft' }, + rollups: [ + { name: 'draftCount', property: 'state.type', operation: 'count' }, + { name: 'draftAmount', property: 'amount', operation: 'sum' }, + ], + }, + { + name: 'posted', + filter: { 'state.type': 'posted' }, + rollups: [ + { name: 'postedCount', property: 'state.type', operation: 'count' }, + { name: 'postedAmount', property: 'amount', operation: 'sum' }, + ], + }, + { + name: 'authorised', + filter: { 'state.type': 'authorised' }, + rollups: [ + { name: 'authorisedCount', property: 'state.type', operation: 'count' }, + { name: 'authorisedAmount', property: 'amount', operation: 'sum' }, + ], + }, + { + name: 'declined', + filter: { 'state.type': 'declined' }, + rollups: [ + { name: 'declinedCount', property: 'state.type', operation: 'count' }, + { name: 'declinedAmount', property: 'amount', operation: 'sum' }, + ], + }, + { + name: 'cancelled', + filter: { 'state.type': 'cancelled' }, + rollups: [ + { name: 'cancelledCount', property: 'state.type', operation: 'count' }, + { name: 'cancelledAmount', property: 'amount', operation: 'sum' }, + ], + }, +]; + +paymentSchema.statics.stats = async function () { + const results = await aggregateRollups({ + model: this, + rollupConfigs: rollupConfigs, + }); + + // Transform the results to match the expected format + return results; +}; + +paymentSchema.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; +}; + +paymentSchema.statics.recalculate = async function (payment, user) { + const paymentId = payment._id || payment; + if (!paymentId) { + return; + } + + // For payments, the amount is set directly + const amount = payment.amount || 0; + + const updateData = { + amount: parseFloat(amount).toFixed(2), + }; + + await editObject({ + model: this, + id: paymentId, + updateData, + user, + recalculate: false, + }); +}; + +// Add virtual id getter +paymentSchema.virtual('id').get(function () { + return this._id; +}); + +// Configure JSON serialization to include virtuals +paymentSchema.set('toJSON', { virtuals: true }); + +// Create and export the model +export const paymentModel = mongoose.model('payment', paymentSchema); diff --git a/src/database/schemas/finance/paymentpolicy.schema.js b/src/database/schemas/finance/paymentpolicy.schema.js new file mode 100644 index 0000000..6520018 --- /dev/null +++ b/src/database/schemas/finance/paymentpolicy.schema.js @@ -0,0 +1,25 @@ +import mongoose from 'mongoose'; +import { generateId } from '../../utils.js'; +import { marketplaceSyncMappingSchema } from '../sales/marketplaceMapping.schema.js'; + +const paymentPolicySchema = new mongoose.Schema( + { + _reference: { type: String, default: () => generateId()() }, + name: { type: String, required: true }, + description: { type: String, required: false }, + immediatePay: { type: Boolean, required: false, default: true }, + paymentInstructions: { type: String, required: false }, + marketplaces: { type: [marketplaceSyncMappingSchema()], default: [] }, + }, + { timestamps: true } +); + +paymentPolicySchema.index({ name: 'text', description: 'text', paymentInstructions: 'text' }); + +paymentPolicySchema.virtual('id').get(function () { + return this._id; +}); + +paymentPolicySchema.set('toJSON', { virtuals: true }); + +export const paymentPolicyModel = mongoose.model('paymentPolicy', paymentPolicySchema); diff --git a/src/database/schemas/finance/taxrecord.schema.js b/src/database/schemas/finance/taxrecord.schema.js new file mode 100644 index 0000000..d19c141 --- /dev/null +++ b/src/database/schemas/finance/taxrecord.schema.js @@ -0,0 +1,69 @@ +import mongoose from 'mongoose'; +import { generateId } from '../../utils.js'; +import { aggregateRollups, aggregateRollupsHistory } from '../../database.js'; +const { Schema } = mongoose; + +const taxRecordSchema = new Schema( + { + _reference: { type: String, default: () => generateId()() }, + taxRate: { type: Schema.Types.ObjectId, ref: 'taxRate', required: true }, + transactionType: { + type: String, + required: true, + enum: ['purchaseOrder', 'salesOrder', 'other'], + }, + transaction: { type: Schema.Types.ObjectId, refPath: 'transactionType', required: true }, + amount: { type: Number, required: true }, + taxAmount: { type: Number, required: true }, + transactionDate: { required: true, type: Date, default: Date.now }, + }, + { timestamps: true } +); + +taxRecordSchema.index({ transactionType: 'text' }); + +const rollupConfigs = [ + { + name: 'total', + filter: {}, + rollups: [ + { name: 'count', property: 'amount', operation: 'count' }, + { name: 'amount', property: 'amount', operation: 'sum' }, + { name: 'taxAmount', property: 'taxAmount', operation: 'sum' }, + ], + }, + { + name: 'salesOrder', + filter: { transactionType: 'salesOrder' }, + rollups: [{ name: 'taxAmount', property: 'taxAmount', operation: 'sum' }], + }, + { + name: 'purchaseOrder', + filter: { transactionType: 'purchaseOrder' }, + rollups: [{ name: 'taxAmount', property: 'taxAmount', operation: 'sum' }], + }, +]; + +taxRecordSchema.statics.stats = async function () { + return aggregateRollups({ + model: this, + rollupConfigs, + }); +}; + +taxRecordSchema.statics.history = async function (from, to) { + return aggregateRollupsHistory({ + model: this, + startDate: from, + endDate: to, + rollupConfigs, + }); +}; + +taxRecordSchema.virtual('id').get(function () { + return this._id; +}); + +taxRecordSchema.set('toJSON', { virtuals: true }); + +export const taxRecordModel = mongoose.model('taxRecord', taxRecordSchema); diff --git a/src/database/schemas/inventory/filamentstock.schema.js b/src/database/schemas/inventory/filamentstock.schema.js new file mode 100644 index 0000000..4033d94 --- /dev/null +++ b/src/database/schemas/inventory/filamentstock.schema.js @@ -0,0 +1,136 @@ +import mongoose from 'mongoose'; +import { generateId } from '../../utils.js'; +const { Schema } = mongoose; +import { aggregateRollups, aggregateRollupsHistory } from '../../database.js'; +import { updateDraftStockAuditCurrents } from './stockaudit.schema.js'; + +const toId = (value) => { + if (value == null) return null; + if (typeof value === 'object' && value._id) return String(value._id); + return String(value); +}; + +// Define the main filamentStock schema +const filamentStockSchema = new Schema( + { + _reference: { type: String, default: () => generateId()() }, + state: { + type: { type: String, required: true, default: 'draft' }, + progress: { type: Number, required: false }, + }, + postedAt: { type: Date, required: false }, + startingWeight: { + net: { type: Number, required: true }, + gross: { type: Number, required: true }, + }, + currentWeight: { + net: { type: Number, required: true }, + gross: { type: Number, required: true }, + }, + history: [ + { + currentWeight: { + net: { type: Number, required: true }, + gross: { type: Number, required: true }, + }, + timestamp: { type: Date, default: Date.now }, + }, + ], + filament: { type: mongoose.Schema.Types.ObjectId, ref: 'filament', required: true }, + filamentSku: { type: mongoose.Schema.Types.ObjectId, ref: 'filamentSku', required: true }, + stockLocation: { + type: mongoose.Schema.Types.ObjectId, + ref: 'stockLocation', + required: false, + }, + }, + { timestamps: true } +); + +filamentStockSchema.index({ 'state.type': 'text' }); + +filamentStockSchema.pre('validate', async function () { + if (!this.filament && this.filamentSku) { + const sku = await mongoose + .model('filamentSku') + .findById(this.filamentSku) + .select('filament') + .lean(); + if (sku?.filament) this.filament = sku.filament; + } +}); + +const rollupConfigs = [ + { + name: 'totalCurrentWeight', + filter: {}, + rollups: [{ name: 'totalCurrentWeight', property: 'currentWeight.net', operation: 'sum' }], + }, + { + name: 'draft', + filter: { 'state.type': 'draft' }, + rollups: [{ name: 'draft', property: 'state.type', operation: 'count' }], + }, + { + name: 'unconsumed', + filter: { 'state.type': 'unconsumed' }, + rollups: [{ name: 'unconsumed', property: 'state.type', operation: 'count' }], + }, + { + name: 'used', + filter: { 'state.type': 'used' }, + rollups: [{ name: 'used', property: 'state.type', operation: 'count' }], + }, + { + name: 'consumed', + filter: { 'state.type': 'consumed' }, + rollups: [{ name: 'consumed', property: 'state.type', operation: 'count' }], + }, +]; + +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; +}; + +filamentStockSchema.statics.recalculate = async function (filamentStock, user) { + const itemSkuId = toId(filamentStock?.filamentSku); + const stockLocationId = toId(filamentStock?.stockLocation); + if (!itemSkuId || !stockLocationId) return; + + await updateDraftStockAuditCurrents({ + itemType: 'filament', + itemSkuId, + stockLocationId, + user, + }); + + if (filamentStock.state?.type === 'draft' || filamentStock.state?.type === 'consumed') return; +}; + +// Add virtual id getter +filamentStockSchema.virtual('id').get(function () { + return this._id; +}); + +// Configure JSON serialization to include virtuals +filamentStockSchema.set('toJSON', { virtuals: true }); + +// Create and export the model +export const filamentStockModel = mongoose.model('filamentStock', filamentStockSchema); diff --git a/src/database/schemas/inventory/orderitem.schema.js b/src/database/schemas/inventory/orderitem.schema.js new file mode 100644 index 0000000..f93d0da --- /dev/null +++ b/src/database/schemas/inventory/orderitem.schema.js @@ -0,0 +1,319 @@ +import mongoose from 'mongoose'; +import { purchaseOrderModel } from './purchaseorder.schema.js'; +import { salesOrderModel } from '../sales/salesorder.schema.js'; +import { taxRateModel } from '../management/taxrate.schema.js'; +import { filamentModel } from '../management/filament.schema.js'; +import { filamentSkuModel } from '../management/filamentsku.schema.js'; +import { partModel } from '../management/part.schema.js'; +import { partSkuModel } from '../management/partsku.schema.js'; +import { productModel } from '../management/product.schema.js'; +import { productSkuModel } from '../management/productsku.schema.js'; +import { + aggregateRollups, + aggregateRollupsHistory, + editObject, + getObject, +} from '../../database.js'; +import { generateId } from '../../utils.js'; +import { amountWithTax, resolveTaxRate } from '../../tax.js'; +const { Schema } = mongoose; + +const skuModelsByItemType = { + filament: filamentSkuModel, + part: partSkuModel, + product: productSkuModel, +}; + +const parentModelsByItemType = { + filament: filamentModel, + part: partModel, + product: productModel, +}; + +const orderItemSchema = new Schema( + { + _reference: { type: String, default: () => generateId()() }, + orderType: { type: String, required: true }, + name: { type: String, required: true }, + state: { + type: { type: String, required: true, default: 'draft' }, + }, + order: { type: Schema.Types.ObjectId, refPath: 'orderType', required: true }, + itemType: { type: String, required: true }, + item: { type: Schema.Types.ObjectId, refPath: 'itemType', required: false }, + sku: { + type: Schema.Types.ObjectId, + ref: function () { + return ['filament', 'part', 'product'].includes(this.itemType) + ? this.itemType + 'Sku' + : null; + }, + required: false, + }, + syncAmount: { type: String, required: false, default: null }, + itemAmount: { type: Number, required: true }, + quantity: { type: Number, required: true }, + totalAmount: { type: Number, required: true }, + taxRate: { type: Schema.Types.ObjectId, ref: 'taxRate', required: false }, + totalAmountWithTax: { type: Number, required: true }, + invoicedAmountWithTax: { type: Number, required: false, default: 0 }, + invoicedAmount: { type: Number, required: false, default: 0 }, + invoicedQuantity: { type: Number, required: false, default: 0 }, + invoicedAmountRemaining: { type: Number, required: false, default: 0 }, + invoicedAmountWithTaxRemaining: { type: Number, required: false, default: 0 }, + invoicedQuantityRemaining: { type: Number, required: false, default: 0 }, + timestamp: { type: Date, default: Date.now }, + shipment: { type: Schema.Types.ObjectId, ref: 'shipment', required: false }, + listing: { type: Schema.Types.ObjectId, ref: 'listing', required: false }, + listingVarient: { type: Schema.Types.ObjectId, ref: 'listingVarient', required: false }, + externalReference: { type: String, required: false }, + orderedAt: { type: Date, required: false }, + receivedAt: { type: Date, required: false }, + }, + { timestamps: true } +); + +orderItemSchema.index({ name: 'text', itemType: 'text', orderType: 'text' }); +orderItemSchema.index({ order: 1, externalReference: 1 }, { unique: true, sparse: true }); + +const rollupConfigs = [ + { + name: 'draft', + filter: { 'state.type': 'draft' }, + rollups: [{ name: 'draft', property: 'state.type', operation: 'count' }], + }, + { + name: 'ordered', + filter: { 'state.type': 'ordered' }, + rollups: [{ name: 'ordered', property: 'state.type', operation: 'count' }], + }, + { + name: 'shipped', + filter: { 'state.type': 'shipped' }, + rollups: [{ name: 'shipped', property: 'state.type', operation: 'count' }], + }, + { + name: 'received', + filter: { 'state.type': 'received' }, + rollups: [{ name: 'received', property: 'state.type', operation: 'count' }], + }, + { + name: 'shippedValue', + filter: { 'state.type': 'shipped' }, + rollups: [{ name: 'totalAmountWithTax', property: 'totalAmountWithTax', operation: 'sum' }], + }, + { + name: 'receivedValue', + filter: { 'state.type': 'received' }, + rollups: [{ name: 'totalAmountWithTax', property: 'totalAmountWithTax', operation: 'sum' }], + }, +]; + +orderItemSchema.statics.stats = async function () { + const results = await aggregateRollups({ + model: this, + baseFilter: {}, + rollupConfigs: rollupConfigs, + }); + + // Transform the results to match the expected format + return results; +}; + +orderItemSchema.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; +}; + +orderItemSchema.statics.recalculate = async function (orderItem, user) { + if (orderItem.orderType !== 'purchaseOrder' && orderItem.orderType !== 'salesOrder') { + return; + } + + const orderId = orderItem.order?._id || orderItem.order; + if (!orderId) { + return; + } + + // If SKU present and syncAmount is set, check if override is on for the price mode and use that price instead + let effectiveItemAmount = orderItem.itemAmount; + const syncAmount = orderItem.syncAmount; + const skuId = orderItem.sku?._id || orderItem.sku; + const itemType = orderItem.itemType; + if (syncAmount && skuId && itemType && ['filament', 'part', 'product'].includes(itemType)) { + const skuModel = skuModelsByItemType[itemType]; + const parentModel = parentModelsByItemType[itemType]; + if (skuModel && parentModel) { + const sku = await getObject({ + model: skuModel, + id: skuId, + cached: true, + }); + if (sku) { + const parentId = + sku.part?._id || + sku.part || + sku.product?._id || + sku.product || + sku.filament?._id || + sku.filament; + if (syncAmount === 'itemCost') { + if (sku.overrideCost && sku.cost != null) { + effectiveItemAmount = sku.cost; + } else if (parentId) { + const parent = await getObject({ + model: parentModel, + id: parentId, + cached: true, + }); + if (parent && parent.cost != null) { + effectiveItemAmount = parent.cost; + } + } + } else if (syncAmount === 'itemPrice' && itemType !== 'filament') { + if (sku.overridePrice && sku.price != null) { + effectiveItemAmount = sku.price; + } else if (parentId) { + const parent = await getObject({ + model: parentModel, + id: parentId, + cached: true, + }); + if (parent && parent.price != null) { + effectiveItemAmount = parent.price; + } + } + } + } + } + } + + const taxRate = await resolveTaxRate(orderItem.taxRate, getObject, taxRateModel); + + const orderTotalAmount = effectiveItemAmount * orderItem.quantity; + const orderTotalAmountWithTax = amountWithTax(orderTotalAmount, taxRate); + + const orderItemUpdateData = { + totalAmount: orderTotalAmount, + totalAmountWithTax: orderTotalAmountWithTax, + invoicedAmountRemaining: orderTotalAmount - orderItem.invoicedAmount, + invoicedAmountWithTaxRemaining: orderTotalAmountWithTax - orderItem.invoicedAmountWithTax, + invoicedQuantityRemaining: orderItem.quantity - orderItem.invoicedQuantity, + }; + if (effectiveItemAmount !== orderItem.itemAmount) { + orderItemUpdateData.itemAmount = effectiveItemAmount; + orderItem.itemAmount = effectiveItemAmount; + } + + await editObject({ + model: this, + id: orderItem._id, + updateData: orderItemUpdateData, + user, + recalculate: false, + }); + + 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', + }, + ], + }, + { + name: 'overallCount', + rollups: [{ name: 'overallCount', property: '_id', operation: 'count' }], + }, + ...rollupConfigs, + ], + }); + + const totals = rollupResults.orderTotals || {}; + const totalAmount = totals.totalAmount.sum?.toFixed(2) || 0; + const totalAmountWithTax = totals.totalAmountWithTax.sum?.toFixed(2) || 0; + + const orderModel = orderItem.orderType === 'purchaseOrder' ? purchaseOrderModel : salesOrderModel; + const order = await getObject({ + model: orderModel, + id: orderId, + cached: true, + }); + + const grandTotalAmount = + parseFloat(totalAmountWithTax || 0) + parseFloat(order.shippingAmountWithTax || 0); + + var updateData = { + totalAmount: parseFloat(totalAmount).toFixed(2), + totalAmountWithTax: parseFloat(totalAmountWithTax).toFixed(2), + totalTaxAmount: parseFloat((totalAmountWithTax - totalAmount).toFixed(2)), + grandTotalAmount: parseFloat(grandTotalAmount).toFixed(2), + }; + + const overallCount = rollupResults.overallCount.count || 0; + const shippedCount = rollupResults.shipped.count || 0; + const receivedCount = rollupResults.received.count || 0; + + if (orderItem.orderType === 'purchaseOrder') { + if (shippedCount > 0 && shippedCount < overallCount) { + updateData = { ...updateData, state: { type: 'partiallyShipped' } }; + } + if (shippedCount > 0 && shippedCount == overallCount) { + updateData = { ...updateData, state: { type: 'shipped' } }; + } + if (receivedCount > 0 && receivedCount < overallCount) { + updateData = { ...updateData, state: { type: 'partiallyReceived' } }; + } + if (receivedCount > 0 && receivedCount == overallCount) { + updateData = { ...updateData, state: { type: 'received' } }; + } + } else { + if (shippedCount > 0 && shippedCount < overallCount) { + updateData = { ...updateData, state: { type: 'partiallyShipped' } }; + } + if (shippedCount > 0 && shippedCount == overallCount) { + updateData = { ...updateData, state: { type: 'shipped' } }; + } + if (receivedCount > 0 && receivedCount < overallCount) { + updateData = { ...updateData, state: { type: 'partiallyDelivered' } }; + } + if (receivedCount > 0 && receivedCount == overallCount) { + updateData = { ...updateData, state: { type: 'delivered' } }; + } + } + + await editObject({ + model: orderModel, + id: orderId, + updateData: updateData, + user, + }); +}; + +// Add virtual id getter +orderItemSchema.virtual('id').get(function () { + return this._id; +}); + +// Configure JSON serialization to include virtuals +orderItemSchema.set('toJSON', { virtuals: true }); + +// Create and export the model +export const orderItemModel = mongoose.model('orderItem', orderItemSchema); diff --git a/src/database/schemas/inventory/partstock.schema.js b/src/database/schemas/inventory/partstock.schema.js new file mode 100644 index 0000000..7e32b10 --- /dev/null +++ b/src/database/schemas/inventory/partstock.schema.js @@ -0,0 +1,134 @@ +import mongoose from 'mongoose'; +import { generateId } from '../../utils.js'; +import { aggregateRollups, aggregateRollupsHistory, editObject } from '../../database.js'; +import { updateDraftStockAuditCurrents } from './stockaudit.schema.js'; + +const toId = (value) => { + if (value == null) return null; + if (typeof value === 'object' && value._id) return String(value._id); + return String(value); +}; + +// Define the main partStock schema +const partStockSchema = new mongoose.Schema( + { + _reference: { type: String, default: () => generateId()() }, + state: { + type: { type: String, required: true, default: 'draft' }, + progress: { type: Number, required: false }, + }, + postedAt: { type: Date, required: false }, + part: { type: mongoose.Schema.Types.ObjectId, ref: 'part', required: true }, + partSku: { type: mongoose.Schema.Types.ObjectId, ref: 'partSku', required: true }, + stockLocation: { + type: mongoose.Schema.Types.ObjectId, + ref: 'stockLocation', + required: false, + }, + currentQuantity: { type: Number, required: true }, + history: [ + { + currentQuantity: { type: Number, required: true }, + timestamp: { type: Date, default: Date.now }, + }, + ], + }, + { timestamps: true } +); + +partStockSchema.index({ 'state.type': 'text' }); + +partStockSchema.pre('validate', async function () { + if (!this.part && this.partSku) { + const sku = await mongoose.model('partSku').findById(this.partSku).select('part').lean(); + if (sku?.part) this.part = sku.part; + } +}); + +const rollupConfigs = [ + { + name: 'totalCurrentQuantity', + filter: {}, + rollups: [{ name: 'totalCurrentQuantity', property: 'currentQuantity', operation: 'sum' }], + }, + { + name: 'draft', + filter: { 'state.type': 'draft' }, + rollups: [{ name: 'draft', property: 'state.type', operation: 'count' }], + }, + { + name: 'new', + filter: { 'state.type': 'new' }, + rollups: [{ name: 'new', property: 'state.type', operation: 'count' }], + }, + { + name: 'used', + filter: { 'state.type': 'used' }, + rollups: [{ name: 'used', property: 'state.type', operation: 'count' }], + }, + { + name: 'consumed', + filter: { 'state.type': 'consumed' }, + rollups: [{ name: 'consumed', property: 'state.type', operation: 'count' }], + }, +]; + +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; +}; + +partStockSchema.statics.recalculate = async function (partStock, user) { + if (!partStock?._id) return; + + const itemSkuId = toId(partStock.partSku); + const stockLocationId = toId(partStock.stockLocation); + if (itemSkuId && stockLocationId) { + await updateDraftStockAuditCurrents({ + itemType: 'part', + itemSkuId, + stockLocationId, + user, + }); + } + + if (partStock.state?.type === 'draft' || partStock.state?.type === 'consumed') return; + if ((Number(partStock.currentQuantity) || 0) > 0) return; + + await editObject({ + model: this, + id: partStock._id, + updateData: { + state: { ...(partStock.state || {}), type: 'consumed', progress: 0 }, + }, + user, + recalculate: false, + }); +}; + +// Add virtual id getter +partStockSchema.virtual('id').get(function () { + return this._id; +}); + +// Configure JSON serialization to include virtuals +partStockSchema.set('toJSON', { virtuals: true }); + +// Create and export the model +export const partStockModel = mongoose.model('partStock', partStockSchema); diff --git a/src/database/schemas/inventory/productstock.schema.js b/src/database/schemas/inventory/productstock.schema.js new file mode 100644 index 0000000..e3fead2 --- /dev/null +++ b/src/database/schemas/inventory/productstock.schema.js @@ -0,0 +1,218 @@ +import mongoose from 'mongoose'; +import { generateId } from '../../utils.js'; +const { Schema } = mongoose; +import { aggregateRollups, aggregateRollupsHistory, editObject } from '../../database.js'; +import { updateDraftStockAuditCurrents } from './stockaudit.schema.js'; + +const partStockListItemSchema = new Schema({ + part: { type: Schema.Types.ObjectId, ref: 'part', required: true }, + partSku: { type: Schema.Types.ObjectId, ref: 'partSku', required: true }, + partStocks: [{ type: Schema.Types.ObjectId, ref: 'partStock', required: false }], + requiredQuantity: { type: Number, required: true }, +}); + +partStockListItemSchema.virtual('remainingQuantity').get(function () { + const required = this.requiredQuantity || 0; + const stocks = Array.isArray(this.partStocks) ? this.partStocks : []; + const available = stocks.reduce( + (sum, stock) => sum + (Number(stock?.currentQuantity) || 0), + 0 + ); + return required - available; +}); + +partStockListItemSchema.set('toJSON', { virtuals: true }); + +const toId = (value) => { + if (value == null) return null; + if (typeof value === 'object' && value._id) return String(value._id); + return String(value); +}; + +// Define the main productStock schema - tracks assembled products consisting of part stocks +const productStockSchema = new Schema( + { + _reference: { type: String, default: () => generateId()() }, + state: { + type: { type: String, required: true, default: 'draft' }, + progress: { type: Number, required: false }, + }, + postedAt: { type: Date, required: false }, + product: { type: mongoose.Schema.Types.ObjectId, ref: 'product', required: true }, + productSku: { type: mongoose.Schema.Types.ObjectId, ref: 'productSku', required: true }, + stockLocation: { + type: mongoose.Schema.Types.ObjectId, + ref: 'stockLocation', + required: false, + }, + currentQuantity: { type: Number, required: true }, + history: [ + { + currentQuantity: { type: Number, required: true }, + timestamp: { type: Date, default: Date.now }, + }, + ], + partStockList: [partStockListItemSchema], + }, + { timestamps: true } +); + +productStockSchema.index({ 'state.type': 'text' }); + +productStockSchema.pre('validate', async function () { + if (!this.product && this.productSku) { + const sku = await mongoose.model('productSku').findById(this.productSku).select('product').lean(); + if (sku?.product) this.product = sku.product; + } + if (this.partStockList?.length) { + for (const item of this.partStockList) { + if (!item.part && item.partSku) { + const sku = await mongoose.model('partSku').findById(item.partSku).select('part').lean(); + if (sku?.part) item.part = sku.part; + } + } + } +}); + +const rollupConfigs = [ + { + name: 'totalCurrentQuantity', + filter: {}, + rollups: [{ name: 'totalCurrentQuantity', property: 'currentQuantity', operation: 'sum' }], + }, + { + name: 'draft', + filter: { 'state.type': 'draft' }, + rollups: [{ name: 'draft', property: 'state.type', operation: 'count' }], + }, + { + name: 'new', + filter: { 'state.type': 'new' }, + rollups: [{ name: 'new', property: 'state.type', operation: 'count' }], + }, + { + name: 'used', + filter: { 'state.type': 'used' }, + rollups: [{ name: 'used', property: 'state.type', operation: 'count' }], + }, + { + name: 'consumed', + filter: { 'state.type': 'consumed' }, + rollups: [{ name: 'consumed', property: 'state.type', operation: 'count' }], + }, +]; + +productStockSchema.statics.stats = async function () { + const results = await aggregateRollups({ + model: this, + rollupConfigs: rollupConfigs, + }); + + return results; +}; + +productStockSchema.statics.history = async function (from, to) { + const results = await aggregateRollupsHistory({ + model: this, + startDate: from, + endDate: to, + rollupConfigs: rollupConfigs, + }); + + return results; +}; + +productStockSchema.statics.recalculate = async function (productStock, user) { + const productSkuId = toId(productStock?.productSku); + const stockLocationId = toId(productStock?.stockLocation); + if (productSkuId && stockLocationId) { + await updateDraftStockAuditCurrents({ + itemType: 'product', + itemSkuId: productSkuId, + stockLocationId, + user, + }); + } + + if ( + productStock?._id && + productStock.state?.type !== 'draft' && + productStock.state?.type !== 'consumed' && + (Number(productStock.currentQuantity) || 0) <= 0 + ) { + await editObject({ + model: this, + id: productStock._id, + updateData: { + state: { ...(productStock.state || {}), type: 'consumed', progress: 0 }, + }, + user, + recalculate: false, + }); + } + + if (!productSkuId || !stockLocationId) { + return; + } + + let productId = toId(productStock?.product) || toId(productStock?.productSku?.product); + if (!productId) { + const productSku = await mongoose.model('productSku').findById(productSkuId).select('product').lean(); + productId = toId(productSku?.product); + } + if (!productId) { + return; + } + + const rollupResults = await aggregateRollups({ + model: this, + baseFilter: { + productSku: new mongoose.Types.ObjectId(productSkuId), + stockLocation: new mongoose.Types.ObjectId(stockLocationId), + }, + rollupConfigs: [ + { + name: 'stockQuantity', + rollups: [{ name: 'stockQuantity', property: 'currentQuantity', operation: 'sum' }], + }, + ], + }); + const stockQuantity = rollupResults.stockQuantity?.sum || 0; + + const listingVarientModel = mongoose.model('listingVarient'); + const varients = await listingVarientModel + .find({ productSku: productSkuId }) + .populate('listing') + .lean(); + + for (const varient of varients) { + const varientProductId = + toId(varient.product) || toId(varient.listing?.product); + const varientLocationId = toId(varient.listing?.stockLocation); + if (varientProductId !== productId || varientLocationId !== stockLocationId) { + continue; + } + if (varient.stockQuantity === stockQuantity) { + await listingVarientModel.recalculate(varient, user); + continue; + } + + await editObject({ + model: listingVarientModel, + id: varient._id, + updateData: { stockQuantity }, + user, + }); + } +}; + +// Add virtual id getter +productStockSchema.virtual('id').get(function () { + return this._id; +}); + +// Configure JSON serialization to include virtuals +productStockSchema.set('toJSON', { virtuals: true }); + +// Create and export the model +export const productStockModel = mongoose.model('productStock', productStockSchema); diff --git a/src/database/schemas/inventory/purchaseorder.schema.js b/src/database/schemas/inventory/purchaseorder.schema.js new file mode 100644 index 0000000..d6ff0dd --- /dev/null +++ b/src/database/schemas/inventory/purchaseorder.schema.js @@ -0,0 +1,123 @@ +import mongoose from 'mongoose'; +import { generateId } from '../../utils.js'; +const { Schema } = mongoose; +import { aggregateRollups, aggregateRollupsHistory } from '../../database.js'; + +const purchaseOrderSchema = new Schema( + { + _reference: { type: String, default: () => generateId()() }, + totalAmount: { type: Number, required: true, default: 0 }, + totalAmountWithTax: { type: Number, required: true, default: 0 }, + shippingAmount: { type: Number, required: true, default: 0 }, + shippingAmountWithTax: { type: Number, required: true, default: 0 }, + grandTotalAmount: { type: Number, required: true, default: 0 }, + totalTaxAmount: { type: Number, required: true, default: 0 }, + timestamp: { type: Date, default: Date.now }, + vendor: { type: Schema.Types.ObjectId, ref: 'vendor', required: true }, + state: { + type: { type: String, required: true, default: 'draft' }, + }, + postedAt: { type: Date, required: false }, + acknowledgedAt: { type: Date, required: false }, + cancelledAt: { type: Date, required: false }, + completedAt: { type: Date, required: false }, + }, + { timestamps: true } +); + +purchaseOrderSchema.index({ 'state.type': 'text' }); + +const rollupConfigs = [ + { + name: 'draft', + filter: { 'state.type': 'draft' }, + rollups: [{ name: 'draft', property: 'state.type', operation: 'count' }], + }, + { + name: 'sent', + filter: { 'state.type': 'sent' }, + rollups: [{ name: 'sent', property: 'state.type', operation: 'count' }], + }, + { + name: 'acknowledged', + filter: { 'state.type': 'acknowledged' }, + rollups: [{ name: 'acknowledged', property: 'state.type', operation: 'count' }], + }, + { + name: 'partiallyShipped', + filter: { 'state.type': 'partiallyShipped' }, + rollups: [{ name: 'partiallyShipped', property: 'state.type', operation: 'count' }], + }, + { + name: 'shipped', + filter: { 'state.type': 'shipped' }, + rollups: [{ name: 'shipped', property: 'state.type', operation: 'count' }], + }, + { + name: 'partiallyReceived', + filter: { 'state.type': 'partiallyReceived' }, + rollups: [{ name: 'partiallyReceived', property: 'state.type', operation: 'count' }], + }, + { + name: 'received', + filter: { 'state.type': 'received' }, + rollups: [{ name: 'received', property: 'state.type', operation: 'count' }], + }, + { + name: 'cancelled', + filter: { 'state.type': 'cancelled' }, + rollups: [{ name: 'cancelled', property: 'state.type', operation: 'count' }], + }, + { + name: 'completed', + filter: { 'state.type': 'completed' }, + rollups: [{ name: 'completed', property: 'state.type', operation: 'count' }], + }, + { + name: 'awaitingReceiptValue', + filter: { + 'state.type': { + $in: ['sent', 'acknowledged', 'partiallyShipped', 'shipped', 'partiallyReceived'], + }, + }, + rollups: [{ name: 'grandTotalAmount', property: 'grandTotalAmount', operation: 'sum' }], + }, + { + name: 'receivedValue', + filter: { 'state.type': { $in: ['received', 'completed'] } }, + rollups: [{ name: 'grandTotalAmount', property: 'grandTotalAmount', operation: 'sum' }], + }, +]; + +purchaseOrderSchema.statics.stats = async function () { + const results = await aggregateRollups({ + model: this, + rollupConfigs: rollupConfigs, + }); + + // Transform the results to match the expected format + return results; +}; + +purchaseOrderSchema.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 +purchaseOrderSchema.virtual('id').get(function () { + return this._id; +}); + +// Configure JSON serialization to include virtuals +purchaseOrderSchema.set('toJSON', { virtuals: true }); + +// Create and export the model +export const purchaseOrderModel = mongoose.model('purchaseOrder', purchaseOrderSchema); diff --git a/src/database/schemas/inventory/shipment.schema.js b/src/database/schemas/inventory/shipment.schema.js new file mode 100644 index 0000000..8f63b69 --- /dev/null +++ b/src/database/schemas/inventory/shipment.schema.js @@ -0,0 +1,174 @@ +import mongoose from 'mongoose'; +import { generateId } from '../../utils.js'; +const { Schema } = mongoose; +import { purchaseOrderModel } from './purchaseorder.schema.js'; +import { salesOrderModel } from '../sales/salesorder.schema.js'; +import { taxRateModel } from '../management/taxrate.schema.js'; +import { + aggregateRollups, + aggregateRollupsHistory, + editObject, + getObject, +} from '../../database.js'; +import { amountWithTax, resolveTaxRate } from '../../tax.js'; + +const shipmentSchema = new Schema( + { + _reference: { type: String, default: () => generateId()() }, + orderType: { type: String, required: true }, + order: { type: Schema.Types.ObjectId, refPath: 'orderType', required: true }, + courierService: { type: Schema.Types.ObjectId, ref: 'courierService', required: false }, + trackingNumber: { type: String, required: false }, + externalReference: { type: String, required: false }, + amount: { type: Number, required: true }, + amountWithTax: { type: Number, required: true }, + taxRate: { type: Schema.Types.ObjectId, ref: 'taxRate', required: false }, + invoicedAmount: { type: Number, required: false, default: 0 }, + invoicedAmountWithTax: { type: Number, required: false, default: 0 }, + invoicedAmountRemaining: { type: Number, required: false, default: 0 }, + invoicedAmountWithTaxRemaining: { type: Number, required: false, default: 0 }, + shippedAt: { type: Date, required: false }, + expectedAt: { type: Date, required: false }, + deliveredAt: { type: Date, required: false }, + cancelledAt: { type: Date, required: false }, + state: { + type: { + type: String, + required: true, + }, + }, + }, + { timestamps: true } +); + +shipmentSchema.index({ trackingNumber: 'text', orderType: 'text' }); +shipmentSchema.index({ order: 1, externalReference: 1 }, { unique: true, sparse: true }); + +const rollupConfigs = [ + { + name: 'draft', + filter: { 'state.type': 'draft' }, + rollups: [{ name: 'draft', property: 'state.type', operation: 'count' }], + }, + { + name: 'planned', + filter: { 'state.type': 'planned' }, + rollups: [{ name: 'planned', property: 'state.type', operation: 'count' }], + }, + { + name: 'shipped', + filter: { 'state.type': 'shipped' }, + rollups: [{ name: 'shipped', property: 'state.type', operation: 'count' }], + }, + { + name: 'delivered', + filter: { 'state.type': 'delivered' }, + rollups: [{ name: 'delivered', property: 'state.type', operation: 'count' }], + }, + { + name: 'cancelled', + filter: { 'state.type': 'cancelled' }, + rollups: [{ name: 'cancelled', property: 'state.type', operation: 'count' }], + }, + { + name: 'inTransitValue', + filter: { 'state.type': 'shipped' }, + rollups: [{ name: 'amountWithTax', property: 'amountWithTax', operation: 'sum' }], + }, +]; + +shipmentSchema.statics.stats = async function () { + return aggregateRollups({ + model: this, + rollupConfigs, + }); +}; + +shipmentSchema.statics.history = async function (from, to) { + return aggregateRollupsHistory({ + model: this, + startDate: from, + endDate: to, + rollupConfigs, + }); +}; + +shipmentSchema.statics.recalculate = async function (shipment, user) { + if (shipment.orderType !== 'purchaseOrder' && shipment.orderType !== 'salesOrder') { + return; + } + + const orderId = shipment.order?._id || shipment.order; + if (!orderId) { + return; + } + + const taxRate = await resolveTaxRate(shipment.taxRate, getObject, taxRateModel); + + const amountWithTaxValue = amountWithTax(shipment.amount || 0, taxRate); + await editObject({ + model: shipmentModel, + id: shipment._id, + updateData: { + amountWithTax: amountWithTaxValue, + invoicedAmountRemaining: shipment.amount - (shipment.invoicedAmount || 0), + invoicedAmountWithTaxRemaining: + amountWithTaxValue - (shipment.invoicedAmountWithTax || 0), + }, + user, + recalculate: false, + }); + + const rollupResults = await aggregateRollups({ + model: this, + baseFilter: { + order: new mongoose.Types.ObjectId(orderId), + orderType: shipment.orderType, + }, + rollupConfigs: [ + { + name: 'shipmentTotals', + rollups: [ + { name: 'amount', property: 'amount', operation: 'sum' }, + { name: 'amountWithTax', property: 'amountWithTax', operation: 'sum' }, + ], + }, + ], + }); + + const totals = rollupResults.shipmentTotals || {}; + const totalShippingAmount = totals.amount.sum?.toFixed(2) || 0; + const totalShippingAmountWithTax = totals.amountWithTax.sum?.toFixed(2) || 0; + + const orderModel = shipment.orderType === 'purchaseOrder' ? purchaseOrderModel : salesOrderModel; + const order = await getObject({ + model: orderModel, + id: orderId, + cached: true, + }); + + const grandTotalAmount = + parseFloat(order.totalAmountWithTax || 0) + parseFloat(totalShippingAmountWithTax || 0); + await editObject({ + model: orderModel, + id: orderId, + updateData: { + shippingAmount: parseFloat(totalShippingAmount).toFixed(2), + shippingAmountWithTax: parseFloat(totalShippingAmountWithTax).toFixed(2), + grandTotalAmount: parseFloat(grandTotalAmount).toFixed(2), + }, + user, + recalculate: false, + }); +}; + +// Add virtual id getter +shipmentSchema.virtual('id').get(function () { + return this._id; +}); + +// Configure JSON serialization to include virtuals +shipmentSchema.set('toJSON', { virtuals: true }); + +// Create and export the model +export const shipmentModel = mongoose.model('shipment', shipmentSchema); diff --git a/src/database/schemas/inventory/stockaudit.schema.js b/src/database/schemas/inventory/stockaudit.schema.js new file mode 100644 index 0000000..753833e --- /dev/null +++ b/src/database/schemas/inventory/stockaudit.schema.js @@ -0,0 +1,311 @@ +import mongoose from 'mongoose'; +import { generateId } from '../../utils.js'; +import { editObject, getObject } from '../../database.js'; +import { stockAuditLevelModel, normalizeAuditLevelLine } from '../management/stockauditlevel.schema.js'; +import { filamentModel } from '../management/filament.schema.js'; +import { filamentSkuModel } from '../management/filamentsku.schema.js'; +import { partModel } from '../management/part.schema.js'; +import { partSkuModel } from '../management/partsku.schema.js'; +import { productModel } from '../management/product.schema.js'; +import { productSkuModel } from '../management/productsku.schema.js'; + +const { Schema } = mongoose; + +const itemModelsByType = { + filament: filamentModel, + part: partModel, + product: productModel, +}; + +const skuModelsByType = { + filament: filamentSkuModel, + part: partSkuModel, + product: productSkuModel, +}; + +const parentFieldByType = { + filament: 'filament', + part: 'part', + product: 'product', +}; + +const toId = (value) => { + if (value == null) return null; + if (typeof value === 'object' && value._id != null) return String(value._id); + return String(value); +}; + +const stockAuditLineSchema = new Schema( + { + itemType: { + type: String, + enum: ['filament', 'part', 'product'], + required: true, + }, + item: { type: Schema.Types.ObjectId, refPath: 'auditLines.itemType', required: true }, + itemSku: { + type: Schema.Types.ObjectId, + ref: function () { + return ['filament', 'part', 'product'].includes(this.itemType) + ? this.itemType + 'Sku' + : null; + }, + required: true, + }, + current: { type: Number, required: true, default: 0 }, + actual: { type: Number, required: true, default: 0 }, + new: { type: Number, required: true, default: 0 }, + }, + { _id: true } +); + +const stockAuditSchema = new Schema( + { + _reference: { type: String, default: () => generateId()() }, + state: { + type: { type: String, required: true, default: 'draft' }, + progress: { type: Number, required: false }, + }, + auditLevel: { + type: Schema.Types.ObjectId, + ref: 'stockAuditLevel', + required: true, + }, + stockLocation: { + type: Schema.Types.ObjectId, + ref: 'stockLocation', + required: true, + }, + postedAt: { type: Date, required: false }, + auditLines: { type: [stockAuditLineSchema], default: [] }, + }, + { timestamps: true } +); + +stockAuditSchema.index({ 'state.type': 'text' }); + +stockAuditSchema.statics.stats = async function () { + const [draft, complete] = await Promise.all([ + this.countDocuments({ 'state.type': 'draft' }), + this.countDocuments({ 'state.type': 'complete' }), + ]); + return { + draft: { count: draft }, + complete: { count: complete }, + }; +}; + +stockAuditSchema.statics.history = async function () { + return []; +}; + +async function fetchItemsForLevelLine(levelLine) { + const itemType = levelLine.itemType; + const itemModel = itemModelsByType[itemType]; + if (!itemModel) return []; + + if (levelLine.allItems) { + return itemModel.find().select('_id').lean(); + } + + const itemId = toId(levelLine.item); + if (!itemId) return []; + return [{ _id: itemId }]; +} + +async function fetchSkusForLevelLine(levelLine, itemId) { + const itemType = levelLine.itemType; + const skuModel = skuModelsByType[itemType]; + const parentField = parentFieldByType[itemType]; + if (!skuModel || !parentField) return []; + + if (levelLine.allSkus) { + return skuModel.find({ [parentField]: itemId }).select('_id').lean(); + } + + const skuId = toId(levelLine.itemSku); + if (!skuId) return []; + return [{ _id: skuId }]; +} + +export async function getCurrentQuantityAtLocation(itemType, itemSkuId, stockLocationId) { + const locationId = toId(stockLocationId); + const skuId = toId(itemSkuId); + if (!locationId || !skuId) return 0; + + if (itemType === 'filament') { + const stocks = await mongoose + .model('filamentStock') + .find({ filamentSku: skuId, stockLocation: locationId }) + .select('currentWeight.net') + .lean(); + return stocks.reduce((sum, stock) => sum + (Number(stock.currentWeight?.net) || 0), 0); + } + + if (itemType === 'part') { + const stocks = await mongoose + .model('partStock') + .find({ partSku: skuId, stockLocation: locationId }) + .select('currentQuantity') + .lean(); + return stocks.reduce((sum, stock) => sum + (Number(stock.currentQuantity) || 0), 0); + } + + if (itemType === 'product') { + const stocks = await mongoose + .model('productStock') + .find({ productSku: skuId, stockLocation: locationId }) + .select('currentQuantity') + .lean(); + return stocks.reduce((sum, stock) => sum + (Number(stock.currentQuantity) || 0), 0); + } + + return 0; +} + +function buildAuditLineQuantities(current, actual) { + const currentVal = Number(current) || 0; + const actualVal = Number(actual) || 0; + return { + current: currentVal, + actual: actualVal, + new: actualVal, + }; +} + +function getExistingActual(line) { + if (line?.actual != null) return Number(line.actual); + if (line?.actualQuantity != null) return Number(line.actualQuantity); + return null; +} + +function buildAuditLineKey(itemType, itemId, itemSkuId) { + return `${itemType}:${toId(itemId)}:${toId(itemSkuId)}`; +} + +async function expandLevelLinesToAuditLines(levelLines, stockLocationId, existingLines = []) { + const existingByKey = new Map(); + for (const line of existingLines) { + existingByKey.set( + buildAuditLineKey(line.itemType, line.item, line.itemSku), + line + ); + } + + const auditLines = []; + + for (const levelLine of levelLines || []) { + const items = await fetchItemsForLevelLine(levelLine); + for (const item of items) { + const itemId = toId(item._id); + const skus = await fetchSkusForLevelLine(levelLine, itemId); + for (const sku of skus) { + const itemSkuId = toId(sku._id); + const current = await getCurrentQuantityAtLocation( + levelLine.itemType, + itemSkuId, + stockLocationId + ); + const key = buildAuditLineKey(levelLine.itemType, itemId, itemSkuId); + const existing = existingByKey.get(key); + const existingActual = getExistingActual(existing); + const actual = existingActual != null ? existingActual : current; + + auditLines.push({ + itemType: levelLine.itemType, + item: itemId, + itemSku: itemSkuId, + ...buildAuditLineQuantities(current, actual), + }); + } + } + } + + return auditLines; +} + +stockAuditSchema.statics.recalculate = async function (stockAudit, user) { + if (stockAudit?.state?.type !== 'draft') return; + + const auditLevelId = toId(stockAudit.auditLevel?._id ?? stockAudit.auditLevel); + const stockLocationId = toId(stockAudit.stockLocation?._id ?? stockAudit.stockLocation); + if (!auditLevelId || !stockLocationId) return; + + const auditLevel = await getObject({ + model: stockAuditLevelModel, + id: auditLevelId, + populate: [ + { path: 'auditLines.item' }, + { path: 'auditLines.itemSku' }, + ], + }); + + if (!auditLevel || auditLevel.error) return; + + const auditLines = await expandLevelLinesToAuditLines( + (auditLevel.auditLines || []).map((line) => normalizeAuditLevelLine(line)), + stockLocationId, + stockAudit.auditLines + ); + + await editObject({ + model: this, + id: stockAudit._id, + updateData: { auditLines }, + user, + recalculate: false, + }); +}; + +stockAuditSchema.virtual('id').get(function () { + return this._id; +}); + +stockAuditSchema.set('toJSON', { virtuals: true }); + +export const stockAuditModel = mongoose.model('stockAudit', stockAuditSchema); + +export async function updateDraftStockAuditCurrents({ + itemType, + itemSkuId, + stockLocationId, + user, +}) { + const skuId = toId(itemSkuId); + const locationId = toId(stockLocationId); + if (!itemType || !skuId || !locationId) return; + + const current = await getCurrentQuantityAtLocation(itemType, skuId, locationId); + + const draftAudits = await stockAuditModel + .find({ + 'state.type': 'draft', + stockLocation: locationId, + }) + .lean(); + + for (const audit of draftAudits) { + let changed = false; + const auditLines = (audit.auditLines || []).map((line) => { + if (line.itemType !== itemType || toId(line.itemSku) !== skuId) { + return line; + } + const lineCurrent = Number(line.current) || 0; + if (lineCurrent === current) { + return line; + } + changed = true; + return { ...line, current }; + }); + + if (!changed) continue; + + await editObject({ + model: stockAuditModel, + id: audit._id, + updateData: { auditLines }, + user, + recalculate: false, + }); + } +} diff --git a/src/database/schemas/inventory/stockevent.schema.js b/src/database/schemas/inventory/stockevent.schema.js new file mode 100644 index 0000000..4d886fb --- /dev/null +++ b/src/database/schemas/inventory/stockevent.schema.js @@ -0,0 +1,316 @@ +import mongoose from 'mongoose'; +import { generateId } from '../../utils.js'; +import { + getObject, + editObject, + aggregateRollups, + aggregateRollupsHistory, +} from '../../database.js'; +const { Schema } = mongoose; + +const parentStockModelNames = { + filamentStock: 'filamentStock', + partStock: 'partStock', + productStock: 'productStock', +}; + +const initialStockStates = { + filamentStock: 'unconsumed', + partStock: 'new', + productStock: 'new', +}; + +const getStartingAmount = (parentType, parentStock) => { + if (parentType === 'filamentStock') { + return parentStock.startingWeight?.net ?? 0; + } + + return parentStock.startingQuantity ?? 0; +}; + +const buildParentState = (parentType, parentStock, currentAmount, startingAmount) => { + if (parentStock.state?.type === 'draft') { + return undefined; + } + + const fullState = initialStockStates[parentType]; + if (!fullState) { + return undefined; + } + + if (currentAmount <= 0) { + return { ...parentStock.state, type: 'consumed', progress: 0 }; + } + + if (startingAmount <= 0) { + return undefined; + } + + const progress = currentAmount / startingAmount; + + if (currentAmount === startingAmount) { + return { ...parentStock.state, type: fullState, progress: 1 }; + } + + if (currentAmount < startingAmount) { + return { ...parentStock.state, type: 'used', progress }; + } + + return { ...parentStock.state, type: fullState, progress: 1 }; +}; + +const getStockEventTotal = async (parentId, parentType) => { + if (!parentId) return null; + + const objectId = + parentId instanceof mongoose.Types.ObjectId ? parentId : new mongoose.Types.ObjectId(parentId); + + const [result] = await mongoose + .model('stockEvent') + .aggregate([ + { $match: { parent: objectId, parentType } }, + { $group: { _id: null, total: { $sum: '$value' }, count: { $sum: 1 } } }, + ]); + + return { + total: result?.total ?? 0, + count: result?.count ?? 0, + }; +}; + +const buildParentUpdateData = (parentType, parentStock, events, stockEvent) => { + const updateData = {}; + let currentAmount; + + if (parentType === 'filamentStock') { + const net = events.total; + const startingNet = parentStock.startingWeight?.net ?? 0; + const startingGross = parentStock.startingWeight?.gross ?? 0; + const gross = startingNet > 0 ? (startingGross * net) / startingNet : net; + updateData.currentWeight = { net, gross }; + currentAmount = net; + } else { + const eventValue = Number(stockEvent?.value); + if (Number.isFinite(eventValue) && eventValue < 0) { + updateData.currentQuantity = Math.max( + 0, + (Number(parentStock.currentQuantity) || 0) + eventValue + ); + } else { + updateData.currentQuantity = events.total; + } + currentAmount = updateData.currentQuantity; + } + + const state = buildParentState( + parentType, + parentStock, + currentAmount, + getStartingAmount(parentType, parentStock) + ); + if (state) { + updateData.state = state; + } + + return updateData; +}; + +const HISTORY_RATE_LIMIT_MS = 3000; + +const isWithinHistoryRateLimit = (lastEntry, timestamp = new Date()) => { + if (!lastEntry?.timestamp) return false; + const elapsed = new Date(timestamp).getTime() - new Date(lastEntry.timestamp).getTime(); + return elapsed < HISTORY_RATE_LIMIT_MS; +}; + +const getLastParentHistoryValue = (parentType, history = []) => { + const lastEntry = history.at(-1); + if (!lastEntry) return undefined; + + return parentType === 'filamentStock' ? lastEntry.currentWeight : lastEntry.currentQuantity; +}; + +const parentValuesEqual = (parentType, a, b) => { + if (a === b) return true; + if (a == null || b == null) return false; + + if (parentType === 'filamentStock') { + return a.net === b.net && a.gross === b.gross; + } + + return a === b; +}; + +const buildParentHistoryEntry = (parentType, currentValue, timestamp) => { + if (parentType === 'filamentStock') { + return { currentWeight: currentValue, timestamp }; + } + + return { currentQuantity: currentValue, timestamp }; +}; + +const appendParentHistoryIfChanged = ( + parentType, + parentStock, + updateData, + timestamp = new Date() +) => { + const history = parentStock.history || []; + const lastEntry = history.at(-1); + const currentValue = + parentType === 'filamentStock' ? updateData.currentWeight : updateData.currentQuantity; + const lastHistoryValue = getLastParentHistoryValue(parentType, history); + + if (parentValuesEqual(parentType, currentValue, lastHistoryValue)) { + return updateData; + } + + if (isWithinHistoryRateLimit(lastEntry, timestamp)) { + return updateData; + } + + return { + ...updateData, + history: [...history, buildParentHistoryEntry(parentType, currentValue, timestamp)], + }; +}; + +const recalculateParentStock = async (parentType, parentId, user, stockEvent) => { + if (!parentType || !parentId) return; + + const modelName = parentStockModelNames[parentType]; + if (!modelName) return; + + const parentModel = mongoose.model(modelName); + const parentStock = await getObject({ + model: parentModel, + id: parentId, + }); + if (!parentStock || parentStock.error) return; + + const events = await getStockEventTotal(parentId, parentType); + if (!events?.count) return; + + await editObject({ + model: parentModel, + id: parentStock._id, + updateData: appendParentHistoryIfChanged( + parentType, + parentStock, + buildParentUpdateData(parentType, parentStock, events, stockEvent) + ), + user, + recalculate: parentType === 'productStock' || parentType === 'partStock', + }); +}; + +const stockEventSchema = new Schema( + { + _reference: { type: String, default: () => generateId()() }, + value: { type: Number, required: true }, + unit: { type: String, required: true }, + parent: { + type: Schema.Types.ObjectId, + refPath: 'parentType', + required: true, + }, + parentType: { + type: String, + required: true, + enum: ['filamentStock', 'partStock', 'productStock'], // Add other models as needed + }, + owner: { + type: Schema.Types.ObjectId, + refPath: 'ownerType', + required: true, + }, + ownerType: { + type: String, + required: true, + enum: ['user', 'subJob', 'stockAudit', 'stockTransfer', 'productStock'], + }, + history: [ + { + value: { type: Number, required: true }, + timestamp: { type: Date, default: Date.now }, + }, + ], + timestamp: { type: Date, default: Date.now }, + }, + { timestamps: true } +); + +stockEventSchema.index({ parentType: 'text', ownerType: 'text', unit: 'text' }); + +const rollupConfigs = [ + { + name: 'partStock', + filter: { parentType: 'partStock' }, + rollups: [{ name: 'partStock', property: 'parentType', operation: 'count' }], + }, + { + name: 'filamentStock', + filter: { parentType: 'filamentStock' }, + rollups: [{ name: 'filamentStock', property: 'parentType', operation: 'count' }], + }, + { + name: 'productStock', + filter: { parentType: 'productStock' }, + rollups: [{ name: 'productStock', property: 'parentType', operation: 'count' }], + }, +]; + +stockEventSchema.statics.stats = async function () { + const results = await aggregateRollups({ + model: this, + rollupConfigs: rollupConfigs, + }); + + return results; +}; + +stockEventSchema.statics.history = async function (from, to) { + const results = await aggregateRollupsHistory({ + model: this, + startDate: from, + endDate: to, + rollupConfigs: rollupConfigs, + }); + + return results; +}; + +stockEventSchema.statics.recalculate = async function (stockEvent, user) { + const history = stockEvent.history || []; + const lastEntry = history.at(-1); + const lastHistoryValue = lastEntry?.value; + const currentValue = stockEvent.value; + const timestamp = stockEvent.timestamp || new Date(); + + if (currentValue !== lastHistoryValue && !isWithinHistoryRateLimit(lastEntry, timestamp)) { + await editObject({ + model: this, + id: stockEvent._id, + updateData: { + history: [...history, { value: currentValue, timestamp }], + }, + user, + recalculate: false, + }); + } + + const parentType = stockEvent.parentType; + const parentId = stockEvent.parent?._id || stockEvent.parent; + await recalculateParentStock(parentType, parentId, user, stockEvent); +}; + +// Add virtual id getter +stockEventSchema.virtual('id').get(function () { + return this._id; +}); + +// Configure JSON serialization to include virtuals +stockEventSchema.set('toJSON', { virtuals: true }); + +// Create and export the model +export const stockEventModel = mongoose.model('stockEvent', stockEventSchema); diff --git a/src/database/schemas/inventory/stocklocation.schema.js b/src/database/schemas/inventory/stocklocation.schema.js new file mode 100644 index 0000000..796d5b5 --- /dev/null +++ b/src/database/schemas/inventory/stocklocation.schema.js @@ -0,0 +1,41 @@ +import mongoose from 'mongoose'; +import { generateId } from '../../utils.js'; +const { Schema } = mongoose; + +const addressSchema = new Schema({ + building: { required: false, type: String }, + addressLine1: { required: false, type: String }, + addressLine2: { required: false, type: String }, + city: { required: false, type: String }, + state: { required: false, type: String }, + postcode: { required: false, type: String }, + country: { required: false, type: String }, +}); + +const stockLocationSchema = new Schema( + { + _reference: { type: String, default: () => generateId()() }, + name: { type: String, required: true }, + address: { required: false, type: addressSchema }, + }, + { timestamps: true } +); + +stockLocationSchema.index({ name: 'text' }); + +stockLocationSchema.statics.stats = async function () { + const total = await this.countDocuments({}); + return { total: { count: total } }; +}; + +stockLocationSchema.statics.history = async function () { + return []; +}; + +stockLocationSchema.virtual('id').get(function () { + return this._id; +}); + +stockLocationSchema.set('toJSON', { virtuals: true }); + +export const stockLocationModel = mongoose.model('stockLocation', stockLocationSchema); diff --git a/src/database/schemas/inventory/stocktransfer.schema.js b/src/database/schemas/inventory/stocktransfer.schema.js new file mode 100644 index 0000000..3a1eb84 --- /dev/null +++ b/src/database/schemas/inventory/stocktransfer.schema.js @@ -0,0 +1,78 @@ +import mongoose from 'mongoose'; +import { generateId } from '../../utils.js'; +const { Schema } = mongoose; + +const stockTransferLineSchema = new Schema( + { + fromStockType: { + type: String, + required: true, + enum: ['filamentStock', 'partStock', 'productStock'], + }, + fromStock: { + type: Schema.Types.ObjectId, + refPath: 'lines.fromStockType', + required: true, + }, + quantity: { type: Number, required: true }, + toStockType: { + type: String, + required: false, + enum: ['filamentStock', 'partStock', 'productStock'], + }, + toStock: { + type: Schema.Types.ObjectId, + refPath: 'lines.toStockType', + required: false, + }, + }, + { _id: true } +); + +const stockTransferSchema = new Schema( + { + _reference: { type: String, default: () => generateId()() }, + state: { + type: { type: String, required: true, default: 'draft' }, + progress: { type: Number, required: false }, + }, + postedAt: { type: Date, required: false }, + fromLocation: { + type: Schema.Types.ObjectId, + ref: 'stockLocation', + required: true, + }, + toLocation: { + type: Schema.Types.ObjectId, + ref: 'stockLocation', + required: true, + }, + lines: { type: [stockTransferLineSchema], default: [] }, + }, + { timestamps: true } +); + +stockTransferSchema.index({ 'state.type': 'text' }); + +stockTransferSchema.statics.stats = async function () { + const [draft, posted] = await Promise.all([ + this.countDocuments({ 'state.type': 'draft' }), + this.countDocuments({ 'state.type': 'posted' }), + ]); + return { + draft: { count: draft }, + posted: { count: posted }, + }; +}; + +stockTransferSchema.statics.history = async function () { + return []; +}; + +stockTransferSchema.virtual('id').get(function () { + return this._id; +}); + +stockTransferSchema.set('toJSON', { virtuals: true }); + +export const stockTransferModel = mongoose.model('stockTransfer', stockTransferSchema); diff --git a/src/database/schemas/management/apppassword.schema.js b/src/database/schemas/management/apppassword.schema.js new file mode 100644 index 0000000..e4e3904 --- /dev/null +++ b/src/database/schemas/management/apppassword.schema.js @@ -0,0 +1,24 @@ +import mongoose from 'mongoose'; +import { generateId } from '../../utils.js'; +const { Schema } = mongoose; + +const appPasswordSchema = new mongoose.Schema( + { + _reference: { type: String, default: () => generateId()() }, + name: { type: String, required: true }, + user: { type: Schema.Types.ObjectId, ref: 'user', required: true }, + active: { type: Boolean, required: true, default: true }, + secret: { type: String, required: true, select: false }, + }, + { timestamps: true } +); + +appPasswordSchema.index({ name: 'text' }); + +appPasswordSchema.virtual('id').get(function () { + return this._id; +}); + +appPasswordSchema.set('toJSON', { virtuals: true }); + +export const appPasswordModel = mongoose.model('appPassword', appPasswordSchema); diff --git a/src/database/schemas/management/auditlog.schema.js b/src/database/schemas/management/auditlog.schema.js new file mode 100644 index 0000000..1ac2bcf --- /dev/null +++ b/src/database/schemas/management/auditlog.schema.js @@ -0,0 +1,50 @@ +import mongoose from 'mongoose'; +import { generateId } from '../../utils.js'; +const { Schema } = mongoose; + +const auditLogSchema = new Schema( + { + _reference: { type: String, default: () => generateId()() }, + changes: { + old: { type: Object, required: false }, + new: { type: Object, required: false }, + }, + operation: { + type: String, + required: true, + }, + parent: { + type: Schema.Types.ObjectId, + refPath: 'parentType', + required: true, + }, + parentType: { + type: String, + required: true, + }, + owner: { + type: Schema.Types.ObjectId, + refPath: 'ownerType', + required: true, + }, + ownerType: { + type: String, + required: true, + enum: ['user', 'printer', 'host', 'marketplace'], + }, + }, + { timestamps: true } +); + +auditLogSchema.index({ operation: 'text', parentType: 'text', ownerType: 'text' }); + +// Add virtual id getter +auditLogSchema.virtual('id').get(function () { + return this._id; +}); + +// Configure JSON serialization to include virtuals +auditLogSchema.set('toJSON', { virtuals: true }); + +// Create and export the model +export const auditLogModel = mongoose.model('auditLog', auditLogSchema); diff --git a/src/database/schemas/management/courier.schema.js b/src/database/schemas/management/courier.schema.js new file mode 100644 index 0000000..92c8d10 --- /dev/null +++ b/src/database/schemas/management/courier.schema.js @@ -0,0 +1,32 @@ +import mongoose from 'mongoose'; +import { generateId } from '../../utils.js'; + +const courierSchema = new mongoose.Schema( + { + _reference: { type: String, default: () => generateId()() }, + name: { required: true, type: String }, + website: { required: false, type: String }, + email: { required: false, type: String }, + phone: { required: false, type: String }, + contact: { required: false, type: String }, + country: { required: false, type: String }, + }, + { timestamps: true } +); + +courierSchema.index({ + name: 'text', + website: 'text', + email: 'text', + phone: 'text', + contact: 'text', + country: 'text', +}); + +courierSchema.virtual('id').get(function () { + return this._id; +}); + +courierSchema.set('toJSON', { virtuals: true }); + +export const courierModel = mongoose.model('courier', courierSchema); diff --git a/src/database/schemas/management/courierservice.schema.js b/src/database/schemas/management/courierservice.schema.js new file mode 100644 index 0000000..800406d --- /dev/null +++ b/src/database/schemas/management/courierservice.schema.js @@ -0,0 +1,70 @@ +import mongoose from 'mongoose'; +import { generateId } from '../../utils.js'; +import { taxRateModel } from './taxrate.schema.js'; +import { editObject, getObject } from '../../database.js'; +import { amountWithTax, resolveTaxRate } from '../../tax.js'; +const { Schema } = mongoose; + +const marketplaceMappingSchema = new mongoose.Schema( + { + marketplace: { type: Schema.Types.ObjectId, ref: 'marketplace', required: true }, + externalReference: { type: String, required: false }, + }, + { _id: true } +); + +const courierServiceSchema = new mongoose.Schema( + { + _reference: { type: String, default: () => generateId()() }, + name: { required: true, type: String }, + courier: { type: Schema.Types.ObjectId, ref: 'courier', required: true }, + active: { required: true, type: Boolean }, + tracked: { required: true, type: Boolean }, + deliveryTime: { required: true, type: Number }, + website: { required: false, type: String }, + cost: { required: true, type: Number, default: 0 }, + costTaxRate: { type: Schema.Types.ObjectId, ref: 'taxRate', required: false }, + costWithTax: { required: false, type: Number }, + additionalCost: { required: false, type: Number }, + additionalCostWithTax: { required: false, type: Number }, + shippingCurrency: { required: true, type: String, default: 'GBP' }, + international: { required: true, type: Boolean, default: false }, + marketplaces: { type: [marketplaceMappingSchema], default: [] }, + }, + { timestamps: true } +); + +courierServiceSchema.index({ name: 'text', website: 'text' }); + +courierServiceSchema.virtual('id').get(function () { + return this._id; +}); + +courierServiceSchema.set('toJSON', { virtuals: true }); + +courierServiceSchema.statics.recalculate = async function (courierService, user) { + const costTaxRate = await resolveTaxRate(courierService.costTaxRate, getObject, taxRateModel); + const updateData = {}; + + if (courierService.cost != null) { + updateData.costWithTax = amountWithTax(courierService.cost, costTaxRate); + } + if (courierService.additionalCost != null) { + updateData.additionalCostWithTax = amountWithTax( + courierService.additionalCost, + costTaxRate + ); + } + + if (Object.keys(updateData).length > 0) { + await editObject({ + model: this, + id: courierService._id, + updateData, + user, + recalculate: false, + }); + } +}; + +export const courierServiceModel = mongoose.model('courierService', courierServiceSchema); diff --git a/src/database/schemas/management/documentjob.schema.js b/src/database/schemas/management/documentjob.schema.js new file mode 100644 index 0000000..8fbaf7c --- /dev/null +++ b/src/database/schemas/management/documentjob.schema.js @@ -0,0 +1,58 @@ +import mongoose from 'mongoose'; +import { generateId } from '../../utils.js'; +const { Schema } = mongoose; + +const documentJobSchema = new Schema( + { + _reference: { type: String, default: () => generateId()() }, + name: { + type: String, + required: true, + unique: true, + }, + objectType: { type: String, required: false }, + object: { + type: Schema.Types.ObjectId, + refPath: 'objectType', + required: true, + }, + state: { + type: { type: String, required: true, default: 'queued' }, + progress: { type: Number, required: false }, + message: { type: String, required: false }, + }, + documentTemplate: { + type: Schema.Types.ObjectId, + ref: 'documentTemplate', + required: true, + }, + documentPrinter: { + type: Schema.Types.ObjectId, + ref: 'documentPrinter', + required: true, + }, + quantity: { + type: Number, + required: true, + default: 1, + min: 1, + }, + content: { + type: String, + required: false, + }, + }, + { timestamps: true } +); + +documentJobSchema.index({ name: 'text', objectType: 'text' }); + +// Add virtual id getter +documentJobSchema.virtual('id').get(function () { + return this._id; +}); + +// Configure JSON serialization to include virtuals +documentJobSchema.set('toJSON', { virtuals: true }); + +export const documentJobModel = mongoose.model('documentJob', documentJobSchema); diff --git a/src/database/schemas/management/documentprinter.schema.js b/src/database/schemas/management/documentprinter.schema.js new file mode 100644 index 0000000..70f1325 --- /dev/null +++ b/src/database/schemas/management/documentprinter.schema.js @@ -0,0 +1,59 @@ +import mongoose from 'mongoose'; +import { generateId } from '../../utils.js'; +const { Schema } = mongoose; + +const connectionSchema = new Schema( + { + interface: { type: String, required: true }, + protocol: { type: String, required: true }, + host: { type: String, required: true }, + port: { type: Number, required: false }, + username: { type: String, required: false }, + password: { type: String, required: false }, + }, + { _id: false } +); + +const documentPrinterSchema = new Schema( + { + _reference: { type: String, default: () => generateId()() }, + name: { + type: String, + required: true, + unique: true, + }, + connection: { type: connectionSchema, required: true }, + currentDocumentSize: { type: Schema.Types.ObjectId, ref: 'documentSize', required: false }, + supportedDocumentSizes: [{ type: Schema.Types.ObjectId, ref: 'documentSize', required: false }], + rotateOrientation: { type: Boolean, required: false, default: false }, + tags: [{ type: String }], + online: { type: Boolean, required: true, default: false }, + active: { type: Boolean, required: true, default: true }, + state: { + type: { type: String, required: true, default: 'offline' }, + message: { type: String, required: false }, + progress: { type: Number, required: false }, + }, + paperState: { + type: { type: String, required: true, default: 'unknown' }, + message: { type: String, required: false }, + }, + connectedAt: { type: Date, default: null }, + host: { type: Schema.Types.ObjectId, ref: 'host', required: true }, + vendor: { type: Schema.Types.ObjectId, ref: 'vendor', required: false }, + queue: [{ type: Schema.Types.ObjectId, ref: 'documentJob', required: false }], + }, + { timestamps: true } +); + +documentPrinterSchema.index({ name: 'text', tags: 'text' }); + +// Add virtual id getter +documentPrinterSchema.virtual('id').get(function () { + return this._id; +}); + +// Configure JSON serialization to include virtuals +documentPrinterSchema.set('toJSON', { virtuals: true }); + +export const documentPrinterModel = mongoose.model('documentPrinter', documentPrinterSchema); diff --git a/src/database/schemas/management/documentsize.schema.js b/src/database/schemas/management/documentsize.schema.js new file mode 100644 index 0000000..68df07e --- /dev/null +++ b/src/database/schemas/management/documentsize.schema.js @@ -0,0 +1,67 @@ +import mongoose from 'mongoose'; +import { generateId } from '../../utils.js'; +const { Schema } = mongoose; + +const documentSizeSchema = new Schema( + { + _reference: { type: String, default: () => generateId()() }, + name: { + type: String, + required: true, + unique: true, + }, + width: { + type: Number, + required: true, + default: 0, + }, + height: { + type: Number, + required: true, + default: 0, + }, + infiniteHeight: { + type: Boolean, + required: true, + default: false, + }, + printPadding: { + type: Boolean, + required: true, + default: false, + }, + paddingLeft: { + type: Number, + required: true, + default: 0, + }, + paddingRight: { + type: Number, + required: true, + default: 0, + }, + paddingTop: { + type: Number, + required: true, + default: 0, + }, + paddingBottom: { + type: Number, + required: true, + default: 0, + }, + }, + { timestamps: true } +); + +documentSizeSchema.index({ name: 'text' }); + +// Add virtual id getter +documentSizeSchema.virtual('id').get(function () { + return this._id; +}); + +// Configure JSON serialization to include virtuals +documentSizeSchema.set('toJSON', { virtuals: true }); + +export const documentSizeModel = mongoose.model('documentSize', documentSizeSchema); diff --git a/src/database/schemas/management/documenttemplate.schema.js b/src/database/schemas/management/documenttemplate.schema.js new file mode 100644 index 0000000..48e75ea --- /dev/null +++ b/src/database/schemas/management/documenttemplate.schema.js @@ -0,0 +1,166 @@ +import mongoose from 'mongoose'; +import { generateId } from '../../utils.js'; +const { Schema } = mongoose; + +const RENDER_DOCUMENT_TEMPLATE_CALL = + /fc\.renderDocumentTemplate\s*\(\s*(['"])([^'"]+)\1/g; + +function extractRenderDocumentTemplateReferences(content) { + if (content == null || typeof content !== 'string' || content === '') { + return []; + } + + const references = []; + const seen = new Set(); + const regex = new RegExp(RENDER_DOCUMENT_TEMPLATE_CALL.source, 'g'); + let match; + while ((match = regex.exec(content)) !== null) { + const reference = match[2]?.trim(); + if (!reference || seen.has(reference)) { + continue; + } + seen.add(reference); + references.push(reference); + } + return references; +} + +function objectIdsFromFilterValue(value) { + if (value == null) { + return []; + } + if (value instanceof mongoose.Types.ObjectId) { + return [value]; + } + if (typeof value === 'string' && /^[a-f\d]{24}$/i.test(value)) { + return [new mongoose.Types.ObjectId(value)]; + } + if (Array.isArray(value)) { + return value.flatMap(objectIdsFromFilterValue); + } + if (typeof value === 'object') { + if (Array.isArray(value.$in)) { + return objectIdsFromFilterValue(value.$in); + } + if (value._id != null) { + return objectIdsFromFilterValue(value._id); + } + } + return []; +} + +const documentTemplateSchema = new Schema( + { + _reference: { type: String, default: () => generateId()() }, + name: { + type: String, + required: true, + unique: true, + }, + objectType: { type: String, required: false }, + tags: [{ type: String }], + active: { + type: Boolean, + required: true, + default: true, + }, + global: { + type: Boolean, + required: true, + default: false, + }, + parent: { + type: Schema.Types.ObjectId, + ref: 'documentTemplate', + required: false, + }, + documentSize: { + type: Schema.Types.ObjectId, + ref: 'documentSize', + required: true, + }, + documentPrinters: [ + { + type: Schema.Types.ObjectId, + ref: 'documentPrinter', + required: false, + }, + ], + referencedTemplates: [ + { + type: Schema.Types.ObjectId, + ref: 'documentTemplate', + required: false, + }, + ], + content: { + type: String, + required: false, + default: '', + }, + testObject: { + type: Schema.Types.ObjectId, + refPath: 'objectType', + required: false, + }, + }, + { timestamps: true } +); + +documentTemplateSchema.index({ name: 'text', tags: 'text', objectType: 'text' }); + +// Add virtual id getter +documentTemplateSchema.virtual('id').get(function () { + return this._id; +}); + +// Configure JSON serialization to include virtuals +documentTemplateSchema.set('toJSON', { virtuals: true }); + +documentTemplateSchema.statics.recalculate = async function (documentTemplate, user) { + const documentTemplateId = documentTemplate?._id || documentTemplate; + if (!documentTemplateId) { + return; + } + + const stillExists = await this.exists({ _id: documentTemplateId }); + if (!stillExists) { + return; + } + + const { getFilter } = await import('../../../utils.js'); + const references = extractRenderDocumentTemplateReferences(documentTemplate?.content); + const referencedTemplateIds = []; + const seenIds = new Set(); + + for (const reference of references) { + const filter = await getFilter({ parent: reference }, ['parent'], true, this); + const ids = objectIdsFromFilterValue(filter.parent); + for (const id of ids) { + const idString = String(id); + if (!idString || seenIds.has(idString)) { + continue; + } + seenIds.add(idString); + referencedTemplateIds.push(id); + } + } + + if (documentTemplate && typeof documentTemplate === 'object' && !documentTemplate._bsontype) { + documentTemplate.referencedTemplates = referencedTemplateIds.map((id) => ({ + _id: String(id), + })); + } + + const { editObject } = await import('../../database.js'); + await editObject({ + model: this, + id: documentTemplateId, + updateData: { referencedTemplates: referencedTemplateIds }, + user, + populate: [{ path: 'referencedTemplates', strictPopulate: false }], + recalculate: false, + }); +}; + +export const documentTemplateModel = mongoose.model('documentTemplate', documentTemplateSchema); diff --git a/src/database/schemas/management/filament.schema.js b/src/database/schemas/management/filament.schema.js new file mode 100644 index 0000000..8a66537 --- /dev/null +++ b/src/database/schemas/management/filament.schema.js @@ -0,0 +1,59 @@ +import mongoose from 'mongoose'; +import { generateId } from '../../utils.js'; +import { taxRateModel } from '../management/taxrate.schema.js'; +import { editObject, getObject } from '../../database.js'; +import { amountWithTax, resolveTaxRate } from '../../tax.js'; +const { Schema } = mongoose; + +// Filament base - cost and tax; color and cost override at FilamentSKU +const filamentSchema = new mongoose.Schema({ + _reference: { type: String, default: () => generateId()() }, + name: { required: true, type: String }, + vendor: { type: Schema.Types.ObjectId, ref: 'vendor', required: false }, + barcode: { required: false, type: String }, + url: { required: false, type: String }, + image: { required: false, type: Buffer }, + material: { type: Schema.Types.ObjectId, ref: 'material', required: true }, + diameter: { required: true, type: Number }, + density: { required: true, type: Number }, + emptySpoolWeight: { required: true, type: Number }, + cost: { type: Number, required: false }, + costTaxRate: { type: Schema.Types.ObjectId, ref: 'taxRate', required: false }, + costWithTax: { type: Number, required: false }, +}, { timestamps: true }); + +filamentSchema.index({ name: 'text', barcode: 'text', url: 'text' }); + +filamentSchema.virtual('id').get(function () { + return this._id; +}); + +filamentSchema.set('toJSON', { virtuals: true }); + +filamentSchema.statics.recalculate = async function (filament, user) { + const costTaxRate = await resolveTaxRate(filament.costTaxRate, getObject, taxRateModel); + const taxUpdateData = {}; + + if (filament.cost != null) { + taxUpdateData.costWithTax = amountWithTax(filament.cost, costTaxRate); + } + + if (Object.keys(taxUpdateData).length > 0) { + await editObject({ + model: this, + id: filament._id, + updateData: taxUpdateData, + user, + recalculate: false, + }); + Object.assign(filament, taxUpdateData); + } + + const filamentSkuModel = mongoose.model('filamentSku'); + const skus = await filamentSkuModel.find({ filament: filament._id }).select('_id').lean(); + for (const sku of skus) { + await filamentSkuModel.recalculate(sku, user); + } +}; + +export const filamentModel = mongoose.model('filament', filamentSchema); diff --git a/src/database/schemas/management/filamentsku.schema.js b/src/database/schemas/management/filamentsku.schema.js new file mode 100644 index 0000000..60646c1 --- /dev/null +++ b/src/database/schemas/management/filamentsku.schema.js @@ -0,0 +1,82 @@ +import mongoose from 'mongoose'; +import { generateId } from '../../utils.js'; +import { filamentModel } from './filament.schema.js'; +import { taxRateModel } from './taxrate.schema.js'; +import { editObject, getObject } from '../../database.js'; +import { amountWithTax, resolveTaxRate } from '../../tax.js'; +const { Schema } = mongoose; + +// Define the main filament SKU schema - color and cost live at SKU level +const filamentSkuSchema = new Schema( + { + _reference: { type: String, default: () => generateId()() }, + barcode: { type: String, required: false }, + filament: { type: Schema.Types.ObjectId, ref: 'filament', required: true }, + name: { type: String, required: true }, + description: { type: String, required: false }, + color: { type: String, required: true }, + cost: { type: Number, required: false }, + overrideCost: { type: Boolean, default: false }, + costTaxRate: { type: Schema.Types.ObjectId, ref: 'taxRate', required: false }, + costWithTax: { type: Number, required: false }, + }, + { timestamps: true } +); + +filamentSkuSchema.index({ name: 'text', barcode: 'text', description: 'text', color: 'text' }); + +// Add virtual id getter +filamentSkuSchema.virtual('id').get(function () { + return this._id; +}); + +// Configure JSON serialization to include virtuals +filamentSkuSchema.set('toJSON', { virtuals: true }); + +filamentSkuSchema.statics.recalculate = async function (filamentSku, user) { + const parent = await getObject({ + model: filamentModel, + id: filamentSku.filament?._id || filamentSku.filament, + cached: true, + }); + + const taxUpdateData = {}; + + if (filamentSku.overrideCost) { + const costTaxRate = await resolveTaxRate(filamentSku.costTaxRate, getObject, taxRateModel); + if (filamentSku.cost != null) { + taxUpdateData.costWithTax = amountWithTax(filamentSku.cost, costTaxRate); + } + } else if (parent?.costWithTax != null) { + taxUpdateData.costWithTax = parent.costWithTax; + } + + if (Object.keys(taxUpdateData).length > 0) { + await editObject({ + model: this, + id: filamentSku._id, + updateData: taxUpdateData, + user, + recalculate: false, + }); + Object.assign(filamentSku, taxUpdateData); + } + + const orderItemModel = mongoose.model('orderItem'); + const skuId = filamentSku._id; + const draftOrderItems = await orderItemModel + .find({ + 'state.type': 'draft', + itemType: 'filament', + sku: skuId, + }) + .populate('order') + .lean(); + + for (const orderItem of draftOrderItems) { + await orderItemModel.recalculate(orderItem, user); + } +}; + +// Create and export the model +export const filamentSkuModel = mongoose.model('filamentSku', filamentSkuSchema); diff --git a/src/database/schemas/management/file.schema.js b/src/database/schemas/management/file.schema.js new file mode 100644 index 0000000..eab8e34 --- /dev/null +++ b/src/database/schemas/management/file.schema.js @@ -0,0 +1,25 @@ +import mongoose from 'mongoose'; +import { generateId } from '../../utils.js'; + +const fileSchema = new mongoose.Schema( + { + _reference: { type: String, default: () => generateId()() }, + name: { required: true, type: String }, + type: { required: true, type: String }, + extension: { required: true, type: String }, + size: { required: false, type: Number }, + metaData: { required: false, type: Object }, + hasThumbnails: { required: false, type: Boolean, default: false }, + }, + { timestamps: true } +); + +fileSchema.index({ name: 'text', type: 'text', extension: 'text' }); + +fileSchema.virtual('id').get(function () { + return this._id; +}); + +fileSchema.set('toJSON', { virtuals: true }); + +export const fileModel = mongoose.model('file', fileSchema); diff --git a/src/database/schemas/management/host.schema.js b/src/database/schemas/management/host.schema.js new file mode 100644 index 0000000..d406193 --- /dev/null +++ b/src/database/schemas/management/host.schema.js @@ -0,0 +1,64 @@ +import mongoose from 'mongoose'; +import { generateId } from '../../utils.js'; + +// Define the device schema +const deviceInfoSchema = new mongoose.Schema( + { + os: { + platform: { type: String }, + type: { type: String }, + release: { type: String }, + arch: { type: String }, + hostname: { type: String }, + }, + cpu: { + cores: { type: Number }, + model: { type: String }, + speedMHz: { type: Number }, + }, + user: { + uid: { type: Number }, + gid: { type: Number }, + username: { type: String }, + homedir: { type: String }, + shell: { type: String }, + }, + process: { + nodeVersion: { type: String }, + execPath: { type: String }, + }, + }, + { _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 }, + }, + active: { required: true, type: Boolean, default: true }, + connectedAt: { required: false, type: Date }, + authCode: { type: { required: false, type: String } }, + deviceInfo: deviceInfoSchema, + otp: { type: { required: false, type: String } }, + otpExpiresAt: { required: false, type: Date }, + files: [{ type: mongoose.Schema.Types.ObjectId, ref: 'file' }], + }, + { timestamps: true } +); + +hostSchema.index({ name: 'text', tags: 'text' }); + +hostSchema.virtual('id').get(function () { + return this._id; +}); + +hostSchema.set('toJSON', { virtuals: true }); + +export const hostModel = mongoose.model('host', hostSchema); diff --git a/src/database/schemas/management/material.schema.js b/src/database/schemas/management/material.schema.js new file mode 100644 index 0000000..c24d44b --- /dev/null +++ b/src/database/schemas/management/material.schema.js @@ -0,0 +1,22 @@ +import mongoose from 'mongoose'; +import { generateId } from '../../utils.js'; + +const materialSchema = new mongoose.Schema( + { + _reference: { type: String, default: () => generateId()() }, + name: { required: true, type: String }, + url: { required: false, type: String }, + tags: [{ type: String }], + }, + { timestamps: true } +); + +materialSchema.index({ name: 'text', url: 'text', tags: 'text' }); + +materialSchema.virtual('id').get(function () { + return this._id; +}); + +materialSchema.set('toJSON', { virtuals: true }); + +export const materialModel = mongoose.model('material', materialSchema); diff --git a/src/database/schemas/management/notetype.schema.js b/src/database/schemas/management/notetype.schema.js new file mode 100644 index 0000000..c7c76d5 --- /dev/null +++ b/src/database/schemas/management/notetype.schema.js @@ -0,0 +1,36 @@ +import mongoose from 'mongoose'; +import { generateId } from '../../utils.js'; +const { Schema } = mongoose; + +const noteTypeSchema = new Schema( + { + _reference: { type: String, default: () => generateId()() }, + name: { + type: String, + required: true, + unique: true, + }, + color: { + type: String, + required: false, + }, + active: { + type: Boolean, + required: true, + default: true, + }, + }, + { timestamps: true } +); + +noteTypeSchema.index({ name: 'text' }); + +// Add virtual id getter +noteTypeSchema.virtual('id').get(function () { + return this._id; +}); + +// Configure JSON serialization to include virtuals +noteTypeSchema.set('toJSON', { virtuals: true }); + +export const noteTypeModel = mongoose.model('noteType', noteTypeSchema); diff --git a/src/database/schemas/management/part.schema.js b/src/database/schemas/management/part.schema.js new file mode 100644 index 0000000..1456228 --- /dev/null +++ b/src/database/schemas/management/part.schema.js @@ -0,0 +1,68 @@ +import mongoose from 'mongoose'; +import { generateId } from '../../utils.js'; +import { taxRateModel } from '../management/taxrate.schema.js'; +import { editObject, getObject } from '../../database.js'; +import { amountWithTax, resolveTaxRate } from '../../tax.js'; +const { Schema } = mongoose; + +// Define the main part schema - cost/price and tax; override at PartSku +const partSchema = new Schema( + { + _reference: { type: String, default: () => generateId()() }, + name: { type: String, required: true }, + file: { type: mongoose.SchemaTypes.ObjectId, ref: 'file', required: false }, + cost: { type: Number, required: false }, + price: { type: Number, required: false }, + priceMode: { type: String, default: 'margin' }, + margin: { type: Number, required: false }, + amount: { type: Number, required: false }, + costTaxRate: { type: Schema.Types.ObjectId, ref: 'taxRate', required: false }, + priceTaxRate: { type: Schema.Types.ObjectId, ref: 'taxRate', required: false }, + costWithTax: { type: Number, required: false }, + priceWithTax: { type: Number, required: false }, + }, + { timestamps: true } +); + +partSchema.index({ name: 'text' }); + +// Add virtual id getter +partSchema.virtual('id').get(function () { + return this._id; +}); + +// Configure JSON serialization to include virtuals +partSchema.set('toJSON', { virtuals: true }); + +partSchema.statics.recalculate = async function (part, user) { + const costTaxRate = await resolveTaxRate(part.costTaxRate, getObject, taxRateModel); + const priceTaxRate = await resolveTaxRate(part.priceTaxRate, getObject, taxRateModel); + const taxUpdateData = {}; + + if (part.cost != null) { + taxUpdateData.costWithTax = amountWithTax(part.cost, costTaxRate); + } + if (part.price != null) { + taxUpdateData.priceWithTax = amountWithTax(part.price, priceTaxRate); + } + + if (Object.keys(taxUpdateData).length > 0) { + await editObject({ + model: this, + id: part._id, + updateData: taxUpdateData, + user, + recalculate: false, + }); + Object.assign(part, taxUpdateData); + } + + const partSkuModel = mongoose.model('partSku'); + const skus = await partSkuModel.find({ part: part._id }).select('_id').lean(); + for (const sku of skus) { + await partSkuModel.recalculate(sku, user); + } +}; + +// Create and export the model +export const partModel = mongoose.model('part', partSchema); diff --git a/src/database/schemas/management/partsku.schema.js b/src/database/schemas/management/partsku.schema.js new file mode 100644 index 0000000..ed7a1fd --- /dev/null +++ b/src/database/schemas/management/partsku.schema.js @@ -0,0 +1,112 @@ +import mongoose from 'mongoose'; +import { generateId } from '../../utils.js'; +import { partModel } from './part.schema.js'; +import { taxRateModel } from './taxrate.schema.js'; +import { editObject, getObject } from '../../database.js'; +import { + amountWithTax, + effectiveMarginPrice, + resolveTaxRate, +} from '../../tax.js'; +const { Schema } = mongoose; + +// Define the main part SKU schema - pricing lives at SKU level +const partSkuSchema = new Schema( + { + _reference: { type: String, default: () => generateId()() }, + barcode: { type: String, required: false }, + part: { type: Schema.Types.ObjectId, ref: 'part', required: true }, + name: { type: String, required: true }, + description: { type: String, required: false }, + priceMode: { type: String, default: 'margin' }, + price: { type: Number, required: false }, + cost: { type: Number, required: false }, + overrideCost: { type: Boolean, default: false }, + overridePrice: { type: Boolean, default: false }, + margin: { type: Number, required: false }, + amount: { type: Number, required: false }, + priceTaxRate: { type: Schema.Types.ObjectId, ref: 'taxRate', required: false }, + costTaxRate: { type: Schema.Types.ObjectId, ref: 'taxRate', required: false }, + priceWithTax: { type: Number, required: false }, + costWithTax: { type: Number, required: false }, + }, + { timestamps: true } +); + +partSkuSchema.index({ name: 'text', barcode: 'text', description: 'text' }); + +// Add virtual id getter +partSkuSchema.virtual('id').get(function () { + return this._id; +}); + +// Configure JSON serialization to include virtuals +partSkuSchema.set('toJSON', { virtuals: true }); + +partSkuSchema.statics.recalculate = async function (partSku, user) { + const parent = await getObject({ + model: partModel, + id: partSku.part?._id || partSku.part, + cached: true, + }); + + const taxUpdateData = {}; + + if (partSku.overrideCost) { + const costTaxRate = await resolveTaxRate(partSku.costTaxRate, getObject, taxRateModel); + if (partSku.cost != null) { + taxUpdateData.costWithTax = amountWithTax(partSku.cost, costTaxRate); + } + } else if (parent?.costWithTax != null) { + taxUpdateData.costWithTax = parent.costWithTax; + } + + if (partSku.overridePrice) { + const priceTaxRate = await resolveTaxRate( + partSku.priceTaxRate ?? parent?.priceTaxRate, + getObject, + taxRateModel + ); + const cost = partSku.overrideCost ? partSku.cost : parent?.cost; + const price = effectiveMarginPrice({ + priceMode: partSku.priceMode ?? parent?.priceMode, + price: partSku.price, + cost, + margin: partSku.margin ?? parent?.margin, + }); + if (price != null) { + taxUpdateData.priceWithTax = amountWithTax(price, priceTaxRate); + } + } else if (parent?.priceWithTax != null) { + taxUpdateData.priceWithTax = parent.priceWithTax; + } + + if (Object.keys(taxUpdateData).length > 0) { + await editObject({ + model: this, + id: partSku._id, + updateData: taxUpdateData, + user, + recalculate: false, + }); + Object.assign(partSku, taxUpdateData); + } + + const orderItemModel = mongoose.model('orderItem'); + const skuId = partSku._id; + const draftOrderItems = await orderItemModel + .find({ + 'state.type': 'draft', + itemType: 'part', + sku: skuId, + }) + .populate('order') + .lean(); + + for (const orderItem of draftOrderItems) { + await orderItemModel.recalculate(orderItem, user); + } +}; + +// Create and export the model +export const partSkuModel = mongoose.model('partSku', partSkuSchema); diff --git a/src/database/schemas/management/permissionsetting.schema.js b/src/database/schemas/management/permissionsetting.schema.js new file mode 100644 index 0000000..bc9aefa --- /dev/null +++ b/src/database/schemas/management/permissionsetting.schema.js @@ -0,0 +1,62 @@ +import mongoose from 'mongoose'; +import { generateId } from '../../utils.js'; +import { excludeIdFromList, getPermissionSettingsId } from '../../permissions.js'; + +const { Schema } = mongoose; + +const permissionSettingSchema = new Schema( + { + _reference: { type: String, default: () => generateId()() }, + name: { type: String, required: true }, + permissions: { type: Schema.Types.Mixed, required: false, default: () => ({}) }, + }, + { timestamps: true } +); + +permissionSettingSchema.index({ name: 'text' }); + +permissionSettingSchema.virtual('id').get(function () { + return this._id; +}); + +permissionSettingSchema.set('toJSON', { virtuals: true }); + +const recalculateAssignees = async (model, permissionSettingId, stillExists, user) => { + const assigned = await model.find({ permissionSettings: permissionSettingId }).lean(); + + if (!stillExists) { + await model.updateMany( + { permissionSettings: permissionSettingId }, + { $pull: { permissionSettings: permissionSettingId } } + ); + } + + for (const item of assigned) { + const nextItem = stillExists + ? item + : { + ...item, + permissionSettings: excludeIdFromList(item.permissionSettings, permissionSettingId), + }; + await model.recalculate(nextItem, user); + } +}; + +permissionSettingSchema.statics.recalculate = async function (permissionSetting, user) { + const permissionSettingId = getPermissionSettingsId(permissionSetting); + if (!permissionSettingId) { + return; + } + + const stillExists = await this.exists({ _id: permissionSettingId }); + const userGroupModel = mongoose.model('userGroup'); + const userModel = mongoose.model('user'); + + await recalculateAssignees(userGroupModel, permissionSettingId, stillExists, user); + await recalculateAssignees(userModel, permissionSettingId, stillExists, user); +}; + +export const permissionSettingModel = mongoose.model( + 'permissionSetting', + permissionSettingSchema +); diff --git a/src/database/schemas/management/product.schema.js b/src/database/schemas/management/product.schema.js new file mode 100644 index 0000000..9e1cb9d --- /dev/null +++ b/src/database/schemas/management/product.schema.js @@ -0,0 +1,87 @@ +import mongoose from 'mongoose'; +import { generateId } from '../../utils.js'; +import { taxRateModel } from '../management/taxrate.schema.js'; +import { editObject, getObject } from '../../database.js'; +import { amountWithTax, resolveTaxRate } from '../../tax.js'; +const { Schema } = mongoose; + +// Define the main product schema +const productSchema = new Schema( + { + _reference: { type: String, default: () => generateId()() }, + name: { type: String, required: true }, + productCategory: { type: Schema.Types.ObjectId, ref: 'productCategory', required: true }, + tags: [{ type: String }], + version: { type: String }, + vendor: { type: Schema.Types.ObjectId, ref: 'vendor', required: true }, + cost: { type: Number, required: false }, + price: { type: Number, required: false }, + priceMode: { type: String, default: 'margin' }, + margin: { type: Number, required: false }, + amount: { type: Number, required: false }, + costTaxRate: { type: Schema.Types.ObjectId, ref: 'taxRate', required: false }, + priceTaxRate: { type: Schema.Types.ObjectId, ref: 'taxRate', required: false }, + costWithTax: { type: Number, required: false }, + priceWithTax: { type: Number, required: false }, + }, + { timestamps: true } +); + +productSchema.index({ name: 'text', tags: 'text', version: 'text' }); + +// Add virtual id getter +productSchema.virtual('id').get(function () { + return this._id; +}); + +// Configure JSON serialization to include virtuals +productSchema.set('toJSON', { virtuals: true }); + +productSchema.statics.recalculate = async function (product, user) { + const costTaxRate = await resolveTaxRate(product.costTaxRate, getObject, taxRateModel); + const priceTaxRate = await resolveTaxRate(product.priceTaxRate, getObject, taxRateModel); + const taxUpdateData = {}; + + if (product.cost != null) { + taxUpdateData.costWithTax = amountWithTax(product.cost, costTaxRate); + } + if (product.price != null) { + taxUpdateData.priceWithTax = amountWithTax(product.price, priceTaxRate); + } + + if (Object.keys(taxUpdateData).length > 0) { + await editObject({ + model: this, + id: product._id, + updateData: taxUpdateData, + user, + recalculate: false, + }); + Object.assign(product, taxUpdateData); + } + + const productSkuModel = mongoose.model('productSku'); + const skus = await productSkuModel.find({ product: product._id }).select('_id').lean(); + for (const sku of skus) { + await productSkuModel.recalculate(sku, user); + } + + const orderItemModel = mongoose.model('orderItem'); + const itemId = product._id; + const draftOrderItems = await orderItemModel + .find({ + 'state.type': 'draft', + itemType: 'product', + item: itemId, + }) + .populate('order') + .lean(); + + for (const orderItem of draftOrderItems) { + await orderItemModel.recalculate(orderItem, user); + } + +}; + +// Create and export the model +export const productModel = mongoose.model('product', productSchema); diff --git a/src/database/schemas/management/productcategory.schema.js b/src/database/schemas/management/productcategory.schema.js new file mode 100644 index 0000000..1a75e04 --- /dev/null +++ b/src/database/schemas/management/productcategory.schema.js @@ -0,0 +1,30 @@ +import mongoose from 'mongoose'; +import { generateId } from '../../utils.js'; +const { Schema } = mongoose; + +const marketplaceMappingSchema = new mongoose.Schema( + { + marketplace: { type: Schema.Types.ObjectId, ref: 'marketplace', required: true }, + externalReference: { type: String, required: false }, + }, + { _id: true } +); + +const productCategorySchema = new mongoose.Schema( + { + _reference: { type: String, default: () => generateId()() }, + name: { required: true, type: String }, + marketplaces: { type: [marketplaceMappingSchema], default: [] }, + }, + { timestamps: true } +); + +productCategorySchema.index({ name: 'text' }); + +productCategorySchema.virtual('id').get(function () { + return this._id; +}); + +productCategorySchema.set('toJSON', { virtuals: true }); + +export const productCategoryModel = mongoose.model('productCategory', productCategorySchema); diff --git a/src/database/schemas/management/productsku.schema.js b/src/database/schemas/management/productsku.schema.js new file mode 100644 index 0000000..49c2696 --- /dev/null +++ b/src/database/schemas/management/productsku.schema.js @@ -0,0 +1,119 @@ +import mongoose from 'mongoose'; +import { generateId } from '../../utils.js'; +import { productModel } from './product.schema.js'; +import { taxRateModel } from './taxrate.schema.js'; +import { editObject, getObject } from '../../database.js'; +import { + amountWithTax, + effectiveMarginPrice, + resolveTaxRate, +} from '../../tax.js'; +const { Schema } = mongoose; + +const partSkuUsageSchema = new Schema({ + part: { type: Schema.Types.ObjectId, ref: 'part', required: true }, + partSku: { type: Schema.Types.ObjectId, ref: 'partSku', required: true }, + quantity: { type: Number, required: true }, +}); + +// Define the main product SKU schema +const productSkuSchema = new Schema( + { + _reference: { type: String, default: () => generateId()() }, + barcode: { type: String, required: false }, + product: { type: Schema.Types.ObjectId, ref: 'product', required: true }, + name: { type: String, required: true }, + description: { type: String, required: false }, + priceMode: { type: String, default: 'margin' }, + price: { type: Number, required: false }, + cost: { type: Number, required: false }, + overrideCost: { type: Boolean, default: false }, + overridePrice: { type: Boolean, default: false }, + margin: { type: Number, required: false }, + amount: { type: Number, required: false }, + parts: { type: [partSkuUsageSchema], default: [] }, + priceTaxRate: { type: Schema.Types.ObjectId, ref: 'taxRate', required: false }, + costTaxRate: { type: Schema.Types.ObjectId, ref: 'taxRate', required: false }, + priceWithTax: { type: Number, required: false }, + costWithTax: { type: Number, required: false }, + }, + { timestamps: true } +); + +productSkuSchema.index({ name: 'text', barcode: 'text', description: 'text' }); + +// Add virtual id getter +productSkuSchema.virtual('id').get(function () { + return this._id; +}); + +// Configure JSON serialization to include virtuals +productSkuSchema.set('toJSON', { virtuals: true }); + +productSkuSchema.statics.recalculate = async function (productSku, user) { + const parent = await getObject({ + model: productModel, + id: productSku.product?._id || productSku.product, + cached: true, + }); + + const taxUpdateData = {}; + + if (productSku.overrideCost) { + const costTaxRate = await resolveTaxRate(productSku.costTaxRate, getObject, taxRateModel); + if (productSku.cost != null) { + taxUpdateData.costWithTax = amountWithTax(productSku.cost, costTaxRate); + } + } else if (parent?.costWithTax != null) { + taxUpdateData.costWithTax = parent.costWithTax; + } + + if (productSku.overridePrice) { + const priceTaxRate = await resolveTaxRate( + productSku.priceTaxRate ?? parent?.priceTaxRate, + getObject, + taxRateModel + ); + const cost = productSku.overrideCost ? productSku.cost : parent?.cost; + const price = effectiveMarginPrice({ + priceMode: productSku.priceMode ?? parent?.priceMode, + price: productSku.price, + cost, + margin: productSku.margin ?? parent?.margin, + }); + if (price != null) { + taxUpdateData.priceWithTax = amountWithTax(price, priceTaxRate); + } + } else if (parent?.priceWithTax != null) { + taxUpdateData.priceWithTax = parent.priceWithTax; + } + + if (Object.keys(taxUpdateData).length > 0) { + await editObject({ + model: this, + id: productSku._id, + updateData: taxUpdateData, + user, + recalculate: false, + }); + Object.assign(productSku, taxUpdateData); + } + + const orderItemModel = mongoose.model('orderItem'); + const skuId = productSku._id; + const draftOrderItems = await orderItemModel + .find({ + 'state.type': 'draft', + itemType: 'product', + sku: skuId, + }) + .populate('order') + .lean(); + + for (const orderItem of draftOrderItems) { + await orderItemModel.recalculate(orderItem, user); + } +}; + +// Create and export the model +export const productSkuModel = mongoose.model('productSku', productSkuSchema); diff --git a/src/database/schemas/management/stockauditlevel.schema.js b/src/database/schemas/management/stockauditlevel.schema.js new file mode 100644 index 0000000..91f647b --- /dev/null +++ b/src/database/schemas/management/stockauditlevel.schema.js @@ -0,0 +1,108 @@ +import mongoose from 'mongoose'; +import { generateId } from '../../utils.js'; +import { editObject } from '../../database.js'; + +const { Schema } = mongoose; + +const toId = (value) => { + if (value == null) return null; + if (typeof value === 'object' && value._id != null) return String(value._id); + return String(value); +}; + +export function normalizeAuditLevelLine(line) { + const allItems = Boolean(line?.allItems); + const allSkus = allItems ? true : Boolean(line?.allSkus); + const item = allItems ? null : (line?.item?._id ?? line?.item ?? null); + const itemSku = allItems || allSkus ? null : (line?.itemSku?._id ?? line?.itemSku ?? null); + + return { + _id: line?._id, + itemType: line?.itemType, + allItems, + allSkus, + item, + itemSku, + }; +} + +function auditLevelLineChanged(before, after) { + return ( + Boolean(before?.allItems) !== Boolean(after.allItems) || + Boolean(before?.allSkus) !== Boolean(after.allSkus) || + toId(before?.item) !== toId(after.item) || + toId(before?.itemSku) !== toId(after.itemSku) + ); +} + +const stockAuditLevelLineSchema = new Schema( + { + itemType: { + type: String, + enum: ['filament', 'part', 'product'], + required: true, + }, + allItems: { type: Boolean, default: false }, + item: { type: Schema.Types.ObjectId, refPath: 'auditLines.itemType', required: false }, + allSkus: { type: Boolean, default: false }, + itemSku: { + type: Schema.Types.ObjectId, + ref: function () { + return ['filament', 'part', 'product'].includes(this.itemType) + ? this.itemType + 'Sku' + : null; + }, + required: false, + }, + }, + { _id: true } +); + +const stockAuditLevelSchema = new Schema( + { + _reference: { type: String, default: () => generateId()() }, + name: { type: String, required: true }, + tags: [{ type: String, required: true }], + auditLines: { type: [stockAuditLevelLineSchema], default: [] }, + }, + { timestamps: true } +); + +stockAuditLevelSchema.index({ name: 'text', tags: 'text' }); + +stockAuditLevelSchema.statics.stats = async function () { + const count = await this.countDocuments(); + return { total: { count } }; +}; + +stockAuditLevelSchema.statics.history = async function () { + return []; +}; + +stockAuditLevelSchema.statics.recalculate = async function (stockAuditLevel, user) { + if (!stockAuditLevel?._id) return; + + const auditLines = stockAuditLevel.auditLines || []; + const normalizedLines = auditLines.map((line) => normalizeAuditLevelLine(line)); + const changed = auditLines.some((line, index) => + auditLevelLineChanged(line, normalizedLines[index]) + ); + + if (!changed) return; + + await editObject({ + model: this, + id: stockAuditLevel._id, + updateData: { auditLines: normalizedLines }, + user, + recalculate: false, + }); +}; + +stockAuditLevelSchema.virtual('id').get(function () { + return this._id; +}); + +stockAuditLevelSchema.set('toJSON', { virtuals: true }); + +export const stockAuditLevelModel = mongoose.model('stockAuditLevel', stockAuditLevelSchema); diff --git a/src/database/schemas/management/taxrate.schema.js b/src/database/schemas/management/taxrate.schema.js new file mode 100644 index 0000000..ba00165 --- /dev/null +++ b/src/database/schemas/management/taxrate.schema.js @@ -0,0 +1,31 @@ +import mongoose from 'mongoose'; +import { generateId } from '../../utils.js'; +import { marketplaceSyncMappingSchema } from '../sales/marketplaceMapping.schema.js'; + +const taxRateSchema = new mongoose.Schema( + { + _reference: { type: String, default: () => generateId()() }, + name: { required: true, type: String }, + rate: { required: true, type: Number }, + rateType: { required: true, type: String, enum: ['percentage', 'fixed'] }, + active: { required: true, type: Boolean, default: true }, + description: { required: false, type: String }, + country: { required: false, type: String }, + jurisdiction: { required: false, type: String }, + shippingAndHandlingTaxed: { required: false, type: Boolean, default: false }, + effectiveFrom: { required: false, type: Date }, + effectiveTo: { required: false, type: Date }, + marketplaces: { type: [marketplaceSyncMappingSchema()], default: [] }, + }, + { timestamps: true } +); + +taxRateSchema.index({ name: 'text', description: 'text', country: 'text' }); + +taxRateSchema.virtual('id').get(function () { + return this._id; +}); + +taxRateSchema.set('toJSON', { virtuals: true }); + +export const taxRateModel = mongoose.model('taxRate', taxRateSchema); diff --git a/src/database/schemas/management/user.schema.js b/src/database/schemas/management/user.schema.js new file mode 100644 index 0000000..8233075 --- /dev/null +++ b/src/database/schemas/management/user.schema.js @@ -0,0 +1,64 @@ +import mongoose from 'mongoose'; +import { generateId } from '../../utils.js'; +import { + applyPermissionSettingsList, + resolvePermissionSettings, + resolveReferencedDocs, + saveUserPermissionsToRedis, +} from '../../permissions.js'; + +const { Schema } = mongoose; + +const userSchema = new mongoose.Schema( + { + _reference: { type: String, default: () => generateId()() }, + username: { required: true, type: String }, + name: { required: true, type: String }, + firstName: { required: false, type: String }, + lastName: { required: false, type: String }, + email: { required: true, type: String }, + profileImage: { type: mongoose.SchemaTypes.ObjectId, ref: 'file', required: false }, + appPasswordHash: { type: String, required: false, select: false }, + groups: [{ type: Schema.Types.ObjectId, ref: 'userGroup', required: false }], + permissionSettings: [ + { type: Schema.Types.ObjectId, ref: 'permissionSetting', required: false }, + ], + permissions: { type: Schema.Types.Mixed, required: false, default: () => ({}) }, + }, + { timestamps: true } +); + +userSchema.index({ username: 'text', name: 'text', firstName: 'text', lastName: 'text', email: 'text' }); + +userSchema.virtual('id').get(function () { + return this._id; +}); + +userSchema.set('toJSON', { virtuals: true }); + +userSchema.statics.recalculate = async function (user, actingUser) { + const userId = user?._id || user; + if (!userId) { + return; + } + + const groups = await resolveReferencedDocs('userGroup', user?.groups); + const settings = await resolvePermissionSettings(user); + const permissions = applyPermissionSettingsList([...groups, ...settings]); + if (user && typeof user === 'object' && !user._bsontype) { + user.permissions = permissions; + } + await saveUserPermissionsToRedis(userId, permissions); + const { editObject } = await import('../../database.js'); + + await editObject({ + model: this, + id: userId, + updateData: { permissions }, + user: actingUser, + populate: ['profileImage', 'permissionSettings', 'groups'], + recalculate: false, + }); +}; + +export const userModel = mongoose.model('user', userSchema); diff --git a/src/database/schemas/management/usergroup.schema.js b/src/database/schemas/management/usergroup.schema.js new file mode 100644 index 0000000..a748e59 --- /dev/null +++ b/src/database/schemas/management/usergroup.schema.js @@ -0,0 +1,74 @@ +import mongoose from 'mongoose'; +import { generateId } from '../../utils.js'; +import { + applyPermissionSettingsList, + excludeIdFromList, + resolvePermissionSettings, +} from '../../permissions.js'; + +const { Schema } = mongoose; + +const userGroupSchema = new Schema( + { + _reference: { type: String, default: () => generateId()() }, + name: { type: String, required: true }, + permissionSettings: [ + { type: Schema.Types.ObjectId, ref: 'permissionSetting', required: false }, + ], + permissions: { type: Schema.Types.Mixed, required: false, default: () => ({}) }, + }, + { timestamps: true } +); + +userGroupSchema.index({ name: 'text' }); + +userGroupSchema.virtual('id').get(function () { + return this._id; +}); + +userGroupSchema.set('toJSON', { virtuals: true }); + +userGroupSchema.statics.recalculate = async function (userGroup, actingUser) { + const userGroupId = userGroup?._id || userGroup; + if (!userGroupId) { + return; + } + + const stillExists = await this.exists({ _id: userGroupId }); + if (stillExists) { + const settings = await resolvePermissionSettings(userGroup); + const permissions = applyPermissionSettingsList(settings); + if (userGroup && typeof userGroup === 'object' && !userGroup._bsontype) { + userGroup.permissions = permissions; + } + const { editObject } = await import('../../database.js'); + + await editObject({ + model: this, + id: userGroupId, + updateData: { permissions }, + user: actingUser, + populate: ['permissionSettings'], + recalculate: false, + }); + } + + const userModel = mongoose.model('user'); + const users = await userModel.find({ groups: userGroupId }).lean(); + + if (!stillExists) { + await userModel.updateMany({ groups: userGroupId }, { $pull: { groups: userGroupId } }); + } + + for (const assignedUser of users) { + const nextUser = stillExists + ? assignedUser + : { + ...assignedUser, + groups: excludeIdFromList(assignedUser.groups, userGroupId), + }; + await userModel.recalculate(nextUser, actingUser); + } +}; + +export const userGroupModel = mongoose.model('userGroup', userGroupSchema); diff --git a/src/database/schemas/management/vendor.schema.js b/src/database/schemas/management/vendor.schema.js new file mode 100644 index 0000000..8dfe6d3 --- /dev/null +++ b/src/database/schemas/management/vendor.schema.js @@ -0,0 +1,37 @@ +import mongoose from 'mongoose'; +import { generateId } from '../../utils.js'; + +const addressSchema = new mongoose.Schema({ + building: { required: false, type: String }, + addressLine1: { required: false, type: String }, + addressLine2: { required: false, type: String }, + city: { required: false, type: String }, + state: { required: false, type: String }, + postcode: { required: false, type: String }, + country: { required: false, type: String }, +}); + +const vendorSchema = new mongoose.Schema( + { + _reference: { type: String, default: () => generateId()() }, + name: { required: true, type: String }, + website: { required: false, type: String }, + email: { required: false, type: String }, + phone: { required: false, type: String }, + contact: { required: false, type: String }, + country: { required: false, type: String }, + active: { required: true, type: Boolean, default: true }, + address: { required: false, type: addressSchema }, + }, + { timestamps: true } +); + +vendorSchema.index({ name: 'text', website: 'text', email: 'text', phone: 'text', contact: 'text', country: 'text' }); + +vendorSchema.virtual('id').get(function () { + return this._id; +}); + +vendorSchema.set('toJSON', { virtuals: true }); + +export const vendorModel = mongoose.model('vendor', vendorSchema); diff --git a/src/database/schemas/misc/note.schema.js b/src/database/schemas/misc/note.schema.js new file mode 100644 index 0000000..a37a793 --- /dev/null +++ b/src/database/schemas/misc/note.schema.js @@ -0,0 +1,48 @@ +import mongoose from 'mongoose'; +import { generateId } from '../../utils.js'; +const { Schema } = mongoose; + +const noteSchema = new mongoose.Schema({ + _reference: { type: String, default: () => generateId()() }, + parent: { + type: Schema.Types.ObjectId, + refPath: 'parentType', + required: true, + }, + parentType: { + type: String, + required: true, + }, + content: { + type: String, + required: true, + }, + noteType: { + type: Schema.Types.ObjectId, + ref: 'noteType', + required: true, + }, + createdAt: { + type: Date, + required: true, + default: Date.now, + }, + updatedAt: { + type: Date, + required: true, + default: Date.now, + }, + user: { + type: Schema.Types.ObjectId, + ref: 'user', + required: false, + }, +}); + +noteSchema.virtual('id').get(function () { + return this._id; +}); + +noteSchema.set('toJSON', { virtuals: true }); + +export const noteModel = mongoose.model('note', noteSchema); diff --git a/src/database/schemas/misc/notification.schema.js b/src/database/schemas/misc/notification.schema.js new file mode 100644 index 0000000..aff1168 --- /dev/null +++ b/src/database/schemas/misc/notification.schema.js @@ -0,0 +1,51 @@ +import mongoose from 'mongoose'; +import { generateId } from '../../utils.js'; +const { Schema } = mongoose; + +const notificationSchema = new mongoose.Schema({ + _reference: { type: String, default: () => generateId()() }, + user: { + type: Schema.Types.ObjectId, + ref: 'user', + required: true, + }, + title: { + type: String, + required: true, + }, + message: { + type: String, + required: true, + }, + type: { + type: String, + required: true, + default: 'info', + }, + read: { + type: Boolean, + default: false, + }, + metadata: { + type: Object, + required: false, + }, + createdAt: { + type: Date, + required: true, + default: Date.now, + }, + updatedAt: { + type: Date, + required: true, + default: Date.now, + }, +}); + +notificationSchema.virtual('id').get(function () { + return this._id; +}); + +notificationSchema.set('toJSON', { virtuals: true }); + +export const notificationModel = mongoose.model('notification', notificationSchema); diff --git a/src/database/schemas/misc/objectview.schema.js b/src/database/schemas/misc/objectview.schema.js new file mode 100644 index 0000000..4a99201 --- /dev/null +++ b/src/database/schemas/misc/objectview.schema.js @@ -0,0 +1,58 @@ +import mongoose from 'mongoose'; +const { Schema } = mongoose; + +const objectViewSchema = new mongoose.Schema({ + user: { + type: Schema.Types.ObjectId, + ref: 'user', + required: true, + }, + objectType: { + type: String, + required: true, + }, + name: { + type: String, + required: true, + }, + color: { + type: String, + required: true, + default: '#3498DB', + }, + private: { + type: Boolean, + required: true, + default: true, + }, + filter: { + type: Schema.Types.Mixed, + default: () => ({}), + }, + sort: { + type: Schema.Types.Mixed, + default: () => ({}), + }, + viewMode: { + type: Schema.Types.Mixed, + default: null, + }, + createdAt: { + type: Date, + required: true, + default: Date.now, + }, + updatedAt: { + type: Date, + required: true, + default: Date.now, + }, +}); + +objectViewSchema.virtual('id').get(function () { + return this._id; +}); + +objectViewSchema.set('toJSON', { virtuals: true }); + +export const objectViewModel = mongoose.model('objectView', objectViewSchema); diff --git a/src/database/schemas/misc/usernotifier.schema.js b/src/database/schemas/misc/usernotifier.schema.js new file mode 100644 index 0000000..eb9cd3a --- /dev/null +++ b/src/database/schemas/misc/usernotifier.schema.js @@ -0,0 +1,44 @@ +import mongoose from 'mongoose'; +import { generateId } from '../../utils.js'; +const { Schema } = mongoose; + +const userNotifierSchema = new mongoose.Schema({ + _reference: { type: String, default: () => generateId()() }, + user: { + type: Schema.Types.ObjectId, + ref: 'user', + required: true, + }, + email: { + type: Boolean, + required: true, + default: false, + }, + object: { + type: Schema.Types.ObjectId, + refPath: 'objectType', + required: true, + }, + objectType: { + type: String, + required: true, + }, + createdAt: { + type: Date, + required: true, + default: Date.now, + }, + updatedAt: { + type: Date, + required: true, + default: Date.now, + }, +}); + +userNotifierSchema.virtual('id').get(function () { + return this._id; +}); + +userNotifierSchema.set('toJSON', { virtuals: true }); + +export const userNotifierModel = mongoose.model('userNotifier', userNotifierSchema); diff --git a/src/database/schemas/misc/usersettings.schema.js b/src/database/schemas/misc/usersettings.schema.js new file mode 100644 index 0000000..6c03158 --- /dev/null +++ b/src/database/schemas/misc/usersettings.schema.js @@ -0,0 +1,47 @@ +import mongoose from 'mongoose'; +import { generateId } from '../../utils.js'; +const { Schema } = mongoose; + +const userSettingsSchema = new mongoose.Schema({ + _reference: { type: String, default: () => generateId()() }, + user: { + type: Schema.Types.ObjectId, + ref: 'user', + required: true, + unique: true, + }, + defaults: { + viewMode: { type: Schema.Types.Mixed, default: () => ({}) }, + filterSidebarVisibility: { + type: Schema.Types.Mixed, + default: () => ({}), + }, + sortSidebarVisibility: { + type: Schema.Types.Mixed, + default: () => ({}), + }, + columnVisibility: { type: Schema.Types.Mixed, default: () => ({}) }, + collapseState: { type: Schema.Types.Mixed, default: () => ({}) }, + }, + pageLayout: { type: Schema.Types.Mixed, default: () => ({}) }, + appearance: { type: Schema.Types.Mixed, default: () => ({}) }, + + createdAt: { + type: Date, + required: true, + default: Date.now, + }, + updatedAt: { + type: Date, + required: true, + default: Date.now, + }, +}); + +userSettingsSchema.virtual('id').get(function () { + return this._id; +}); + +userSettingsSchema.set('toJSON', { virtuals: true }); + +export const userSettingsModel = mongoose.model('userSettings', userSettingsSchema); diff --git a/src/database/schemas/models.js b/src/database/schemas/models.js new file mode 100644 index 0000000..9c67b04 --- /dev/null +++ b/src/database/schemas/models.js @@ -0,0 +1,132 @@ +import { jobModel } from './production/job.schema.js'; +import { subJobModel } from './production/subjob.schema.js'; +import { printerModel } from './production/printer.schema.js'; +import { printerProfileModel } from './production/printerprofile.schema.js'; +import { filamentProfileModel } from './production/filamentprofile.schema.js'; +import { filamentModel } from './management/filament.schema.js'; +import { filamentSkuModel } from './management/filamentsku.schema.js'; +import { gcodeFileModel } from './production/gcodefile.schema.js'; +import { partModel } from './management/part.schema.js'; +import { partSkuModel } from './management/partsku.schema.js'; +import { productModel } from './management/product.schema.js'; +import { productCategoryModel } from './management/productcategory.schema.js'; +import { productSkuModel } from './management/productsku.schema.js'; +import { vendorModel } from './management/vendor.schema.js'; +import { materialModel } from './management/material.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 { stockAuditLevelModel } from './management/stockauditlevel.schema.js'; +import { partStockModel } from './inventory/partstock.schema.js'; +import { productStockModel } from './inventory/productstock.schema.js'; +import { stockLocationModel } from './inventory/stocklocation.schema.js'; +import { stockTransferModel } from './inventory/stocktransfer.schema.js'; +import { auditLogModel } from './management/auditlog.schema.js'; +import { userModel } from './management/user.schema.js'; +import { userGroupModel } from './management/usergroup.schema.js'; +import { permissionSettingModel } from './management/permissionsetting.schema.js'; +import { appPasswordModel } from './management/apppassword.schema.js'; +import { noteTypeModel } from './management/notetype.schema.js'; +import { noteModel } from './misc/note.schema.js'; +import { notificationModel } from './misc/notification.schema.js'; +import { userNotifierModel } from './misc/usernotifier.schema.js'; +import { objectViewModel } from './misc/objectview.schema.js'; +import { documentSizeModel } from './management/documentsize.schema.js'; +import { documentTemplateModel } from './management/documenttemplate.schema.js'; +import { hostModel } from './management/host.schema.js'; +import { documentPrinterModel } from './management/documentprinter.schema.js'; +import { documentJobModel } from './management/documentjob.schema.js'; +import { fileModel } from './management/file.schema.js'; +import { courierServiceModel } from './management/courierservice.schema.js'; +import { courierModel } from './management/courier.schema.js'; +import { taxRateModel } from './management/taxrate.schema.js'; +import { taxRecordModel } from './finance/taxrecord.schema.js'; +import { shipmentModel } from './inventory/shipment.schema.js'; +import { invoiceModel } from './finance/invoice.schema.js'; +import { clientModel } from './sales/client.schema.js'; +import { salesOrderModel } from './sales/salesorder.schema.js'; +import { marketplaceModel } from './sales/marketplace.schema.js'; +import { listingModel } from './sales/listing.schema.js'; +import { listingVarientModel } from './sales/listingvarient.schema.js'; +import { marketplaceEventModel } from './sales/marketplaceevent.schema.js'; +import { paymentModel } from './finance/payment.schema.js'; +import { paymentPolicyModel } from './finance/paymentpolicy.schema.js'; +import { fulfillmentPolicyModel } from './sales/fulfillmentpolicy.schema.js'; +import { returnPolicyModel } from './sales/returnpolicy.schema.js'; + +function modelEntry(getModel, type, label) { + return { + get model() { + return getModel(); + }, + idField: '_id', + type, + referenceField: '_reference', + label, + }; +} + +// Map prefixes to models and id fields. +// Model getters are lazy so circular ESM imports (schema -> utils -> models -> schema) +// do not read bindings that are still in the temporal dead zone. +export const models = { + PRN: modelEntry(() => printerModel, 'printer', 'Printer'), + PPF: modelEntry(() => printerProfileModel, 'printerProfile', 'Printer Profile'), + FPF: modelEntry(() => filamentProfileModel, 'filamentProfile', 'Filament Profile'), + FIL: modelEntry(() => filamentModel, 'filament', 'Filament'), + FSU: modelEntry(() => filamentSkuModel, 'filamentSku', 'Filament SKU'), + GCF: modelEntry(() => gcodeFileModel, 'gcodeFile', 'G-Code File'), + JOB: modelEntry(() => jobModel, 'job', 'Job'), + PRT: modelEntry(() => partModel, 'part', 'Part'), + PSU: modelEntry(() => partSkuModel, 'partSku', 'Part SKU'), + PRD: modelEntry(() => productModel, 'product', 'Product'), + PCG: modelEntry(() => productCategoryModel, 'productCategory', 'Product Category'), + SKU: modelEntry(() => productSkuModel, 'productSku', 'Product SKU'), + VEN: modelEntry(() => vendorModel, 'vendor', 'Vendor'), + MAT: modelEntry(() => materialModel, 'material', 'Material'), + SJB: modelEntry(() => subJobModel, 'subJob', 'Sub Job'), + FLS: modelEntry(() => filamentStockModel, 'filamentStock', 'Filament Stock'), + SEV: modelEntry(() => stockEventModel, 'stockEvent', 'Stock Event'), + SAU: modelEntry(() => stockAuditModel, 'stockAudit', 'Stock Audit'), + SAL: modelEntry(() => stockAuditLevelModel, 'stockAuditLevel', 'Stock Audit Level'), + PTS: modelEntry(() => partStockModel, 'partStock', 'Part Stock'), + PDS: modelEntry(() => productStockModel, 'productStock', 'Product Stock'), + SLN: modelEntry(() => stockLocationModel, 'stockLocation', 'Stock Location'), + STT: modelEntry(() => stockTransferModel, 'stockTransfer', 'Stock Transfer'), + ADL: modelEntry(() => auditLogModel, 'auditLog', 'Audit Log'), + USR: modelEntry(() => userModel, 'user', 'User'), + UGP: modelEntry(() => userGroupModel, 'userGroup', 'User Group'), + PMS: modelEntry(() => permissionSettingModel, 'permissionSetting', 'Permission Settings'), + APP: modelEntry(() => appPasswordModel, 'appPassword', 'App Password'), + NTY: modelEntry(() => noteTypeModel, 'noteType', 'Note Type'), + NTE: modelEntry(() => noteModel, 'note', 'Note'), + NTF: modelEntry(() => notificationModel, 'notification', 'Notification'), + ONF: modelEntry(() => userNotifierModel, 'userNotifier', 'User Notifier'), + OVW: modelEntry(() => objectViewModel, 'objectView', 'Object View'), + DSZ: modelEntry(() => documentSizeModel, 'documentSize', 'Document Size'), + DTP: modelEntry(() => documentTemplateModel, 'documentTemplate', 'Document Template'), + DPR: modelEntry(() => documentPrinterModel, 'documentPrinter', 'Document Printer'), + DJB: modelEntry(() => documentJobModel, 'documentJob', 'Document Job'), + HST: modelEntry(() => hostModel, 'host', 'Host'), + FLE: modelEntry(() => fileModel, 'file', 'File'), + POR: modelEntry(() => purchaseOrderModel, 'purchaseOrder', 'Purchase Order'), + ODI: modelEntry(() => orderItemModel, 'orderItem', 'Order Item'), + COS: modelEntry(() => courierServiceModel, 'courierService', 'Courier Service'), + COR: modelEntry(() => courierModel, 'courier', 'Courier'), + TXR: modelEntry(() => taxRateModel, 'taxRate', 'Tax Rate'), + TXD: modelEntry(() => taxRecordModel, 'taxRecord', 'Tax Record'), + SHP: modelEntry(() => shipmentModel, 'shipment', 'Shipment'), + INV: modelEntry(() => invoiceModel, 'invoice', 'Invoice'), + CLI: modelEntry(() => clientModel, 'client', 'Client'), + SOR: modelEntry(() => salesOrderModel, 'salesOrder', 'Sales Order'), + MKT: modelEntry(() => marketplaceModel, 'marketplace', 'Marketplace'), + LST: modelEntry(() => listingModel, 'listing', 'Listing'), + LVR: modelEntry(() => listingVarientModel, 'listingVarient', 'Listing Varient'), + MKE: modelEntry(() => marketplaceEventModel, 'marketplaceEvent', 'Marketplace Event'), + PAY: modelEntry(() => paymentModel, 'payment', 'Payment'), + PPL: modelEntry(() => paymentPolicyModel, 'paymentPolicy', 'Payment Policy'), + FPL: modelEntry(() => fulfillmentPolicyModel, 'fulfillmentPolicy', 'Fulfillment Policy'), + RPL: modelEntry(() => returnPolicyModel, 'returnPolicy', 'Return Policy'), +}; diff --git a/src/database/schemas/production/filamentprofile.schema.js b/src/database/schemas/production/filamentprofile.schema.js new file mode 100644 index 0000000..91b893b --- /dev/null +++ b/src/database/schemas/production/filamentprofile.schema.js @@ -0,0 +1,165 @@ +import mongoose from 'mongoose'; +import { generateId } from '../../utils.js'; + +const filamentProfileSchema = new mongoose.Schema( + { + _reference: { type: String, default: () => generateId()() }, + name: { type: String, required: true }, + filamentType: { type: String, required: true }, + filament: { + type: mongoose.Schema.Types.ObjectId, + refPath: 'filamentType', + required: true, + }, + filamentIsSupport: { type: Boolean }, + filamentSoluble: { type: Boolean }, + filamentPrintable: { type: Number }, + filamentAdhesivenessCategory: { type: Number }, + temperatureVitrification: { type: Number }, + idleTemperature: { type: Number }, + pelletFlowCoefficient: { type: Number }, + requiredNozzleHrc: { type: Number }, + filamentFlowRatio: { type: Number }, + enablePressureAdvance: { type: Boolean }, + pressureAdvance: { type: Number }, + adaptivePressureAdvance: { type: Boolean }, + adaptivePressureAdvanceBridges: { type: Boolean }, + adaptivePressureAdvanceOverhangs: { type: Boolean }, + adaptivePressureAdvanceModel: { type: String }, + activateChamberTempControl: { type: Boolean }, + chamberTemperature: { type: Number }, + chamberMinimalTemperature: { type: Number }, + nozzleTemperatureInitialLayer: { type: Number }, + nozzleTemperature: { type: Number }, + nozzleTemperatureRangeLow: { type: Number }, + nozzleTemperatureRangeHigh: { type: Number }, + hotPlateTempInitialLayer: { type: Number }, + hotPlateTemp: { type: Number }, + coolPlateTempInitialLayer: { type: Number }, + coolPlateTemp: { type: Number }, + engPlateTempInitialLayer: { type: Number }, + engPlateTemp: { type: Number }, + texturedPlateTempInitialLayer: { type: Number }, + texturedPlateTemp: { type: Number }, + texturedCoolPlateTempInitialLayer: { type: Number }, + texturedCoolPlateTemp: { type: Number }, + supertackPlateTempInitialLayer: { type: Number }, + supertackPlateTemp: { type: Number }, + filamentAdaptiveVolumetricSpeed: { type: Boolean }, + filamentMaxVolumetricSpeed: { type: Number }, + volumetricSpeedCoefficients: { type: String }, + closeFanTheFirstXLayers: { type: Number }, + fullFanSpeedLayer: { type: Number }, + fanMinSpeed: { type: Number }, + fanMaxSpeed: { type: Number }, + reduceFanStopStartFreq: { type: Boolean }, + slowDownForLayerCooling: { type: Boolean }, + dontSlowDownOuterWall: { type: Boolean }, + slowDownMinSpeed: { type: Number }, + slowDownLayerTime: { type: Number }, + fanCoolingLayerTime: { type: Number }, + enableOverhangBridgeFan: { type: Boolean }, + overhangFanThreshold: { type: String }, + overhangFanSpeed: { type: Number }, + internalBridgeFanSpeed: { type: Number }, + supportMaterialInterfaceFanSpeed: { type: Number }, + ironingFanSpeed: { type: Number }, + initialLayerFanSpeed: { type: Number }, + firstXLayerFanSpeed: { type: Number }, + additionalCoolingFanSpeed: { type: Number }, + additionalFanFullSpeedLayer: { type: Number }, + closeAdditionalFanFirstXLayers: { type: Boolean }, + activateAirFiltration: { type: Boolean }, + activateAirFiltrationDuringPrint: { type: Boolean }, + activateAirFiltrationOnCompletion: { type: Boolean }, + duringPrintExhaustFanSpeed: { type: Number }, + completePrintExhaustFanSpeed: { type: Number }, + filamentRetractionLength: { type: Number }, + filamentRetractionSpeed: { type: Number }, + filamentDeretractionSpeed: { type: Number }, + filamentRetractionMinimumTravel: { type: Number }, + filamentRetractWhenChangingLayer: { type: Boolean }, + filamentRetractBeforeWipe: { type: Number }, + filamentRetractAfterWipe: { type: Number }, + filamentRetractRestartExtra: { type: Number }, + filamentRetractLiftAbove: { type: Number }, + filamentRetractLiftBelow: { type: Number }, + filamentRetractLiftEnforce: { type: String }, + filamentWipe: { type: Boolean }, + filamentWipeDistance: { type: Number }, + filamentZHop: { type: Number }, + filamentZHopTypes: { type: String }, + filamentLongRetractionsWhenCut: { type: Boolean }, + filamentRetractionDistancesWhenCut: { type: Number }, + longRetractionsWhenEc: { type: Boolean }, + retractionDistancesWhenEc: { type: Number }, + filamentLoadingSpeed: { type: Number }, + filamentLoadingSpeedStart: { type: Number }, + filamentUnloadingSpeed: { type: Number }, + filamentUnloadingSpeedStart: { type: Number }, + filamentChangeLength: { type: Number }, + filamentChangeLengthNc: { type: Number }, + filamentToolchangeDelay: { type: Number }, + filamentExtruderCompatibility: { type: Number }, + filamentExtruderVariant: { type: String }, + filamentMultitoolRamming: { type: Boolean }, + filamentMultitoolRammingFlow: { type: Number }, + filamentMultitoolRammingVolume: { type: Number }, + filamentRammingParameters: { type: String }, + filamentRammingTravelTime: { type: Number }, + filamentRammingTravelTimeNc: { type: Number }, + filamentRammingVolumetricSpeed: { type: Number }, + filamentRammingVolumetricSpeedNc: { type: Number }, + filamentMinimalPurgeOnWipeTower: { type: Number }, + filamentTowerInterfacePreExtrusionDist: { type: Number }, + filamentTowerInterfacePreExtrusionLength: { type: Number }, + filamentTowerInterfacePrintTemp: { type: Number }, + filamentTowerInterfacePurgeVolume: { type: Number }, + filamentTowerIroningArea: { type: Number }, + filamentCoolingBeforeTower: { type: Number }, + filamentCoolingInitialSpeed: { type: Number }, + filamentCoolingFinalSpeed: { type: Number }, + filamentCoolingMoves: { type: Number }, + filamentFlushTemp: { type: Number }, + filamentFlushTempFast: { type: Number }, + filamentFlushVolumetricSpeed: { type: Number }, + filamentPreCoolingTemperature: { type: Number }, + filamentPreCoolingTemperatureNc: { type: Number }, + filamentPreheatTemperatureDelta: { type: Number }, + filamentPrimeVolumeNc: { type: Number }, + filamentRetractLengthNc: { type: Number }, + filamentStampingDistance: { type: Number }, + filamentStampingLoadingSpeed: { type: Number }, + filamentStartGcode: { type: String }, + filamentEndGcode: { type: String }, + filamentChangeExtrusionRoleGcode: { type: String }, + filamentShrink: { type: String }, + filamentShrinkageCompensationZ: { type: String }, + filamentDevAmsDryingTemperature: { type: Number }, + filamentDevAmsDryingTime: { type: Number }, + filamentDevAmsDryingHeatDistortionTemperature: { type: Number }, + filamentDevAmsDryingAmsLimitations: { type: String }, + filamentDevChamberDryingBedTemperature: { type: Number }, + filamentDevChamberDryingTime: { type: Number }, + filamentDevDryingCoolingTemperature: { type: Number }, + filamentDevDryingSofteningTemperature: { type: Number }, + compatiblePrinters: [ + { type: mongoose.Schema.Types.ObjectId, ref: 'printerProfile' }, + ], + compatiblePrintersCondition: { type: String }, + compatiblePrints: [{ type: String }], + compatiblePrintsCondition: { type: String }, + filamentNotes: { type: String }, + }, + { timestamps: true } +); + +filamentProfileSchema.index({ name: 'text', filamentNotes: 'text' }); + +filamentProfileSchema.virtual('id').get(function () { + return this._id; +}); + +filamentProfileSchema.set('toJSON', { virtuals: true }); + +export const filamentProfileModel = mongoose.model('filamentProfile', filamentProfileSchema); diff --git a/src/database/schemas/production/gcodefile.schema.js b/src/database/schemas/production/gcodefile.schema.js new file mode 100644 index 0000000..964f619 --- /dev/null +++ b/src/database/schemas/production/gcodefile.schema.js @@ -0,0 +1,49 @@ +import mongoose from 'mongoose'; +import { generateId } from '../../utils.js'; +const { Schema } = mongoose; + +const partSchema = new mongoose.Schema({ + part: { type: Schema.Types.ObjectId, ref: 'part', required: true }, + partSku: { type: Schema.Types.ObjectId, ref: 'partSku', required: true }, + quantity: { type: Number, required: true }, +}); + +const gcodeFileSchema = new mongoose.Schema( + { + _reference: { type: String, default: () => generateId()() }, + name: { required: true, type: String }, + gcodeFileName: { required: false, type: String }, + size: { type: Number, required: false }, + filament: { type: Schema.Types.ObjectId, ref: 'filament', required: true }, + filamentSku: { type: Schema.Types.ObjectId, ref: 'filamentSku', required: true }, + parts: [partSchema], + file: { type: mongoose.SchemaTypes.ObjectId, ref: 'file', required: false }, + cost: { type: Number, required: false }, + }, + { timestamps: true } +); + +gcodeFileSchema.pre('validate', async function () { + if (!this.filament && this.filamentSku) { + const sku = await mongoose.model('filamentSku').findById(this.filamentSku).select('filament').lean(); + if (sku?.filament) this.filament = sku.filament; + } + if (this.parts?.length) { + for (const partItem of this.parts) { + if (!partItem.part && partItem.partSku) { + const sku = await mongoose.model('partSku').findById(partItem.partSku).select('part').lean(); + if (sku?.part) partItem.part = sku.part; + } + } + } +}); + +gcodeFileSchema.index({ name: 'text', gcodeFileName: 'text' }); + +gcodeFileSchema.virtual('id').get(function () { + return this._id; +}); + +gcodeFileSchema.set('toJSON', { virtuals: true }); + +export const gcodeFileModel = mongoose.model('gcodeFile', gcodeFileSchema); diff --git a/src/database/schemas/production/job.schema.js b/src/database/schemas/production/job.schema.js new file mode 100644 index 0000000..71a2f02 --- /dev/null +++ b/src/database/schemas/production/job.schema.js @@ -0,0 +1,190 @@ +import mongoose from 'mongoose'; +import { generateId } from '../../utils.js'; +const { Schema } = mongoose; +import { + aggregateRollups, + aggregateRollupsHistory, + listObjects, + editObject, + newObject, + deleteObject, +} from '../../database.js'; +import { subJobModel } from './subjob.schema.js'; + +const jobSchema = new mongoose.Schema( + { + _reference: { type: String, default: () => generateId()() }, + state: { + type: { required: true, type: String }, + progress: { type: Number, required: false }, + }, + printers: [{ type: Schema.Types.ObjectId, ref: 'printer', required: false }], + createdAt: { required: true, type: Date }, + updatedAt: { required: true, type: Date }, + startedAt: { required: false, type: Date, default: null }, + finishedAt: { required: false, type: Date, default: null }, + gcodeFile: { + type: Schema.Types.ObjectId, + ref: 'gcodeFile', + required: false, + }, + quantity: { + type: Number, + required: true, + default: 1, + min: 1, + }, + subJobs: [{ type: Schema.Types.ObjectId, ref: 'subJob', required: false }], + notes: [{ type: Schema.Types.ObjectId, ref: 'note', required: false }], + }, + { timestamps: true } +); + +jobSchema.index({ 'state.type': 'text' }); + +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; +}; + +const normalizeId = (value) => value?._id ?? value; + +jobSchema.statics.recalculate = async function (job, user) { + const jobId = job._id || job; + if (!jobId || job?.state?.type !== 'draft') { + return; + } + + const quantity = job.quantity || 1; + const printers = (job.printers || []).map((printer) => normalizeId(printer)); + const gcodeFile = normalizeId(job.gcodeFile); + + const existingSubJobs = await listObjects({ + model: subJobModel, + filter: { job: jobId }, + sort: 'number', + order: 'ascend', + pagination: false, + }); + + if (existingSubJobs?.error) { + throw existingSubJobs; + } + + const existingSubJobsByNumber = new Map( + existingSubJobs.map((subJob) => [subJob.number, subJob]) + ); + + var printerCount = 0; + + for (let i = 0; i < quantity; i++) { + const subJobNumber = i + 1; + const printer = printers[printerCount]; + const subJobUpdateData = { + updatedAt: new Date(), + printer, + gcodeFile, + number: subJobNumber, + }; + + const existingSubJob = existingSubJobsByNumber.get(subJobNumber); + + if (existingSubJob) { + const subJobResult = await editObject({ + model: subJobModel, + id: existingSubJob._id, + updateData: subJobUpdateData, + user, + }); + if (subJobResult.error) { + throw subJobResult; + } + } else { + const subJobResult = await newObject({ + model: subJobModel, + newData: { + createdAt: new Date(), + ...subJobUpdateData, + job: jobId, + state: { type: 'draft' }, + }, + user, + }); + if (subJobResult.error) { + throw subJobResult; + } + } + + if (printerCount >= printers.length - 1) { + printerCount = 0; + } else { + printerCount += 1; + } + } + + for (const existingSubJob of existingSubJobs) { + if (existingSubJob.number > quantity) { + const deleteResult = await deleteObject({ + model: subJobModel, + id: existingSubJob._id, + user, + }); + if (deleteResult.error) { + throw deleteResult; + } + } + } +}; + +jobSchema.virtual('id').get(function () { + return this._id; +}); + +jobSchema.set('toJSON', { virtuals: true }); + +export const jobModel = mongoose.model('job', jobSchema); diff --git a/src/database/schemas/production/printer.schema.js b/src/database/schemas/production/printer.schema.js new file mode 100644 index 0000000..750d003 --- /dev/null +++ b/src/database/schemas/production/printer.schema.js @@ -0,0 +1,158 @@ +import mongoose from 'mongoose'; +import { generateId } from '../../utils.js'; +const { Schema } = mongoose; +import { aggregateRollups, aggregateRollupsHistory, editObject } from '../../database.js'; + +// Define the moonraker connection schema +const moonrakerSchema = new Schema( + { + host: { type: String, required: true }, + port: { type: Number, required: true }, + protocol: { type: String, required: true }, + apiKey: { type: String, default: null, required: false }, + }, + { _id: false } +); + +const alertSchema = new Schema( + { + type: { type: String, required: true }, // error, info, message + message: { type: String, required: false }, + actions: [{ type: String, required: false, default: [] }], + _id: { type: String, required: true, default: () => generateId()() }, + code: { type: String, required: false }, + canDismiss: { type: Boolean, required: true, default: true }, + }, + { timestamps: true, _id: false } +); + +const pendingSlicerUploadSchema = new Schema( + { + file: { type: Schema.Types.ObjectId, ref: 'file', required: true }, + gcodeFile: { type: Schema.Types.ObjectId, ref: 'gcodeFile', default: null }, + job: { type: Schema.Types.ObjectId, ref: 'job', default: null }, + shouldPrint: { type: Boolean, required: true, default: false }, + new: { type: Boolean, required: true, default: true }, + properties: { type: Object, required: true, default: () => ({}) }, + }, + { timestamps: true } +); + +// Define the main printer schema +const printerSchema = new Schema( + { + _reference: { type: String, default: () => generateId()() }, + name: { type: String, required: true }, + online: { type: Boolean, required: true, default: false }, + active: { type: Boolean, required: true, default: true }, + state: { + type: { type: String, required: true, default: 'offline' }, + message: { type: String, required: false }, + progress: { type: Number, required: false }, + }, + connectedAt: { type: Date, default: null }, + loadedFilament: { + type: Schema.Types.ObjectId, + ref: 'filament', + default: null, + }, + moonraker: { type: moonrakerSchema, required: true }, + tags: [{ type: String }], + firmware: { type: String }, + currentJob: { type: Schema.Types.ObjectId, ref: 'job' }, + currentSubJob: { type: Schema.Types.ObjectId, ref: 'subJob' }, + currentFilamentStock: { type: Schema.Types.ObjectId, ref: 'filamentStock' }, + queue: [{ type: Schema.Types.ObjectId, ref: 'subJob' }], + vendor: { type: Schema.Types.ObjectId, ref: 'vendor', default: null }, + host: { type: Schema.Types.ObjectId, ref: 'host', default: null }, + alerts: [alertSchema], + pendingSlicerUploads: { type: [pendingSlicerUploadSchema], default: [] }, + }, + { timestamps: true } +); + +printerSchema.index({ name: 'text', tags: 'text', firmware: 'text' }); + +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, + }); + + // 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; +}; + +printerSchema.statics.recalculate = async function (printer, user) { + if (printer.active === false && printer.state?.type !== 'inactive') { + await editObject({ + model: this, + id: printer._id, + updateData: { state: { type: 'inactive' } }, + user, + recalculate: false, + }); + return; + } + if (printer.active === true && printer.state?.type === 'inactive' && printer.online === false) { + await editObject({ + model: this, + id: printer._id, + updateData: { state: { type: 'offline' } }, + user, + recalculate: false, + }); + } +}; + +// Add virtual id getter +printerSchema.virtual('id').get(function () { + return this._id; +}); + +// Configure JSON serialization to include virtuals +printerSchema.set('toJSON', { virtuals: true }); + +// Create and export the model +export const printerModel = mongoose.model('printer', printerSchema); diff --git a/src/database/schemas/production/printerprofile.schema.js b/src/database/schemas/production/printerprofile.schema.js new file mode 100644 index 0000000..eea6ea7 --- /dev/null +++ b/src/database/schemas/production/printerprofile.schema.js @@ -0,0 +1,111 @@ +import mongoose from 'mongoose'; +import { generateId } from '../../utils.js'; + +const minMaxNumberSchema = { + min: { type: Number }, + max: { type: Number } +}; + +const thumbnailSchema = new mongoose.Schema( + { + width: { type: Number }, + height: { type: Number } + }, + { _id: true } +); + +const bedExcludeAreaSchema = new mongoose.Schema( + { + x: { type: Number }, + y: { type: Number } + }, + { _id: true } +); + +const extruderSchema = new mongoose.Schema( + { + defaultNozzleVolumeType: { type: String }, + nozzleDiameter: { type: Number }, + nozzleVolume: { type: Number }, + nozzleType: { type: String }, + nozzleFlushDataset: { type: String }, + minLayerHeight: { type: Number }, + maxLayerHeight: { type: Number }, + extruderColour: { type: String }, + extruderType: { type: String }, + extruderVariantList: { type: String }, + extruderPrintableHeight: { type: Number }, + printerExtruderId: { type: String }, + printerExtruderVariant: { type: String }, + positionOffsetX: { type: Number }, + positionOffsetY: { type: Number }, + retractionLength: { type: Number }, + retractionExtraLengthOnRestart: { type: Number }, + retractionSpeed: { type: Number }, + deretractionSpeed: { type: Number }, + retractionTravelDistanceThreshold: { type: Number }, + retractOnLayerChange: { type: Boolean }, + wipeWhileRetracting: { type: Boolean }, + wipeDistance: { type: Number }, + retractAmountBeforeWipe: { type: Number }, + retractAmountAfterWipe: { type: Number }, + zHopOnSurfaces: { type: String }, + zHopType: { type: String }, + zHopHeight: { type: Number }, + zHopTravelingAngle: { type: Number }, + zHopOnlyLiftZAbove: { type: Number }, + zHopOnlyLiftZBelow: { type: Number }, + materialSwitchRetractionLength: { type: Number }, + materialSwitchExtraLengthOnRestart: { type: Number }, + longRetractionWhenCut: { type: Boolean }, + retractionDistancesWhenCut: { type: Number }, + }, + { _id: true } +); + +const printerProfileSchema = new mongoose.Schema( + { + _reference: { type: String, default: () => generateId()() }, + name: { type: String, required: true }, + printer: { type: mongoose.Schema.Types.ObjectId, ref: 'printer', default: null }, + extruders: [extruderSchema], + printBedWidth: { type: Number }, + printBedHeight: { type: Number }, + originX: { type: Number }, + originY: { type: Number }, + bedExcludeArea: [bedExcludeAreaSchema], + printableHeight: { type: Number }, + supportMultiBedTypes: { type: Boolean }, + bestObjectPositionX: { type: Number }, + bestObjectPositionY: { type: Number }, + zOffset: { type: Number }, + preferredOrientation: { type: Number }, + machineMaxAccelerationRetracting: minMaxNumberSchema, + machineMaxSpeedE: minMaxNumberSchema, + machineMaxSpeedX: minMaxNumberSchema, + machineMaxSpeedY: minMaxNumberSchema, + machinePauseGcode: { type: String }, + machineStartGcode: { type: String }, + machineEndGcode: { type: String }, + layerChangeGcode: { type: String }, + beforeLayerChangeGcode: { type: String }, + printerNotes: { type: String }, + scanFirstLayer: { type: Boolean }, + machineLoadFilamentTime: { type: Number }, + machineUnloadFilamentTime: { type: Number }, + thumbnails: [thumbnailSchema], + auxiliaryFan: { type: Boolean }, + machineMaxJunctionDeviation: minMaxNumberSchema, + }, + { timestamps: true } +); + +printerProfileSchema.index({ name: 'text', printerNotes: 'text' }); + +printerProfileSchema.virtual('id').get(function () { + return this._id; +}); + +printerProfileSchema.set('toJSON', { virtuals: true }); + +export const printerProfileModel = mongoose.model('printerProfile', printerProfileSchema); diff --git a/src/database/schemas/production/subjob.schema.js b/src/database/schemas/production/subjob.schema.js new file mode 100644 index 0000000..02d2522 --- /dev/null +++ b/src/database/schemas/production/subjob.schema.js @@ -0,0 +1,54 @@ +import mongoose from 'mongoose'; +import { generateId } from '../../utils.js'; +const { Schema } = mongoose; + +const subJobSchema = new mongoose.Schema({ + _reference: { type: String, default: () => generateId()() }, + printer: { + type: Schema.Types.ObjectId, + ref: 'printer', + required: true, + }, + job: { + type: Schema.Types.ObjectId, + ref: 'job', + required: true, + }, + moonrakerJobId: { + type: String, + required: false, + }, + gcodeFile: { + type: Schema.Types.ObjectId, + ref: 'gcodeFile', + required: true, + }, + state: { + type: { required: true, type: String }, + progress: { required: false, type: Number }, + }, + number: { + type: Number, + required: true, + }, + createdAt: { + type: Date, + default: Date.now, + }, + updatedAt: { + type: Date, + default: Date.now, + }, + startedAt: { required: false, type: Date, default: null }, + finishedAt: { required: false, type: Date, default: null }, +}); + +subJobSchema.index({ moonrakerJobId: 'text', 'state.type': 'text' }); + +subJobSchema.virtual('id').get(function () { + return this._id; +}); + +subJobSchema.set('toJSON', { virtuals: true }); + +export const subJobModel = mongoose.model('subJob', subJobSchema); diff --git a/src/database/schemas/sales/client.schema.js b/src/database/schemas/sales/client.schema.js new file mode 100644 index 0000000..5588d5d --- /dev/null +++ b/src/database/schemas/sales/client.schema.js @@ -0,0 +1,73 @@ +import mongoose from 'mongoose'; +import { generateId } from '../../utils.js'; +import { aggregateRollups, aggregateRollupsHistory } from '../../database.js'; + +const addressSchema = new mongoose.Schema({ + building: { required: false, type: String }, + addressLine1: { required: false, type: String }, + addressLine2: { required: false, type: String }, + city: { required: false, type: String }, + state: { required: false, type: String }, + postcode: { required: false, type: String }, + country: { required: false, type: String }, +}); + +const clientSchema = new mongoose.Schema( + { + _reference: { type: String, default: () => generateId()() }, + name: { required: true, type: String }, + marketplace: { type: mongoose.Schema.Types.ObjectId, ref: 'marketplace', required: false }, + externalReference: { type: String, required: false }, + email: { required: false, type: String }, + phone: { required: false, type: String }, + country: { required: false, type: String }, + active: { required: true, type: Boolean, default: true }, + address: { required: false, type: addressSchema }, + tags: [{ required: false, type: String }], + }, + { timestamps: true } +); + +clientSchema.index({ name: 'text', email: 'text', phone: 'text', country: 'text', tags: 'text' }); +clientSchema.index({ marketplace: 1, externalReference: 1 }, { unique: true, sparse: true }); + +clientSchema.virtual('id').get(function () { + return this._id; +}); + +clientSchema.set('toJSON', { virtuals: true }); + +const rollupConfigs = [ + { + name: 'active', + filter: { active: true }, + rollups: [{ name: 'active', property: 'active', operation: 'count' }], + }, + { + name: 'inactive', + filter: { active: false }, + rollups: [{ name: 'inactive', property: 'active', operation: 'count' }], + }, +]; + +clientSchema.statics.stats = async function () { + const results = await aggregateRollups({ + model: this, + rollupConfigs: rollupConfigs, + }); + + return results; +}; + +clientSchema.statics.history = async function (from, to) { + const results = await aggregateRollupsHistory({ + model: this, + startDate: from, + endDate: to, + rollupConfigs: rollupConfigs, + }); + + return results; +}; + +export const clientModel = mongoose.model('client', clientSchema); diff --git a/src/database/schemas/sales/fulfillmentpolicy.schema.js b/src/database/schemas/sales/fulfillmentpolicy.schema.js new file mode 100644 index 0000000..25e91d3 --- /dev/null +++ b/src/database/schemas/sales/fulfillmentpolicy.schema.js @@ -0,0 +1,31 @@ +import mongoose from 'mongoose'; +import { generateId } from '../../utils.js'; +import { marketplaceSyncMappingSchema } from './marketplaceMapping.schema.js'; + +const { Schema } = mongoose; + +const fulfillmentPolicySchema = new Schema( + { + _reference: { type: String, default: () => generateId()() }, + name: { type: String, required: true }, + description: { type: String, required: false }, + handlingTime: { type: Number, required: false, default: 1 }, + localPickup: { type: Boolean, required: false, default: false }, + globalShipping: { type: Boolean, required: false, default: false }, + freightShipping: { type: Boolean, required: false, default: false }, + pickupDropOff: { type: Boolean, required: false, default: false }, + courierServices: [{ type: Schema.Types.ObjectId, ref: 'courierService', required: false }], + marketplaces: { type: [marketplaceSyncMappingSchema()], default: [] }, + }, + { timestamps: true } +); + +fulfillmentPolicySchema.index({ name: 'text', description: 'text' }); + +fulfillmentPolicySchema.virtual('id').get(function () { + return this._id; +}); + +fulfillmentPolicySchema.set('toJSON', { virtuals: true }); + +export const fulfillmentPolicyModel = mongoose.model('fulfillmentPolicy', fulfillmentPolicySchema); diff --git a/src/database/schemas/sales/listing.schema.js b/src/database/schemas/sales/listing.schema.js new file mode 100644 index 0000000..7e6039b --- /dev/null +++ b/src/database/schemas/sales/listing.schema.js @@ -0,0 +1,260 @@ +import mongoose from 'mongoose'; +import { generateId } from '../../utils.js'; +import { + editObject, + newObject, + deleteObject, + aggregateRollups, + aggregateRollupsHistory, +} from '../../database.js'; +const { Schema } = mongoose; + +const listingSchema = new Schema( + { + _reference: { type: String, default: () => generateId()() }, + product: { type: Schema.Types.ObjectId, ref: 'product', required: false }, + vendor: { type: Schema.Types.ObjectId, ref: 'vendor', required: true }, + stockLocation: { type: Schema.Types.ObjectId, ref: 'stockLocation', required: true }, + marketplace: { type: Schema.Types.ObjectId, ref: 'marketplace', required: true }, + title: { type: String, required: false }, + state: { + type: { + type: String, + enum: [ + 'draft', + 'active', + 'inactive', + 'deleted', + 'suspended', + 'syncing', + 'publishing', + 'unpublishing', + ], + default: 'draft', + }, + progress: { type: Number, required: false }, + message: { type: String, required: false }, + }, + url: { type: String, required: false }, + description: { type: String, required: false }, + listingImages: [{ type: Schema.Types.ObjectId, ref: 'file', required: false }], + externalReference: { type: String, required: false }, + price: { type: Number, required: false }, + currency: { type: String, required: false }, + lastSyncedAt: { type: Date, required: false }, + syncHash: { type: String, required: false }, + syncImageHash: { type: String, required: false }, + marketplaceImageUrls: [{ type: String, required: false }], + stockQuantity: { type: Number, required: false, default: 0 }, + condition: { + type: String, + enum: [ + 'new', + 'likeNew', + 'newOther', + 'newWithDefects', + 'manufacturerRefurbished', + 'certifiedRefurbished', + 'excellentRefurbished', + 'veryGoodRefurbished', + 'goodRefurbished', + 'sellerRefurbished', + 'usedExcellent', + 'usedVeryGood', + 'usedGood', + 'usedAcceptable', + 'forPartsOrNotWorking', + 'preOwnedExcellent', + 'preOwnedFair', + ], + default: 'new', + required: false, + }, + courierServices: [{ type: Schema.Types.ObjectId, ref: 'courierService', required: true }], + fulfillmentPolicy: { type: Schema.Types.ObjectId, ref: 'fulfillmentPolicy', required: false }, + paymentPolicy: { type: Schema.Types.ObjectId, ref: 'paymentPolicy', required: false }, + returnPolicy: { type: Schema.Types.ObjectId, ref: 'returnPolicy', required: false }, + }, + { timestamps: true } +); + +listingSchema.index({ title: 'text', url: 'text' }); +listingSchema.index({ marketplace: 1, externalReference: 1 }, { unique: true, sparse: true }); + +listingSchema.virtual('id').get(function () { + return this._id; +}); + +listingSchema.set('toJSON', { + virtuals: true, + transform(doc, ret) { + if (!ret.state && ret.status) { + ret.state = { type: ret.status, message: null }; + } + if (ret.status) delete ret.status; + return ret; + }, +}); + +const refId = (value) => value?._id ?? value; + +listingSchema.statics.recalculate = async function (listing, user) { + const listingId = refId(listing); + if (!listingId) { + return; + } + + const listingVarientModel = mongoose.model('listingVarient'); + const productSkuModel = mongoose.model('productSku'); + const listingProductId = refId(listing.product); + + const findVarients = () => + listingVarientModel.find({ listing: listingId }).sort({ createdAt: 1 }).lean(); + + let varients = await findVarients(); + + if (listingProductId) { + const productSkus = await productSkuModel + .find({ product: listingProductId }) + .sort({ createdAt: 1 }) + .lean(); + + const varientsBySkuId = new Map(); + const unmatchedVarients = []; + for (const varient of varients) { + const skuId = refId(varient.productSku); + if (skuId && !varientsBySkuId.has(String(skuId))) { + varientsBySkuId.set(String(skuId), varient); + } else { + unmatchedVarients.push(varient); + } + } + + for (const sku of productSkus) { + const skuId = sku._id; + const varientUpdateData = { + product: listingProductId, + productSku: skuId, + }; + const existingVarient = varientsBySkuId.get(String(skuId)) || unmatchedVarients.shift(); + + if (existingVarient) { + varientsBySkuId.delete(String(skuId)); + const existingProductId = refId(existingVarient.product); + const existingSkuId = refId(existingVarient.productSku); + if ( + String(existingProductId) === String(listingProductId) && + String(existingSkuId) === String(skuId) + ) { + continue; + } + const varientResult = await editObject({ + model: listingVarientModel, + id: existingVarient._id, + updateData: varientUpdateData, + user, + recalculate: false, + }); + if (varientResult.error) { + throw varientResult; + } + } else { + const varientResult = await newObject({ + model: listingVarientModel, + newData: { + ...varientUpdateData, + listing: listingId, + state: { type: listing.state?.type || 'draft' }, + }, + user, + recalculate: false, + }); + if (varientResult.error) { + throw varientResult; + } + } + } + + for (const extra of [...varientsBySkuId.values(), ...unmatchedVarients]) { + const deleteResult = await deleteObject({ + model: listingVarientModel, + id: extra._id, + user, + }); + if (deleteResult.error) { + throw deleteResult; + } + } + + varients = await findVarients(); + } + + for (const varient of varients) { + await listingVarientModel.recalculate(varient, user); + } +}; + +const rollupConfigs = [ + { + name: 'draft', + filter: { 'state.type': 'draft' }, + rollups: [{ name: 'draft', property: 'state.type', operation: 'count' }], + }, + { + name: 'active', + filter: { 'state.type': 'active' }, + rollups: [{ name: 'active', property: 'state.type', operation: 'count' }], + }, + { + name: 'inactive', + filter: { 'state.type': 'inactive' }, + rollups: [{ name: 'inactive', property: 'state.type', operation: 'count' }], + }, + { + name: 'syncing', + filter: { 'state.type': 'syncing' }, + rollups: [{ name: 'syncing', property: 'state.type', operation: 'count' }], + }, + { + name: 'publishing', + filter: { 'state.type': 'publishing' }, + rollups: [{ name: 'publishing', property: 'state.type', operation: 'count' }], + }, + { + name: 'unpublishing', + filter: { 'state.type': 'unpublishing' }, + rollups: [{ name: 'unpublishing', property: 'state.type', operation: 'count' }], + }, + { + name: 'suspended', + filter: { 'state.type': 'suspended' }, + rollups: [{ name: 'suspended', property: 'state.type', operation: 'count' }], + }, + { + name: 'deleted', + filter: { 'state.type': 'deleted' }, + rollups: [{ name: 'deleted', property: 'state.type', operation: 'count' }], + }, +]; + +listingSchema.statics.stats = async function () { + const results = await aggregateRollups({ + model: this, + rollupConfigs: rollupConfigs, + }); + + return results; +}; + +listingSchema.statics.history = async function (from, to) { + const results = await aggregateRollupsHistory({ + model: this, + startDate: from, + endDate: to, + rollupConfigs: rollupConfigs, + }); + + return results; +}; + +export const listingModel = mongoose.model('listing', listingSchema); diff --git a/src/database/schemas/sales/listingvarient.schema.js b/src/database/schemas/sales/listingvarient.schema.js new file mode 100644 index 0000000..975ecba --- /dev/null +++ b/src/database/schemas/sales/listingvarient.schema.js @@ -0,0 +1,171 @@ +import mongoose from 'mongoose'; +import { generateId } from '../../utils.js'; +import { aggregateRollups, editObject } from '../../database.js'; +const { Schema } = mongoose; + +const toId = (value) => { + if (value == null) return null; + if (typeof value === 'object' && value._id) return String(value._id); + return String(value); +}; + +const listingVarientAspectSchema = new Schema( + { + name: { type: String, required: true }, + value: { type: String, required: true }, + }, + { _id: true } +); + +const listingVarientSchema = new Schema( + { + _reference: { type: String, default: () => generateId()() }, + listing: { type: Schema.Types.ObjectId, ref: 'listing', required: true }, + product: { type: Schema.Types.ObjectId, ref: 'product', required: false }, + productSku: { type: Schema.Types.ObjectId, ref: 'productSku', required: false }, + aspects: { type: [listingVarientAspectSchema], default: [] }, + state: { + type: { + type: String, + enum: [ + 'draft', + 'active', + 'inactive', + 'deleted', + 'suspended', + 'syncing', + 'publishing', + 'unpublishing', + ], + default: 'draft', + }, + message: { type: String, required: false }, + }, + externalReference: { type: String, required: false }, + price: { type: Number, required: false }, + currency: { type: String, required: false }, + priceTaxRate: { type: Schema.Types.ObjectId, ref: 'taxRate', required: false }, + priceWithTax: { type: Number, required: false }, + lastSyncedAt: { type: Date, required: false }, + syncHash: { type: String, required: false }, + syncImageHash: { type: String, required: false }, + marketplaceImageUrls: [{ type: String, required: false }], + listingImages: [{ type: Schema.Types.ObjectId, ref: 'file', required: false }], + stockQuantity: { type: Number, required: false, default: 0 }, + }, + { timestamps: true } +); + +listingVarientSchema.index({ currency: 'text', 'state.type': 'text' }); +listingVarientSchema.index( + { listing: 1, externalReference: 1 }, + { + unique: true, + name: 'listing_1_externalReference_1', + partialFilterExpression: { externalReference: { $type: 'string', $gt: '' } }, + } +); + +listingVarientSchema.virtual('id').get(function () { + return this._id; +}); + +listingVarientSchema.set('toJSON', { + virtuals: true, + transform(doc, ret) { + if (!ret.state && ret.status) { + ret.state = { type: ret.status, message: null }; + } + if (ret.status) delete ret.status; + return ret; + }, +}); + +listingVarientSchema.statics.recalculate = async function (listingVarient, user) { + const listingId = listingVarient?.listing?._id || listingVarient?.listing; + const varientId = listingVarient?._id; + const productSkuId = toId(listingVarient?.productSku); + + if (varientId && productSkuId && (await this.exists({ _id: varientId }))) { + let listing = listingVarient.listing; + if (!listing?.stockLocation) { + listing = await mongoose + .model('listing') + .findById(listingId) + .select('stockLocation product') + .lean(); + } + const stockLocationId = toId(listing?.stockLocation); + if (stockLocationId) { + const stockRollup = await aggregateRollups({ + model: mongoose.model('productStock'), + baseFilter: { + productSku: new mongoose.Types.ObjectId(productSkuId), + stockLocation: new mongoose.Types.ObjectId(stockLocationId), + }, + rollupConfigs: [ + { + name: 'stockQuantity', + rollups: [{ name: 'stockQuantity', property: 'currentQuantity', operation: 'sum' }], + }, + ], + }); + const stockQuantity = stockRollup.stockQuantity?.sum || 0; + if (listingVarient.stockQuantity !== stockQuantity) { + await editObject({ + model: this, + id: varientId, + updateData: { stockQuantity }, + user, + recalculate: false, + }); + } + } + } + + if (!listingId) { + return; + } + + const rollupResults = await aggregateRollups({ + model: this, + baseFilter: { listing: new mongoose.Types.ObjectId(listingId) }, + rollupConfigs: [ + { + name: 'stockQuantity', + rollups: [{ name: 'stockQuantity', property: 'stockQuantity', operation: 'sum' }], + }, + ], + }); + + await editObject({ + model: mongoose.model('listing'), + id: listingId, + updateData: { + stockQuantity: rollupResults.stockQuantity?.sum || 0, + }, + user, + recalculate: false, + }); +}; + +export const listingVarientModel = mongoose.model('listingVarient', listingVarientSchema); + +async function replaceSparseExternalReferenceIndex() { + try { + const indexes = await listingVarientModel.collection.indexes(); + const current = indexes.find((idx) => idx.name === 'listing_1_externalReference_1'); + if (current && (current.sparse || !current.partialFilterExpression)) { + await listingVarientModel.collection.dropIndex('listing_1_externalReference_1'); + } + await listingVarientModel.createIndexes(); + } catch { + // Collection/index may not exist until Mongo is connected. + } +} + +if (mongoose.connection.readyState === 1) { + replaceSparseExternalReferenceIndex(); +} else { + mongoose.connection.once('open', replaceSparseExternalReferenceIndex); +} diff --git a/src/database/schemas/sales/marketplace.schema.js b/src/database/schemas/sales/marketplace.schema.js new file mode 100644 index 0000000..2e931c0 --- /dev/null +++ b/src/database/schemas/sales/marketplace.schema.js @@ -0,0 +1,125 @@ +import mongoose from 'mongoose'; +import { editObject, aggregateRollups, aggregateRollupsHistory } from '../../database.js'; +import { generateId } from '../../utils.js'; + +const marketplaceSchema = new mongoose.Schema( + { + _reference: { type: String, default: () => generateId()() }, + name: { required: true, type: String }, + provider: { + type: String, + required: true, + enum: ['ebay', 'etsy', 'tiktokShop'], + }, + active: { required: true, type: Boolean, default: true }, + connected: { type: Boolean, required: true, default: false }, + connectedAt: { type: Date, required: false }, + state: { + type: { + type: String, + enum: ['active', 'inactive', 'suspended', 'ready', 'offline', 'syncing', 'disconnected'], + default: 'offline', + }, + message: { type: String, required: false }, + }, + // Provider-specific API configuration (flexible for eBay, Etsy, TikTok Shop) + config: { type: mongoose.Schema.Types.Mixed, default: {} }, + defaultFulfillmentPolicy: { + type: mongoose.Schema.Types.ObjectId, + ref: 'fulfillmentPolicy', + required: false, + }, + defaultPaymentPolicy: { + type: mongoose.Schema.Types.ObjectId, + ref: 'paymentPolicy', + required: false, + }, + defaultReturnPolicy: { + type: mongoose.Schema.Types.ObjectId, + ref: 'returnPolicy', + required: false, + }, + eBay: { + availableShippingServices: { type: [String], default: [] }, + categoryReferences: { type: [mongoose.Schema.Types.Mixed], default: [] }, + }, + }, + { timestamps: true } +); + +marketplaceSchema.index({ name: 'text', provider: 'text' }); + +marketplaceSchema.virtual('id').get(function () { + return this._id; +}); + +marketplaceSchema.statics.recalculate = async function (marketplace, user) { + let stateType; + if (marketplace.active === false) { + stateType = 'inactive'; + } else if (marketplace.connected === false) { + stateType = 'disconnected'; + } else { + stateType = 'ready'; + } + marketplace.state = { type: stateType }; + await editObject({ + model: this, + id: marketplace._id, + updateData: { state: { type: stateType } }, + user, + recalculate: false, + }); +}; + +const rollupConfigs = [ + { + name: 'ready', + filter: { 'state.type': 'ready' }, + rollups: [{ name: 'ready', property: 'state.type', operation: 'count' }], + }, + { + name: 'syncing', + filter: { 'state.type': 'syncing' }, + rollups: [{ name: 'syncing', property: 'state.type', operation: 'count' }], + }, + { + name: 'disconnected', + filter: { 'state.type': 'disconnected' }, + rollups: [{ name: 'disconnected', property: 'state.type', operation: 'count' }], + }, + { + name: 'inactive', + filter: { 'state.type': 'inactive' }, + rollups: [{ name: 'inactive', property: 'state.type', operation: 'count' }], + }, + { + name: 'offline', + filter: { 'state.type': 'offline' }, + rollups: [{ name: 'offline', property: 'state.type', operation: 'count' }], + }, +]; + +marketplaceSchema.statics.stats = async function () { + const results = await aggregateRollups({ + model: this, + rollupConfigs: rollupConfigs, + }); + + return results; +}; + +marketplaceSchema.statics.history = async function (from, to) { + const results = await aggregateRollupsHistory({ + model: this, + startDate: from, + endDate: to, + rollupConfigs: rollupConfigs, + }); + + return results; +}; + +marketplaceSchema.set('toJSON', { virtuals: true }); + +export const marketplaceModel = mongoose.model('marketplace', marketplaceSchema); diff --git a/src/database/schemas/sales/marketplaceMapping.schema.js b/src/database/schemas/sales/marketplaceMapping.schema.js new file mode 100644 index 0000000..92043ff --- /dev/null +++ b/src/database/schemas/sales/marketplaceMapping.schema.js @@ -0,0 +1,24 @@ +import mongoose from 'mongoose'; + +const { Schema } = mongoose; + +export const MARKETPLACE_MAPPING_STATES = ['pending', 'syncing', 'ready', 'failed']; + +export function marketplaceSyncMappingSchema() { + return new Schema( + { + marketplace: { type: Schema.Types.ObjectId, ref: 'marketplace', required: true }, + externalReference: { type: String, required: false }, + syncHash: { type: String, required: false }, + state: { + type: { + type: String, + enum: MARKETPLACE_MAPPING_STATES, + default: 'pending', + }, + message: { type: String, required: false }, + }, + }, + { _id: true } + ); +} diff --git a/src/database/schemas/sales/marketplaceevent.schema.js b/src/database/schemas/sales/marketplaceevent.schema.js new file mode 100644 index 0000000..8baee58 --- /dev/null +++ b/src/database/schemas/sales/marketplaceevent.schema.js @@ -0,0 +1,31 @@ +import mongoose from 'mongoose'; +import { generateId } from '../../utils.js'; + +const marketplaceEventSchema = new mongoose.Schema( + { + _reference: { type: String, default: () => generateId()() }, + marketplace: { type: mongoose.Schema.Types.ObjectId, ref: 'marketplace', required: true }, + externalReference: { type: String, required: true }, + topic: { type: String, required: true }, + status: { + type: String, + required: true, + enum: ['received', 'processed', 'failed'], + default: 'received', + }, + processedAt: { type: Date, required: false }, + error: { type: String, required: false }, + }, + { timestamps: true } +); + +marketplaceEventSchema.index({ marketplace: 1, externalReference: 1 }, { unique: true }); +marketplaceEventSchema.index({ topic: 'text', status: 'text' }); + +marketplaceEventSchema.virtual('id').get(function () { + return this._id; +}); + +marketplaceEventSchema.set('toJSON', { virtuals: true }); + +export const marketplaceEventModel = mongoose.model('marketplaceEvent', marketplaceEventSchema); diff --git a/src/database/schemas/sales/returnpolicy.schema.js b/src/database/schemas/sales/returnpolicy.schema.js new file mode 100644 index 0000000..b416d62 --- /dev/null +++ b/src/database/schemas/sales/returnpolicy.schema.js @@ -0,0 +1,46 @@ +import mongoose from 'mongoose'; +import { generateId } from '../../utils.js'; +import { marketplaceSyncMappingSchema } from './marketplaceMapping.schema.js'; + +const returnPolicySchema = new mongoose.Schema( + { + _reference: { type: String, default: () => generateId()() }, + name: { type: String, required: true }, + description: { type: String, required: false }, + returnsAccepted: { type: Boolean, required: true, default: true }, + returnPeriodDays: { type: Number, required: false, default: 30 }, + returnShippingCostPayer: { + type: String, + enum: ['buyer', 'seller'], + required: false, + default: 'buyer', + }, + refundMethod: { + type: String, + enum: ['moneyBack', 'merchandiseCredit'], + required: false, + default: 'moneyBack', + }, + restockingFeePercentage: { type: Number, required: false }, + returnInstructions: { type: String, required: false }, + internationalReturnsAccepted: { type: Boolean, required: false }, + internationalReturnPeriodDays: { type: Number, required: false }, + internationalReturnShippingCostPayer: { + type: String, + enum: ['buyer', 'seller'], + required: false, + }, + marketplaces: { type: [marketplaceSyncMappingSchema()], default: [] }, + }, + { timestamps: true } +); + +returnPolicySchema.index({ name: 'text', description: 'text', returnInstructions: 'text' }); + +returnPolicySchema.virtual('id').get(function () { + return this._id; +}); + +returnPolicySchema.set('toJSON', { virtuals: true }); + +export const returnPolicyModel = mongoose.model('returnPolicy', returnPolicySchema); diff --git a/src/database/schemas/sales/salesorder.schema.js b/src/database/schemas/sales/salesorder.schema.js new file mode 100644 index 0000000..3690856 --- /dev/null +++ b/src/database/schemas/sales/salesorder.schema.js @@ -0,0 +1,229 @@ +import mongoose from 'mongoose'; +import { generateId } from '../../utils.js'; +const { Schema } = mongoose; +import { aggregateRollups, aggregateRollupsHistory, editObject } from '../../database.js'; + +const salesOrderSchema = new Schema( + { + _reference: { type: String, default: () => generateId()() }, + totalAmount: { type: Number, required: true, default: 0 }, + totalAmountWithTax: { type: Number, required: true, default: 0 }, + shippingAmount: { type: Number, required: true, default: 0 }, + shippingAmountWithTax: { type: Number, required: true, default: 0 }, + grandTotalAmount: { type: Number, required: true, default: 0 }, + totalTaxAmount: { type: Number, required: true, default: 0 }, + timestamp: { type: Date, default: Date.now }, + client: { type: Schema.Types.ObjectId, ref: 'client', required: true }, + marketplace: { type: Schema.Types.ObjectId, ref: 'marketplace', required: false }, + externalReference: { type: String, required: false }, + state: { + type: { type: String, required: true, default: 'draft' }, + }, + postedAt: { type: Date, required: false }, + confirmedAt: { type: Date, required: false }, + cancelledAt: { type: Date, required: false }, + completedAt: { type: Date, required: false }, + }, + { timestamps: true } +); + +salesOrderSchema.index({ 'state.type': 'text' }); +salesOrderSchema.index({ marketplace: 1, externalReference: 1 }, { unique: true, sparse: true }); + +const rollupConfigs = [ + { + name: 'draft', + filter: { 'state.type': 'draft' }, + rollups: [{ name: 'draft', property: 'state.type', operation: 'count' }], + }, + { + name: 'sent', + filter: { 'state.type': 'sent' }, + rollups: [{ name: 'sent', property: 'state.type', operation: 'count' }], + }, + { + name: 'confirmed', + filter: { 'state.type': 'confirmed' }, + rollups: [{ name: 'confirmed', property: 'state.type', operation: 'count' }], + }, + { + name: 'partiallyShipped', + filter: { 'state.type': 'partiallyShipped' }, + rollups: [{ name: 'partiallyShipped', property: 'state.type', operation: 'count' }], + }, + { + name: 'shipped', + filter: { 'state.type': 'shipped' }, + rollups: [{ name: 'shipped', property: 'state.type', operation: 'count' }], + }, + { + name: 'partiallyDelivered', + filter: { 'state.type': 'partiallyDelivered' }, + rollups: [{ name: 'partiallyDelivered', property: 'state.type', operation: 'count' }], + }, + { + name: 'delivered', + filter: { 'state.type': 'delivered' }, + rollups: [{ name: 'delivered', property: 'state.type', operation: 'count' }], + }, + { + name: 'cancelled', + filter: { 'state.type': 'cancelled' }, + rollups: [{ name: 'cancelled', property: 'state.type', operation: 'count' }], + }, + { + name: 'completed', + filter: { 'state.type': 'completed' }, + rollups: [{ name: 'completed', property: 'state.type', operation: 'count' }], + }, + { + name: 'pipelineValue', + filter: { + 'state.type': { + $in: ['sent', 'confirmed', 'partiallyShipped', 'shipped', 'partiallyDelivered'], + }, + }, + rollups: [{ name: 'grandTotalAmount', property: 'grandTotalAmount', operation: 'sum' }], + }, + { + name: 'completedValue', + filter: { 'state.type': { $in: ['delivered', 'completed'] } }, + rollups: [{ name: 'grandTotalAmount', property: 'grandTotalAmount', operation: 'sum' }], + }, +]; + +salesOrderSchema.statics.stats = async function () { + const results = await aggregateRollups({ + model: this, + rollupConfigs: rollupConfigs, + }); + + // Transform the results to match the expected format + return results; +}; + +salesOrderSchema.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; +}; + +salesOrderSchema.statics.recalculate = async function (salesOrder, user) { + const orderId = salesOrder._id || salesOrder; + if (!orderId) { + return; + } + + const orderItemModel = mongoose.model('orderItem'); + const shipmentModel = mongoose.model('shipment'); + + const orderIdObj = new mongoose.Types.ObjectId(orderId); + const baseFilter = { order: orderIdObj, orderType: 'salesOrder' }; + + const orderItemRollupResults = await aggregateRollups({ + model: orderItemModel, + baseFilter, + rollupConfigs: [ + { + name: 'orderTotals', + rollups: [ + { name: 'totalAmount', property: 'totalAmount', operation: 'sum' }, + { name: 'totalAmountWithTax', property: 'totalAmountWithTax', operation: 'sum' }, + ], + }, + { + name: 'overallCount', + rollups: [{ name: 'overallCount', property: '_id', operation: 'count' }], + }, + { + name: 'shipped', + filter: { 'state.type': 'shipped' }, + rollups: [{ name: 'shipped', property: 'state.type', operation: 'count' }], + }, + { + name: 'received', + filter: { 'state.type': 'received' }, + rollups: [{ name: 'received', property: 'state.type', operation: 'count' }], + }, + ], + }); + + const shipmentRollupResults = await aggregateRollups({ + model: shipmentModel, + baseFilter, + rollupConfigs: [ + { + name: 'shipmentTotals', + rollups: [ + { name: 'amount', property: 'amount', operation: 'sum' }, + { name: 'amountWithTax', property: 'amountWithTax', operation: 'sum' }, + ], + }, + ], + }); + + const orderTotals = orderItemRollupResults.orderTotals || {}; + const totalAmount = orderTotals.totalAmount?.sum?.toFixed(2) || 0; + const totalAmountWithTax = orderTotals.totalAmountWithTax?.sum?.toFixed(2) || 0; + + const shipmentTotals = shipmentRollupResults.shipmentTotals || {}; + const totalShippingAmount = shipmentTotals.amount?.sum?.toFixed(2) || 0; + const totalShippingAmountWithTax = shipmentTotals.amountWithTax?.sum?.toFixed(2) || 0; + + const grandTotalAmount = + parseFloat(totalAmountWithTax || 0) + parseFloat(totalShippingAmountWithTax || 0); + + const overallCount = orderItemRollupResults.overallCount?.count || 0; + const shippedCount = orderItemRollupResults.shipped?.count || 0; + const receivedCount = orderItemRollupResults.received?.count || 0; + + let updateData = { + totalAmount: parseFloat(totalAmount).toFixed(2), + totalAmountWithTax: parseFloat(totalAmountWithTax).toFixed(2), + totalTaxAmount: parseFloat((totalAmountWithTax - totalAmount).toFixed(2)), + shippingAmount: parseFloat(totalShippingAmount).toFixed(2), + shippingAmountWithTax: parseFloat(totalShippingAmountWithTax).toFixed(2), + grandTotalAmount: parseFloat(grandTotalAmount).toFixed(2), + }; + + if (shippedCount > 0 && shippedCount < overallCount) { + updateData = { ...updateData, state: { type: 'partiallyShipped' } }; + } + + if (shippedCount > 0 && shippedCount === overallCount) { + updateData = { ...updateData, state: { type: 'shipped' } }; + } + + if (receivedCount > 0 && receivedCount < overallCount) { + updateData = { ...updateData, state: { type: 'partiallyDelivered' } }; + } + + if (receivedCount > 0 && receivedCount === overallCount) { + updateData = { ...updateData, state: { type: 'delivered' } }; + } + + await editObject({ + model: this, + id: orderId, + updateData, + user, + recalculate: false, + }); +}; + +// Add virtual id getter +salesOrderSchema.virtual('id').get(function () { + return this._id; +}); + +// Configure JSON serialization to include virtuals +salesOrderSchema.set('toJSON', { virtuals: true }); + +// Create and export the model +export const salesOrderModel = mongoose.model('salesOrder', salesOrderSchema); diff --git a/src/database/tax.js b/src/database/tax.js new file mode 100644 index 0000000..078094b --- /dev/null +++ b/src/database/tax.js @@ -0,0 +1,54 @@ +/** + * Tax calculation helpers mirroring farmcontrol-ui model value functions. + */ + +export function isPopulatedTaxRate(taxRate) { + return taxRate != null && taxRate.rateType != null; +} + +export async function resolveTaxRate(taxRateRef, getObject, taxRateModel) { + if (!taxRateRef) { + return null; + } + if (isPopulatedTaxRate(taxRateRef)) { + return taxRateRef; + } + const id = taxRateRef._id ?? taxRateRef; + if (!id) { + return null; + } + if ( + typeof taxRateRef === 'object' && + taxRateRef._id && + Object.keys(taxRateRef).length === 1 + ) { + return await getObject({ model: taxRateModel, id, cached: true }); + } + return await getObject({ model: taxRateModel, id, cached: true }); +} + +export function amountWithTax(amount, taxRate) { + const base = Number.parseFloat(amount) || 0; + if (!base) { + return 0; + } + if (!taxRate) { + return Number.parseFloat(base.toFixed(2)); + } + + const rate = Number.parseFloat(taxRate.rate) || 0; + if (taxRate.rateType === 'percentage') { + return Number.parseFloat((base * (1 + rate / 100)).toFixed(2)); + } + if (taxRate.rateType === 'amount' || taxRate.rateType === 'fixed') { + return Number.parseFloat((base + rate).toFixed(2)); + } + return Number.parseFloat(base.toFixed(2)); +} + +export function effectiveMarginPrice({ priceMode, price, cost, margin }) { + if (priceMode === 'margin' && margin != null && cost != null) { + return cost * (1 + margin / 100); + } + return price; +} diff --git a/src/database/utils.js b/src/database/utils.js new file mode 100644 index 0000000..a5ee793 --- /dev/null +++ b/src/database/utils.js @@ -0,0 +1,755 @@ +import { ObjectId } from 'mongodb'; +import { auditLogModel } from './schemas/management/auditlog.schema.js'; +import { notificationModel } from './schemas/misc/notification.schema.js'; +import { userNotifierModel } from './schemas/misc/usernotifier.schema.js'; +import { natsServer } from './nats.js'; + +import { customAlphabet } from 'nanoid'; + +const NOTIFICATION_EXCLUDED_MODELS = [ + 'notification', + 'userNotifier', + 'objectView', + 'auditLog' +]; +const AUDIT_EXCLUDED_MODELS = [ + 'notification', + 'userNotifier', + 'objectView', + 'marketplaceEvent' +]; +const AUDIT_EXCLUDED_CHANGES = ['state.message']; +const SENSITIVE_KEYS = ['secret']; + +function omitSensitive(obj) { + if (obj == null || typeof obj !== 'object') return obj; + if (Array.isArray(obj)) return obj.map(omitSensitive); + const result = {}; + for (const [key, value] of Object.entries(obj)) { + if (SENSITIVE_KEYS.includes(key)) continue; + result[key] = omitSensitive(value); + } + return result; +} + +function omitPath(obj, path) { + if (obj == null || typeof obj !== 'object') return obj; + const keys = path.split('.'); + if (keys.length === 1) { + const result = { ...obj }; + delete result[keys[0]]; + return result; + } + const [first, ...rest] = keys; + if ( + obj[first] == null || + typeof obj[first] !== 'object' || + Array.isArray(obj[first]) + ) { + return obj; + } + const nested = obj[first]; + const leafKey = rest[rest.length - 1]; + const result = { ...obj }; + + if (rest.length === 1) { + const nestedKeys = Object.keys(nested); + if (nestedKeys.length === 1 && nestedKeys[0] === leafKey) { + delete result[first]; + } + return result; + } + + const nestedKeys = Object.keys(nested); + if ( + nestedKeys.length > 1 || + (nestedKeys.length === 1 && nestedKeys[0] !== rest[0]) + ) { + return obj; + } + + const updatedNested = omitPath({ ...nested }, rest.join('.')); + if (updatedNested == null || Object.keys(updatedNested).length === 0) { + delete result[first]; + } else { + result[first] = updatedNested; + } + return result; +} + +function omitExcludedChanges(obj) { + if (obj == null || typeof obj !== 'object') return obj; + return AUDIT_EXCLUDED_CHANGES.reduce((acc, path) => omitPath(acc, path), { + ...obj + }); +} + +let modelsCache = null; + +async function getModelEntryByType(parentType) { + if (!modelsCache) { + modelsCache = (await import('./schemas/models.js')).models; + } + return Object.values(modelsCache).find( + entry => entry.type === parentType || entry.model?.modelName === parentType + ); +} + +function notificationUserFromOwner(owner, ownerType) { + if (!owner) return null; + if (ownerType === 'user') { + return owner; + } + if (ownerType === 'host' || ownerType === 'marketplace') { + return { + _id: owner._id, + firstName: owner.name ?? 'unknown', + lastName: '' + }; + } + return { + _id: owner._id, + firstName: owner.name ?? owner.firstName ?? 'unknown', + lastName: owner.lastName ?? '' + }; +} + +const ALPHABET = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'; +export const generateId = () => { + // 10 characters + return customAlphabet(ALPHABET, 12); +}; + +function parseFilter(property, value) { + if (typeof value === 'string') { + var trimmed = value.trim(); + if (trimmed.charAt(3) == ':') { + trimmed = value.split(':')[1]; + } + + // Handle booleans + if (trimmed.toLowerCase() === 'true') return { [property]: true }; + if (trimmed.toLowerCase() === 'false') return { [property]: false }; + + // Handle ObjectId (24-char hex) + + if (/^[a-f\d]{24}$/i.test(trimmed) && trimmed.length >= 24) { + return { [property]: new ObjectId(trimmed) }; + } + + // Handle numbers + if (!isNaN(trimmed)) { + return { [property]: parseFloat(trimmed) }; + } + + // Default to case-insensitive regex for non-numeric strings + return { + [property]: { + $regex: trimmed, + $options: 'i' + } + }; + } + + // Handle actual booleans, numbers, objects, etc. + return { [property]: value }; +} + +function convertToCamelCase(obj) { + const result = {}; + + for (const key in obj) { + if (Object.prototype.hasOwnProperty.call(obj, key)) { + const value = obj[key]; + + // Convert the key to camelCase + let camelKey = key + // First handle special cases with spaces, brackets and other characters + .replace(/\s*\[.*?\]\s*/g, '') // Remove brackets and their contents + .replace(/\s+/g, ' ') // Normalize spaces + .trim() + // Split by common separators (space, underscore, hyphen) + .split(/[\s_-]/) + // Convert to camelCase + .map((word, index) => { + // Remove any non-alphanumeric characters + word = word.replace(/[^a-zA-Z0-9]/g, ''); + + // Lowercase first word, uppercase others + return index === 0 + ? word.toLowerCase() + : word.charAt(0).toUpperCase() + word.slice(1).toLowerCase(); + }) + .join(''); + + // Handle values that are objects recursively + if ( + value !== null && + typeof value === 'object' && + !Array.isArray(value) + ) { + result[camelKey] = convertToCamelCase(value); + } else { + result[camelKey] = value; + } + } + } + + return result; +} + +function extractConfigBlock(fileContent, useCamelCase = true) { + const configObject = {}; + + // Extract header information + const headerBlockRegex = + /; HEADER_BLOCK_START([\s\S]*?)(?:; HEADER_BLOCK_END|$)/; + const headerBlockMatch = fileContent.match(headerBlockRegex); + if (headerBlockMatch && headerBlockMatch[1]) { + const headerLines = headerBlockMatch[1].split('\n'); + headerLines.forEach(line => { + const keyValueRegex = /^\s*;\s*([^:]+?):\s*(.*?)\s*$/; + const simpleValueRegex = /^\s*;\s*(.*?)\s*$/; + + // Try key-value format first + let match = line.match(keyValueRegex); + if (match) { + const key = match[1].trim(); + let value = match[2].trim(); + + // Try to convert value to appropriate type + if (!isNaN(value) && value !== '') { + value = Number(value); + } + configObject[key] = value; + } else { + // Try the simple format like "; generated by OrcaSlicer 2.1.1 on 2025-04-28 at 13:30:11" + match = line.match(simpleValueRegex); + if (match && match[1] && !match[1].includes('HEADER_BLOCK')) { + const text = match[1].trim(); + + // Extract slicer info + const slicerMatch = text.match( + /generated by (.*?) on (.*?) at (.*?)$/ + ); + if (slicerMatch) { + configObject['slicer'] = slicerMatch[1].trim(); + configObject['date'] = slicerMatch[2].trim(); + configObject['time'] = slicerMatch[3].trim(); + } else { + // Just add as a general header entry if it doesn't match any specific pattern + const key = `header_${Object.keys(configObject).length}`; + configObject[key] = text; + } + } + } + }); + } + + // Extract thumbnail data + const thumbnailBlockRegex = + /; THUMBNAIL_BLOCK_START([\s\S]*?)(?:; THUMBNAIL_BLOCK_END|$)/; + const thumbnailBlockMatch = fileContent.match(thumbnailBlockRegex); + if (thumbnailBlockMatch && thumbnailBlockMatch[1]) { + const thumbnailLines = thumbnailBlockMatch[1].split('\n'); + let base64Data = ''; + let thumbnailInfo = {}; + + thumbnailLines.forEach(line => { + // Extract thumbnail dimensions and size from the line "thumbnail begin 640x640 27540" + const thumbnailHeaderRegex = /^\s*;\s*thumbnail begin (\d+)x(\d+) (\d+)/; + const match = line.match(thumbnailHeaderRegex); + + if (match) { + thumbnailInfo.width = parseInt(match[1], 10); + thumbnailInfo.height = parseInt(match[2], 10); + thumbnailInfo.size = parseInt(match[3], 10); + } else if ( + line.trim().startsWith('; ') && + !line.includes('THUMBNAIL_BLOCK') + ) { + // Collect base64 data (remove the leading semicolon and space and thumbnail end) + const dataLine = line.trim().substring(2); + if (dataLine && dataLine != 'thumbnail end') { + base64Data += dataLine; + } + } + }); + + // Add thumbnail data to config object + if (base64Data) { + configObject.thumbnail = { + data: base64Data, + ...thumbnailInfo + }; + } + } + + // Extract CONFIG_BLOCK + const configBlockRegex = + /; CONFIG_BLOCK_START([\s\S]*?)(?:; CONFIG_BLOCK_END|$)/; + const configBlockMatch = fileContent.match(configBlockRegex); + if (configBlockMatch && configBlockMatch[1]) { + // Extract each config line + const configLines = configBlockMatch[1].split('\n'); + // Process each line + configLines.forEach(line => { + // Check if the line starts with a semicolon and has an equals sign + const configLineRegex = /^\s*;\s*([^=]+?)\s*=\s*(.*?)\s*$/; + const match = line.match(configLineRegex); + if (match) { + const key = match[1].trim(); + let value = match[2].trim(); + // Try to convert value to appropriate type + if (value === 'true' || value === 'false') { + value = value === 'true'; + } else if (!isNaN(value) && value !== '') { + // Check if it's a number (but not a percentage) + if (!value.includes('%')) { + value = Number(value); + } + } + configObject[key] = value; + } + }); + } + + // Extract additional variables that appear after EXECUTABLE_BLOCK_END + const additionalVarsRegex = + /; EXECUTABLE_BLOCK_(?:START|END)([\s\S]*?)(?:; CONFIG_BLOCK_START|$)/i; + const additionalVarsMatch = fileContent.match(additionalVarsRegex); + if (additionalVarsMatch && additionalVarsMatch[1]) { + const additionalLines = additionalVarsMatch[1].split('\n'); + additionalLines.forEach(line => { + // Match both standard format and the special case for "total filament cost" + const varRegex = + /^\s*;\s*((?:filament used|filament cost|total filament used|total filament cost|total layers count|estimated printing time)[^=]*?)\s*=\s*(.*?)\s*$/; + const match = line.match(varRegex); + if (match) { + const key = match[1].replace(/\[([^\]]+)\]/g, '$1').trim(); + let value = match[2].trim(); + // Clean up values - remove units in brackets and handle special cases + if (key.includes('filament used')) { + // Extract just the numeric value, ignoring units in brackets + const numMatch = value.match(/(\d+\.\d+)/); + if (numMatch) { + value = parseFloat(numMatch[1]); + } + } else if (key.includes('filament cost')) { + // Extract just the numeric value + const numMatch = value.match(/(\d+\.\d+)/); + if (numMatch) { + value = parseFloat(numMatch[1]); + } + } else if (key.includes('total layers count')) { + value = parseInt(value, 10); + } else if (key.includes('estimated printing time')) { + // Keep as string but trim any additional whitespace + value = value.trim(); + } + configObject[key] = value; + } + }); + } + + // Also extract extrusion width settings + const extrusionWidthRegex = /;\s*(.*?)\s*extrusion width\s*=\s*(.*?)mm/g; + let extrusionMatch; + while ((extrusionMatch = extrusionWidthRegex.exec(fileContent)) !== null) { + const settingName = extrusionMatch[1].trim(); + const settingValue = parseFloat(extrusionMatch[2].trim()); + configObject[`${settingName} extrusion width`] = settingValue; + } + + // Extract additional parameters after CONFIG_BLOCK_END if they exist + const postConfigParams = /; CONFIG_BLOCK_END\s*\n([\s\S]*?)$/; + const postConfigMatch = fileContent.match(postConfigParams); + if (postConfigMatch && postConfigMatch[1]) { + const postConfigLines = postConfigMatch[1].split('\n'); + postConfigLines.forEach(line => { + // Match lines with format "; parameter_name = value" + const paramRegex = /^\s*;\s*([^=]+?)\s*=\s*(.*?)\s*$/; + const match = line.match(paramRegex); + if (match) { + const key = match[1].trim(); + let value = match[2].trim(); + + // Try to convert value to appropriate type + if (value === 'true' || value === 'false') { + value = value === 'true'; + } else if (!isNaN(value) && value !== '') { + // Check if it's a number (but not a percentage) + if (!value.includes('%')) { + value = Number(value); + } + } + + // Add to config object if not already present + if (!configObject[key]) { + configObject[key] = value; + } + } + }); + } + + // Apply camelCase conversion if requested + return useCamelCase ? convertToCamelCase(configObject) : configObject; +} + +function getChangedValues(oldObj, newObj, old = false) { + const changes = {}; + + const combinedObj = { ...oldObj, ...newObj }; + + // Check all keys in the new object + for (const key in combinedObj) { + // Skip if the key is _id or timestamps + if (key === 'createdAt' || key === 'updatedAt' || key === '_id') continue; + + const oldVal = oldObj ? oldObj[key] : undefined; + const newVal = newObj ? newObj[key] : undefined; + + // If both values are objects (but not arrays or null), recurse + if ( + oldVal && + newVal && + typeof oldVal === 'object' && + typeof newVal === 'object' && + !Array.isArray(oldVal) && + !Array.isArray(newVal) && + oldVal !== null && + newVal !== null + ) { + if (oldVal?._id || newVal?._id) { + if (JSON.stringify(oldVal?._id) !== JSON.stringify(newVal?._id)) { + changes[key] = old ? oldVal : newVal; + } + } else { + const nestedChanges = getChangedValues(oldVal, newVal, old); + if (Object.keys(nestedChanges).length > 0) { + // Exclude progress and currentWeight from nested changes + const excludeKeys = ['progress', 'currentWeight', 'net', 'gross']; + const filteredChanges = Object.keys(nestedChanges) + .filter(nestedKey => !excludeKeys.includes(nestedKey)) + .reduce((acc, nestedKey) => { + acc[nestedKey] = nestedChanges[nestedKey]; + return acc; + }, {}); + + if (Object.keys(filteredChanges).length > 0) { + changes[key] = filteredChanges; + } + } + } + } else if (JSON.stringify(oldVal) !== JSON.stringify(newVal)) { + // If the old value is different from the new value, include it + changes[key] = old ? oldVal : newVal; + } + } + + return changes; +} + +async function newAuditLog(newValue, parentId, parentType, owner, ownerType) { + if (AUDIT_EXCLUDED_MODELS.includes(parentType)) return; + + // Filter out createdAt, updatedAt, and sensitive fields from newValue + const filteredNewValue = omitSensitive({ ...newValue }); + delete filteredNewValue.createdAt; + delete filteredNewValue.updatedAt; + const auditLog = new auditLogModel({ + changes: { + new: filteredNewValue + }, + parent: parentId, + parentType, + owner: owner._id, + ownerType: ownerType, + operation: 'new' + }); + + await auditLog.save(); + + await distributeNew(auditLog, 'auditLog'); +} + +async function editAuditLog( + oldValue, + newValue, + parentId, + parentType, + owner, + ownerType +) { + if (parentType === 'stockEvent') { + return; + } + const filteredOldValue = omitExcludedChanges(oldValue); + const filteredNewValue = omitExcludedChanges(newValue); + // Get only the changed values + const changedOldValues = getChangedValues( + filteredOldValue, + filteredNewValue, + true + ); + const changedNewValues = getChangedValues( + filteredOldValue, + filteredNewValue, + false + ); + + // If no values changed, don't create an audit log + if ( + Object.keys(changedOldValues).length === 0 || + Object.keys(changedNewValues).length === 0 + ) { + return; + } + + const auditLog = new auditLogModel({ + changes: { + old: changedOldValues, + new: changedNewValues + }, + parent: parentId, + parentType, + owner: owner._id, + ownerType: ownerType, + operation: 'edit' + }); + + await auditLog.save(); + + await distributeNew(auditLog, 'auditLog'); +} + +async function deleteAuditLog( + deleteValue, + parentId, + parentType, + owner, + ownerType +) { + const auditLog = new auditLogModel({ + changes: { + old: deleteValue + }, + parent: parentId, + parentType, + owner: owner._id, + ownerType: ownerType, + operation: 'delete' + }); + + await auditLog.save(); + + await distributeNew(auditLog, 'auditLog'); +} + +async function getAuditLogs(idOrIds) { + if (Array.isArray(idOrIds)) { + return auditLogModel.find({ parent: { $in: idOrIds } }).populate('owner'); + } else { + return auditLogModel.find({ parent: idOrIds }).populate('owner'); + } +} + +async function distributeUpdate(value, id, type) { + await natsServer.publish(`${type}s.${id}.object`, { ...value, _id: id }); +} + +async function distributeStats(value, type) { + await natsServer.publish(`${type}s.stats`, value); +} + +async function distributeNew(value, type) { + await natsServer.publish(`${type}s.new`, value); +} + +async function editNotification( + oldValue, + newValue, + parentId, + parentType, + owner, + ownerType +) { + if (NOTIFICATION_EXCLUDED_MODELS.includes(parentType)) return; + + const modelEntry = await getModelEntryByType(parentType); + const user = notificationUserFromOwner(owner, ownerType); + const objectName = + oldValue?.name ?? newValue?.name ?? modelEntry?.label ?? parentType; + const changedOldValues = omitSensitive( + getChangedValues(oldValue, newValue, true) + ); + const changedNewValues = omitSensitive( + getChangedValues(oldValue, newValue, false) + ); + + if ( + Object.keys(changedOldValues).length === 0 || + Object.keys(changedNewValues).length === 0 + ) { + return; + } + + await notfiyObjectUserNotifiers( + parentId, + parentType, + `${objectName} edited by ${user?.firstName ?? 'unknown'} ${user?.lastName ?? ''}`, + `The ${parentType} ${parentId} has been updated.`, + 'editObject', + { + old: changedOldValues, + new: changedNewValues, + objectType: parentType, + object: { _id: String(parentId ?? '') }, + user: { + _id: String(user?._id ?? ''), + firstName: user?.firstName, + lastName: user?.lastName + } + } + ); +} + +async function notfiyObjectUserNotifiers( + id, + objectType, + title, + message, + type = 'info', + metadata +) { + const userNotifiers = await userNotifierModel + .find({ object: id, objectType }) + .populate('user'); + for (const userNotifier of userNotifiers) { + await createNotification( + userNotifier.user._id, + title, + message, + type, + metadata + ); + } +} + +async function createNotification( + user, + title, + message, + type = 'info', + metadata +) { + const notification = new notificationModel({ + user, + title, + message, + type, + metadata: omitSensitive(metadata ?? {}) + }); + await notification.save(); + const value = notification.toJSON ? notification.toJSON() : notification; + await natsServer.publish(`notifications.${user._id ?? user}`, value); + return notification; +} + +function flatternObjectIds(object) { + if (!object || typeof object !== 'object') { + return object; + } + + const result = {}; + + for (const [key, value] of Object.entries(object)) { + if (value && typeof value === 'object' && value._id) { + // If the value is an object with _id, convert to just the _id + result[key] = value._id; + } else { + // Keep primitive values as is + result[key] = value; + } + } + + return result; +} + +function expandObjectIds(input) { + // Helper to check if a value is an ObjectId or a 24-char hex string + function isObjectId(val) { + // Check for MongoDB ObjectId instance + if (val instanceof ObjectId) return true; + // Check for exactly 24 hex characters (no special characters) + if (typeof val === 'string' && /^[a-fA-F\d]{24}$/.test(val)) return true; + return false; + } + + // Recursive function + function expand(value) { + if (Array.isArray(value)) { + return value.map(expand); + } else if ( + value && + typeof value === 'object' && + !(value instanceof ObjectId) + ) { + var result = {}; + for (const [key, val] of Object.entries(value)) { + if (key === '_id') { + // Do not expand keys that are already named _id + result[key] = val; + } else if (isObjectId(val)) { + result[key] = { _id: val }; + } else if (Array.isArray(val)) { + result[key] = val.map(expand); + } else if (val instanceof Date) { + result[key] = val; + } else if (val && typeof val === 'object') { + result[key] = expand(val); + } else { + result[key] = val; + } + } + return result; + } else if (isObjectId(value)) { + return { _id: value }; + } else { + return value; + } + } + + return expand(input); +} + +export { getFilter } from './filter.js'; + +// Converts a properties argument (string or array) to an array of strings +function convertPropertiesString(properties) { + if (typeof properties === 'string') { + return properties.split(','); + } else if (!Array.isArray(properties)) { + return []; + } + return properties; +} + +export { + parseFilter, + convertToCamelCase, + extractConfigBlock, + newAuditLog, + editAuditLog, + deleteAuditLog, + getAuditLogs, + flatternObjectIds, + expandObjectIds, + distributeUpdate, + distributeNew, + distributeStats, + editNotification, + notfiyObjectUserNotifiers, + createNotification, + convertPropertiesString +}; diff --git a/src/index.js b/src/index.js new file mode 100644 index 0000000..d5f15f7 --- /dev/null +++ b/src/index.js @@ -0,0 +1,57 @@ +import { loadConfig } from "./config.js"; +import { SchedulerManager } from "./scheduler/schedulermanager.js"; +import { natsServer } from "./database/nats.js"; +import { redisServer } from "./database/redis.js"; +import log4js from "log4js"; +import { mongoServer } from "./database/mongo.js"; + +(async () => { + // Load configuration + const config = loadConfig(); + + // Setup logger + const logger = log4js.getLogger("FarmControl Scheduler"); + logger.level = config.server.logLevel; + + // Connect to NATS (await) + try { + await natsServer.connect(); + } catch (err) { + logger.error("Failed to connect to NATS:", err); + throw err; + } + + // Connect to Mongo DB (await) + try { + await mongoServer.connect(); + } 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; + } + + const schedulerManager = new SchedulerManager( + redisServer, + mongoServer, + natsServer, + ); + + await schedulerManager.start(); + + process.on("SIGINT", async () => { + logger.info("Shutting down..."); + await schedulerManager.stop(); + await redisServer.disconnect(); + await mongoServer.disconnect(); + await natsServer.disconnect(); + logger.info("Shutdown complete"); + process.exit(0); + }); +})(); diff --git a/src/scheduler/__tests__/scheduler.test.js b/src/scheduler/__tests__/scheduler.test.js new file mode 100644 index 0000000..8594245 --- /dev/null +++ b/src/scheduler/__tests__/scheduler.test.js @@ -0,0 +1,57 @@ +import { jest } from "@jest/globals"; +import { Scheduler } from "../scheduler.js"; + +describe("Scheduler", () => { + const object = { _id: "inv-1" }; + const model = { onSchedulerEvent: jest.fn() }; + + it("should generate an id and coerce a string value to a Date", () => { + const scheduler = new Scheduler({ + objectType: "invoice", + object, + property: "dueAt", + value: "2026-09-06T12:00:00.000Z", + model, + }); + + expect(scheduler.id).toEqual(expect.any(String)); + expect(scheduler.objectType).toBe("invoice"); + expect(scheduler.object).toBe(object); + expect(scheduler.property).toBe("dueAt"); + expect(scheduler.model).toBe(model); + expect(scheduler.value).toEqual(new Date("2026-09-06T12:00:00.000Z")); + }); + + it("should keep an explicit id and Date value", () => { + const value = new Date("2026-09-07T09:00:00.000Z"); + const scheduler = new Scheduler({ + id: "scheduler-1", + objectType: "invoice", + object, + property: "dueAt", + value, + model, + }); + + expect(scheduler.id).toBe("scheduler-1"); + expect(scheduler.value).toBe(value); + }); + + it("should serialize only the worker-safe payload", () => { + const scheduler = new Scheduler({ + id: "scheduler-1", + objectType: "invoice", + object, + property: "dueAt", + value: "2026-09-06T12:00:00.000Z", + model, + }); + + expect(scheduler.toPayload()).toEqual({ + id: "scheduler-1", + objectType: "invoice", + property: "dueAt", + value: "2026-09-06T12:00:00.000Z", + }); + }); +}); diff --git a/src/scheduler/__tests__/schedulermanager.test.js b/src/scheduler/__tests__/schedulermanager.test.js new file mode 100644 index 0000000..279cebb --- /dev/null +++ b/src/scheduler/__tests__/schedulermanager.test.js @@ -0,0 +1,520 @@ +import { jest } from "@jest/globals"; + +const workerInstances = []; + +jest.unstable_mockModule("worker_threads", () => ({ + Worker: class Worker { + constructor(filename) { + this.filename = filename; + this.postMessage = jest.fn(); + this.terminate = jest.fn().mockResolvedValue(undefined); + this.listeners = {}; + workerInstances.push(this); + } + + on(event, handler) { + this.listeners[event] = handler; + } + }, +})); + +jest.unstable_mockModule("log4js", () => ({ + default: { + getLogger: () => ({ + level: "info", + debug: jest.fn(), + error: jest.fn(), + warn: jest.fn(), + trace: jest.fn(), + info: jest.fn(), + }), + }, +})); + +jest.unstable_mockModule("../../config.js", () => ({ + loadConfig: jest.fn(() => ({ + server: { + logLevel: "info", + }, + })), +})); + +const onSchedulerEvent = jest.fn().mockResolvedValue(undefined); + +const invoiceMongooseModel = { + scheduledProperties: [ + { + name: "dueAt", + filter: { state: "due|sent|acknowledged" }, + type: "dateTime", + }, + { + name: "remindAt", + filter: { state: "due|sent|acknowledged" }, + type: "dateTime", + }, + ], + onSchedulerEvent, +}; + +const invoiceModelEntry = { + type: "invoice", + label: "Invoice", + model: invoiceMongooseModel, +}; + +const printerModelEntry = { + type: "printer", + label: "Printer", + model: {}, +}; + +jest.unstable_mockModule("../../database/schemas/models.js", () => ({ + models: { + INV: invoiceModelEntry, + PRN: printerModelEntry, + }, +})); + +jest.unstable_mockModule("../../database/database.js", () => ({ + listObjects: jest.fn(async () => []), + getObject: jest.fn(async ({ id }) => ({ _id: id })), +})); + +jest.unstable_mockModule("../../database/utils.js", () => ({ + getFilter: jest.fn(async (filter) => filter), +})); + +const updateManagerInstances = []; + +jest.unstable_mockModule("../../updates/updatemanager.js", () => ({ + UpdateManager: class UpdateManager { + constructor(socketClient) { + this.socketClient = socketClient; + this.subscribeToObjectNew = jest.fn().mockResolvedValue(undefined); + this.subscribeToObjectDelete = jest.fn().mockResolvedValue(undefined); + this.subscribeToAllObjectUpdates = jest.fn().mockResolvedValue(undefined); + this.removeAllListeners = jest.fn().mockResolvedValue(undefined); + updateManagerInstances.push(this); + } + }, +})); + +const { SchedulerManager } = await import("../schedulermanager.js"); +const { Scheduler } = await import("../scheduler.js"); +const { listObjects, getObject } = await import("../../database/database.js"); +const { getFilter } = await import("../../database/utils.js"); + +const flushPromises = () => new Promise((resolve) => setImmediate(resolve)); + +const createManager = () => + new SchedulerManager( + { name: "redis" }, + { name: "mongo" }, + { name: "nats" }, + ); + +const dueAt = new Date("2026-09-06T12:00:00.000Z"); + +describe("SchedulerManager", () => { + beforeEach(() => { + jest.clearAllMocks(); + workerInstances.length = 0; + updateManagerInstances.length = 0; + onSchedulerEvent.mockClear(); + listObjects.mockResolvedValue([]); + getObject.mockImplementation(async ({ id }) => ({ _id: id })); + }); + + describe("start", () => { + it("should start the worker, discover scheduled properties, subscribe, and seed schedulers", async () => { + const object = { _id: "inv-1", dueAt, remindAt: null }; + listObjects.mockResolvedValue([object]); + + const manager = createManager(); + await manager.start(); + + expect(workerInstances).toHaveLength(1); + expect(manager.scheduledProperties).toEqual([ + { + model: invoiceModelEntry, + property: "dueAt", + filter: { state: "due|sent|acknowledged" }, + type: "dateTime", + }, + { + model: invoiceModelEntry, + property: "remindAt", + filter: { state: "due|sent|acknowledged" }, + type: "dateTime", + }, + ]); + + const updateManager = manager.updateManager; + expect(updateManager.subscribeToObjectNew).toHaveBeenCalledTimes(1); + expect(updateManager.subscribeToObjectDelete).toHaveBeenCalledTimes(1); + expect(updateManager.subscribeToAllObjectUpdates).toHaveBeenCalledTimes(1); + expect(updateManager.subscribeToObjectNew).toHaveBeenCalledWith( + "invoice", + { state: "due|sent|acknowledged" }, + ); + + expect(getFilter).toHaveBeenCalledWith( + { state: "due|sent|acknowledged", dueAt: "TODAY.." }, + ["*"], + true, + invoiceMongooseModel, + ); + expect(manager.schedulers).toHaveLength(1); + expect(manager.schedulers[0]).toMatchObject({ + objectType: "invoice", + object, + property: "dueAt", + value: dueAt, + }); + expect(workerInstances[0].postMessage).toHaveBeenCalledWith({ + type: "add", + scheduler: manager.schedulers[0].toPayload(), + }); + }); + + it("should skip objects that are missing a scheduled property value", async () => { + listObjects.mockResolvedValue([{ _id: "inv-1" }]); + + const manager = createManager(); + await manager.start(); + + expect(manager.schedulers).toHaveLength(0); + }); + }); + + describe("handleWorkerMessage", () => { + it("should fire a known scheduler and remove it afterwards", async () => { + const manager = createManager(); + manager.startWorker(); + const scheduler = new Scheduler({ + id: "scheduler-1", + objectType: "invoice", + object: { _id: "inv-1" }, + property: "dueAt", + value: dueAt, + model: invoiceMongooseModel, + }); + manager.addScheduler(scheduler); + + manager.handleWorkerMessage({ type: "due", id: "scheduler-1" }); + await flushPromises(); + + expect(onSchedulerEvent).toHaveBeenCalledWith( + { _id: "inv-1" }, + "dueAt", + ); + expect(manager.schedulers).toHaveLength(0); + expect(workerInstances[0].postMessage).toHaveBeenCalledWith({ + type: "remove", + id: "scheduler-1", + }); + }); + + it("should ignore unknown schedulers and non-due messages", async () => { + const manager = createManager(); + manager.startWorker(); + + manager.handleWorkerMessage({ type: "due", id: "missing" }); + manager.handleWorkerMessage({ type: "add", id: "scheduler-1" }); + await flushPromises(); + + expect(onSchedulerEvent).not.toHaveBeenCalled(); + }); + + it("should still remove a scheduler when firing fails", async () => { + onSchedulerEvent.mockRejectedValueOnce(new Error("boom")); + + const manager = createManager(); + manager.startWorker(); + const scheduler = new Scheduler({ + id: "scheduler-1", + objectType: "invoice", + object: { _id: "inv-1" }, + property: "dueAt", + value: dueAt, + model: invoiceMongooseModel, + }); + manager.addScheduler(scheduler); + + manager.handleWorkerMessage({ type: "due", id: "scheduler-1" }); + await flushPromises(); + + expect(manager.schedulers).toHaveLength(0); + }); + }); + + describe("upsertScheduler", () => { + it("should no-op when the object has no id", () => { + const manager = createManager(); + manager.startWorker(); + + manager.upsertScheduler({ + objectType: "invoice", + object: {}, + property: "dueAt", + value: dueAt, + }); + + expect(manager.schedulers).toHaveLength(0); + expect(workerInstances[0].postMessage).not.toHaveBeenCalled(); + }); + + it("should remove an existing scheduler when the value is cleared", () => { + const manager = createManager(); + manager.startWorker(); + const scheduler = new Scheduler({ + id: "scheduler-1", + objectType: "invoice", + object: { _id: "inv-1" }, + property: "dueAt", + value: dueAt, + model: invoiceMongooseModel, + }); + manager.addScheduler(scheduler); + workerInstances[0].postMessage.mockClear(); + + manager.upsertScheduler({ + objectType: "invoice", + object: { _id: "inv-1" }, + property: "dueAt", + value: null, + }); + + expect(manager.schedulers).toHaveLength(0); + expect(workerInstances[0].postMessage).toHaveBeenCalledWith({ + type: "remove", + id: "scheduler-1", + }); + }); + + it("should update an existing scheduler without notifying the worker when the time is unchanged", () => { + const manager = createManager(); + manager.startWorker(); + const scheduler = new Scheduler({ + id: "scheduler-1", + objectType: "invoice", + object: { _id: "inv-1", name: "old" }, + property: "dueAt", + value: dueAt, + model: invoiceMongooseModel, + }); + manager.addScheduler(scheduler); + workerInstances[0].postMessage.mockClear(); + + const nextObject = { _id: "inv-1", name: "new" }; + manager.upsertScheduler({ + objectType: "invoice", + object: nextObject, + property: "dueAt", + value: dueAt.toISOString(), + }); + + expect(manager.schedulers).toHaveLength(1); + expect(manager.schedulers[0].object).toBe(nextObject); + expect(workerInstances[0].postMessage).not.toHaveBeenCalled(); + }); + + it("should notify the worker when an existing scheduler time changes", () => { + const manager = createManager(); + manager.startWorker(); + const scheduler = new Scheduler({ + id: "scheduler-1", + objectType: "invoice", + object: { _id: "inv-1" }, + property: "dueAt", + value: dueAt, + model: invoiceMongooseModel, + }); + manager.addScheduler(scheduler); + workerInstances[0].postMessage.mockClear(); + + const nextValue = new Date("2026-09-08T12:00:00.000Z"); + manager.upsertScheduler({ + objectType: "invoice", + object: { _id: "inv-1" }, + property: "dueAt", + value: nextValue, + }); + + expect(manager.schedulers[0].value).toEqual(nextValue); + expect(workerInstances[0].postMessage).toHaveBeenCalledWith({ + type: "add", + scheduler: manager.schedulers[0].toPayload(), + }); + }); + }); + + describe("findScheduler and removeSchedulersForObject", () => { + it("should match schedulers by type, property, and stringified object id", () => { + const manager = createManager(); + const scheduler = new Scheduler({ + id: "scheduler-1", + objectType: "invoice", + object: { _id: 123 }, + property: "dueAt", + value: dueAt, + model: invoiceMongooseModel, + }); + manager.schedulers.push(scheduler); + + expect(manager.findScheduler("invoice", "123", "dueAt")).toBe(scheduler); + expect(manager.findScheduler("invoice", "123", "remindAt")).toBeUndefined(); + }); + + it("should remove every scheduler for an object and ignore missing ids", () => { + const manager = createManager(); + manager.startWorker(); + manager.addScheduler( + new Scheduler({ + id: "scheduler-1", + objectType: "invoice", + object: { _id: "inv-1" }, + property: "dueAt", + value: dueAt, + model: invoiceMongooseModel, + }), + ); + manager.addScheduler( + new Scheduler({ + id: "scheduler-2", + objectType: "invoice", + object: { _id: "inv-1" }, + property: "remindAt", + value: dueAt, + model: invoiceMongooseModel, + }), + ); + manager.addScheduler( + new Scheduler({ + id: "scheduler-3", + objectType: "invoice", + object: { _id: "inv-2" }, + property: "dueAt", + value: dueAt, + model: invoiceMongooseModel, + }), + ); + + manager.removeSchedulersForObject("invoice", {}); + expect(manager.schedulers).toHaveLength(3); + + manager.removeSchedulersForObject("invoice", { _id: "inv-1" }); + expect(manager.schedulers.map((scheduler) => scheduler.id)).toEqual([ + "scheduler-3", + ]); + }); + }); + + describe("handleObjectEvent", () => { + it("should sync schedulers from objectNew and objectUpdate events", async () => { + const manager = createManager(); + manager.startWorker(); + manager.scheduledProperties = [ + { + model: invoiceModelEntry, + property: "dueAt", + filter: { state: "due|sent|acknowledged" }, + type: "dateTime", + }, + ]; + getObject.mockResolvedValue({ _id: "inv-1", dueAt }); + + manager.handleObjectEvent("objectNew", { + objectType: "invoice", + object: { _id: "inv-1" }, + filter: { state: "due|sent|acknowledged" }, + }); + await flushPromises(); + + expect(getObject).toHaveBeenCalledWith({ + model: invoiceMongooseModel, + id: "inv-1", + }); + expect(manager.schedulers).toHaveLength(1); + + getObject.mockResolvedValue({ + _id: "inv-1", + dueAt: new Date("2026-09-09T12:00:00.000Z"), + }); + manager.handleObjectEvent("objectUpdate", { + objectType: "invoice", + _id: "inv-1", + object: { dueAt: new Date("2026-09-09T12:00:00.000Z") }, + filter: { state: "due|sent|acknowledged" }, + }); + await flushPromises(); + + expect(manager.schedulers[0].value).toEqual( + new Date("2026-09-09T12:00:00.000Z"), + ); + }); + + it("should ignore events whose filter does not match a scheduled property", async () => { + const manager = createManager(); + manager.startWorker(); + manager.scheduledProperties = [ + { + model: invoiceModelEntry, + property: "dueAt", + filter: { state: "due|sent|acknowledged" }, + type: "dateTime", + }, + ]; + + manager.handleObjectEvent("objectNew", { + objectType: "invoice", + object: { _id: "inv-1" }, + filter: { state: "draft" }, + }); + await flushPromises(); + + expect(manager.schedulers).toHaveLength(0); + }); + + it("should remove schedulers on objectDelete", () => { + const manager = createManager(); + manager.startWorker(); + manager.addScheduler( + new Scheduler({ + id: "scheduler-1", + objectType: "invoice", + object: { _id: "inv-1" }, + property: "dueAt", + value: dueAt, + model: invoiceMongooseModel, + }), + ); + + manager.handleObjectEvent("objectDelete", { + objectType: "invoice", + object: { _id: "inv-1" }, + }); + + expect(manager.schedulers).toHaveLength(0); + }); + }); + + describe("stop", () => { + it("should remove listeners and terminate the worker", async () => { + const manager = createManager(); + await manager.start(); + manager.updateSubscriptions.add("invoice:{}"); + + await manager.stop(); + + expect(manager.updateManager.removeAllListeners).toHaveBeenCalled(); + expect(manager.updateSubscriptions.size).toBe(0); + expect(workerInstances[0].postMessage).toHaveBeenCalledWith({ + type: "stop", + }); + expect(workerInstances[0].terminate).toHaveBeenCalled(); + expect(manager.worker).toBeNull(); + }); + }); +}); diff --git a/src/scheduler/__tests__/schedulerworker.test.js b/src/scheduler/__tests__/schedulerworker.test.js new file mode 100644 index 0000000..78823de --- /dev/null +++ b/src/scheduler/__tests__/schedulerworker.test.js @@ -0,0 +1,115 @@ +import { jest } from "@jest/globals"; + +jest.useFakeTimers(); + +jest.unstable_mockModule("worker_threads", () => ({ + parentPort: { + postMessage: jest.fn(), + on: jest.fn(), + close: jest.fn(), + }, +})); + +const { parentPort } = await import("worker_threads"); +await import("../schedulerworker.js"); + +const handleParentMessage = parentPort.on.mock.calls[0][1]; + +describe("scheduler worker", () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + afterAll(() => { + handleParentMessage({ type: "stop" }); + jest.useRealTimers(); + }); + + it("should fire due schedulers and remove them immediately", () => { + handleParentMessage({ + type: "add", + scheduler: { + id: "due-1", + objectType: "invoice", + property: "dueAt", + value: "2020-01-01T00:00:00.000Z", + }, + }); + + jest.advanceTimersByTime(10); + + expect(parentPort.postMessage).toHaveBeenCalledWith({ + type: "due", + id: "due-1", + objectType: "invoice", + property: "dueAt", + value: "2020-01-01T00:00:00.000Z", + }); + + parentPort.postMessage.mockClear(); + jest.advanceTimersByTime(20); + + expect(parentPort.postMessage).not.toHaveBeenCalled(); + }); + + it("should not fire schedulers that are still in the future", () => { + const future = new Date(Date.now() + 60_000).toISOString(); + + handleParentMessage({ + type: "add", + scheduler: { + id: "future-1", + objectType: "invoice", + property: "dueAt", + value: future, + }, + }); + + jest.advanceTimersByTime(20); + + expect(parentPort.postMessage).not.toHaveBeenCalled(); + + handleParentMessage({ type: "remove", id: "future-1" }); + }); + + it("should not fire a removed scheduler", () => { + handleParentMessage({ + type: "add", + scheduler: { + id: "remove-1", + objectType: "invoice", + property: "dueAt", + value: "2020-01-01T00:00:00.000Z", + }, + }); + handleParentMessage({ type: "remove", id: "remove-1" }); + + jest.advanceTimersByTime(10); + + expect(parentPort.postMessage).not.toHaveBeenCalled(); + }); + + it("should ignore unknown message types", () => { + expect(() => handleParentMessage({ type: "unknown" })).not.toThrow(); + }); + + it("should stop polling and close the parent port", () => { + handleParentMessage({ type: "stop" }); + + expect(parentPort.close).toHaveBeenCalled(); + + handleParentMessage({ + type: "add", + scheduler: { + id: "after-stop", + objectType: "invoice", + property: "dueAt", + value: "2020-01-01T00:00:00.000Z", + }, + }); + + jest.advanceTimersByTime(20); + + expect(parentPort.postMessage).not.toHaveBeenCalled(); + }); +}); diff --git a/src/scheduler/scheduler.js b/src/scheduler/scheduler.js new file mode 100644 index 0000000..05b299f --- /dev/null +++ b/src/scheduler/scheduler.js @@ -0,0 +1,53 @@ +import { randomUUID } from "crypto"; + +/** + * Represents a single scheduled property change on a model object. + * + * A Scheduler is created whenever a scheduled property on some object needs + * to "fire" once a target date/time has passed (e.g. a `publishAt` field). + * The SchedulerManager watches these from a worker thread and, once + * `value` is <= the current date/time, calls + * `object.onSchedulerEvent(property, value)` back on the main thread. + */ +export class Scheduler { + /** + * @param {Object} params + * @param {string} [params.id] - Unique id for this scheduler. Generated if omitted. + * @param {string} params.objectType - The model/object type (e.g. model.type). + * @param {Object} params.object - The live mongoose document/object this scheduler acts on. + * @param {string} params.property - The name of the scheduled property. + * @param {Date|string} params.value - The date/time the property should fire at. + */ + constructor({ id, objectType, object, property, value, model }) { + console.log("Scheduler constructor", { + id, + objectType, + object, + property, + value, + model, + }); + this.id = id || randomUUID(); + this.objectType = objectType; + this.object = object; + this.property = property; + this.value = value instanceof Date ? value : new Date(value); + this.model = model; + } + + /** + * Plain, serializable representation of this scheduler. + * + * Used to hand scheduler data off to the worker thread - the live + * `object` (a mongoose document, functions, etc.) cannot cross the + * worker_threads boundary, so only the primitive fields are sent. + */ + toPayload() { + return { + id: this.id, + objectType: this.objectType, + property: this.property, + value: this.value.toISOString(), + }; + } +} diff --git a/src/scheduler/schedulermanager.js b/src/scheduler/schedulermanager.js new file mode 100644 index 0000000..9418988 --- /dev/null +++ b/src/scheduler/schedulermanager.js @@ -0,0 +1,340 @@ +import { Worker } from "worker_threads"; +import path from "path"; +import { fileURLToPath } from "url"; +import { models } from "../database/schemas/models.js"; +import log4js from "log4js"; +import { loadConfig } from "../config.js"; +import { listObjects, getObject } from "../database/database.js"; +import { getFilter } from "../database/utils.js"; +import { UpdateManager } from "../updates/updatemanager.js"; +import { Scheduler } from "./scheduler.js"; + +const config = loadConfig(); +const logger = log4js.getLogger("Scheduler Manager"); +logger.level = config.server.logLevel; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); + +const filtersEqual = (left, right) => + JSON.stringify(left || {}) === JSON.stringify(right || {}); + +export class SchedulerManager { + constructor(redisServer, mongoServer, natsServer) { + this.redisServer = redisServer; + this.mongoServer = mongoServer; + this.natsServer = natsServer; + this.scheduledProperties = []; + this.schedulers = []; + this.worker = null; + this.updateSubscriptions = new Set(); + this.updateManager = new UpdateManager({ + socketId: "scheduler-manager", + socket: { + emit: (eventName, data) => this.handleObjectEvent(eventName, data), + }, + }); + } + + async start() { + logger.info("Starting scheduler manager..."); + this.startWorker(); + logger.debug("Getting scheduled properties..."); + this.populateScheduledProperties(); + logger.debug("Subscribing to scheduled object updates..."); + await this.setupUpdateSubscriptions(); + logger.debug("Setting up schedulers..."); + await this.setupSchedulers(); + } + + /** + * Spins up the worker thread that runs the polling loop, and wires up + * its messages back to this manager. The loop itself lives entirely in + * schedulerWorker.js so it never blocks the main event loop. + */ + startWorker() { + this.worker = new Worker(path.join(__dirname, "schedulerWorker.js")); + + this.worker.on("message", (message) => this.handleWorkerMessage(message)); + + this.worker.on("error", (error) => { + logger.error("Scheduler worker error:", error); + }); + + this.worker.on("exit", (code) => { + if (code !== 0) { + logger.error(`Scheduler worker stopped with exit code ${code}`); + } + }); + } + + handleWorkerMessage(message) { + if (message.type !== "due") return; + + const scheduler = this.schedulers.find((s) => s.id === message.id); + if (!scheduler) { + logger.warn(`Received due event for unknown scheduler id ${message.id}`); + return; + } + + this.fireScheduler(scheduler); + } + + async fireScheduler(scheduler) { + try { + logger.debug( + `Firing scheduler ${scheduler.id} for ${scheduler.objectType}.${scheduler.property}`, + ); + await scheduler.model.onSchedulerEvent( + scheduler.object, + scheduler.property, + ); + } catch (error) { + logger.error( + `Error firing scheduler ${scheduler.id} for ${scheduler.objectType}.${scheduler.property}:`, + error, + ); + } finally { + this.removeScheduler(scheduler.id); + } + } + + addScheduler(scheduler) { + this.schedulers.push(scheduler); + this.worker?.postMessage({ type: "add", scheduler: scheduler.toPayload() }); + } + + removeScheduler(id) { + this.schedulers = this.schedulers.filter((s) => s.id !== id); + this.worker?.postMessage({ type: "remove", id }); + } + + findScheduler(objectType, objectId, property) { + return this.schedulers.find( + (scheduler) => + scheduler.objectType === objectType && + scheduler.property === property && + String(scheduler.object?._id) === String(objectId), + ); + } + + getScheduledPropertiesForEvent(objectType, filter) { + return this.scheduledProperties.filter( + (scheduledProperty) => + scheduledProperty.model.type === objectType && + filtersEqual(scheduledProperty.filter, filter), + ); + } + + upsertScheduler({ objectType, object, property, value }) { + const objectId = object?._id; + if (objectId == null) { + return; + } + + logger.debug("Upserting scheduler for", { + objectType, + objectId, + property, + value, + }); + const existing = this.findScheduler(objectType, objectId, property); + if (!value) { + if (existing) { + this.removeScheduler(existing.id); + } + return; + } + + if (existing) { + logger.debug("Existing scheduler found", { existing }); + const nextValue = value instanceof Date ? value : new Date(value); + existing.object = object; + if (existing.value.getTime() !== nextValue.getTime()) { + existing.value = nextValue; + this.worker?.postMessage({ + type: "add", + scheduler: existing.toPayload(), + }); + } + return; + } + + const model = Object.values(models).find( + (m) => m.type === objectType, + ).model; + + logger.debug("Creating new scheduler", { + objectType, + objectId, + property, + value, + }); + + const scheduler = new Scheduler({ + objectType, + object, + property, + value, + model, + }); + this.addScheduler(scheduler); + } + + removeSchedulersForObject(objectType, object) { + const objectId = object?._id; + if (objectId == null) { + return; + } + + const schedulersToRemove = this.schedulers.filter( + (scheduler) => + scheduler.objectType === objectType && + String(scheduler.object?._id) === String(objectId), + ); + for (const scheduler of schedulersToRemove) { + this.removeScheduler(scheduler.id); + } + } + + async syncSchedulersForObject(objectType, object, filter) { + const model = Object.values(models).find( + (m) => m.type === objectType, + ).model; + const retrievedObject = await getObject({ + model, + id: object._id, + }); + for (const scheduledProperty of this.getScheduledPropertiesForEvent( + objectType, + filter, + )) { + this.upsertScheduler({ + objectType, + object: retrievedObject, + property: scheduledProperty.property, + value: retrievedObject?.[scheduledProperty.property], + }); + } + } + + handleObjectEvent(eventName, data) { + if (eventName === "objectNew") { + logger.debug( + `Received objectNew for ${data.objectType} ${data.object?._id}`, + ); + this.syncSchedulersForObject(data.objectType, data.object, data.filter); + return; + } + + if (eventName === "objectDelete") { + logger.debug( + `Received objectDelete for ${data.objectType} ${data.object?._id}`, + ); + this.removeSchedulersForObject(data.objectType, data.object); + return; + } + + if (eventName === "objectUpdate") { + const object = { _id: data._id, ...data.object }; + logger.debug(`Received objectUpdate for ${data.objectType} ${data._id}`); + this.syncSchedulersForObject(data.objectType, object, data.filter); + } + } + + async setupUpdateSubscriptions() { + for (const scheduledProperty of this.scheduledProperties) { + const objectType = scheduledProperty.model.type; + const filter = scheduledProperty.filter || {}; + const subscriptionKey = `${objectType}:${JSON.stringify(filter)}`; + if (this.updateSubscriptions.has(subscriptionKey)) { + continue; + } + + logger.debug( + `Subscribing to ${objectType} updates with filter ${JSON.stringify(filter)}`, + ); + await this.updateManager.subscribeToObjectNew(objectType, filter); + await this.updateManager.subscribeToObjectDelete(objectType, filter); + await this.updateManager.subscribeToAllObjectUpdates(objectType, filter); + this.updateSubscriptions.add(subscriptionKey); + } + } + + populateScheduledProperties() { + for (const model of Object.values(models)) { + const modelLabel = model.label; + const modelName = model.type; + const mongooseModel = model.model; + logger.trace(`Checking model: ${modelLabel} for scheduled properties...`); + const scheduledProperties = mongooseModel?.scheduledProperties || []; + for (const scheduledProperty of scheduledProperties) { + const property = scheduledProperty.name; + const filter = scheduledProperty.filter; + const type = scheduledProperty.type; + logger.debug( + `Found scheduled property: ${modelName}.${property} of type ${type} with filter ${JSON.stringify(filter)}`, + ); + this.scheduledProperties.push({ + model, + property, + filter, + type, + }); + } + } + } + + async setupSchedulers() { + for (const scheduledProperty of this.scheduledProperties) { + const model = scheduledProperty.model; + const mongooseModel = model.model; + const property = scheduledProperty.property; + const filter = { ...scheduledProperty.filter, [property]: "TODAY.." }; + const type = scheduledProperty.type; + logger.debug( + `Setting up scheduler for: ${model.type}.${property} of type ${type} with filter ${JSON.stringify(filter)}`, + ); + const retrievedFilter = await getFilter( + filter, + ["*"], + true, + mongooseModel, + ); + const objects = await listObjects({ + model: mongooseModel, + filter: retrievedFilter, + }); + for (const object of objects) { + const propertyValue = object[property]; + if (!propertyValue) { + logger.warn( + `Object ${object._id} of type ${model.type} has no value for scheduled property ${property}, skipping.`, + ); + continue; + } + + this.upsertScheduler({ + objectType: model.type, + object, + property, + value: propertyValue, + }); + logger.debug( + `Created scheduler for ${model.type}.${property} on ${object._id}`, + ); + } + } + } + + async stop() { + logger.info("Stopping scheduler manager..."); + await this.updateManager.removeAllListeners(); + this.updateSubscriptions.clear(); + if (this.worker) { + this.worker.postMessage({ type: "stop" }); + await this.worker.terminate(); + this.worker = null; + } + } +} diff --git a/src/scheduler/schedulerworker.js b/src/scheduler/schedulerworker.js new file mode 100644 index 0000000..f069b10 --- /dev/null +++ b/src/scheduler/schedulerworker.js @@ -0,0 +1,50 @@ +import { parentPort } from "worker_threads"; + +// How often to check whether any scheduler's value has passed. +const POLL_INTERVAL_MS = 10; + +// id -> { objectType, property, value: Date } +// Only plain, serializable data lives here - the real mongoose objects +// stay on the main thread since they can't cross the worker boundary. +const schedulers = new Map(); + +function checkSchedulers() { + const now = Date.now(); + for (const [id, scheduler] of schedulers) { + if (scheduler.value.getTime() <= now) { + // Remove immediately so we don't fire the same scheduler twice + // while waiting for the main thread to process/remove it. + schedulers.delete(id); + parentPort.postMessage({ + type: "due", + id, + objectType: scheduler.objectType, + property: scheduler.property, + value: scheduler.value.toISOString(), + }); + } + } +} + +let intervalHandle = setInterval(checkSchedulers, POLL_INTERVAL_MS); + +parentPort.on("message", (message) => { + switch (message.type) { + case "add": + schedulers.set(message.scheduler.id, { + objectType: message.scheduler.objectType, + property: message.scheduler.property, + value: new Date(message.scheduler.value), + }); + break; + case "remove": + schedulers.delete(message.id); + break; + case "stop": + clearInterval(intervalHandle); + parentPort.close(); + break; + default: + break; + } +}); diff --git a/src/updates/__tests__/updatemanager.test.js b/src/updates/__tests__/updatemanager.test.js new file mode 100644 index 0000000..f655033 --- /dev/null +++ b/src/updates/__tests__/updatemanager.test.js @@ -0,0 +1,336 @@ +import { jest } from "@jest/globals"; + +jest.unstable_mockModule("../../database/nats.js", () => ({ + natsServer: { + subscribe: jest.fn().mockResolvedValue({ success: true }), + removeSubscription: jest.fn().mockResolvedValue({ success: true }), + }, +})); + +jest.unstable_mockModule("log4js", () => ({ + default: { + getLogger: () => ({ + level: "info", + debug: jest.fn(), + error: jest.fn(), + warn: jest.fn(), + trace: jest.fn(), + info: jest.fn(), + }), + }, +})); + +jest.unstable_mockModule("../../config.js", () => ({ + loadConfig: jest.fn(() => ({ + server: { + logLevel: "info", + }, + })), +})); + +jest.unstable_mockModule("../../database/filter.js", () => ({ + getFilter: jest.fn(async (query) => query), + getObjectTypeModel: jest.fn(async () => null), +})); + +const { UpdateManager } = await import("../updatemanager.js"); +const { natsServer } = await import("../../database/nats.js"); +const { getFilter } = await import("../../database/filter.js"); + +describe("UpdateManager", () => { + let mockSocketClient; + let updateManager; + + beforeEach(() => { + jest.clearAllMocks(); + + mockSocketClient = { + socketId: "test-socket-id", + socket: { + emit: jest.fn(), + }, + }; + + updateManager = new UpdateManager(mockSocketClient); + }); + + describe("subscribeToObjectNew", () => { + it("should subscribe to new object events and emit", async () => { + await updateManager.subscribeToObjectNew("printer"); + + expect(natsServer.subscribe).toHaveBeenCalledWith( + "printers.new", + "test-socket-id:{}", + expect.any(Function), + ); + + const natsCallback = natsServer.subscribe.mock.calls[0][2]; + const data = { name: "New Printer" }; + await natsCallback("printers.new", data); + + expect(mockSocketClient.socket.emit).toHaveBeenCalledWith("objectNew", { + object: data, + objectType: "printer", + filter: {}, + }); + }); + + it("should emit filtered new object events when the list filter matches", async () => { + const filter = { "state.type": "ready" }; + const data = { + _id: "123", + name: "New Printer", + state: { type: "ready" }, + }; + + await updateManager.subscribeToObjectNew("printer", filter); + + expect(getFilter).toHaveBeenCalledWith( + filter, + Object.keys(filter), + true, + null, + ); + expect(natsServer.subscribe).toHaveBeenCalledWith( + "printers.new", + 'test-socket-id:{"state.type":"ready"}', + expect.any(Function), + ); + + const natsCallback = natsServer.subscribe.mock.calls[0][2]; + await natsCallback("printers.new", data); + + expect(mockSocketClient.socket.emit).toHaveBeenCalledWith("objectNew", { + object: data, + objectType: "printer", + filter, + }); + }); + + it("should skip filtered new object events when the list filter misses", async () => { + const filter = { "state.type": "ready" }; + + await updateManager.subscribeToObjectNew("printer", filter); + + const natsCallback = natsServer.subscribe.mock.calls[0][2]; + await natsCallback("printers.new", { _id: "123" }); + + expect(mockSocketClient.socket.emit).not.toHaveBeenCalled(); + }); + + it("should match reference filters when the id is populated or flat", async () => { + const filter = { "parent._id": "parent-id" }; + + await updateManager.subscribeToObjectNew("note", filter); + const natsCallback = natsServer.subscribe.mock.calls[0][2]; + + await natsCallback("notes.new", { + _id: "note-1", + parent: { _id: "parent-id" }, + }); + expect(mockSocketClient.socket.emit).toHaveBeenCalledTimes(1); + + mockSocketClient.socket.emit.mockClear(); + + await natsCallback("notes.new", { + _id: "note-2", + parent: "parent-id", + }); + expect(mockSocketClient.socket.emit).toHaveBeenCalledTimes(1); + + mockSocketClient.socket.emit.mockClear(); + + await natsCallback("notes.new", { + _id: "note-3", + parent: "other-parent", + }); + expect(mockSocketClient.socket.emit).not.toHaveBeenCalled(); + }); + }); + + describe("subscribeToObjectDelete", () => { + it("should subscribe to delete events and emit", async () => { + await updateManager.subscribeToObjectDelete("printer"); + + expect(natsServer.subscribe).toHaveBeenCalledWith( + "printers.delete", + "test-socket-id:{}", + expect.any(Function), + ); + + const natsCallback = natsServer.subscribe.mock.calls[0][2]; + const data = { _id: "123" }; + await natsCallback("printers.delete", data); + + expect(mockSocketClient.socket.emit).toHaveBeenCalledWith( + "objectDelete", + { + object: data, + objectType: "printer", + filter: {}, + }, + ); + }); + }); + + describe("subscribeToObjectUpdate", () => { + it("should subscribe to update events for specific object", async () => { + await updateManager.subscribeToObjectUpdate("123", "printer"); + + expect(natsServer.subscribe).toHaveBeenCalledWith( + "printers.123.object", + "test-socket-id", + expect.any(Function), + ); + + const natsCallback = natsServer.subscribe.mock.calls[0][2]; + const data = { _id: "123", status: "idle" }; + natsCallback("printers.123.object", data); + + expect(mockSocketClient.socket.emit).toHaveBeenCalledWith( + "objectUpdate", + { + _id: "123", + objectType: "printer", + object: { status: "idle" }, + filter: {}, + }, + ); + }); + }); + + describe("subscribeToAllObjectUpdates", () => { + it("should subscribe to all update events for an object type", async () => { + await updateManager.subscribeToAllObjectUpdates("printer"); + + expect(natsServer.subscribe).toHaveBeenCalledWith( + "printers.*.object", + "test-socket-id:{}", + expect.any(Function), + ); + + const natsCallback = natsServer.subscribe.mock.calls[0][2]; + const data = { _id: "456", status: "idle" }; + natsCallback("printers.456.object", data); + + expect(mockSocketClient.socket.emit).toHaveBeenCalledWith( + "objectUpdate", + { + _id: "456", + objectType: "printer", + object: { status: "idle" }, + filter: {}, + }, + ); + }); + + it("should emit filtered update events when the list filter matches", async () => { + const filter = { "state.type": "ready" }; + const data = { + _id: "456", + status: "idle", + state: { type: "ready" }, + }; + + await updateManager.subscribeToAllObjectUpdates("printer", filter); + + expect(getFilter).toHaveBeenCalledWith( + filter, + Object.keys(filter), + true, + null, + ); + expect(natsServer.subscribe).toHaveBeenCalledWith( + "printers.*.object", + 'test-socket-id:{"state.type":"ready"}', + expect.any(Function), + ); + + const natsCallback = natsServer.subscribe.mock.calls[0][2]; + natsCallback("printers.456.object", data); + + expect(mockSocketClient.socket.emit).toHaveBeenCalledWith( + "objectUpdate", + { + _id: "456", + objectType: "printer", + object: { status: "idle", state: { type: "ready" } }, + filter, + }, + ); + }); + + it("should skip filtered update events when the list filter misses", async () => { + const filter = { "state.type": "ready" }; + + await updateManager.subscribeToAllObjectUpdates("printer", filter); + + const natsCallback = natsServer.subscribe.mock.calls[0][2]; + natsCallback("printers.456.object", { _id: "456", status: "idle" }); + + expect(mockSocketClient.socket.emit).not.toHaveBeenCalled(); + }); + + it("should skip all-object updates when a specific subscription exists", async () => { + await updateManager.subscribeToObjectUpdate("123", "printer"); + await updateManager.subscribeToAllObjectUpdates("printer"); + + const allUpdatesCallback = natsServer.subscribe.mock.calls[1][2]; + allUpdatesCallback("printers.123.object", { + _id: "123", + status: "idle", + }); + + expect(mockSocketClient.socket.emit).toHaveBeenCalledTimes(0); + }); + }); + + describe("remove methods", () => { + it("should remove new listener", async () => { + await updateManager.removeObjectNewListener("printer"); + expect(natsServer.removeSubscription).toHaveBeenCalledWith( + "printers.new", + "test-socket-id:{}", + ); + }); + + it("should remove delete listener", async () => { + await updateManager.removeObjectDeleteListener("printer"); + expect(natsServer.removeSubscription).toHaveBeenCalledWith( + "printers.delete", + "test-socket-id:{}", + ); + }); + + it("should remove update listener", async () => { + await updateManager.removeObjectUpdateListener("123", "printer"); + expect(natsServer.removeSubscription).toHaveBeenCalledWith( + "printers.123.object", + "test-socket-id", + ); + }); + + it("should remove all object updates listener", async () => { + await updateManager.removeAllObjectUpdatesListener("printer"); + expect(natsServer.removeSubscription).toHaveBeenCalledWith( + "printers.*.object", + "test-socket-id:{}", + ); + }); + }); + + describe("removeAllListeners", () => { + it("should remove all subscriptions", async () => { + await updateManager.subscribeToObjectNew("printer"); + await updateManager.subscribeToObjectDelete("printer"); + await updateManager.subscribeToObjectUpdate("123", "printer"); + + expect(updateManager.subscriptions.size).toBe(3); + + await updateManager.removeAllListeners(); + + expect(natsServer.removeSubscription).toHaveBeenCalledTimes(3); + expect(updateManager.subscriptions.size).toBe(0); + }); + }); +}); diff --git a/src/updates/updatemanager.js b/src/updates/updatemanager.js new file mode 100644 index 0000000..a43a668 --- /dev/null +++ b/src/updates/updatemanager.js @@ -0,0 +1,448 @@ +import log4js from "log4js"; +import _ from "lodash"; +import { loadConfig } from "../config.js"; +import { natsServer } from "../database/nats.js"; +import { expandObjectIds } from "../database/utils.js"; +import { getFilter, getObjectTypeModel } from "../database/filter.js"; +import { formatTraceData } from "../utils.js"; +const config = loadConfig(); + +// Setup logger +const logger = log4js.getLogger("Update Manager"); +logger.level = config.server.logLevel; + +const normalizeFilter = (filter) => + filter && typeof filter === "object" && !Array.isArray(filter) ? filter : {}; + +const unwrapId = (value) => { + if ( + value && + typeof value === "object" && + !Array.isArray(value) && + value._id != null + ) { + return value._id; + } + return value; +}; + +const getFilterValue = (object, key) => { + if (key.endsWith("._id")) { + const refPath = key.slice(0, -4); + const ref = _.get(object, refPath); + if (ref && typeof ref === "object" && ref._id) { + return ref._id; + } + return ref; + } + + return unwrapId(_.get(object, key)); +}; + +const valuesMatch = (actual, expected) => { + const left = unwrapId(actual); + const right = unwrapId(expected); + + if (left == right) { + return true; + } + + if (left != null && right != null) { + return String(left) === String(right); + } + + return false; +}; + +const isOperatorObject = (value) => { + if (!value || typeof value !== "object" || Array.isArray(value)) { + return false; + } + if (value instanceof Date || value._bsontype === "ObjectId") { + return false; + } + const keys = Object.keys(value); + return keys.length > 0 && keys.every((key) => key.startsWith("$")); +}; + +const compareValues = (actual, expected, operator) => { + if (actual == null || expected == null) { + return false; + } + if (operator === "$gt") return actual > expected; + if (operator === "$gte") return actual >= expected; + if (operator === "$lt") return actual < expected; + if (operator === "$lte") return actual <= expected; + return false; +}; + +const matchesOperators = (actual, operators) => { + if (Object.prototype.hasOwnProperty.call(operators, "$eq")) { + if (!valuesMatch(actual, operators.$eq)) return false; + } + if (Object.prototype.hasOwnProperty.call(operators, "$ne")) { + if (valuesMatch(actual, operators.$ne)) return false; + } + if (Object.prototype.hasOwnProperty.call(operators, "$in")) { + if ( + !Array.isArray(operators.$in) || + !operators.$in.some((value) => valuesMatch(actual, value)) + ) { + return false; + } + } + if (Object.prototype.hasOwnProperty.call(operators, "$nin")) { + if ( + Array.isArray(operators.$nin) && + operators.$nin.some((value) => valuesMatch(actual, value)) + ) { + return false; + } + } + if (Object.prototype.hasOwnProperty.call(operators, "$regex")) { + const regex = new RegExp(operators.$regex, operators.$options || ""); + if (!regex.test(String(actual ?? ""))) return false; + } + if (Object.prototype.hasOwnProperty.call(operators, "$not")) { + if (matchesExpected(actual, operators.$not)) return false; + } + for (const operator of ["$gt", "$gte", "$lt", "$lte"]) { + if ( + Object.prototype.hasOwnProperty.call(operators, operator) && + !compareValues(actual, operators[operator], operator) + ) { + return false; + } + } + return true; +}; + +const matchesExpected = (actual, expected) => { + if (isOperatorObject(expected)) { + return matchesOperators(actual, expected); + } + return valuesMatch(actual, expected); +}; + +const matchesFilter = (object, filter) => { + if (!filter || Object.keys(filter).length === 0) { + return true; + } + + if (object == null) { + return false; + } + + const normalizedObject = + typeof object === "object" && !Array.isArray(object) + ? object + : { _id: object }; + + if (Array.isArray(filter.$and)) { + return filter.$and.every((clause) => + matchesFilter(normalizedObject, clause), + ); + } + if (Array.isArray(filter.$or)) { + return filter.$or.some((clause) => matchesFilter(normalizedObject, clause)); + } + + for (const [key, expectedValue] of Object.entries(filter)) { + if (key === "$and" || key === "$or") { + continue; + } + if ( + !matchesExpected(getFilterValue(normalizedObject, key), expectedValue) + ) { + return false; + } + } + + return true; +}; + +const resolveObjectTypeFilter = async (objectType, filter = {}) => { + const normalizedFilter = normalizeFilter(filter); + if (Object.keys(normalizedFilter).length === 0) { + return { normalizedFilter, processedFilter: normalizedFilter }; + } + + const model = await getObjectTypeModel(objectType); + const processedFilter = await getFilter( + normalizedFilter, + Object.keys(normalizedFilter), + true, + model, + ); + return { normalizedFilter, processedFilter }; +}; + +const stableStringify = (value) => { + if (Array.isArray(value)) { + return `[${value.map(stableStringify).join(",")}]`; + } + + if (value && typeof value === "object") { + return `{${Object.keys(value) + .sort() + .map((key) => `${JSON.stringify(key)}:${stableStringify(value[key])}`) + .join(",")}}`; + } + + return JSON.stringify(value); +}; + +const getSubscriptionOwner = (socketId, filter) => + `${socketId}:${stableStringify(normalizeFilter(filter))}`; + +const getSubscriptionKey = (subject, owner) => `${subject}:${owner}`; + +/** + * UpdateManager handles tracking object updates and broadcasts update events via websockets. + */ +export class UpdateManager { + constructor(socketClient) { + this.socketClient = socketClient; + this.subscriptions = new Set(); + this.objectUpdateSubscriptions = new Set(); + } + + getObjectUpdateSubscriptionKey(objectType, id) { + return `${objectType}:${id}`; + } + + matchesObjectTypeFilter(objectType, filter, value) { + return matchesFilter(value, normalizeFilter(filter)); + } + + emitObjectTypeEvent( + eventName, + objectType, + filter, + value, + matchFilter = filter, + ) { + const normalizedFilter = normalizeFilter(filter); + const matches = this.matchesObjectTypeFilter( + objectType, + matchFilter, + value, + ); + + if (!matches) { + logger.trace( + `Filtered ${eventName} event: ${formatTraceData({ + objectType, + filter: normalizedFilter, + value, + })}`, + ); + return; + } + + this.socketClient.socket.emit(eventName, { + object: value, + objectType: objectType, + filter: normalizedFilter, + }); + } + + async subscribeToObjectNew(objectType, filter = {}) { + const { normalizedFilter, processedFilter } = await resolveObjectTypeFilter( + objectType, + filter, + ); + const subject = `${objectType}s.new`; + const owner = getSubscriptionOwner( + this.socketClient.socketId, + normalizedFilter, + ); + + await natsServer.subscribe(subject, owner, async (key, value) => { + logger.trace(`Object new event: ${formatTraceData(value)}`); + this.emitObjectTypeEvent( + "objectNew", + objectType, + normalizedFilter, + value, + processedFilter, + ); + }); + + this.subscriptions.add(getSubscriptionKey(subject, owner)); + return { success: true }; + } + + async subscribeToObjectDelete(objectType, filter = {}) { + const { normalizedFilter, processedFilter } = await resolveObjectTypeFilter( + objectType, + filter, + ); + const subject = `${objectType}s.delete`; + const owner = getSubscriptionOwner( + this.socketClient.socketId, + normalizedFilter, + ); + + await natsServer.subscribe(subject, owner, async (key, value) => { + logger.trace(`Object delete event: ${formatTraceData(value)}`); + this.emitObjectTypeEvent( + "objectDelete", + objectType, + normalizedFilter, + value, + processedFilter, + ); + }); + + this.subscriptions.add(getSubscriptionKey(subject, owner)); + return { success: true }; + } + + async subscribeToObjectUpdate(id, objectType) { + logger.debug("Subscribing to object update...", id, objectType); + const subject = `${objectType}s.${id}.object`; + const owner = this.socketClient.socketId; + + await natsServer.subscribe(subject, owner, (key, value) => { + this.emitObjectUpdate(objectType, value); + }); + + this.objectUpdateSubscriptions.add( + this.getObjectUpdateSubscriptionKey(objectType, id), + ); + this.subscriptions.add(getSubscriptionKey(subject, owner)); + return { success: true }; + } + + emitObjectUpdate(objectType, value, filter = {}, matchFilter = filter) { + const expandedValue = expandObjectIds(value); + if (!expandedValue || expandedValue._id == null) { + logger.warn("Object update missing _id:", objectType, value); + return; + } + + const normalizedFilter = normalizeFilter(filter); + const matches = this.matchesObjectTypeFilter( + objectType, + matchFilter, + expandedValue, + ); + + if (!matches) { + logger.trace( + `Filtered objectUpdate event: ${formatTraceData({ + objectType, + filter: normalizedFilter, + value: expandedValue, + })}`, + ); + return; + } + + const { _id, ...object } = { ...expandedValue }; + logger.trace("Object update event:", _id, objectType); + this.socketClient.socket.emit("objectUpdate", { + _id, + objectType, + object, + filter: normalizedFilter, + }); + } + + async subscribeToAllObjectUpdates(objectType, filter = {}) { + logger.debug("Subscribing to all object updates...", objectType); + const { normalizedFilter, processedFilter } = await resolveObjectTypeFilter( + objectType, + filter, + ); + const subject = `${objectType}s.*.object`; + const owner = getSubscriptionOwner( + this.socketClient.socketId, + normalizedFilter, + ); + + await natsServer.subscribe(subject, owner, (key, value) => { + const id = value?._id; + if (id == null) { + logger.warn("Object update missing _id:", objectType, value); + return; + } + + if ( + this.objectUpdateSubscriptions.has( + this.getObjectUpdateSubscriptionKey(objectType, id), + ) + ) { + return; + } + + this.emitObjectUpdate( + objectType, + value, + normalizedFilter, + processedFilter, + ); + }); + + this.subscriptions.add(getSubscriptionKey(subject, owner)); + return { success: true }; + } + + async removeObjectNewListener(objectType, filter = {}) { + const subject = `${objectType}s.new`; + const owner = getSubscriptionOwner(this.socketClient.socketId, filter); + + await natsServer.removeSubscription(subject, owner); + this.subscriptions.delete(getSubscriptionKey(subject, owner)); + return { success: true }; + } + + async removeObjectDeleteListener(objectType, filter = {}) { + const subject = `${objectType}s.delete`; + const owner = getSubscriptionOwner(this.socketClient.socketId, filter); + + await natsServer.removeSubscription(subject, owner); + this.subscriptions.delete(getSubscriptionKey(subject, owner)); + return { success: true }; + } + + async removeObjectUpdateListener(id, objectType) { + const subject = `${objectType}s.${id}.object`; + const owner = this.socketClient.socketId; + + await natsServer.removeSubscription(subject, owner); + this.objectUpdateSubscriptions.delete( + this.getObjectUpdateSubscriptionKey(objectType, id), + ); + this.subscriptions.delete(getSubscriptionKey(subject, owner)); + return { success: true }; + } + + async removeAllObjectUpdatesListener(objectType, filter = {}) { + const subject = `${objectType}s.*.object`; + const owner = getSubscriptionOwner(this.socketClient.socketId, filter); + + await natsServer.removeSubscription(subject, owner); + this.subscriptions.delete(getSubscriptionKey(subject, owner)); + return { success: true }; + } + + async removeAllListeners() { + logger.debug("Removing all update listeners..."); + const removePromises = Array.from(this.subscriptions).map( + (subscriptionKey) => { + const separatorIndex = subscriptionKey.indexOf(":"); + const subject = subscriptionKey.slice(0, separatorIndex); + const owner = subscriptionKey.slice(separatorIndex + 1); + return natsServer.removeSubscription(subject, owner); + }, + ); + + await Promise.all(removePromises); + this.subscriptions.clear(); + this.objectUpdateSubscriptions.clear(); + logger.debug(`Removed ${removePromises.length} update listener(s)`); + return { success: true }; + } +} diff --git a/src/utils.js b/src/utils.js new file mode 100644 index 0000000..dad11b7 --- /dev/null +++ b/src/utils.js @@ -0,0 +1,17 @@ +export function formatTraceData(value, maxLength = 128) { + if (value === undefined) return "undefined"; + const text = typeof value === "string" ? value : JSON.stringify(value); + return text.length > maxLength ? `${text.slice(0, maxLength)}...` : text; +} + +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(",")}`; +}