diff --git a/config.yml b/config.yml index 51e4c22..540bad4 100644 --- a/config.yml +++ b/config.yml @@ -30,7 +30,7 @@ settings: # application level settings application: # queue - queue: ${QUEUE}:auth + queue: ${QUEUE}:alkemio-whiteboards # MILLISECONDS wait time for a response after a request on the message queue queue_response_timeout: ${QUEUE_RESPONSE_TIMEOUT}:10000 # the collaboration experience @@ -42,12 +42,7 @@ settings: # time is in SECONDS contribution_window: ${CONTRIBUTION_WINDOW}:600 # SECONDS between auto saves - save_interval: ${AUTOSAVE_INTERVAL}:15 - # SECONDS to wait for an acknowledgment that the save is successful, before retrying - save_timeout: ${AUTOSAVE_TIMEOUT}:20 - # Amount of consecutive failed attempts to save before a collaborator is not picked for auto save - # This is useful when you want to avoid a lot of errors, or to mark a client not being reliable to save - save_consecutive_failed_attempts: ${SAVE_CONSECUTIVE_FAILED_ATTEMPTS}:5 + save_interval: ${AUTOSAVE_INTERVAL}:10 # SECONDS of inactivity before a collaborator is made view-only collaborator_inactivity: ${COLLABORATOR_INACTIVITY}:1800 # how often the inactivity timer is reset; @@ -56,4 +51,4 @@ settings: reset_collaborator_mode_debounce: ${INACTIVITY_DEBOUNCE}:1000 rest: # the REST API - port: ${REST_PORT}:4005 \ No newline at end of file + port: ${REST_PORT}:4005 diff --git a/package-lock.json b/package-lock.json index eee0afc..102aca8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "whiteboard-collaboration-service", - "version": "0.3.1", + "version": "0.4.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "whiteboard-collaboration-service", - "version": "0.3.1", + "version": "0.4.0", "license": "EUPL-1.2", "dependencies": { "@nestjs/common": "^10.0.0", @@ -17,7 +17,6 @@ "@socket.io/redis-adapter": "^8.3.0", "amqp-connection-manager": "^4.1.14", "amqplib": "^0.10.4", - "http": "^0.0.1-security", "lodash": "^4.17.21", "nest-winston": "^1.9.6", "redis": "^4.6.13", @@ -34,7 +33,7 @@ "@types/express": "^4.17.17", "@types/jest": "^29.5.2", "@types/lodash": "^4.17.4", - "@types/node": "^20.3.1", + "@types/node": "^20.16.5", "@types/supertest": "^6.0.0", "@typescript-eslint/eslint-plugin": "^6.0.0", "@typescript-eslint/parser": "^6.0.0", @@ -49,7 +48,7 @@ "ts-loader": "^9.4.3", "ts-node": "^10.9.1", "tsconfig-paths": "^4.2.0", - "typescript": "^5.1.3" + "typescript": "^5.6.2" }, "engines": { "node": ">=20.9.0", @@ -133,15 +132,16 @@ } }, "node_modules/@angular-devkit/schematics-cli": { - "version": "17.1.2", - "resolved": "https://registry.npmjs.org/@angular-devkit/schematics-cli/-/schematics-cli-17.1.2.tgz", - "integrity": "sha512-bvXykYzSST05qFdlgIzUguNOb3z0hCa8HaTwtqdmQo9aFPf+P+/AC56I64t1iTchMjQtf3JrBQhYM25gUdcGbg==", + "version": "17.3.8", + "resolved": "https://registry.npmjs.org/@angular-devkit/schematics-cli/-/schematics-cli-17.3.8.tgz", + "integrity": "sha512-TjmiwWJarX7oqvNiRAroQ5/LeKUatxBOCNEuKXO/PV8e7pn/Hr/BqfFm+UcYrQoFdZplmtNAfqmbqgVziKvCpA==", "dev": true, + "license": "MIT", "dependencies": { - "@angular-devkit/core": "17.1.2", - "@angular-devkit/schematics": "17.1.2", + "@angular-devkit/core": "17.3.8", + "@angular-devkit/schematics": "17.3.8", "ansi-colors": "4.1.3", - "inquirer": "9.2.12", + "inquirer": "9.2.15", "symbol-observable": "4.0.0", "yargs-parser": "21.1.1" }, @@ -154,11 +154,59 @@ "yarn": ">= 1.13.0" } }, + "node_modules/@angular-devkit/schematics-cli/node_modules/@angular-devkit/core": { + "version": "17.3.8", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-17.3.8.tgz", + "integrity": "sha512-Q8q0voCGudbdCgJ7lXdnyaxKHbNQBARH68zPQV72WT8NWy+Gw/tys870i6L58NWbBaCJEUcIj/kb6KoakSRu+Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "8.12.0", + "ajv-formats": "2.1.1", + "jsonc-parser": "3.2.1", + "picomatch": "4.0.1", + "rxjs": "7.8.1", + "source-map": "0.7.4" + }, + "engines": { + "node": "^18.13.0 || >=20.9.0", + "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", + "yarn": ">= 1.13.0" + }, + "peerDependencies": { + "chokidar": "^3.5.2" + }, + "peerDependenciesMeta": { + "chokidar": { + "optional": true + } + } + }, + "node_modules/@angular-devkit/schematics-cli/node_modules/@angular-devkit/schematics": { + "version": "17.3.8", + "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-17.3.8.tgz", + "integrity": "sha512-QRVEYpIfgkprNHc916JlPuNbLzOgrm9DZalHasnLUz4P6g7pR21olb8YCyM2OTJjombNhya9ZpckcADU5Qyvlg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@angular-devkit/core": "17.3.8", + "jsonc-parser": "3.2.1", + "magic-string": "0.30.8", + "ora": "5.4.1", + "rxjs": "7.8.1" + }, + "engines": { + "node": "^18.13.0 || >=20.9.0", + "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", + "yarn": ">= 1.13.0" + } + }, "node_modules/@angular-devkit/schematics-cli/node_modules/chalk": { "version": "5.3.0", "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz", "integrity": "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==", "dev": true, + "license": "MIT", "engines": { "node": "^12.17.0 || ^14.13 || >=16.0.0" }, @@ -171,51 +219,25 @@ "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-4.1.0.tgz", "integrity": "sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ==", "dev": true, + "license": "ISC", "engines": { "node": ">= 12" } }, - "node_modules/@angular-devkit/schematics-cli/node_modules/escape-string-regexp": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz", - "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==", - "dev": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@angular-devkit/schematics-cli/node_modules/figures": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/figures/-/figures-5.0.0.tgz", - "integrity": "sha512-ej8ksPF4x6e5wvK9yevct0UCXh8TTFlWGVLlgjZuoBH1HwjIfKE/IdL5mq89sFA7zELi1VhKpmtDnrs7zWyeyg==", - "dev": true, - "dependencies": { - "escape-string-regexp": "^5.0.0", - "is-unicode-supported": "^1.2.0" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/@angular-devkit/schematics-cli/node_modules/inquirer": { - "version": "9.2.12", - "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-9.2.12.tgz", - "integrity": "sha512-mg3Fh9g2zfuVWJn6lhST0O7x4n03k7G8Tx5nvikJkbq8/CK47WDVm+UznF0G6s5Zi0KcyUisr6DU8T67N5U+1Q==", + "version": "9.2.15", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-9.2.15.tgz", + "integrity": "sha512-vI2w4zl/mDluHt9YEQ/543VTCwPKWiHzKtm9dM2V0NdFcqEexDAjUHzO1oA60HRNaVifGXXM1tRRNluLVHa0Kg==", "dev": true, + "license": "MIT", "dependencies": { - "@ljharb/through": "^2.3.11", + "@ljharb/through": "^2.3.12", "ansi-escapes": "^4.3.2", "chalk": "^5.3.0", "cli-cursor": "^3.1.0", "cli-width": "^4.1.0", "external-editor": "^3.1.0", - "figures": "^5.0.0", + "figures": "^3.2.0", "lodash": "^4.17.21", "mute-stream": "1.0.0", "ora": "^5.4.1", @@ -226,19 +248,27 @@ "wrap-ansi": "^6.2.0" }, "engines": { - "node": ">=14.18.0" + "node": ">=18" } }, - "node_modules/@angular-devkit/schematics-cli/node_modules/is-unicode-supported": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-1.3.0.tgz", - "integrity": "sha512-43r2mRvz+8JRIKnWJ+3j8JtjRKZ6GmjzfaE/qiBJnikNnYv/6bagRJ1kUhNk8R5EX/GkobD+r+sfxCPJsiKBLQ==", + "node_modules/@angular-devkit/schematics-cli/node_modules/jsonc-parser": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.2.1.tgz", + "integrity": "sha512-AilxAyFOAcK5wA1+LeaySVBrHsGQvUFCDWXKpZjzaL0PqW+xfBOttn8GNtWKFWqneyMZj41MWF9Kl6iPWLwgOA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@angular-devkit/schematics-cli/node_modules/magic-string": { + "version": "0.30.8", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.8.tgz", + "integrity": "sha512-ISQTe55T2ao7XtlAStud6qwYPZjE4GK1S/BeVPus4jrq6JuOnQ00YKQC581RWhR122W7msZV263KzVeLoqidyQ==", "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.4.15" + }, "engines": { "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/@angular-devkit/schematics-cli/node_modules/mute-stream": { @@ -246,15 +276,30 @@ "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-1.0.0.tgz", "integrity": "sha512-avsJQhyd+680gKXyG/sQc0nXaC6rBkPOfyHYcFb9+hdkqQkR9bdnkJ0AMZhke0oesPqIO+mFFJ+IdBc7mst4IA==", "dev": true, + "license": "ISC", "engines": { "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, + "node_modules/@angular-devkit/schematics-cli/node_modules/picomatch": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.1.tgz", + "integrity": "sha512-xUXwsxNjwTQ8K3GnT4pCJm+xq3RUPQbmkYJTP5aFIfNIvbcc/4MUxgBaaRSZJ6yGJZiGSyYlM6MzwTsRk8SYCg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, "node_modules/@angular-devkit/schematics-cli/node_modules/run-async": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/run-async/-/run-async-3.0.0.tgz", "integrity": "sha512-540WwVDOMxA6dN6We19EcT9sc3hkXPw5mzRNGM3FkdN/vtE9NFvj5lFAPNwUDmJjXidm3v7TC1cTE7t17Ulm1Q==", "dev": true, + "license": "MIT", "engines": { "node": ">=0.12.0" } @@ -843,6 +888,7 @@ "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz", "integrity": "sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==", "dev": true, + "license": "MIT", "optional": true, "engines": { "node": ">=0.1.90" @@ -1041,6 +1087,7 @@ "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", "dev": true, + "license": "ISC", "dependencies": { "string-width": "^5.1.2", "string-width-cjs": "npm:string-width@^4.2.0", @@ -1054,10 +1101,11 @@ } }, "node_modules/@isaacs/cliui/node_modules/ansi-regex": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", - "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", + "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", "dev": true, + "license": "MIT", "engines": { "node": ">=12" }, @@ -1070,6 +1118,7 @@ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", "dev": true, + "license": "MIT", "engines": { "node": ">=12" }, @@ -1081,13 +1130,15 @@ "version": "9.2.2", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/@isaacs/cliui/node_modules/string-width": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", "dev": true, + "license": "MIT", "dependencies": { "eastasianwidth": "^0.2.0", "emoji-regex": "^9.2.2", @@ -1105,6 +1156,7 @@ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", "dev": true, + "license": "MIT", "dependencies": { "ansi-regex": "^6.0.1" }, @@ -1120,6 +1172,7 @@ "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", "dev": true, + "license": "MIT", "dependencies": { "ansi-styles": "^6.1.0", "string-width": "^5.0.1", @@ -1623,6 +1676,7 @@ "resolved": "https://registry.npmjs.org/@ljharb/through/-/through-2.3.13.tgz", "integrity": "sha512-/gKJun8NNiWGZJkGzI/Ragc53cOdcLNdzjLaIa+GEjguQs0ulsurx8WN0jijdK9yPqDvziX995sMRLyLt1uZMQ==", "dev": true, + "license": "MIT", "dependencies": { "call-bind": "^1.0.7" }, @@ -1639,32 +1693,30 @@ } }, "node_modules/@nestjs/cli": { - "version": "10.3.2", - "resolved": "https://registry.npmjs.org/@nestjs/cli/-/cli-10.3.2.tgz", - "integrity": "sha512-aWmD1GLluWrbuC4a1Iz/XBk5p74Uj6nIVZj6Ov03JbTfgtWqGFLtXuMetvzMiHxfrHehx/myt2iKAPRhKdZvTg==", + "version": "10.4.5", + "resolved": "https://registry.npmjs.org/@nestjs/cli/-/cli-10.4.5.tgz", + "integrity": "sha512-FP7Rh13u8aJbHe+zZ7hM0CC4785g9Pw4lz4r2TTgRtf0zTxSWMkJaPEwyjX8SK9oWK2GsYxl+fKpwVZNbmnj9A==", "dev": true, + "license": "MIT", "dependencies": { - "@angular-devkit/core": "17.1.2", - "@angular-devkit/schematics": "17.1.2", - "@angular-devkit/schematics-cli": "17.1.2", + "@angular-devkit/core": "17.3.8", + "@angular-devkit/schematics": "17.3.8", + "@angular-devkit/schematics-cli": "17.3.8", "@nestjs/schematics": "^10.0.1", "chalk": "4.1.2", "chokidar": "3.6.0", - "cli-table3": "0.6.3", + "cli-table3": "0.6.5", "commander": "4.1.1", "fork-ts-checker-webpack-plugin": "9.0.2", - "glob": "10.3.10", + "glob": "10.4.2", "inquirer": "8.2.6", "node-emoji": "1.11.0", "ora": "5.4.1", - "rimraf": "4.4.1", - "shelljs": "0.8.5", - "source-map-support": "0.5.21", "tree-kill": "1.2.2", "tsconfig-paths": "4.2.0", "tsconfig-paths-webpack-plugin": "4.1.0", "typescript": "5.3.3", - "webpack": "5.90.1", + "webpack": "5.94.0", "webpack-node-externals": "3.0.0" }, "bin": { @@ -1674,7 +1726,7 @@ "node": ">= 16.14" }, "peerDependencies": { - "@swc/cli": "^0.1.62 || ^0.3.0", + "@swc/cli": "^0.1.62 || ^0.3.0 || ^0.4.0", "@swc/core": "^1.3.62" }, "peerDependenciesMeta": { @@ -1686,26 +1738,84 @@ } } }, - "node_modules/@nestjs/cli/node_modules/eslint-scope": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", - "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "node_modules/@nestjs/cli/node_modules/@angular-devkit/core": { + "version": "17.3.8", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-17.3.8.tgz", + "integrity": "sha512-Q8q0voCGudbdCgJ7lXdnyaxKHbNQBARH68zPQV72WT8NWy+Gw/tys870i6L58NWbBaCJEUcIj/kb6KoakSRu+Q==", "dev": true, + "license": "MIT", "dependencies": { - "esrecurse": "^4.3.0", - "estraverse": "^4.1.1" + "ajv": "8.12.0", + "ajv-formats": "2.1.1", + "jsonc-parser": "3.2.1", + "picomatch": "4.0.1", + "rxjs": "7.8.1", + "source-map": "0.7.4" }, "engines": { - "node": ">=8.0.0" + "node": "^18.13.0 || >=20.9.0", + "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", + "yarn": ">= 1.13.0" + }, + "peerDependencies": { + "chokidar": "^3.5.2" + }, + "peerDependenciesMeta": { + "chokidar": { + "optional": true + } } }, - "node_modules/@nestjs/cli/node_modules/estraverse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", - "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "node_modules/@nestjs/cli/node_modules/@angular-devkit/schematics": { + "version": "17.3.8", + "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-17.3.8.tgz", + "integrity": "sha512-QRVEYpIfgkprNHc916JlPuNbLzOgrm9DZalHasnLUz4P6g7pR21olb8YCyM2OTJjombNhya9ZpckcADU5Qyvlg==", "dev": true, + "license": "MIT", + "dependencies": { + "@angular-devkit/core": "17.3.8", + "jsonc-parser": "3.2.1", + "magic-string": "0.30.8", + "ora": "5.4.1", + "rxjs": "7.8.1" + }, "engines": { - "node": ">=4.0" + "node": "^18.13.0 || >=20.9.0", + "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", + "yarn": ">= 1.13.0" + } + }, + "node_modules/@nestjs/cli/node_modules/jsonc-parser": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.2.1.tgz", + "integrity": "sha512-AilxAyFOAcK5wA1+LeaySVBrHsGQvUFCDWXKpZjzaL0PqW+xfBOttn8GNtWKFWqneyMZj41MWF9Kl6iPWLwgOA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@nestjs/cli/node_modules/magic-string": { + "version": "0.30.8", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.8.tgz", + "integrity": "sha512-ISQTe55T2ao7XtlAStud6qwYPZjE4GK1S/BeVPus4jrq6JuOnQ00YKQC581RWhR122W7msZV263KzVeLoqidyQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.4.15" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@nestjs/cli/node_modules/picomatch": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.1.tgz", + "integrity": "sha512-xUXwsxNjwTQ8K3GnT4pCJm+xq3RUPQbmkYJTP5aFIfNIvbcc/4MUxgBaaRSZJ6yGJZiGSyYlM6MzwTsRk8SYCg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" } }, "node_modules/@nestjs/cli/node_modules/typescript": { @@ -1721,53 +1831,6 @@ "node": ">=14.17" } }, - "node_modules/@nestjs/cli/node_modules/webpack": { - "version": "5.90.1", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.90.1.tgz", - "integrity": "sha512-SstPdlAC5IvgFnhiRok8hqJo/+ArAbNv7rhU4fnWGHNVfN59HSQFaxZDSAL3IFG2YmqxuRs+IU33milSxbPlog==", - "dev": true, - "dependencies": { - "@types/eslint-scope": "^3.7.3", - "@types/estree": "^1.0.5", - "@webassemblyjs/ast": "^1.11.5", - "@webassemblyjs/wasm-edit": "^1.11.5", - "@webassemblyjs/wasm-parser": "^1.11.5", - "acorn": "^8.7.1", - "acorn-import-assertions": "^1.9.0", - "browserslist": "^4.21.10", - "chrome-trace-event": "^1.0.2", - "enhanced-resolve": "^5.15.0", - "es-module-lexer": "^1.2.1", - "eslint-scope": "5.1.1", - "events": "^3.2.0", - "glob-to-regexp": "^0.4.1", - "graceful-fs": "^4.2.9", - "json-parse-even-better-errors": "^2.3.1", - "loader-runner": "^4.2.0", - "mime-types": "^2.1.27", - "neo-async": "^2.6.2", - "schema-utils": "^3.2.0", - "tapable": "^2.1.1", - "terser-webpack-plugin": "^5.3.10", - "watchpack": "^2.4.0", - "webpack-sources": "^3.2.3" - }, - "bin": { - "webpack": "bin/webpack.js" - }, - "engines": { - "node": ">=10.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependenciesMeta": { - "webpack-cli": { - "optional": true - } - } - }, "node_modules/@nestjs/common": { "version": "10.3.8", "resolved": "https://registry.npmjs.org/@nestjs/common/-/common-10.3.8.tgz", @@ -1812,16 +1875,17 @@ } }, "node_modules/@nestjs/core": { - "version": "10.3.8", - "resolved": "https://registry.npmjs.org/@nestjs/core/-/core-10.3.8.tgz", - "integrity": "sha512-AxF4tpYLDNn5Wfb3C4bNaaHJ4pREH5FJrSisR2A5zkYpQFORFs0Tc36lOFPMwBTy8Iv2wUwWLUVc5ftBnxEv4w==", + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/@nestjs/core/-/core-10.4.3.tgz", + "integrity": "sha512-6OQz+5C8mT8yRtfvE5pPCq+p6w5jDot+oQku1KzQ24ABn+lay1KGuJwcKZhdVNuselx+8xhdMxknZTA8wrGLIg==", "hasInstallScript": true, + "license": "MIT", "dependencies": { "@nuxtjs/opencollective": "0.3.2", "fast-safe-stringify": "2.1.1", "iterare": "1.2.1", - "path-to-regexp": "3.2.0", - "tslib": "2.6.2", + "path-to-regexp": "3.3.0", + "tslib": "2.7.0", "uid": "2.0.2" }, "funding": { @@ -1848,6 +1912,12 @@ } } }, + "node_modules/@nestjs/core/node_modules/tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", + "license": "0BSD" + }, "node_modules/@nestjs/microservices": { "version": "10.3.8", "resolved": "https://registry.npmjs.org/@nestjs/microservices/-/microservices-10.3.8.tgz", @@ -1906,15 +1976,16 @@ } }, "node_modules/@nestjs/platform-express": { - "version": "10.3.8", - "resolved": "https://registry.npmjs.org/@nestjs/platform-express/-/platform-express-10.3.8.tgz", - "integrity": "sha512-sifLoxgEJvAgbim1UuW6wyScMfkS9SVQRH+lN33N/9ZvZSjO6NSDLOe+wxqsnZkia+QrjFC0qy0ITRAsggfqbg==", + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/@nestjs/platform-express/-/platform-express-10.4.3.tgz", + "integrity": "sha512-ss7gkofVm3eO+1P9iRhmGq6Xcjg+mIN3dWisKJZYelSV+msb0QpJmqChLvWjLkWtlqDnx915FKUk0IzCa0TVzw==", + "license": "MIT", "dependencies": { - "body-parser": "1.20.2", + "body-parser": "1.20.3", "cors": "2.8.5", - "express": "4.19.2", + "express": "4.21.0", "multer": "1.4.4-lts.1", - "tslib": "2.6.2" + "tslib": "2.7.0" }, "funding": { "type": "opencollective", @@ -1925,6 +1996,12 @@ "@nestjs/core": "^10.0.0" } }, + "node_modules/@nestjs/platform-express/node_modules/tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", + "license": "0BSD" + }, "node_modules/@nestjs/schematics": { "version": "10.1.1", "resolved": "https://registry.npmjs.org/@nestjs/schematics/-/schematics-10.1.1.tgz", @@ -2031,6 +2108,7 @@ "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", "dev": true, + "license": "MIT", "optional": true, "engines": { "node": ">=14" @@ -2052,14 +2130,16 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/@redis/bloom/-/bloom-1.2.0.tgz", "integrity": "sha512-HG2DFjYKbpNmVXsa0keLHp/3leGJz1mjh09f2RLGGLQZzSHpkmZWuwJbAvo3QcRY8p80m5+ZdXZdYOSBLlp7Cg==", + "license": "MIT", "peerDependencies": { "@redis/client": "^1.0.0" } }, "node_modules/@redis/client": { - "version": "1.5.14", - "resolved": "https://registry.npmjs.org/@redis/client/-/client-1.5.14.tgz", - "integrity": "sha512-YGn0GqsRBFUQxklhY7v562VMOP0DcmlrHHs3IV1mFE3cbxe31IITUkqhBcIhVSI/2JqtWAJXg5mjV4aU+zD0HA==", + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@redis/client/-/client-1.6.0.tgz", + "integrity": "sha512-aR0uffYI700OEEH4gYnitAnv3vzVGXCFvYfdpu/CJKvk4pHfLPEy/JSZyrpQ+15WhXe1yJRXLtfQ84s4mEXnPg==", + "license": "MIT", "dependencies": { "cluster-key-slot": "1.1.2", "generic-pool": "3.9.0", @@ -2072,36 +2152,41 @@ "node_modules/@redis/client/node_modules/yallist": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "license": "ISC" }, "node_modules/@redis/graph": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/@redis/graph/-/graph-1.1.1.tgz", "integrity": "sha512-FEMTcTHZozZciLRl6GiiIB4zGm5z5F3F6a6FZCyrfxdKOhFlGkiAqlexWMBzCi4DcRoyiOsuLfW+cjlGWyExOw==", + "license": "MIT", "peerDependencies": { "@redis/client": "^1.0.0" } }, "node_modules/@redis/json": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/@redis/json/-/json-1.0.6.tgz", - "integrity": "sha512-rcZO3bfQbm2zPRpqo82XbW8zg4G/w4W3tI7X8Mqleq9goQjAGLL7q/1n1ZX4dXEAmORVZ4s1+uKLaUOg7LrUhw==", + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/@redis/json/-/json-1.0.7.tgz", + "integrity": "sha512-6UyXfjVaTBTJtKNG4/9Z8PSpKE6XgSyEb8iwaqDcy+uKrd/DGYHTWkUdnQDyzm727V7p21WUMhsqz5oy65kPcQ==", + "license": "MIT", "peerDependencies": { "@redis/client": "^1.0.0" } }, "node_modules/@redis/search": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/@redis/search/-/search-1.1.6.tgz", - "integrity": "sha512-mZXCxbTYKBQ3M2lZnEddwEAks0Kc7nauire8q20oA0oA/LoA+E/b5Y5KZn232ztPb1FkIGqo12vh3Lf+Vw5iTw==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@redis/search/-/search-1.2.0.tgz", + "integrity": "sha512-tYoDBbtqOVigEDMAcTGsRlMycIIjwMCgD8eR2t0NANeQmgK/lvxNAvYyb6bZDD4frHRhIHkJu2TBRvB0ERkOmw==", + "license": "MIT", "peerDependencies": { "@redis/client": "^1.0.0" } }, "node_modules/@redis/time-series": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/@redis/time-series/-/time-series-1.0.5.tgz", - "integrity": "sha512-IFjIgTusQym2B5IZJG3XKr5llka7ey84fw/NOYqESP5WUfQs9zz1ww/9+qoz4ka/S6KcGBodzlCeZ5UImKbscg==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@redis/time-series/-/time-series-1.1.0.tgz", + "integrity": "sha512-c1Q99M5ljsIuc4YdaCwfUEXsofakb9c8+Zse2qxTadu8TalLXuAESzLvFAvNVbkmSlvlzIQOLpBCmWI9wTOt+g==", + "license": "MIT", "peerDependencies": { "@redis/client": "^1.0.0" } @@ -2139,6 +2224,7 @@ "version": "8.3.0", "resolved": "https://registry.npmjs.org/@socket.io/redis-adapter/-/redis-adapter-8.3.0.tgz", "integrity": "sha512-ly0cra+48hDmChxmIpnESKrc94LjRL80TEmZVscuQ/WWkRP81nNj8W8cCGMqbI4L6NCuAaPRSzZF1a9GlAxxnA==", + "license": "MIT", "dependencies": { "debug": "~4.3.1", "notepack.io": "~3.0.1", @@ -2259,21 +2345,13 @@ "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.56.10.tgz", "integrity": "sha512-Shavhk87gCtY2fhXDctcfS3e6FdxWkCx1iUZ9eEUbh7rTqlZT0/IzOkCOVt0fCjcFuZ9FPYfuezTBImfHCDBGQ==", "dev": true, + "optional": true, + "peer": true, "dependencies": { "@types/estree": "*", "@types/json-schema": "*" } }, - "node_modules/@types/eslint-scope": { - "version": "3.7.7", - "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.7.tgz", - "integrity": "sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==", - "dev": true, - "dependencies": { - "@types/eslint": "*", - "@types/estree": "*" - } - }, "node_modules/@types/estree": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", @@ -2378,11 +2456,12 @@ "dev": true }, "node_modules/@types/node": { - "version": "20.12.11", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.12.11.tgz", - "integrity": "sha512-vDg9PZ/zi+Nqp6boSOT7plNuthRugEKixDv5sFTIpkE89MmNtEArAShI4mxuX2+UrLEe9pxC1vm2cjm9YlWbJw==", + "version": "20.16.5", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.16.5.tgz", + "integrity": "sha512-VwYCweNo3ERajwy0IUlqqcyZ8/A7Zwa9ZP3MnENWcB11AejO+tLy3pu850goUW2FC/IJMdZUfKpX/yxL1gymCA==", + "license": "MIT", "dependencies": { - "undici-types": "~5.26.4" + "undici-types": "~6.19.2" } }, "node_modules/@types/qs": { @@ -2850,11 +2929,12 @@ "node": ">=0.4.0" } }, - "node_modules/acorn-import-assertions": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/acorn-import-assertions/-/acorn-import-assertions-1.9.0.tgz", - "integrity": "sha512-cmMwop9x+8KFhxvKrKfPYmN6/pKTYYHBqLa0DfvVZcKMJWNyWLnaqND7dx/qn66R7ewM1UX5XMaDVP5wlVTaVA==", + "node_modules/acorn-import-attributes": { + "version": "1.9.5", + "resolved": "https://registry.npmjs.org/acorn-import-attributes/-/acorn-import-attributes-1.9.5.tgz", + "integrity": "sha512-n02Vykv5uA3eHGM/Z2dQrcD56kL8TyDb2p1+0P83PClMnC/nc+anbQRhIOWnSq4Ke/KvDPrY3C9hDtC/A3eHnQ==", "dev": true, + "license": "MIT", "peerDependencies": { "acorn": "^8" } @@ -2965,6 +3045,7 @@ "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz", "integrity": "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==", "dev": true, + "license": "MIT", "engines": { "node": ">=6" } @@ -3064,7 +3145,8 @@ "node_modules/array-flatten": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", - "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==" + "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", + "license": "MIT" }, "node_modules/array-timsort": { "version": "1.0.3", @@ -3287,9 +3369,10 @@ } }, "node_modules/body-parser": { - "version": "1.20.2", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.2.tgz", - "integrity": "sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==", + "version": "1.20.3", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz", + "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==", + "license": "MIT", "dependencies": { "bytes": "3.1.2", "content-type": "~1.0.5", @@ -3299,7 +3382,7 @@ "http-errors": "2.0.0", "iconv-lite": "0.4.24", "on-finished": "2.4.1", - "qs": "6.11.0", + "qs": "6.13.0", "raw-body": "2.5.2", "type-is": "~1.6.18", "unpipe": "1.0.0" @@ -3313,6 +3396,7 @@ "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", "dependencies": { "ms": "2.0.0" } @@ -3320,7 +3404,8 @@ "node_modules/body-parser/node_modules/ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" }, "node_modules/brace-expansion": { "version": "2.0.1", @@ -3445,6 +3530,7 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "license": "MIT", "engines": { "node": ">= 0.8" } @@ -3614,10 +3700,11 @@ } }, "node_modules/cli-table3": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/cli-table3/-/cli-table3-0.6.3.tgz", - "integrity": "sha512-w5Jac5SykAeZJKntOxJCrm63Eg5/4dhMWIcuTbo9rpE+brgaSZo0RuNJZeOyMgsUdhDeojvgyQLmjI+K50ZGyg==", + "version": "0.6.5", + "resolved": "https://registry.npmjs.org/cli-table3/-/cli-table3-0.6.5.tgz", + "integrity": "sha512-+W/5efTR7y5HRD7gACw9yQjqMVvEMLBHmboM/kPWam+H+Hmyrgjh6YncVKK122YZkXrLudzTuAukUw9FnMf7IQ==", "dev": true, + "license": "MIT", "dependencies": { "string-width": "^4.2.0" }, @@ -3681,6 +3768,7 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/cluster-key-slot/-/cluster-key-slot-1.1.2.tgz", "integrity": "sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA==", + "license": "Apache-2.0", "engines": { "node": ">=0.10.0" } @@ -3837,6 +3925,7 @@ "version": "0.5.4", "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "license": "MIT", "dependencies": { "safe-buffer": "5.2.1" }, @@ -3848,6 +3937,7 @@ "version": "1.0.5", "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "license": "MIT", "engines": { "node": ">= 0.6" } @@ -3862,6 +3952,7 @@ "version": "0.6.0", "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz", "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==", + "license": "MIT", "engines": { "node": ">= 0.6" } @@ -3869,7 +3960,8 @@ "node_modules/cookie-signature": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", - "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==" + "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==", + "license": "MIT" }, "node_modules/cookiejar": { "version": "2.1.4", @@ -4047,6 +4139,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "license": "MIT", "engines": { "node": ">= 0.8" } @@ -4055,6 +4148,7 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "license": "MIT", "engines": { "node": ">= 0.8", "npm": "1.2.8000 || >= 1.4.16" @@ -4144,12 +4238,14 @@ "version": "0.2.0", "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/ee-first": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", - "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + "license": "MIT" }, "node_modules/electron-to-chromium": { "version": "1.4.762", @@ -4182,9 +4278,10 @@ "peer": true }, "node_modules/encodeurl": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", - "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "license": "MIT", "engines": { "node": ">= 0.8" } @@ -4226,10 +4323,11 @@ } }, "node_modules/enhanced-resolve": { - "version": "5.16.1", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.16.1.tgz", - "integrity": "sha512-4U5pNsuDl0EhuZpq46M5xPslstkviJuhrdobaRDBk2Jy2KO37FDAJl4lb2KlNabxT0m4MTK2UHNrsAcphE8nyw==", + "version": "5.17.1", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.17.1.tgz", + "integrity": "sha512-LMHl3dXhTcfv8gM4kEzIUeTQ+7fpdA0l2tUf34BddXPkz2A5xJ5L/Pchd5BL6rdccM9QGvu0sWZzK1Z1t4wwyg==", "dev": true, + "license": "MIT", "dependencies": { "graceful-fs": "^4.2.4", "tapable": "^2.2.0" @@ -4284,7 +4382,8 @@ "node_modules/escape-html": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", - "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==" + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "license": "MIT" }, "node_modules/escape-string-regexp": { "version": "4.0.0", @@ -4555,6 +4654,7 @@ "version": "1.8.1", "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "license": "MIT", "engines": { "node": ">= 0.6" } @@ -4623,36 +4723,37 @@ } }, "node_modules/express": { - "version": "4.19.2", - "resolved": "https://registry.npmjs.org/express/-/express-4.19.2.tgz", - "integrity": "sha512-5T6nhjsT+EOMzuck8JjBHARTHfMht0POzlA60WV2pMD3gyXw2LZnZ+ueGdNxG+0calOJcWKbpFcuzLZ91YWq9Q==", + "version": "4.21.0", + "resolved": "https://registry.npmjs.org/express/-/express-4.21.0.tgz", + "integrity": "sha512-VqcNGcj/Id5ZT1LZ/cfihi3ttTn+NJmkli2eZADigjq29qTlWi/hAQ43t/VLPq8+UX06FCEx3ByOYet6ZFblng==", + "license": "MIT", "dependencies": { "accepts": "~1.3.8", "array-flatten": "1.1.1", - "body-parser": "1.20.2", + "body-parser": "1.20.3", "content-disposition": "0.5.4", "content-type": "~1.0.4", "cookie": "0.6.0", "cookie-signature": "1.0.6", "debug": "2.6.9", "depd": "2.0.0", - "encodeurl": "~1.0.2", + "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "etag": "~1.8.1", - "finalhandler": "1.2.0", + "finalhandler": "1.3.1", "fresh": "0.5.2", "http-errors": "2.0.0", - "merge-descriptors": "1.0.1", + "merge-descriptors": "1.0.3", "methods": "~1.1.2", "on-finished": "2.4.1", "parseurl": "~1.3.3", - "path-to-regexp": "0.1.7", + "path-to-regexp": "0.1.10", "proxy-addr": "~2.0.7", - "qs": "6.11.0", + "qs": "6.13.0", "range-parser": "~1.2.1", "safe-buffer": "5.2.1", - "send": "0.18.0", - "serve-static": "1.15.0", + "send": "0.19.0", + "serve-static": "1.16.2", "setprototypeof": "1.2.0", "statuses": "2.0.1", "type-is": "~1.6.18", @@ -4667,6 +4768,7 @@ "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", "dependencies": { "ms": "2.0.0" } @@ -4674,12 +4776,14 @@ "node_modules/express/node_modules/ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" }, "node_modules/express/node_modules/path-to-regexp": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", - "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==" + "version": "0.1.10", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.10.tgz", + "integrity": "sha512-7lf7qcQidTku0Gu3YDPc8DJ1q7OOucfa/BSsIwjuh56VU7katFvuM8hULfkwB3Fns/rsVF7PwPKVw1sl5KQS9w==", + "license": "MIT" }, "node_modules/external-editor": { "version": "3.1.0", @@ -4813,12 +4917,13 @@ } }, "node_modules/finalhandler": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz", - "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==", + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz", + "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==", + "license": "MIT", "dependencies": { "debug": "2.6.9", - "encodeurl": "~1.0.2", + "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "on-finished": "2.4.1", "parseurl": "~1.3.3", @@ -4833,6 +4938,7 @@ "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", "dependencies": { "ms": "2.0.0" } @@ -4840,7 +4946,8 @@ "node_modules/finalhandler/node_modules/ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" }, "node_modules/find-up": { "version": "5.0.0", @@ -4942,10 +5049,11 @@ "peer": true }, "node_modules/foreground-child": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.1.1.tgz", - "integrity": "sha512-TMKDUnIte6bfb5nWv7V/caI169OHgvwjb7V4WkeUvbQQdjr5rWKqHFiKWb/fcOwB+CzBT+qbWjvj+DVwRskpIg==", + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.0.tgz", + "integrity": "sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg==", "dev": true, + "license": "ISC", "dependencies": { "cross-spawn": "^7.0.0", "signal-exit": "^4.0.1" @@ -5040,6 +5148,7 @@ "version": "0.2.0", "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "license": "MIT", "engines": { "node": ">= 0.6" } @@ -5048,6 +5157,7 @@ "version": "0.5.2", "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "license": "MIT", "engines": { "node": ">= 0.6" } @@ -5104,6 +5214,7 @@ "version": "3.9.0", "resolved": "https://registry.npmjs.org/generic-pool/-/generic-pool-3.9.0.tgz", "integrity": "sha512-hymDOu5B53XvN4QT9dBmZxPX4CWhBPPLguTZ9MMFeFa/Kg0xWVfylOVNlJji/E7yTZWFd/q9GO5TxDLq156D7g==", + "license": "MIT", "engines": { "node": ">= 4" } @@ -5166,22 +5277,24 @@ } }, "node_modules/glob": { - "version": "10.3.10", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.3.10.tgz", - "integrity": "sha512-fa46+tv1Ak0UPK1TOy/pZrIybNNt4HCv7SDzwyfiOZkvZLEbjsZkJBPtDHVshZjbecAoAGSC20MjLDG/qr679g==", + "version": "10.4.2", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.2.tgz", + "integrity": "sha512-GwMlUF6PkPo3Gk21UxkCohOv0PLcIXVtKyLlpEI28R/cO/4eNOdmLk3CMW1wROV/WR/EsZOWAfBbBOqYvs88/w==", "dev": true, + "license": "ISC", "dependencies": { "foreground-child": "^3.1.0", - "jackspeak": "^2.3.5", - "minimatch": "^9.0.1", - "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0", - "path-scurry": "^1.10.1" + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" }, "bin": { "glob": "dist/esm/bin.mjs" }, "engines": { - "node": ">=16 || 14 >=14.17" + "node": ">=16 || 14 >=14.18" }, "funding": { "url": "https://github.com/sponsors/isaacs" @@ -5205,6 +5318,22 @@ "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==", "dev": true }, + "node_modules/glob/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/globals": { "version": "13.24.0", "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", @@ -5339,15 +5468,11 @@ "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", "dev": true }, - "node_modules/http": { - "version": "0.0.1-security", - "resolved": "https://registry.npmjs.org/http/-/http-0.0.1-security.tgz", - "integrity": "sha512-RnDvP10Ty9FxqOtPZuxtebw1j4L/WiqNMDtuc1YMH1XQm5TgDRaR1G9u8upL6KD1bXHSp9eSXo/ED+8Q7FAr+g==" - }, "node_modules/http-errors": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "license": "MIT", "dependencies": { "depd": "2.0.0", "inherits": "2.0.4", @@ -5493,19 +5618,11 @@ "node": ">=12.0.0" } }, - "node_modules/interpret": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.4.0.tgz", - "integrity": "sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA==", - "dev": true, - "engines": { - "node": ">= 0.10" - } - }, "node_modules/ipaddr.js": { "version": "1.9.1", "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "license": "MIT", "engines": { "node": ">= 0.10" } @@ -5724,16 +5841,14 @@ } }, "node_modules/jackspeak": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-2.3.6.tgz", - "integrity": "sha512-N3yCS/NegsOBokc8GAdM8UcmfsKiSS8cipheD/nivzr700H+nsMOxJjQnvwOcRYVuFkdH0wGUvW2WbXGmrZGbQ==", + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", "dev": true, + "license": "BlueOak-1.0.0", "dependencies": { "@isaacs/cliui": "^8.0.2" }, - "engines": { - "node": ">=14" - }, "funding": { "url": "https://github.com/sponsors/isaacs" }, @@ -6702,9 +6817,13 @@ } }, "node_modules/merge-descriptors": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", - "integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==" + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", + "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } }, "node_modules/merge-stream": { "version": "2.0.0", @@ -6730,12 +6849,13 @@ } }, "node_modules/micromatch": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", - "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", "dev": true, + "license": "MIT", "dependencies": { - "braces": "^3.0.2", + "braces": "^3.0.3", "picomatch": "^2.3.1" }, "engines": { @@ -6758,6 +6878,7 @@ "version": "1.6.0", "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "license": "MIT", "bin": { "mime": "cli.js" }, @@ -6817,10 +6938,11 @@ } }, "node_modules/minipass": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.1.tgz", - "integrity": "sha512-UZ7eQ+h8ywIRAW1hIEl2AqdwzJucU/Kp59+8kkZeSvafXhZjul247BvIJjEVFVeON6d7lM46XX1HXCduKAS8VA==", + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", "dev": true, + "license": "ISC", "engines": { "node": ">=16 || 14 >=14.17" } @@ -6954,7 +7076,8 @@ "node_modules/notepack.io": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/notepack.io/-/notepack.io-3.0.1.tgz", - "integrity": "sha512-TKC/8zH5pXIAMVQio2TvVDTtPRX+DJPHDqjRbxogtFiByHyzKmy96RA0JtCQJ+WouyyL4A10xomQzgbUT+1jCg==" + "integrity": "sha512-TKC/8zH5pXIAMVQio2TvVDTtPRX+DJPHDqjRbxogtFiByHyzKmy96RA0JtCQJ+WouyyL4A10xomQzgbUT+1jCg==", + "license": "MIT" }, "node_modules/npm-run-path": { "version": "4.0.1", @@ -6988,6 +7111,7 @@ "version": "2.4.1", "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "license": "MIT", "dependencies": { "ee-first": "1.1.1" }, @@ -7116,6 +7240,13 @@ "node": ">=6" } }, + "node_modules/package-json-from-dist": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.0.tgz", + "integrity": "sha512-dATvCeZN/8wQsGywez1mzHtTlP22H8OEfPrVMLNr4/eGa+ijtLn/6M5f0dY8UKNrC2O9UCU6SSoG3qRKnt7STw==", + "dev": true, + "license": "BlueOak-1.0.0" + }, "node_modules/parent-module": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", @@ -7150,6 +7281,7 @@ "version": "1.3.3", "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "license": "MIT", "engines": { "node": ">= 0.8" } @@ -7188,34 +7320,34 @@ "dev": true }, "node_modules/path-scurry": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.0.tgz", - "integrity": "sha512-LNHTaVkzaYaLGlO+0u3rQTz7QrHTFOuKyba9JMTQutkmtNew8dw8wOD7mTU/5fCPZzCWpfW0XnQKzY61P0aTaw==", + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", "dev": true, + "license": "BlueOak-1.0.0", "dependencies": { "lru-cache": "^10.2.0", "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" }, "engines": { - "node": ">=16 || 14 >=14.17" + "node": ">=16 || 14 >=14.18" }, "funding": { "url": "https://github.com/sponsors/isaacs" } }, "node_modules/path-scurry/node_modules/lru-cache": { - "version": "10.2.2", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.2.2.tgz", - "integrity": "sha512-9hp3Vp2/hFQUiIwKo8XCeFVnrg8Pk3TYNPIR7tJADKi5YfcF7vEaK7avFHTlSy3kOKYaJQaalfEo6YuXdceBOQ==", + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", "dev": true, - "engines": { - "node": "14 || >=16.14" - } + "license": "ISC" }, "node_modules/path-to-regexp": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-3.2.0.tgz", - "integrity": "sha512-jczvQbCUS7XmS7o+y1aEO9OBVFeZBQ1MDSEqmO7xSoPgOPoowY/SxLpZ6Vh97/8qHZOteiCKb7gkG9gA2ZUxJA==" + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-3.3.0.tgz", + "integrity": "sha512-qyCH421YQPS2WFDxDjftfc1ZR5WKQzVzqsp4n9M2kQhVOo/ByahFoUNJfl58kOcEGfQ//7weFTDhm+ss8Ecxgw==", + "license": "MIT" }, "node_modules/path-type": { "version": "4.0.0", @@ -7415,6 +7547,7 @@ "version": "2.0.7", "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "license": "MIT", "dependencies": { "forwarded": "0.2.0", "ipaddr.js": "1.9.1" @@ -7449,11 +7582,12 @@ ] }, "node_modules/qs": { - "version": "6.11.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", - "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", + "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", + "license": "BSD-3-Clause", "dependencies": { - "side-channel": "^1.0.4" + "side-channel": "^1.0.6" }, "engines": { "node": ">=0.6" @@ -7500,6 +7634,7 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "license": "MIT", "engines": { "node": ">= 0.6" } @@ -7508,6 +7643,7 @@ "version": "2.5.2", "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", + "license": "MIT", "dependencies": { "bytes": "3.1.2", "http-errors": "2.0.0", @@ -7567,32 +7703,21 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, - "node_modules/rechoir": { - "version": "0.6.2", - "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.6.2.tgz", - "integrity": "sha512-HFM8rkZ+i3zrV+4LQjwQ0W+ez98pApMGM3HUrN04j3CqzPOzl9nmP15Y8YXNm8QHGv/eacOVEjqhmWpkRV0NAw==", - "dev": true, - "dependencies": { - "resolve": "^1.1.6" - }, - "engines": { - "node": ">= 0.10" - } - }, "node_modules/redis": { - "version": "4.6.13", - "resolved": "https://registry.npmjs.org/redis/-/redis-4.6.13.tgz", - "integrity": "sha512-MHgkS4B+sPjCXpf+HfdetBwbRz6vCtsceTmw1pHNYJAsYxrfpOP6dz+piJWGos8wqG7qb3vj/Rrc5qOlmInUuA==", + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/redis/-/redis-4.7.0.tgz", + "integrity": "sha512-zvmkHEAdGMn+hMRXuMBtu4Vo5P6rHQjLoHftu+lBqq8ZTA3RCVC/WzD790bkKKiNFp7d5/9PcSD19fJyyRvOdQ==", + "license": "MIT", "workspaces": [ "./packages/*" ], "dependencies": { "@redis/bloom": "1.2.0", - "@redis/client": "1.5.14", + "@redis/client": "1.6.0", "@redis/graph": "1.1.1", - "@redis/json": "1.0.6", - "@redis/search": "1.1.6", - "@redis/time-series": "1.0.5" + "@redis/json": "1.0.7", + "@redis/search": "1.2.0", + "@redis/time-series": "1.1.0" } }, "node_modules/reflect-metadata": { @@ -7707,74 +7832,14 @@ "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", "dev": true }, - "node_modules/reusify": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", - "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", - "dev": true, - "engines": { - "iojs": ">=1.0.0", - "node": ">=0.10.0" - } - }, - "node_modules/rimraf": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-4.4.1.tgz", - "integrity": "sha512-Gk8NlF062+T9CqNGn6h4tls3k6T1+/nXdOcSZVikNVtlRdYpA7wRJJMoXmuvOnLW844rPjdQ7JgXCYM6PPC/og==", - "dev": true, - "dependencies": { - "glob": "^9.2.0" - }, - "bin": { - "rimraf": "dist/cjs/src/bin.js" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/rimraf/node_modules/glob": { - "version": "9.3.5", - "resolved": "https://registry.npmjs.org/glob/-/glob-9.3.5.tgz", - "integrity": "sha512-e1LleDykUz2Iu+MTYdkSsuWX8lvAjAcs0Xef0lNIu0S2wOAzuTxCJtcd9S3cijlwYF18EsU3rzb8jPVobxDh9Q==", - "dev": true, - "dependencies": { - "fs.realpath": "^1.0.0", - "minimatch": "^8.0.2", - "minipass": "^4.2.4", - "path-scurry": "^1.6.1" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/rimraf/node_modules/minimatch": { - "version": "8.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-8.0.4.tgz", - "integrity": "sha512-W0Wvr9HyFXZRGIDgCicunpQ299OKXs9RgZfaukz4qAW/pJhcpUfupc9c+OObPOFueNy8VSrZgEmDtk6Kh4WzDA==", - "dev": true, - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/rimraf/node_modules/minipass": { - "version": "4.2.8", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-4.2.8.tgz", - "integrity": "sha512-fNzuVyifolSLFL4NzpF+wEF4qrgqaaKX0haXPQEdQ7NKAN+WecoKMHV09YcuL/DHxrUsYQOK3MiuDf7Ip2OXfQ==", + "node_modules/reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", "dev": true, "engines": { - "node": ">=8" + "iojs": ">=1.0.0", + "node": ">=0.10.0" } }, "node_modules/run-async": { @@ -7912,9 +7977,10 @@ } }, "node_modules/send": { - "version": "0.18.0", - "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", - "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz", + "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==", + "license": "MIT", "dependencies": { "debug": "2.6.9", "depd": "2.0.0", @@ -7938,6 +8004,7 @@ "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", "dependencies": { "ms": "2.0.0" } @@ -7945,12 +8012,23 @@ "node_modules/send/node_modules/debug/node_modules/ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" + }, + "node_modules/send/node_modules/encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } }, "node_modules/send/node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" }, "node_modules/serialize-javascript": { "version": "6.0.2", @@ -7962,14 +8040,15 @@ } }, "node_modules/serve-static": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz", - "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==", + "version": "1.16.2", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz", + "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==", + "license": "MIT", "dependencies": { - "encodeurl": "~1.0.2", + "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "parseurl": "~1.3.3", - "send": "0.18.0" + "send": "0.19.0" }, "engines": { "node": ">= 0.8.0" @@ -7994,7 +8073,8 @@ "node_modules/setprototypeof": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", - "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "license": "ISC" }, "node_modules/shebang-command": { "version": "2.0.0", @@ -8017,65 +8097,6 @@ "node": ">=8" } }, - "node_modules/shelljs": { - "version": "0.8.5", - "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.8.5.tgz", - "integrity": "sha512-TiwcRcrkhHvbrZbnRcFYMLl30Dfov3HKqzp5tO5b4pt6G/SezKcYhmDg15zXVBswHmctSAQKznqNW2LO5tTDow==", - "dev": true, - "dependencies": { - "glob": "^7.0.0", - "interpret": "^1.0.0", - "rechoir": "^0.6.2" - }, - "bin": { - "shjs": "bin/shjs" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/shelljs/node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/shelljs/node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "dev": true, - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/shelljs/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, "node_modules/side-channel": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz", @@ -8098,6 +8119,7 @@ "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", "dev": true, + "license": "ISC", "engines": { "node": ">=14" }, @@ -8241,6 +8263,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "license": "MIT", "engines": { "node": ">= 0.8" } @@ -8299,6 +8322,7 @@ "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", "dev": true, + "license": "MIT", "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", @@ -8326,6 +8350,7 @@ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", "dev": true, + "license": "MIT", "dependencies": { "ansi-regex": "^5.0.1" }, @@ -8438,6 +8463,7 @@ "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-4.0.0.tgz", "integrity": "sha512-b19dMThMV4HVFynSAM1++gBHAbk2Tc/osgLIBZMKsyqh34jb2e8Os7T6ZW/Bt3pJFdBTd2JwAnAAEQV7rSNvcQ==", "dev": true, + "license": "MIT", "engines": { "node": ">=0.10" } @@ -8671,6 +8697,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "license": "MIT", "engines": { "node": ">=0.6" } @@ -8909,10 +8936,11 @@ "integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==" }, "node_modules/typescript": { - "version": "5.4.5", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.5.tgz", - "integrity": "sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ==", + "version": "5.6.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.6.2.tgz", + "integrity": "sha512-NW8ByodCSNCwZeghjN3o+JX5OFH0Ojg6sadjEKY4huZ52TqbJTJnDo5+Tw98lSy63NZvi4n+ez5m2u5d4PkZyw==", "dev": true, + "license": "Apache-2.0", "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -8936,14 +8964,16 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/uid2/-/uid2-1.0.0.tgz", "integrity": "sha512-+I6aJUv63YAcY9n4mQreLUt0d4lvwkkopDNmpomkAUz0fAkEMV9pRWxN0EjhW1YfRhcuyHg2v3mwddCDW1+LFQ==", + "license": "MIT", "engines": { "node": ">= 4.0.0" } }, "node_modules/undici-types": { - "version": "5.26.5", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", - "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==" + "version": "6.19.8", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz", + "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==", + "license": "MIT" }, "node_modules/universalify": { "version": "2.0.1", @@ -8958,6 +8988,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "license": "MIT", "engines": { "node": ">= 0.8" } @@ -9019,6 +9050,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", + "license": "MIT", "engines": { "node": ">= 0.4.0" } @@ -9100,22 +9132,21 @@ "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" }, "node_modules/webpack": { - "version": "5.91.0", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.91.0.tgz", - "integrity": "sha512-rzVwlLeBWHJbmgTC/8TvAcu5vpJNII+MelQpylD4jNERPwpBJOE2lEcko1zJX3QJeLjTTAnQxn/OJ8bjDzVQaw==", + "version": "5.94.0", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.94.0.tgz", + "integrity": "sha512-KcsGn50VT+06JH/iunZJedYGUJS5FGjow8wb9c0v5n1Om8O1g4L6LjtfxwlXIATopoQu+vOXXa7gYisWxCoPyg==", "dev": true, - "peer": true, + "license": "MIT", "dependencies": { - "@types/eslint-scope": "^3.7.3", "@types/estree": "^1.0.5", "@webassemblyjs/ast": "^1.12.1", "@webassemblyjs/wasm-edit": "^1.12.1", "@webassemblyjs/wasm-parser": "^1.12.1", "acorn": "^8.7.1", - "acorn-import-assertions": "^1.9.0", + "acorn-import-attributes": "^1.9.5", "browserslist": "^4.21.10", "chrome-trace-event": "^1.0.2", - "enhanced-resolve": "^5.16.0", + "enhanced-resolve": "^5.17.1", "es-module-lexer": "^1.2.1", "eslint-scope": "5.1.1", "events": "^3.2.0", @@ -9170,7 +9201,6 @@ "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", "dev": true, - "peer": true, "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^4.1.1" @@ -9184,7 +9214,6 @@ "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", "dev": true, - "peer": true, "engines": { "node": ">=4.0" } @@ -9315,6 +9344,7 @@ "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", "dev": true, + "license": "MIT", "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", @@ -9511,19 +9541,46 @@ } }, "@angular-devkit/schematics-cli": { - "version": "17.1.2", - "resolved": "https://registry.npmjs.org/@angular-devkit/schematics-cli/-/schematics-cli-17.1.2.tgz", - "integrity": "sha512-bvXykYzSST05qFdlgIzUguNOb3z0hCa8HaTwtqdmQo9aFPf+P+/AC56I64t1iTchMjQtf3JrBQhYM25gUdcGbg==", + "version": "17.3.8", + "resolved": "https://registry.npmjs.org/@angular-devkit/schematics-cli/-/schematics-cli-17.3.8.tgz", + "integrity": "sha512-TjmiwWJarX7oqvNiRAroQ5/LeKUatxBOCNEuKXO/PV8e7pn/Hr/BqfFm+UcYrQoFdZplmtNAfqmbqgVziKvCpA==", "dev": true, "requires": { - "@angular-devkit/core": "17.1.2", - "@angular-devkit/schematics": "17.1.2", + "@angular-devkit/core": "17.3.8", + "@angular-devkit/schematics": "17.3.8", "ansi-colors": "4.1.3", - "inquirer": "9.2.12", + "inquirer": "9.2.15", "symbol-observable": "4.0.0", "yargs-parser": "21.1.1" }, "dependencies": { + "@angular-devkit/core": { + "version": "17.3.8", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-17.3.8.tgz", + "integrity": "sha512-Q8q0voCGudbdCgJ7lXdnyaxKHbNQBARH68zPQV72WT8NWy+Gw/tys870i6L58NWbBaCJEUcIj/kb6KoakSRu+Q==", + "dev": true, + "requires": { + "ajv": "8.12.0", + "ajv-formats": "2.1.1", + "jsonc-parser": "3.2.1", + "picomatch": "4.0.1", + "rxjs": "7.8.1", + "source-map": "0.7.4" + } + }, + "@angular-devkit/schematics": { + "version": "17.3.8", + "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-17.3.8.tgz", + "integrity": "sha512-QRVEYpIfgkprNHc916JlPuNbLzOgrm9DZalHasnLUz4P6g7pR21olb8YCyM2OTJjombNhya9ZpckcADU5Qyvlg==", + "dev": true, + "requires": { + "@angular-devkit/core": "17.3.8", + "jsonc-parser": "3.2.1", + "magic-string": "0.30.8", + "ora": "5.4.1", + "rxjs": "7.8.1" + } + }, "chalk": { "version": "5.3.0", "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz", @@ -9536,35 +9593,19 @@ "integrity": "sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ==", "dev": true }, - "escape-string-regexp": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz", - "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==", - "dev": true - }, - "figures": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/figures/-/figures-5.0.0.tgz", - "integrity": "sha512-ej8ksPF4x6e5wvK9yevct0UCXh8TTFlWGVLlgjZuoBH1HwjIfKE/IdL5mq89sFA7zELi1VhKpmtDnrs7zWyeyg==", - "dev": true, - "requires": { - "escape-string-regexp": "^5.0.0", - "is-unicode-supported": "^1.2.0" - } - }, "inquirer": { - "version": "9.2.12", - "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-9.2.12.tgz", - "integrity": "sha512-mg3Fh9g2zfuVWJn6lhST0O7x4n03k7G8Tx5nvikJkbq8/CK47WDVm+UznF0G6s5Zi0KcyUisr6DU8T67N5U+1Q==", + "version": "9.2.15", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-9.2.15.tgz", + "integrity": "sha512-vI2w4zl/mDluHt9YEQ/543VTCwPKWiHzKtm9dM2V0NdFcqEexDAjUHzO1oA60HRNaVifGXXM1tRRNluLVHa0Kg==", "dev": true, "requires": { - "@ljharb/through": "^2.3.11", + "@ljharb/through": "^2.3.12", "ansi-escapes": "^4.3.2", "chalk": "^5.3.0", "cli-cursor": "^3.1.0", "cli-width": "^4.1.0", "external-editor": "^3.1.0", - "figures": "^5.0.0", + "figures": "^3.2.0", "lodash": "^4.17.21", "mute-stream": "1.0.0", "ora": "^5.4.1", @@ -9575,18 +9616,33 @@ "wrap-ansi": "^6.2.0" } }, - "is-unicode-supported": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-1.3.0.tgz", - "integrity": "sha512-43r2mRvz+8JRIKnWJ+3j8JtjRKZ6GmjzfaE/qiBJnikNnYv/6bagRJ1kUhNk8R5EX/GkobD+r+sfxCPJsiKBLQ==", + "jsonc-parser": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.2.1.tgz", + "integrity": "sha512-AilxAyFOAcK5wA1+LeaySVBrHsGQvUFCDWXKpZjzaL0PqW+xfBOttn8GNtWKFWqneyMZj41MWF9Kl6iPWLwgOA==", "dev": true }, + "magic-string": { + "version": "0.30.8", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.8.tgz", + "integrity": "sha512-ISQTe55T2ao7XtlAStud6qwYPZjE4GK1S/BeVPus4jrq6JuOnQ00YKQC581RWhR122W7msZV263KzVeLoqidyQ==", + "dev": true, + "requires": { + "@jridgewell/sourcemap-codec": "^1.4.15" + } + }, "mute-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-1.0.0.tgz", "integrity": "sha512-avsJQhyd+680gKXyG/sQc0nXaC6rBkPOfyHYcFb9+hdkqQkR9bdnkJ0AMZhke0oesPqIO+mFFJ+IdBc7mst4IA==", "dev": true }, + "picomatch": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.1.tgz", + "integrity": "sha512-xUXwsxNjwTQ8K3GnT4pCJm+xq3RUPQbmkYJTP5aFIfNIvbcc/4MUxgBaaRSZJ6yGJZiGSyYlM6MzwTsRk8SYCg==", + "dev": true + }, "run-async": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/run-async/-/run-async-3.0.0.tgz", @@ -10206,9 +10262,9 @@ }, "dependencies": { "ansi-regex": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", - "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", + "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", "dev": true }, "ansi-styles": { @@ -10658,49 +10714,78 @@ "integrity": "sha512-Z7C/xXCiGWsg0KuKsHTKJxbWhpI3Vs5GwLfOean7MGyVFGqdRgBbAjOCh6u4bbjPc/8MJ2pZmK/0DLdCbivLDA==" }, "@nestjs/cli": { - "version": "10.3.2", - "resolved": "https://registry.npmjs.org/@nestjs/cli/-/cli-10.3.2.tgz", - "integrity": "sha512-aWmD1GLluWrbuC4a1Iz/XBk5p74Uj6nIVZj6Ov03JbTfgtWqGFLtXuMetvzMiHxfrHehx/myt2iKAPRhKdZvTg==", + "version": "10.4.5", + "resolved": "https://registry.npmjs.org/@nestjs/cli/-/cli-10.4.5.tgz", + "integrity": "sha512-FP7Rh13u8aJbHe+zZ7hM0CC4785g9Pw4lz4r2TTgRtf0zTxSWMkJaPEwyjX8SK9oWK2GsYxl+fKpwVZNbmnj9A==", "dev": true, "requires": { - "@angular-devkit/core": "17.1.2", - "@angular-devkit/schematics": "17.1.2", - "@angular-devkit/schematics-cli": "17.1.2", + "@angular-devkit/core": "17.3.8", + "@angular-devkit/schematics": "17.3.8", + "@angular-devkit/schematics-cli": "17.3.8", "@nestjs/schematics": "^10.0.1", "chalk": "4.1.2", "chokidar": "3.6.0", - "cli-table3": "0.6.3", + "cli-table3": "0.6.5", "commander": "4.1.1", "fork-ts-checker-webpack-plugin": "9.0.2", - "glob": "10.3.10", + "glob": "10.4.2", "inquirer": "8.2.6", "node-emoji": "1.11.0", "ora": "5.4.1", - "rimraf": "4.4.1", - "shelljs": "0.8.5", - "source-map-support": "0.5.21", "tree-kill": "1.2.2", "tsconfig-paths": "4.2.0", "tsconfig-paths-webpack-plugin": "4.1.0", "typescript": "5.3.3", - "webpack": "5.90.1", + "webpack": "5.94.0", "webpack-node-externals": "3.0.0" }, "dependencies": { - "eslint-scope": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", - "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "@angular-devkit/core": { + "version": "17.3.8", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-17.3.8.tgz", + "integrity": "sha512-Q8q0voCGudbdCgJ7lXdnyaxKHbNQBARH68zPQV72WT8NWy+Gw/tys870i6L58NWbBaCJEUcIj/kb6KoakSRu+Q==", "dev": true, "requires": { - "esrecurse": "^4.3.0", - "estraverse": "^4.1.1" + "ajv": "8.12.0", + "ajv-formats": "2.1.1", + "jsonc-parser": "3.2.1", + "picomatch": "4.0.1", + "rxjs": "7.8.1", + "source-map": "0.7.4" } }, - "estraverse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", - "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "@angular-devkit/schematics": { + "version": "17.3.8", + "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-17.3.8.tgz", + "integrity": "sha512-QRVEYpIfgkprNHc916JlPuNbLzOgrm9DZalHasnLUz4P6g7pR21olb8YCyM2OTJjombNhya9ZpckcADU5Qyvlg==", + "dev": true, + "requires": { + "@angular-devkit/core": "17.3.8", + "jsonc-parser": "3.2.1", + "magic-string": "0.30.8", + "ora": "5.4.1", + "rxjs": "7.8.1" + } + }, + "jsonc-parser": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.2.1.tgz", + "integrity": "sha512-AilxAyFOAcK5wA1+LeaySVBrHsGQvUFCDWXKpZjzaL0PqW+xfBOttn8GNtWKFWqneyMZj41MWF9Kl6iPWLwgOA==", + "dev": true + }, + "magic-string": { + "version": "0.30.8", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.8.tgz", + "integrity": "sha512-ISQTe55T2ao7XtlAStud6qwYPZjE4GK1S/BeVPus4jrq6JuOnQ00YKQC581RWhR122W7msZV263KzVeLoqidyQ==", + "dev": true, + "requires": { + "@jridgewell/sourcemap-codec": "^1.4.15" + } + }, + "picomatch": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.1.tgz", + "integrity": "sha512-xUXwsxNjwTQ8K3GnT4pCJm+xq3RUPQbmkYJTP5aFIfNIvbcc/4MUxgBaaRSZJ6yGJZiGSyYlM6MzwTsRk8SYCg==", "dev": true }, "typescript": { @@ -10708,38 +10793,6 @@ "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.3.3.tgz", "integrity": "sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw==", "dev": true - }, - "webpack": { - "version": "5.90.1", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.90.1.tgz", - "integrity": "sha512-SstPdlAC5IvgFnhiRok8hqJo/+ArAbNv7rhU4fnWGHNVfN59HSQFaxZDSAL3IFG2YmqxuRs+IU33milSxbPlog==", - "dev": true, - "requires": { - "@types/eslint-scope": "^3.7.3", - "@types/estree": "^1.0.5", - "@webassemblyjs/ast": "^1.11.5", - "@webassemblyjs/wasm-edit": "^1.11.5", - "@webassemblyjs/wasm-parser": "^1.11.5", - "acorn": "^8.7.1", - "acorn-import-assertions": "^1.9.0", - "browserslist": "^4.21.10", - "chrome-trace-event": "^1.0.2", - "enhanced-resolve": "^5.15.0", - "es-module-lexer": "^1.2.1", - "eslint-scope": "5.1.1", - "events": "^3.2.0", - "glob-to-regexp": "^0.4.1", - "graceful-fs": "^4.2.9", - "json-parse-even-better-errors": "^2.3.1", - "loader-runner": "^4.2.0", - "mime-types": "^2.1.27", - "neo-async": "^2.6.2", - "schema-utils": "^3.2.0", - "tapable": "^2.1.1", - "terser-webpack-plugin": "^5.3.10", - "watchpack": "^2.4.0", - "webpack-sources": "^3.2.3" - } } } }, @@ -10765,16 +10818,23 @@ } }, "@nestjs/core": { - "version": "10.3.8", - "resolved": "https://registry.npmjs.org/@nestjs/core/-/core-10.3.8.tgz", - "integrity": "sha512-AxF4tpYLDNn5Wfb3C4bNaaHJ4pREH5FJrSisR2A5zkYpQFORFs0Tc36lOFPMwBTy8Iv2wUwWLUVc5ftBnxEv4w==", + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/@nestjs/core/-/core-10.4.3.tgz", + "integrity": "sha512-6OQz+5C8mT8yRtfvE5pPCq+p6w5jDot+oQku1KzQ24ABn+lay1KGuJwcKZhdVNuselx+8xhdMxknZTA8wrGLIg==", "requires": { "@nuxtjs/opencollective": "0.3.2", "fast-safe-stringify": "2.1.1", "iterare": "1.2.1", - "path-to-regexp": "3.2.0", - "tslib": "2.6.2", + "path-to-regexp": "3.3.0", + "tslib": "2.7.0", "uid": "2.0.2" + }, + "dependencies": { + "tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==" + } } }, "@nestjs/microservices": { @@ -10787,15 +10847,22 @@ } }, "@nestjs/platform-express": { - "version": "10.3.8", - "resolved": "https://registry.npmjs.org/@nestjs/platform-express/-/platform-express-10.3.8.tgz", - "integrity": "sha512-sifLoxgEJvAgbim1UuW6wyScMfkS9SVQRH+lN33N/9ZvZSjO6NSDLOe+wxqsnZkia+QrjFC0qy0ITRAsggfqbg==", + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/@nestjs/platform-express/-/platform-express-10.4.3.tgz", + "integrity": "sha512-ss7gkofVm3eO+1P9iRhmGq6Xcjg+mIN3dWisKJZYelSV+msb0QpJmqChLvWjLkWtlqDnx915FKUk0IzCa0TVzw==", "requires": { - "body-parser": "1.20.2", + "body-parser": "1.20.3", "cors": "2.8.5", - "express": "4.19.2", + "express": "4.21.0", "multer": "1.4.4-lts.1", - "tslib": "2.6.2" + "tslib": "2.7.0" + }, + "dependencies": { + "tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==" + } } }, "@nestjs/schematics": { @@ -10884,9 +10951,9 @@ "requires": {} }, "@redis/client": { - "version": "1.5.14", - "resolved": "https://registry.npmjs.org/@redis/client/-/client-1.5.14.tgz", - "integrity": "sha512-YGn0GqsRBFUQxklhY7v562VMOP0DcmlrHHs3IV1mFE3cbxe31IITUkqhBcIhVSI/2JqtWAJXg5mjV4aU+zD0HA==", + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@redis/client/-/client-1.6.0.tgz", + "integrity": "sha512-aR0uffYI700OEEH4gYnitAnv3vzVGXCFvYfdpu/CJKvk4pHfLPEy/JSZyrpQ+15WhXe1yJRXLtfQ84s4mEXnPg==", "requires": { "cluster-key-slot": "1.1.2", "generic-pool": "3.9.0", @@ -10907,21 +10974,21 @@ "requires": {} }, "@redis/json": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/@redis/json/-/json-1.0.6.tgz", - "integrity": "sha512-rcZO3bfQbm2zPRpqo82XbW8zg4G/w4W3tI7X8Mqleq9goQjAGLL7q/1n1ZX4dXEAmORVZ4s1+uKLaUOg7LrUhw==", + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/@redis/json/-/json-1.0.7.tgz", + "integrity": "sha512-6UyXfjVaTBTJtKNG4/9Z8PSpKE6XgSyEb8iwaqDcy+uKrd/DGYHTWkUdnQDyzm727V7p21WUMhsqz5oy65kPcQ==", "requires": {} }, "@redis/search": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/@redis/search/-/search-1.1.6.tgz", - "integrity": "sha512-mZXCxbTYKBQ3M2lZnEddwEAks0Kc7nauire8q20oA0oA/LoA+E/b5Y5KZn232ztPb1FkIGqo12vh3Lf+Vw5iTw==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@redis/search/-/search-1.2.0.tgz", + "integrity": "sha512-tYoDBbtqOVigEDMAcTGsRlMycIIjwMCgD8eR2t0NANeQmgK/lvxNAvYyb6bZDD4frHRhIHkJu2TBRvB0ERkOmw==", "requires": {} }, "@redis/time-series": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/@redis/time-series/-/time-series-1.0.5.tgz", - "integrity": "sha512-IFjIgTusQym2B5IZJG3XKr5llka7ey84fw/NOYqESP5WUfQs9zz1ww/9+qoz4ka/S6KcGBodzlCeZ5UImKbscg==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@redis/time-series/-/time-series-1.1.0.tgz", + "integrity": "sha512-c1Q99M5ljsIuc4YdaCwfUEXsofakb9c8+Zse2qxTadu8TalLXuAESzLvFAvNVbkmSlvlzIQOLpBCmWI9wTOt+g==", "requires": {} }, "@sinclair/typebox": { @@ -11071,21 +11138,13 @@ "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.56.10.tgz", "integrity": "sha512-Shavhk87gCtY2fhXDctcfS3e6FdxWkCx1iUZ9eEUbh7rTqlZT0/IzOkCOVt0fCjcFuZ9FPYfuezTBImfHCDBGQ==", "dev": true, + "optional": true, + "peer": true, "requires": { "@types/estree": "*", "@types/json-schema": "*" } }, - "@types/eslint-scope": { - "version": "3.7.7", - "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.7.tgz", - "integrity": "sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==", - "dev": true, - "requires": { - "@types/eslint": "*", - "@types/estree": "*" - } - }, "@types/estree": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", @@ -11190,11 +11249,11 @@ "dev": true }, "@types/node": { - "version": "20.12.11", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.12.11.tgz", - "integrity": "sha512-vDg9PZ/zi+Nqp6boSOT7plNuthRugEKixDv5sFTIpkE89MmNtEArAShI4mxuX2+UrLEe9pxC1vm2cjm9YlWbJw==", + "version": "20.16.5", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.16.5.tgz", + "integrity": "sha512-VwYCweNo3ERajwy0IUlqqcyZ8/A7Zwa9ZP3MnENWcB11AejO+tLy3pu850goUW2FC/IJMdZUfKpX/yxL1gymCA==", "requires": { - "undici-types": "~5.26.4" + "undici-types": "~6.19.2" } }, "@types/qs": { @@ -11564,10 +11623,10 @@ "integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==", "dev": true }, - "acorn-import-assertions": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/acorn-import-assertions/-/acorn-import-assertions-1.9.0.tgz", - "integrity": "sha512-cmMwop9x+8KFhxvKrKfPYmN6/pKTYYHBqLa0DfvVZcKMJWNyWLnaqND7dx/qn66R7ewM1UX5XMaDVP5wlVTaVA==", + "acorn-import-attributes": { + "version": "1.9.5", + "resolved": "https://registry.npmjs.org/acorn-import-attributes/-/acorn-import-attributes-1.9.5.tgz", + "integrity": "sha512-n02Vykv5uA3eHGM/Z2dQrcD56kL8TyDb2p1+0P83PClMnC/nc+anbQRhIOWnSq4Ke/KvDPrY3C9hDtC/A3eHnQ==", "dev": true, "requires": {} }, @@ -11893,9 +11952,9 @@ } }, "body-parser": { - "version": "1.20.2", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.2.tgz", - "integrity": "sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==", + "version": "1.20.3", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz", + "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==", "requires": { "bytes": "3.1.2", "content-type": "~1.0.5", @@ -11905,7 +11964,7 @@ "http-errors": "2.0.0", "iconv-lite": "0.4.24", "on-finished": "2.4.1", - "qs": "6.11.0", + "qs": "6.13.0", "raw-body": "2.5.2", "type-is": "~1.6.18", "unpipe": "1.0.0" @@ -12108,9 +12167,9 @@ "dev": true }, "cli-table3": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/cli-table3/-/cli-table3-0.6.3.tgz", - "integrity": "sha512-w5Jac5SykAeZJKntOxJCrm63Eg5/4dhMWIcuTbo9rpE+brgaSZo0RuNJZeOyMgsUdhDeojvgyQLmjI+K50ZGyg==", + "version": "0.6.5", + "resolved": "https://registry.npmjs.org/cli-table3/-/cli-table3-0.6.5.tgz", + "integrity": "sha512-+W/5efTR7y5HRD7gACw9yQjqMVvEMLBHmboM/kPWam+H+Hmyrgjh6YncVKK122YZkXrLudzTuAukUw9FnMf7IQ==", "dev": true, "requires": { "@colors/colors": "1.5.0", @@ -12533,9 +12592,9 @@ "peer": true }, "encodeurl": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", - "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==" + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==" }, "engine.io": { "version": "6.5.5", @@ -12567,9 +12626,9 @@ "integrity": "sha512-RcyUFKA93/CXH20l4SoVvzZfrSDMOTUS3bWVpTt2FuFP+XYrL8i8oonHP7WInRyVHXh0n/ORtoeiE1os+8qkSw==" }, "enhanced-resolve": { - "version": "5.16.1", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.16.1.tgz", - "integrity": "sha512-4U5pNsuDl0EhuZpq46M5xPslstkviJuhrdobaRDBk2Jy2KO37FDAJl4lb2KlNabxT0m4MTK2UHNrsAcphE8nyw==", + "version": "5.17.1", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.17.1.tgz", + "integrity": "sha512-LMHl3dXhTcfv8gM4kEzIUeTQ+7fpdA0l2tUf34BddXPkz2A5xJ5L/Pchd5BL6rdccM9QGvu0sWZzK1Z1t4wwyg==", "dev": true, "requires": { "graceful-fs": "^4.2.4", @@ -12851,36 +12910,36 @@ } }, "express": { - "version": "4.19.2", - "resolved": "https://registry.npmjs.org/express/-/express-4.19.2.tgz", - "integrity": "sha512-5T6nhjsT+EOMzuck8JjBHARTHfMht0POzlA60WV2pMD3gyXw2LZnZ+ueGdNxG+0calOJcWKbpFcuzLZ91YWq9Q==", + "version": "4.21.0", + "resolved": "https://registry.npmjs.org/express/-/express-4.21.0.tgz", + "integrity": "sha512-VqcNGcj/Id5ZT1LZ/cfihi3ttTn+NJmkli2eZADigjq29qTlWi/hAQ43t/VLPq8+UX06FCEx3ByOYet6ZFblng==", "requires": { "accepts": "~1.3.8", "array-flatten": "1.1.1", - "body-parser": "1.20.2", + "body-parser": "1.20.3", "content-disposition": "0.5.4", "content-type": "~1.0.4", "cookie": "0.6.0", "cookie-signature": "1.0.6", "debug": "2.6.9", "depd": "2.0.0", - "encodeurl": "~1.0.2", + "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "etag": "~1.8.1", - "finalhandler": "1.2.0", + "finalhandler": "1.3.1", "fresh": "0.5.2", "http-errors": "2.0.0", - "merge-descriptors": "1.0.1", + "merge-descriptors": "1.0.3", "methods": "~1.1.2", "on-finished": "2.4.1", "parseurl": "~1.3.3", - "path-to-regexp": "0.1.7", + "path-to-regexp": "0.1.10", "proxy-addr": "~2.0.7", - "qs": "6.11.0", + "qs": "6.13.0", "range-parser": "~1.2.1", "safe-buffer": "5.2.1", - "send": "0.18.0", - "serve-static": "1.15.0", + "send": "0.19.0", + "serve-static": "1.16.2", "setprototypeof": "1.2.0", "statuses": "2.0.1", "type-is": "~1.6.18", @@ -12902,9 +12961,9 @@ "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" }, "path-to-regexp": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", - "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==" + "version": "0.1.10", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.10.tgz", + "integrity": "sha512-7lf7qcQidTku0Gu3YDPc8DJ1q7OOucfa/BSsIwjuh56VU7katFvuM8hULfkwB3Fns/rsVF7PwPKVw1sl5KQS9w==" } } }, @@ -13021,12 +13080,12 @@ } }, "finalhandler": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz", - "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==", + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz", + "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==", "requires": { "debug": "2.6.9", - "encodeurl": "~1.0.2", + "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "on-finished": "2.4.1", "parseurl": "~1.3.3", @@ -13127,9 +13186,9 @@ "peer": true }, "foreground-child": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.1.1.tgz", - "integrity": "sha512-TMKDUnIte6bfb5nWv7V/caI169OHgvwjb7V4WkeUvbQQdjr5rWKqHFiKWb/fcOwB+CzBT+qbWjvj+DVwRskpIg==", + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.0.tgz", + "integrity": "sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg==", "dev": true, "requires": { "cross-spawn": "^7.0.0", @@ -13287,16 +13346,28 @@ "dev": true }, "glob": { - "version": "10.3.10", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.3.10.tgz", - "integrity": "sha512-fa46+tv1Ak0UPK1TOy/pZrIybNNt4HCv7SDzwyfiOZkvZLEbjsZkJBPtDHVshZjbecAoAGSC20MjLDG/qr679g==", + "version": "10.4.2", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.2.tgz", + "integrity": "sha512-GwMlUF6PkPo3Gk21UxkCohOv0PLcIXVtKyLlpEI28R/cO/4eNOdmLk3CMW1wROV/WR/EsZOWAfBbBOqYvs88/w==", "dev": true, "requires": { "foreground-child": "^3.1.0", - "jackspeak": "^2.3.5", - "minimatch": "^9.0.1", - "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0", - "path-scurry": "^1.10.1" + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "dependencies": { + "minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "requires": { + "brace-expansion": "^2.0.1" + } + } } }, "glob-parent": { @@ -13406,11 +13477,6 @@ "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", "dev": true }, - "http": { - "version": "0.0.1-security", - "resolved": "https://registry.npmjs.org/http/-/http-0.0.1-security.tgz", - "integrity": "sha512-RnDvP10Ty9FxqOtPZuxtebw1j4L/WiqNMDtuc1YMH1XQm5TgDRaR1G9u8upL6KD1bXHSp9eSXo/ED+8Q7FAr+g==" - }, "http-errors": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", @@ -13513,12 +13579,6 @@ "wrap-ansi": "^6.0.1" } }, - "interpret": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.4.0.tgz", - "integrity": "sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA==", - "dev": true - }, "ipaddr.js": { "version": "1.9.1", "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", @@ -13680,9 +13740,9 @@ "integrity": "sha512-RKYVTCjAnRthyJes037NX/IiqeidgN1xc3j1RjFfECFp28A1GVwK9nA+i0rJPaHqSZwygLzRnFlzUuHFoWWy+Q==" }, "jackspeak": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-2.3.6.tgz", - "integrity": "sha512-N3yCS/NegsOBokc8GAdM8UcmfsKiSS8cipheD/nivzr700H+nsMOxJjQnvwOcRYVuFkdH0wGUvW2WbXGmrZGbQ==", + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", "dev": true, "requires": { "@isaacs/cliui": "^8.0.2", @@ -14442,9 +14502,9 @@ } }, "merge-descriptors": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", - "integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==" + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", + "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==" }, "merge-stream": { "version": "2.0.0", @@ -14464,12 +14524,12 @@ "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==" }, "micromatch": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", - "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", "dev": true, "requires": { - "braces": "^3.0.2", + "braces": "^3.0.3", "picomatch": "^2.3.1" }, "dependencies": { @@ -14520,9 +14580,9 @@ "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==" }, "minipass": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.1.tgz", - "integrity": "sha512-UZ7eQ+h8ywIRAW1hIEl2AqdwzJucU/Kp59+8kkZeSvafXhZjul247BvIJjEVFVeON6d7lM46XX1HXCduKAS8VA==", + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", "dev": true }, "mkdirp": { @@ -14744,6 +14804,12 @@ "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", "dev": true }, + "package-json-from-dist": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.0.tgz", + "integrity": "sha512-dATvCeZN/8wQsGywez1mzHtTlP22H8OEfPrVMLNr4/eGa+ijtLn/6M5f0dY8UKNrC2O9UCU6SSoG3qRKnt7STw==", + "dev": true + }, "parent-module": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", @@ -14795,9 +14861,9 @@ "dev": true }, "path-scurry": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.0.tgz", - "integrity": "sha512-LNHTaVkzaYaLGlO+0u3rQTz7QrHTFOuKyba9JMTQutkmtNew8dw8wOD7mTU/5fCPZzCWpfW0XnQKzY61P0aTaw==", + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", "dev": true, "requires": { "lru-cache": "^10.2.0", @@ -14805,17 +14871,17 @@ }, "dependencies": { "lru-cache": { - "version": "10.2.2", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.2.2.tgz", - "integrity": "sha512-9hp3Vp2/hFQUiIwKo8XCeFVnrg8Pk3TYNPIR7tJADKi5YfcF7vEaK7avFHTlSy3kOKYaJQaalfEo6YuXdceBOQ==", + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", "dev": true } } }, "path-to-regexp": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-3.2.0.tgz", - "integrity": "sha512-jczvQbCUS7XmS7o+y1aEO9OBVFeZBQ1MDSEqmO7xSoPgOPoowY/SxLpZ6Vh97/8qHZOteiCKb7gkG9gA2ZUxJA==" + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-3.3.0.tgz", + "integrity": "sha512-qyCH421YQPS2WFDxDjftfc1ZR5WKQzVzqsp4n9M2kQhVOo/ByahFoUNJfl58kOcEGfQ//7weFTDhm+ss8Ecxgw==" }, "path-type": { "version": "4.0.0", @@ -14977,11 +15043,11 @@ "dev": true }, "qs": { - "version": "6.11.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", - "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", + "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", "requires": { - "side-channel": "^1.0.4" + "side-channel": "^1.0.6" } }, "querystringify": { @@ -15064,26 +15130,17 @@ } } }, - "rechoir": { - "version": "0.6.2", - "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.6.2.tgz", - "integrity": "sha512-HFM8rkZ+i3zrV+4LQjwQ0W+ez98pApMGM3HUrN04j3CqzPOzl9nmP15Y8YXNm8QHGv/eacOVEjqhmWpkRV0NAw==", - "dev": true, - "requires": { - "resolve": "^1.1.6" - } - }, "redis": { - "version": "4.6.13", - "resolved": "https://registry.npmjs.org/redis/-/redis-4.6.13.tgz", - "integrity": "sha512-MHgkS4B+sPjCXpf+HfdetBwbRz6vCtsceTmw1pHNYJAsYxrfpOP6dz+piJWGos8wqG7qb3vj/Rrc5qOlmInUuA==", + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/redis/-/redis-4.7.0.tgz", + "integrity": "sha512-zvmkHEAdGMn+hMRXuMBtu4Vo5P6rHQjLoHftu+lBqq8ZTA3RCVC/WzD790bkKKiNFp7d5/9PcSD19fJyyRvOdQ==", "requires": { "@redis/bloom": "1.2.0", - "@redis/client": "1.5.14", + "@redis/client": "1.6.0", "@redis/graph": "1.1.1", - "@redis/json": "1.0.6", - "@redis/search": "1.1.6", - "@redis/time-series": "1.0.5" + "@redis/json": "1.0.7", + "@redis/search": "1.2.0", + "@redis/time-series": "1.1.0" } }, "reflect-metadata": { @@ -15178,44 +15235,6 @@ "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", "dev": true }, - "rimraf": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-4.4.1.tgz", - "integrity": "sha512-Gk8NlF062+T9CqNGn6h4tls3k6T1+/nXdOcSZVikNVtlRdYpA7wRJJMoXmuvOnLW844rPjdQ7JgXCYM6PPC/og==", - "dev": true, - "requires": { - "glob": "^9.2.0" - }, - "dependencies": { - "glob": { - "version": "9.3.5", - "resolved": "https://registry.npmjs.org/glob/-/glob-9.3.5.tgz", - "integrity": "sha512-e1LleDykUz2Iu+MTYdkSsuWX8lvAjAcs0Xef0lNIu0S2wOAzuTxCJtcd9S3cijlwYF18EsU3rzb8jPVobxDh9Q==", - "dev": true, - "requires": { - "fs.realpath": "^1.0.0", - "minimatch": "^8.0.2", - "minipass": "^4.2.4", - "path-scurry": "^1.6.1" - } - }, - "minimatch": { - "version": "8.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-8.0.4.tgz", - "integrity": "sha512-W0Wvr9HyFXZRGIDgCicunpQ299OKXs9RgZfaukz4qAW/pJhcpUfupc9c+OObPOFueNy8VSrZgEmDtk6Kh4WzDA==", - "dev": true, - "requires": { - "brace-expansion": "^2.0.1" - } - }, - "minipass": { - "version": "4.2.8", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-4.2.8.tgz", - "integrity": "sha512-fNzuVyifolSLFL4NzpF+wEF4qrgqaaKX0haXPQEdQ7NKAN+WecoKMHV09YcuL/DHxrUsYQOK3MiuDf7Ip2OXfQ==", - "dev": true - } - } - }, "run-async": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.4.1.tgz", @@ -15300,9 +15319,9 @@ "dev": true }, "send": { - "version": "0.18.0", - "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", - "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz", + "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==", "requires": { "debug": "2.6.9", "depd": "2.0.0", @@ -15334,6 +15353,11 @@ } } }, + "encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==" + }, "ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", @@ -15351,14 +15375,14 @@ } }, "serve-static": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz", - "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==", + "version": "1.16.2", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz", + "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==", "requires": { - "encodeurl": "~1.0.2", + "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "parseurl": "~1.3.3", - "send": "0.18.0" + "send": "0.19.0" } }, "set-function-length": { @@ -15394,52 +15418,6 @@ "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", "dev": true }, - "shelljs": { - "version": "0.8.5", - "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.8.5.tgz", - "integrity": "sha512-TiwcRcrkhHvbrZbnRcFYMLl30Dfov3HKqzp5tO5b4pt6G/SezKcYhmDg15zXVBswHmctSAQKznqNW2LO5tTDow==", - "dev": true, - "requires": { - "glob": "^7.0.0", - "interpret": "^1.0.0", - "rechoir": "^0.6.2" - }, - "dependencies": { - "brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "requires": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "dev": true, - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - }, - "minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "requires": { - "brace-expansion": "^1.1.7" - } - } - } - }, "side-channel": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz", @@ -16035,9 +16013,9 @@ "integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==" }, "typescript": { - "version": "5.4.5", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.5.tgz", - "integrity": "sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ==", + "version": "5.6.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.6.2.tgz", + "integrity": "sha512-NW8ByodCSNCwZeghjN3o+JX5OFH0Ojg6sadjEKY4huZ52TqbJTJnDo5+Tw98lSy63NZvi4n+ez5m2u5d4PkZyw==", "dev": true }, "uid": { @@ -16054,9 +16032,9 @@ "integrity": "sha512-+I6aJUv63YAcY9n4mQreLUt0d4lvwkkopDNmpomkAUz0fAkEMV9pRWxN0EjhW1YfRhcuyHg2v3mwddCDW1+LFQ==" }, "undici-types": { - "version": "5.26.5", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", - "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==" + "version": "6.19.8", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz", + "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==" }, "universalify": { "version": "2.0.1", @@ -16168,22 +16146,20 @@ "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" }, "webpack": { - "version": "5.91.0", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.91.0.tgz", - "integrity": "sha512-rzVwlLeBWHJbmgTC/8TvAcu5vpJNII+MelQpylD4jNERPwpBJOE2lEcko1zJX3QJeLjTTAnQxn/OJ8bjDzVQaw==", + "version": "5.94.0", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.94.0.tgz", + "integrity": "sha512-KcsGn50VT+06JH/iunZJedYGUJS5FGjow8wb9c0v5n1Om8O1g4L6LjtfxwlXIATopoQu+vOXXa7gYisWxCoPyg==", "dev": true, - "peer": true, "requires": { - "@types/eslint-scope": "^3.7.3", "@types/estree": "^1.0.5", "@webassemblyjs/ast": "^1.12.1", "@webassemblyjs/wasm-edit": "^1.12.1", "@webassemblyjs/wasm-parser": "^1.12.1", "acorn": "^8.7.1", - "acorn-import-assertions": "^1.9.0", + "acorn-import-attributes": "^1.9.5", "browserslist": "^4.21.10", "chrome-trace-event": "^1.0.2", - "enhanced-resolve": "^5.16.0", + "enhanced-resolve": "^5.17.1", "es-module-lexer": "^1.2.1", "eslint-scope": "5.1.1", "events": "^3.2.0", @@ -16205,7 +16181,6 @@ "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", "dev": true, - "peer": true, "requires": { "esrecurse": "^4.3.0", "estraverse": "^4.1.1" @@ -16215,8 +16190,7 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", - "dev": true, - "peer": true + "dev": true } } }, diff --git a/package.json b/package.json index 6a6512e..96956c6 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "whiteboard-collaboration-service", - "version": "0.3.2", + "version": "0.4.0", "description": "Alkemio Whiteboard Collaboration Service for Excalidraw backend", "author": "Alkemio Foundation", "private": false, @@ -24,7 +24,6 @@ "@socket.io/redis-adapter": "^8.3.0", "amqp-connection-manager": "^4.1.14", "amqplib": "^0.10.4", - "http": "^0.0.1-security", "lodash": "^4.17.21", "nest-winston": "^1.9.6", "redis": "^4.6.13", @@ -41,7 +40,7 @@ "@types/express": "^4.17.17", "@types/jest": "^29.5.2", "@types/lodash": "^4.17.4", - "@types/node": "^20.3.1", + "@types/node": "^20.16.5", "@types/supertest": "^6.0.0", "@typescript-eslint/eslint-plugin": "^6.0.0", "@typescript-eslint/parser": "^6.0.0", @@ -56,7 +55,7 @@ "ts-loader": "^9.4.3", "ts-node": "^10.9.1", "tsconfig-paths": "^4.2.0", - "typescript": "^5.1.3" + "typescript": "^5.6.2" }, "jest": { "moduleFileExtensions": [ diff --git a/src/config/config.type.ts b/src/config/config.type.ts index b55903c..ae0b1c5 100644 --- a/src/config/config.type.ts +++ b/src/config/config.type.ts @@ -25,8 +25,6 @@ export interface ConfigType { port: number; contribution_window: number; save_interval: number; - save_timeout: number; - save_consecutive_failed_attempts: number; collaborator_inactivity: number; reset_collaborator_mode_debounce: number; }; diff --git a/src/excalidraw-backend/middlewares/attach.user.info.middleware.ts b/src/excalidraw-backend/middlewares/attach.user.info.or.fail.middleware.ts similarity index 88% rename from src/excalidraw-backend/middlewares/attach.user.info.middleware.ts rename to src/excalidraw-backend/middlewares/attach.user.info.or.fail.middleware.ts index 252cc51..3709b91 100644 --- a/src/excalidraw-backend/middlewares/attach.user.info.middleware.ts +++ b/src/excalidraw-backend/middlewares/attach.user.info.or.fail.middleware.ts @@ -3,7 +3,7 @@ import { WrappedMiddlewareHandler } from './middleware.handler.type'; import { UserInfo } from '../../services/whiteboard-integration/user.info'; import { SocketIoSocket } from '../types'; -export const attachUserInfoMiddleware: WrappedMiddlewareHandler = +export const attachUserInfoOrFailMiddleware: WrappedMiddlewareHandler = (getter: (socket: SocketIoSocket) => Promise) => async (socket, next) => { try { diff --git a/src/excalidraw-backend/middlewares/index.ts b/src/excalidraw-backend/middlewares/index.ts index 5a09923..c1dd66b 100644 --- a/src/excalidraw-backend/middlewares/index.ts +++ b/src/excalidraw-backend/middlewares/index.ts @@ -1,2 +1,2 @@ -export * from './attach.user.info.middleware'; +export * from './attach.user.info.or.fail.middleware'; export * from './init.user.data.middleware'; diff --git a/src/excalidraw-backend/middlewares/init.user.data.middleware.ts b/src/excalidraw-backend/middlewares/init.user.data.middleware.ts index 28d2c6f..32e7645 100644 --- a/src/excalidraw-backend/middlewares/init.user.data.middleware.ts +++ b/src/excalidraw-backend/middlewares/init.user.data.middleware.ts @@ -15,9 +15,6 @@ export const initUserDataMiddleware: SimpleMiddlewareHandler = ( socket.data.state = UserIdleState.ACTIVE; - socket.data.consecutiveFailedSaves = 0; - socket.data.canSave = false; - socket.data.session = undefined; next(); diff --git a/src/excalidraw-backend/server.ts b/src/excalidraw-backend/server.ts index d00267a..0e9de64 100644 --- a/src/excalidraw-backend/server.ts +++ b/src/excalidraw-backend/server.ts @@ -1,5 +1,5 @@ import { WINSTON_MODULE_NEST_PROVIDER } from 'nest-winston'; -import { setTimeout, setInterval } from 'node:timers/promises'; +import { setInterval, setTimeout } from 'node:timers/promises'; import { Inject, Injectable, @@ -7,44 +7,43 @@ import { UnauthorizedException, } from '@nestjs/common'; import { ConfigService } from '@nestjs/config'; -import { debounce } from 'lodash'; +import { debounce, DebouncedFunc, throttle } from 'lodash'; import { - CLIENT_BROADCAST, COLLABORATOR_MODE, CollaboratorModeReasons, CONNECTION, defaultCollaboratorInactivity, defaultContributionInterval, - defaultSaveConsecutiveFailedAttempts, defaultSaveInterval, - defaultSaveTimeout, DISCONNECT, DISCONNECTING, + ExcalidrawElement, + ExcalidrawFileStore, IDLE_STATE, INIT_ROOM, + InMemorySnapshot, JOIN_ROOM, - RemoteSocketIoSocket, resetCollaboratorModeDebounceWait, - SaveResponse, + ROOM_SAVED, SCENE_INIT, SERVER_BROADCAST, - SERVER_SAVE_REQUEST, SERVER_SIDE_ROOM_DELETED, SERVER_VOLATILE_BROADCAST, + SocketEventData, SocketIoServer, SocketIoSocket, } from './types'; import { getExcalidrawBaseServerOrFail } from './index'; import { - attachUserInfoMiddleware, + attachUserInfoOrFailMiddleware, initUserDataMiddleware, } from './middlewares'; import { UserInfo } from '../services/whiteboard-integration/user.info'; import { UtilService } from '../services/util/util.service'; import { authorizeWithRoomAndJoinHandler, - canSocketSave, closeConnection, + DeepReadonly, disconnectEventHandler, disconnectingEventHandler, idleStateEventHandler, @@ -53,27 +52,35 @@ import { } from './utils'; import { CREATE_ROOM, DELETE_ROOM } from './adapters/adapter.event.names'; import { APP_ID } from '../app.id'; -import { arrayRandomElement, isAbortError } from '../util'; +import { isAbortError, jsonToArrayBuffer } from '../util'; import { ConfigType } from '../config'; +import { tryDecodeIncoming } from './utils/decode.incoming'; +import { SceneInitPayload, ServerBroadcastPayload } from './types/events'; +import { isSaveErrorData } from '../services/whiteboard-integration/outputs'; -type SaveMessageOpts = { timeout: number }; type RoomTrackers = Map; type SocketTrackers = Map; +type SaveRoomFunction = (roomId: string) => Promise; + +type ThrottledFunc any> = DebouncedFunc; +type ThrottledSaveFunction = ThrottledFunc; +type ThrottledSaveFunctionMap = Map; @Injectable() export class Server { private readonly wsServer: SocketIoServer; private readonly contributionTrackers: RoomTrackers = new Map(); - private readonly autoSaveTrackers: RoomTrackers = new Map(); private readonly collaboratorInactivityTrackers: SocketTrackers = new Map(); + // todo: try WeakMap or better yet - WeakSet + private readonly throttledSaveFnMap: ThrottledSaveFunctionMap = new Map(); private readonly contributionWindowMs: number; private readonly saveIntervalMs: number; - private readonly saveTimeoutMs: number; - private readonly saveConsecutiveFailedAttempts: number; private readonly collaboratorInactivityMs: number; + private snapshots: Map = new Map(); + constructor( @Inject(WINSTON_MODULE_NEST_PROVIDER) private logger: LoggerService, private readonly utilService: UtilService, @@ -89,15 +96,10 @@ export class Server { .then(() => this.logger.verbose?.('Excalidraw server initialized and running'), ) - .catch(this.logger.error); + .catch((err) => this.logger.error(err)); - const { - contribution_window, - save_interval, - save_timeout, - save_consecutive_failed_attempts, - collaborator_inactivity, - } = this.configService.get('settings.collaboration', { infer: true }); + const { contribution_window, save_interval, collaborator_inactivity } = + this.configService.get('settings.collaboration', { infer: true }); this.contributionWindowMs = (contribution_window ?? defaultContributionInterval) * 1000; @@ -105,9 +107,6 @@ export class Server { (collaborator_inactivity ?? defaultCollaboratorInactivity) * 1000; this.saveIntervalMs = (save_interval ?? defaultSaveInterval) * 1000; - this.saveTimeoutMs = (save_timeout ?? defaultSaveTimeout) * 1000; - this.saveConsecutiveFailedAttempts = - save_consecutive_failed_attempts ?? defaultSaveConsecutiveFailedAttempts; } private async fetchSocketsSafe(roomID: string) { @@ -142,14 +141,10 @@ export class Server { this.contributionTrackers.set(roomId, ac); } - const autosaveTracker = this.autoSaveTrackers.get(roomId); - if (!autosaveTracker) { - this.logger.verbose?.( - `Starting auto save tracker for room '${roomId}'`, - ); - const ac = this.startAutoSaveTrackerForRoom(roomId); - this.autoSaveTrackers.set(roomId, ac); - } + this.createAndStoreThrottledSaveAndNotifyRoom( + roomId, + this.saveIntervalMs, + ); }); adapter.on(DELETE_ROOM, async (roomId: string) => { if (!isRoomId(roomId)) { @@ -176,6 +171,12 @@ export class Server { ); // delete trackers that were left locally this.deleteTrackersForRoom(roomId); + // execute immediately the queued call (if any) + await this.flushThrottledSave(roomId); + // delete the throttled save function + this.cancelThrottledSave(roomId); + // todo: should we keep it cached? + this.snapshots.delete(roomId); }); adapter.on('error', (error: Error) => { this.logger.error(error, error.stack); @@ -186,7 +187,9 @@ export class Server { this.adapterInit(); // middlewares this.wsServer.use(initUserDataMiddleware); - this.wsServer.use(attachUserInfoMiddleware(this.getUserInfo.bind(this))); + this.wsServer.use( + attachUserInfoOrFailMiddleware(this.getUserInfo.bind(this)), + ); this.wsServer.on(CONNECTION, async (socket: SocketIoSocket) => { this.logger.verbose?.( @@ -200,16 +203,17 @@ export class Server { return; } - // this.logger.error(err.message, err.stack, LogContext.EXCALIDRAW_SERVER); - console.error(err); + this.logger.error(err); if (err && err instanceof UnauthorizedException) { closeConnection(socket, err.message); + this.deleteCollaboratorInactivityTrackerForSocket(socket); } - this.deleteCollaboratorInactivityTrackerForSocket(socket); }); socket.on(JOIN_ROOM, async (roomID) => { + await this.loadSnapshot(roomID); + // this logic could be provided by an entitlement (license) service await authorizeWithRoomAndJoinHandler( roomID, @@ -219,19 +223,45 @@ export class Server { (roomId, userId) => this.utilService.getUserInfoForRoom(userId, roomId), ); + await this.initSceneForSocket(socket, roomID); if (socket.data.collaborator) { this.startCollaboratorInactivityTrackerForSocket(socket); // user can broadcast content change events - socket.on(SERVER_BROADCAST, (roomID: string, data: ArrayBuffer) => { - serverBroadcastEventHandler(roomID, data, socket, (roomId) => - this.utilService.contentModified(socket.data.userInfo.id, roomId), - ); - this.resetCollaboratorInactivityTrackerForSocket(socket); - }); - socket.on(SCENE_INIT, (roomID: string, data: ArrayBuffer) => { - socket.broadcast.to(roomID).emit(CLIENT_BROADCAST, data); - }); + socket.on( + SERVER_BROADCAST, + async (roomID: string, data: ArrayBuffer) => { + serverBroadcastEventHandler(roomID, data, socket, (roomId) => + this.utilService.contentModified( + socket.data.userInfo.id, + roomId, + ), + ); + this.resetCollaboratorInactivityTrackerForSocket(socket); + // todo extract to a function + let eventData: + | SocketEventData + | undefined; + try { + eventData = tryDecodeIncoming(data); + } catch (e) { + this.logger.error({ + message: e?.message ?? JSON.stringify(e), + }); + } + if (!eventData) { + return; + } + + this.createAndStoreLatestSnapshot( + roomID, + eventData.payload.elements, + eventData.payload.files, + ); + + this.queueSave(roomID); + }, + ); } this.logger.verbose?.( `User '${socket.data.userInfo.email}' read=${socket.data.viewer}, update=${socket.data.collaborator}`, @@ -311,117 +341,22 @@ export class Server { return this.utilService.contribution(roomId, users); } - private startAutoSaveTrackerForRoom(roomId: string) { - const ac = new AbortController(); - (async () => { - // eslint-disable-next-line @typescript-eslint/no-unused-vars - for await (const _ of setInterval(this.saveIntervalMs, null, { - signal: ac.signal, - })) { - const data = await this.sendSaveMessage(roomId, { - timeout: this.saveTimeoutMs, - }); - - if (data === undefined) { - this.logger.verbose?.( - `No eligible sockets found to save '${roomId}'`, - ); - } else if (data.saved) { - data.socket.data.consecutiveFailedSaves = 0; - this.logger.verbose?.( - `Room '${roomId}' saved successful by '${data.socket.data.userInfo.email}'`, - ); - } else { - data.socket.data.consecutiveFailedSaves++; - data.socket.data.canSave = - data.socket.data.consecutiveFailedSaves < - this.saveConsecutiveFailedAttempts; - this.logger.error( - `Saving '${roomId}' failed for '${data.socket.data.userInfo.email}'`, - ); - } - } - })().catch((e) => { - if (isAbortError(e)) { - this.logger.verbose?.( - `Auto save tracker for room '${roomId}' was aborted with reason '${e.cause}'`, - ); - } else { - this.logger.error?.( - `Auto save tracker for room '${roomId}' failed: ${e.message}`, - ); - } - }); - - return ac; - } - - /*** - * Sends save requests to a random sockets until it's successful after a fixed set of retries - * - * @param roomId The room that needs saving - * @param opts Save options - */ - private async sendSaveMessage( - roomId: string, - opts: SaveMessageOpts, - ): Promise<{ saved: boolean; socket: RemoteSocketIoSocket } | undefined> { - const { timeout } = opts; - // get only sockets which can save - const socketsCanSave = (await this.fetchSocketsSafe(roomId)).filter( - canSocketSave, - ); - // return if no eligible sockets - if (!socketsCanSave.length) { - return undefined; - } - // choose a random socket which can save - const randomSocketWithUpdateFlag = arrayRandomElement(socketsCanSave); - // sends a save request to the socket and wait for a response - try { - // we are waiting for a single response, so destruct to just the first element - const [response] = await this.wsServer - .to(randomSocketWithUpdateFlag.id) - .timeout(timeout) - .emitWithAck(SERVER_SAVE_REQUEST); - // log the response - this.logResponse(response, randomSocketWithUpdateFlag, roomId); - } catch (e) { - if (this.autoSaveTrackers.get(roomId)) { - // avoid false-positives where the room is deleted and the save request is still running - this.logger.error?.( - `User '${randomSocketWithUpdateFlag.data.userInfo.email}' did not respond to '${SERVER_SAVE_REQUEST}' event after ${timeout}ms`, - ); - } - return { saved: false, socket: randomSocketWithUpdateFlag }; - } - - return { saved: true, socket: randomSocketWithUpdateFlag }; - } - - private logResponse( - response: SaveResponse, - socket: RemoteSocketIoSocket, + private createAndStoreLatestSnapshot( roomId: string, + remoteElements: readonly ExcalidrawElement[], + remoteFileStore: DeepReadonly, ) { - if (!response.success && this.autoSaveTrackers.get(roomId)) { - // avoid false-positives where the room is deleted and the save request is still running - this.logger.error( - `User ${ - socket.data.userInfo.email - } failed to save whiteboard '${roomId}': ${response.errors?.join( - '; ', - )}`, - ); - } else if (response.errors) { - this.logger.warn( - `User '${ - socket.data.userInfo.email - }' saved Whiteboard '${roomId}' with some errors: ${response.errors?.join( - ';', - )}'`, - ); + const snapshot = this.snapshots.get(roomId); + if (!snapshot) { + return; } + + const reconciledSnapshot = InMemorySnapshot.reconcile( + snapshot, + remoteElements, + remoteFileStore, + ); + this.snapshots.set(roomId, reconciledSnapshot); } private getUserInfo(socket: SocketIoSocket): Promise { @@ -440,13 +375,6 @@ export class Server { `Deleted contribution tracker for room '${roomId}'`, ); } - - const saveAC = this.autoSaveTrackers.get(roomId); - if (saveAC) { - saveAC.abort('deleted'); - this.autoSaveTrackers.delete(roomId); - this.logger.verbose?.(`Deleted auto save tracker for room '${roomId}'`); - } } private createCollaboratorInactivityTracker(socket: SocketIoSocket) { @@ -521,7 +449,7 @@ export class Server { } } } - + // todo: do it without abort controller private resetCollaboratorInactivityTrackerForSocket = debounce( (socket: SocketIoSocket) => { const abortController = this.collaboratorInactivityTrackers.get( @@ -539,6 +467,126 @@ export class Server { resetCollaboratorModeDebounceWait, { leading: true, trailing: false }, ); + + public async initSceneForSocket(socket: SocketIoSocket, roomId: string) { + const snapshot = await this.loadSnapshot(roomId); + const data: SceneInitPayload = { + type: SCENE_INIT, + payload: { + elements: snapshot.content.elements, + files: snapshot.content.files, + }, + }; + this.wsServer.to(socket.id).emit(SCENE_INIT, jsonToArrayBuffer(data)); + this.logger.verbose?.(`Scene init sent to '${socket.data.userInfo.email}'`); + } + + // todo: move to a helper class + private queueSave(roomId: string) { + const throttledSave = this.throttledSaveFnMap.get(roomId); + if (!throttledSave) { + // will produce too much logging + return; + } + + throttledSave(roomId); + } + /** + * Creates a new throttled save function for a room and stores it in __throttledSaveFnMap__.
+ * Called once after __wait__ milliseconds of the last received save request; Guaranteed save every __maxWait__ milliseconds;
+ * To be used when the room is created, and used only for that room.
+ * Use __cancelThrottledSave__ to cancel this function.
+ * Use __flushThrottledSave__ to invoke this function immediately. + */ + private createAndStoreThrottledSaveAndNotifyRoom( + roomId: string, + wait: number, + ): ThrottledSaveFunction { + const throttledSave = throttle( + async (roomId: string) => { + await this.saveRoom(roomId); + this.notifyRoomSaved(roomId); + }, + wait, + { leading: false, trailing: true }, + ); + this.throttledSaveFnMap.set(roomId, throttledSave); + + this.logger.verbose?.(`Throttled save just created for '${roomId}'`); + + return throttledSave; + } + + private cancelThrottledSave(roomId: string) { + const throttledSaveFn = this.throttledSaveFnMap.get(roomId); + + if (!throttledSaveFn) { + this.logger.error( + `No throttled save function found for room '${roomId}'`, + ); + return; + } + + throttledSaveFn.cancel(); + + this.logger.verbose?.(`Throttled save just canceled for '${roomId}'`); + } + + private async flushThrottledSave(roomId: string) { + const throttledSaveFn = this.throttledSaveFnMap.get(roomId); + + if (!throttledSaveFn) { + this.logger.error( + `No throttled save function found for room '${roomId}'`, + ); + return; + } + + this.logger.verbose?.(`Throttled save just flushed for '${roomId}'`); + await throttledSaveFn.flush(); + } + + private notifyRoomSaved(roomId: string) { + this.wsServer.in(roomId).emit(ROOM_SAVED); + } + /** + * Loads the snapshot in the in-memory Map and returns it. + * The snapshot is loaded from the DB if it's not found in the Map. + * @param roomId + * @returns The snapshot + */ + private async loadSnapshot(roomId: string) { + const snapshotContent = this.snapshots.get(roomId); + + if (!snapshotContent) { + const fetchedContent = + await this.utilService.fetchContentFromDbOrEmpty(roomId); + + const newSnapshot = new InMemorySnapshot(fetchedContent, 1); + this.snapshots.set(roomId, newSnapshot); + + return newSnapshot; + } + + return snapshotContent; + } + + private async saveRoom(roomId: string) { + const snapshot = this.snapshots.get(roomId); + if (!snapshot) { + this.logger.error( + `No snapshot found for room '${roomId}' in the local storage!`, + ); + return; + } + + const { data } = await this.utilService.save(roomId, snapshot.content); + if (isSaveErrorData(data)) { + this.logger.error(`Failed to save room '${roomId}': ${data.error}`); + } else { + this.logger.verbose?.(`Room '${roomId}' saved successfully`); + } + } } // not that reliable, but best we can do const isRoomId = (id: string) => id.length === 36; diff --git a/src/excalidraw-backend/types/client.broadcast.payload.type.ts b/src/excalidraw-backend/types/client.broadcast.payload.type.ts deleted file mode 100644 index 2ee89f6..0000000 --- a/src/excalidraw-backend/types/client.broadcast.payload.type.ts +++ /dev/null @@ -1,4 +0,0 @@ -export enum ClientBroadcastPayloadType { - SCENE_INIT = 'SCENE_INIT', - SCENE_UPDATE = 'SCENE_UPDATE', -} diff --git a/src/excalidraw-backend/types/defaults.ts b/src/excalidraw-backend/types/defaults.ts index f248ea0..9652c5d 100644 --- a/src/excalidraw-backend/types/defaults.ts +++ b/src/excalidraw-backend/types/defaults.ts @@ -1,8 +1,6 @@ export const defaultContributionInterval = 600; -export const defaultSaveInterval = 15; -export const defaultSaveTimeout = 10; +export const defaultSaveInterval = 10; export const defaultCollaboratorInactivity = 60 * 30; // 30 minutes -export const defaultSaveConsecutiveFailedAttempts = 3; export const resetCollaboratorModeDebounceWait = 1000; // logically it must always be 1, since it's either multi-user or not diff --git a/src/excalidraw-backend/types/event.names.ts b/src/excalidraw-backend/types/event.names.ts index 2425431..dea6902 100644 --- a/src/excalidraw-backend/types/event.names.ts +++ b/src/excalidraw-backend/types/event.names.ts @@ -1,14 +1,8 @@ -export const CONNECTION = 'connection'; -export const INIT_ROOM = 'init-room'; -export const JOIN_ROOM = 'join-room'; -export const FIRST_IN_ROOM = 'first-in-room'; -export const NEW_USER = 'new-user'; -export const ROOM_USER_CHANGE = 'room-user-change'; export const SCENE_INIT = 'scene-init'; +export const ROOM_SAVED = 'room-saved'; export const SERVER_BROADCAST = 'server-broadcast'; export const SERVER_VOLATILE_BROADCAST = 'server-volatile-broadcast'; -export const IDLE_STATE = 'idle-state'; -export const COLLABORATOR_MODE = 'collaborator-mode'; + export const DISCONNECTING = 'disconnecting'; export const DISCONNECT = 'disconnect'; @@ -16,6 +10,11 @@ export const CLIENT_BROADCAST = 'client-broadcast'; export const CONNECTION_CLOSED = 'connection-closed'; -export const SAVED = 'saved'; -export const SERVER_SAVE_REQUEST = 'save-request'; export const SERVER_SIDE_ROOM_DELETED = 'server-side-room-deleted'; +// messages +export const CONNECTION = 'connection'; +export const COLLABORATOR_MODE = 'collaborator-mode'; +export const IDLE_STATE = 'idle-state'; +export const INIT_ROOM = 'init-room'; +export const JOIN_ROOM = 'join-room'; +export const ROOM_USER_CHANGE = 'room-user-change'; diff --git a/src/excalidraw-backend/types/events/index.ts b/src/excalidraw-backend/types/events/index.ts index 2c4e141..572f8a5 100644 --- a/src/excalidraw-backend/types/events/index.ts +++ b/src/excalidraw-backend/types/events/index.ts @@ -1 +1,3 @@ export * from './idle.state'; +export * from './server.broadcast.payload'; +export * from './scene.init.payload'; diff --git a/src/excalidraw-backend/types/events/scene.init.payload.ts b/src/excalidraw-backend/types/events/scene.init.payload.ts new file mode 100644 index 0000000..9555e16 --- /dev/null +++ b/src/excalidraw-backend/types/events/scene.init.payload.ts @@ -0,0 +1,12 @@ +import { SCENE_INIT } from '../event.names'; +import { DeepReadonly } from '../../utils'; +import { ExcalidrawElement } from '../excalidraw.element'; +import { ExcalidrawFileStore } from '../excalidraw.file'; + +export type SceneInitPayload = { + type: typeof SCENE_INIT; + payload: { + elements: readonly ExcalidrawElement[]; + files: DeepReadonly; + }; +}; diff --git a/src/excalidraw-backend/types/events/server.broadcast.payload.ts b/src/excalidraw-backend/types/events/server.broadcast.payload.ts new file mode 100644 index 0000000..7098ffa --- /dev/null +++ b/src/excalidraw-backend/types/events/server.broadcast.payload.ts @@ -0,0 +1,8 @@ +import { ExcalidrawElement } from '../excalidraw.element'; +import { ExcalidrawFileStore } from '../excalidraw.file'; +import { DeepReadonly } from '../../utils'; + +export type ServerBroadcastPayload = { + elements: readonly ExcalidrawElement[]; + files: DeepReadonly; +}; diff --git a/src/excalidraw-backend/types/excalidraw.content.ts b/src/excalidraw-backend/types/excalidraw.content.ts new file mode 100644 index 0000000..5dd9a55 --- /dev/null +++ b/src/excalidraw-backend/types/excalidraw.content.ts @@ -0,0 +1,11 @@ +import { ExcalidrawElement } from './excalidraw.element'; +import { ExcalidrawFileStore } from './excalidraw.file'; + +export type ExcalidrawContent = { + type: 'excalidraw'; + version: number; + source: string; + elements: ExcalidrawElement[]; + appState: Record; + files: ExcalidrawFileStore; +}; diff --git a/src/excalidraw-backend/types/excalidraw.element.ts b/src/excalidraw-backend/types/excalidraw.element.ts new file mode 100644 index 0000000..58e7c8f --- /dev/null +++ b/src/excalidraw-backend/types/excalidraw.element.ts @@ -0,0 +1,14 @@ +type ExcalidrawBaseElement = { + id: string; + index: number; + type: string; // many types + version: number; + versionNonce: number; +}; + +export type ExcalidrawImageElement = ExcalidrawBaseElement & { + type: 'image'; + fileId: string; +}; + +export type ExcalidrawElement = ExcalidrawBaseElement | ExcalidrawImageElement; diff --git a/src/excalidraw-backend/types/excalidraw.file.ts b/src/excalidraw-backend/types/excalidraw.file.ts new file mode 100644 index 0000000..8b646ca --- /dev/null +++ b/src/excalidraw-backend/types/excalidraw.file.ts @@ -0,0 +1,11 @@ +export type ExcalidrawFile = { + mimeType: string; + id: string; + created: number; + lastRetrieved: number; + url: string; +}; + +export type ExcalidrawFileStore = { + [id: string]: ExcalidrawFile; +}; diff --git a/src/excalidraw-backend/types/in.memory.snapshot.ts b/src/excalidraw-backend/types/in.memory.snapshot.ts new file mode 100644 index 0000000..446feb8 --- /dev/null +++ b/src/excalidraw-backend/types/in.memory.snapshot.ts @@ -0,0 +1,81 @@ +import { ExcalidrawContent } from './excalidraw.content'; +import { DeepReadonly } from '../utils'; +import { ExcalidrawElement } from './excalidraw.element'; +import { ExcalidrawFileStore } from './excalidraw.file'; +import { reconcileElements } from '../utils/reconcile'; +import { reconcileFiles } from '../utils/reconcile.files'; +/** + * Represents an immutable point-in-time-snapshot of an Excalidraw content stored in memory. + * It includes methods to reconcile local and remote content and create a new snapshot. + * Can be used as versioning mechanism for Excalidraw content. + * Data deltas can be calculated between two snapshots to reduce data transfers. + */ +export class InMemorySnapshot { + /** + * The timestamp of the last modification to the snapshot. + * This timestamp is immutable and can be perceived as time of creation + * @type {number} + */ + public readonly lastModified: number; + /** + * The immutable content of the snapshot. + * @type {DeepReadonly} + */ + public readonly content: DeepReadonly; + /** + * The version number of the snapshot. + * This field is immutable and can't be changed. + * @type {number} + */ + public readonly version: number; + /** + * Constructs an instance of `InMemorySnapshot`. + * + * @param {ExcalidrawContent} content - The content of the snapshot. + * @param version The version of the snapshot. Defaults to 1 for new snapshots. + */ + constructor(content: ExcalidrawContent, version?: number) { + this.content = content; + this.lastModified = Date.now(); + this.version = version ?? 1; + } + + /** + * Reconciles a snapshot with incoming remote elements and remote file store. + * Returns a new instance of `InMemorySnapshot` with the reconciled content. + * + * @param {InMemorySnapshot} snapshot - The snapshot to be reconciled with. + * @param {readonly ExcalidrawElement[]} remoteElements - The remote elements to reconcile with. + * @param {DeepReadonly} remoteFileStore - The remote file store to reconcile with. + * @returns {InMemorySnapshot} - A new instance of `InMemorySnapshot` with the reconciled content. + */ + public static reconcile = ( + snapshot: InMemorySnapshot, + remoteElements: readonly ExcalidrawElement[], + remoteFileStore: DeepReadonly, + ): InMemorySnapshot => { + const { + content: { + elements: localElements, + files: localFileStore, + ...restOfContent + }, + } = snapshot; + + const reconciledElements = reconcileElements(localElements, remoteElements); + const reconciledFileStore = reconcileFiles( + localElements, + localFileStore, + remoteFileStore, + ); + + return new InMemorySnapshot( + { + ...restOfContent, + elements: reconciledElements, + files: reconciledFileStore, + }, + snapshot.version + 1, + ); + }; +} diff --git a/src/excalidraw-backend/types/index.ts b/src/excalidraw-backend/types/index.ts index e02305a..90ddeed 100644 --- a/src/excalidraw-backend/types/index.ts +++ b/src/excalidraw-backend/types/index.ts @@ -1,10 +1,8 @@ -export * from './client.broadcast.payload.type'; export * from './collaboration.mode.reasons'; export * from './defaults'; export * from './event.names'; -export * from './save.reponse'; export * from './socket.data'; export * from './socket.event.data'; @@ -14,3 +12,9 @@ export * from './socket.io.socket'; export * from './user.info.for.room'; export * from './user.idle.state'; + +export * from './excalidraw.content'; +export * from './excalidraw.element'; +export * from './excalidraw.file'; + +export * from './in.memory.snapshot'; diff --git a/src/excalidraw-backend/types/save.reponse.ts b/src/excalidraw-backend/types/save.reponse.ts deleted file mode 100644 index f907613..0000000 --- a/src/excalidraw-backend/types/save.reponse.ts +++ /dev/null @@ -1,4 +0,0 @@ -export type SaveResponse = { - success: boolean; - errors?: string[]; -}; diff --git a/src/excalidraw-backend/types/socket.data.ts b/src/excalidraw-backend/types/socket.data.ts index 0ca4908..02ff26d 100644 --- a/src/excalidraw-backend/types/socket.data.ts +++ b/src/excalidraw-backend/types/socket.data.ts @@ -31,17 +31,6 @@ export type SocketData = { * The session of the user connected with the socket */ session?: Session; - /** - * Amount of consecutive times this socket has failed saving - */ - consecutiveFailedSaves: number; - /** - * Can the socket save the whiteboard.
- * This is individual of the collaborator flag.
A socket can still save without being a collaborator. - * Initial value is based on the available privileges for the room.
- * Set to False after SAVE_CONSECUTIVE_FAILED_ATTEMPTS consecutive failed saves. - */ - canSave: boolean; /** * The socket's last known idle state */ diff --git a/src/excalidraw-backend/types/socket.event.data.ts b/src/excalidraw-backend/types/socket.event.data.ts index 2781d63..94d21b5 100644 --- a/src/excalidraw-backend/types/socket.event.data.ts +++ b/src/excalidraw-backend/types/socket.event.data.ts @@ -1,4 +1,4 @@ -type BasePayload = Record; +export type BasePayload = Record; export type SocketEventData = { type: string; diff --git a/src/excalidraw-backend/types/socket.io.server.ts b/src/excalidraw-backend/types/socket.io.server.ts index e96b0a6..2296830 100644 --- a/src/excalidraw-backend/types/socket.io.server.ts +++ b/src/excalidraw-backend/types/socket.io.server.ts @@ -4,19 +4,17 @@ import { CLIENT_BROADCAST, COLLABORATOR_MODE, CONNECTION, - FIRST_IN_ROOM, IDLE_STATE, INIT_ROOM, ROOM_USER_CHANGE, - SAVED, + ROOM_SAVED, + SCENE_INIT, SERVER_BROADCAST, - SERVER_SAVE_REQUEST, SERVER_SIDE_ROOM_DELETED, SERVER_VOLATILE_BROADCAST, } from './event.names'; import { SocketIoSocket } from './socket.io.socket'; import { CollaboratorModeReasons } from './collaboration.mode.reasons'; -import { SaveResponse } from './save.reponse'; type ListenEvents = { [CONNECTION]: (socket: SocketIoSocket) => void; @@ -29,13 +27,12 @@ type EmitEvents = { [INIT_ROOM]: () => void; [CLIENT_BROADCAST]: (data: ArrayBuffer) => void; [ROOM_USER_CHANGE]: (socketIDs: Array) => void; - [SAVED]: () => void; - [FIRST_IN_ROOM]: () => void; + [SCENE_INIT]: (scene: ArrayBuffer) => void; + [ROOM_SAVED]: () => void; [COLLABORATOR_MODE]: (data: { mode: 'read' | 'write'; reason?: CollaboratorModeReasons; }) => void; - [SERVER_SAVE_REQUEST]: (cb: (val: SaveResponse) => void) => void; }; type ServerSideEvents = { [SERVER_SIDE_ROOM_DELETED]: (serverId: string, roomId: string) => void; diff --git a/src/excalidraw-backend/types/socket.io.socket.ts b/src/excalidraw-backend/types/socket.io.socket.ts index 9a5f2e8..9c57aa0 100644 --- a/src/excalidraw-backend/types/socket.io.socket.ts +++ b/src/excalidraw-backend/types/socket.io.socket.ts @@ -9,7 +9,6 @@ import { IDLE_STATE, SCENE_INIT, JOIN_ROOM, - NEW_USER, ROOM_USER_CHANGE, SERVER_BROADCAST, SERVER_VOLATILE_BROADCAST, @@ -27,7 +26,6 @@ type ListenEvents = { }; type EmitEvents = { [CLIENT_BROADCAST]: (data: ArrayBuffer) => void; - [NEW_USER]: (socketId: string) => void; [IDLE_STATE]: (data: ArrayBuffer) => void; [ROOM_USER_CHANGE]: (socketIds: Array) => void; [CONNECTION_CLOSED]: (message?: string) => void; diff --git a/src/excalidraw-backend/utils/array.to.map.ts b/src/excalidraw-backend/utils/array.to.map.ts new file mode 100644 index 0000000..0dc8a5f --- /dev/null +++ b/src/excalidraw-backend/utils/array.to.map.ts @@ -0,0 +1,15 @@ +/** + * Transforms array of objects containing `id` attribute, + * or array of ids (strings), into a Map, keyd by `id`. + */ +export const arrayToMap = ( + items: readonly T[] | Map, +) => { + if (items instanceof Map) { + return items; + } + return items.reduce((acc: Map, element) => { + acc.set(typeof element === 'string' ? element : element.id, element); + return acc; + }, new Map()); +}; diff --git a/src/excalidraw-backend/utils/decode.incoming.ts b/src/excalidraw-backend/utils/decode.incoming.ts new file mode 100644 index 0000000..a2fc064 --- /dev/null +++ b/src/excalidraw-backend/utils/decode.incoming.ts @@ -0,0 +1,25 @@ +import { BasePayload, SocketEventData } from '../types'; + +/** + * Tries to decode incoming binary data. + * Throws an exception otherwise. + * @param {ArrayBuffer}data The incoming binary data + * @returns {SocketEventData} Returns the decoded data in form of event data + * @throws {TypeError} Throws an error if the data cannot be decoded + * @throws {SyntaxError} Thrown if the data to parse is not valid JSON. + */ +export const tryDecodeIncoming = ( + data: ArrayBuffer, +): SocketEventData | never => { + const strEventData = tryDecodeBinary(data); + + return JSON.parse(strEventData) as SocketEventData; +}; +/** + * + * @throws {TypeError} Throws an error if the data cannot be decoded + */ +export const tryDecodeBinary = (data: ArrayBuffer): string => { + const decoder = new TextDecoder('utf-8'); + return decoder.decode(data); +}; diff --git a/src/excalidraw-backend/utils/deep.readonly.ts b/src/excalidraw-backend/utils/deep.readonly.ts new file mode 100644 index 0000000..d51815a --- /dev/null +++ b/src/excalidraw-backend/utils/deep.readonly.ts @@ -0,0 +1,3 @@ +export type DeepReadonly = { + readonly [P in keyof T]: T[P] extends object ? DeepReadonly : T[P]; +}; diff --git a/src/excalidraw-backend/utils/handlers.ts b/src/excalidraw-backend/utils/handlers.ts index bf1d644..8639fea 100644 --- a/src/excalidraw-backend/utils/handlers.ts +++ b/src/excalidraw-backend/utils/handlers.ts @@ -8,15 +8,13 @@ import { import { CLIENT_BROADCAST, COLLABORATOR_MODE, - FIRST_IN_ROOM, IDLE_STATE, - NEW_USER, ROOM_USER_CHANGE, } from '../types'; import { minCollaboratorsInRoom } from '../types'; -import { SocketEventData } from '../types'; import { IdleStatePayload } from '../types/events'; import { closeConnection } from './util'; +import { tryDecodeBinary, tryDecodeIncoming } from './decode.incoming'; const fetchSocketsSafe = async ( wsServer: SocketIoServer, @@ -74,7 +72,6 @@ export const authorizeWithRoomAndJoinHandler = async ( socket.data.viewer = canRead; // the user can't update if the collaborator limit has been reached socket.data.collaborator = !isCollaboratorLimitReached && canUpdate; - socket.data.canSave = canUpdate; const reason = calculateReasonForCollaborationMode( socket, @@ -106,23 +103,16 @@ const joinRoomHandler = async ( logger?.verbose?.(`User '${userInfo.email}' has joined room '${roomID}'`); - const sockets = await fetchSocketsSafe(wsServer, roomID, logger); - if (sockets.length === 1) { - logger?.verbose?.(`User '${userInfo.email}' is first in room '${roomID}'`); - wsServer.to(socket.id).emit(FIRST_IN_ROOM); - } else { - logger?.verbose?.(`User '${userInfo.email}' emitted to room '${roomID}'`); - socket.broadcast.to(roomID).emit(NEW_USER, socket.id); - } - - const socketIDs = sockets.map((socket) => socket.id); + const socketIDs = (await fetchSocketsSafe(wsServer, roomID, logger)).map( + (socket) => socket.id, + ); wsServer.in(roomID).emit(ROOM_USER_CHANGE, socketIDs); }; /* Built-in event for handling broadcast; messages are sent to all sockets except the sender socket in reliable manner */ -export const serverBroadcastEventHandler = async ( +export const serverBroadcastEventHandler = ( roomID: string, data: ArrayBuffer, socket: SocketIoSocket, @@ -157,17 +147,13 @@ export const idleStateEventHandler = ( ) => { socket.broadcast.to(roomID).emit(IDLE_STATE, data); - const decoder = new TextDecoder('utf-8'); - const strEventData = decoder.decode(data); try { - const eventData = JSON.parse( - strEventData, - ) as SocketEventData; + const eventData = tryDecodeIncoming(data); socket.data.state = eventData.payload.userState; } catch (e) { logger.error({ message: e?.message ?? JSON.stringify(e), - data: strEventData, + data: e instanceof SyntaxError ? tryDecodeBinary(data) : undefined, }); } }; diff --git a/src/excalidraw-backend/utils/index.ts b/src/excalidraw-backend/utils/index.ts index c3bd1a5..d4e890b 100644 --- a/src/excalidraw-backend/utils/index.ts +++ b/src/excalidraw-backend/utils/index.ts @@ -1,2 +1,3 @@ export * from './util'; export * from './handlers'; +export * from './deep.readonly'; diff --git a/src/excalidraw-backend/utils/reconcile.files.ts b/src/excalidraw-backend/utils/reconcile.files.ts new file mode 100644 index 0000000..932a4c8 --- /dev/null +++ b/src/excalidraw-backend/utils/reconcile.files.ts @@ -0,0 +1,39 @@ +import { + ExcalidrawElement, + ExcalidrawFileStore, + ExcalidrawImageElement, +} from '../types'; +import { DeepReadonly } from './deep.readonly'; + +export const reconcileFiles = ( + localElements: readonly ExcalidrawElement[], + localFileStore: DeepReadonly, + remoteFileStore: DeepReadonly, +): ExcalidrawFileStore => { + const reconciledFileStore = { ...localFileStore }; + + for (const remoteFileId in remoteFileStore) { + // find a local file that matches the remote file + // if it's already in - discard the remote + if (reconciledFileStore[remoteFileId]) { + continue; + } + // if it's not found locally - check if it's used by any element + const elementUsingTheFile = localElements.find( + (element) => + isExcalidrawImageElement(element) && element.fileId === remoteFileId, + ); + // if it's not found locally and not used by any local element - discard the remote + if (!elementUsingTheFile) { + continue; + } + // if it's not found locally but used by a local element - add it to the list of reconciled files + reconciledFileStore[remoteFileId] = remoteFileStore[remoteFileId]; + } + + return reconciledFileStore; +}; + +const isExcalidrawImageElement = ( + element: ExcalidrawElement, +): element is ExcalidrawImageElement => element.type === 'image'; diff --git a/src/excalidraw-backend/utils/reconcile.ts b/src/excalidraw-backend/utils/reconcile.ts new file mode 100644 index 0000000..6e10c75 --- /dev/null +++ b/src/excalidraw-backend/utils/reconcile.ts @@ -0,0 +1,100 @@ +import { ExcalidrawElement } from '../types'; +import { arrayToMap } from './array.to.map'; +// import { orderByFractionalIndex, syncInvalidIndices } from './fractionalIndex'; + +const shouldDiscardRemoteElement = ( + local: ExcalidrawElement | undefined, + remote: ExcalidrawElement, +): boolean => { + return !!( + local && + // local element is newer + (local.version > remote.version || + // resolve conflicting edits deterministically by taking the one with + // the lowest versionNonce + (local.version === remote.version && + local.versionNonce < remote.versionNonce)) + ); +}; + +/*const validateIndicesThrottled = throttle( + ( + orderedElements: readonly OrderedExcalidrawElement[], + localElements: readonly OrderedExcalidrawElement[], + remoteElements: readonly RemoteExcalidrawElement[], + ) => { + if ( + import.meta.env.DEV || + import.meta.env.MODE === ENV.TEST || + window?.DEBUG_FRACTIONAL_INDICES + ) { + // create new instances due to the mutation + const elements = syncInvalidIndices( + orderedElements.map((x) => ({ ...x })), + ); + + validateFractionalIndices(elements, { + // throw in dev & test only, to remain functional on `DEBUG_FRACTIONAL_INDICES` + shouldThrow: import.meta.env.DEV || import.meta.env.MODE === ENV.TEST, + includeBoundTextValidation: true, + reconciliationContext: { + localElements, + remoteElements, + }, + }); + } + }, + 1000 * 60, + { leading: true, trailing: false }, +);*/ + +export const reconcileElements = ( + localElements: readonly ExcalidrawElement[], + remoteElements: readonly ExcalidrawElement[], +): ExcalidrawElement[] => { + const localElementsMap = arrayToMap(localElements); + const reconciledElements: ExcalidrawElement[] = []; + const added = new Set(); + + // process remote elements + for (const remoteElement of remoteElements) { + if (!added.has(remoteElement.id)) { + const localElement = localElementsMap.get(remoteElement.id); + const discardRemoteElement = shouldDiscardRemoteElement( + localElement, + remoteElement, + ); + + if (localElement && discardRemoteElement) { + reconciledElements.push(localElement); + added.add(localElement.id); + } else { + reconciledElements.push(remoteElement); + added.add(remoteElement.id); + } + } + } + + // process remaining local elements + for (const localElement of localElements) { + if (!added.has(localElement.id)) { + reconciledElements.push(localElement); + added.add(localElement.id); + } + } + + // const orderedElements = orderByFractionalIndex(reconciledElements); + + /** + * todo: not sure how important is this and how does it affect the end result + * since the debounce is set to 60 seconds, which might mean that the room has already closed + * and that the whiteboard is already saved + */ + // validateIndicesThrottled(orderedElements, localElements, remoteElements); + + // de-duplicate indices + // const syncedElemented = syncInvalidIndices(orderedElements); + + // return orderedElements as ReconciledExcalidrawElement[]; + return reconciledElements; +}; diff --git a/src/excalidraw-backend/utils/util.ts b/src/excalidraw-backend/utils/util.ts index 199bebd..6e62050 100644 --- a/src/excalidraw-backend/utils/util.ts +++ b/src/excalidraw-backend/utils/util.ts @@ -1,8 +1,4 @@ -import { - SocketIoSocket, - CONNECTION_CLOSED, - RemoteSocketIoSocket, -} from '../types'; +import { SocketIoSocket, CONNECTION_CLOSED } from '../types'; // closes the connection for this socket // and sends an optional message before disconnecting @@ -13,9 +9,3 @@ export const closeConnection = (socket: SocketIoSocket, message?: string) => { socket.removeAllListeners(); socket.disconnect(true); }; - -export const canSocketSave = ( - socket: SocketIoSocket | RemoteSocketIoSocket, -) => { - return socket.data.canSave && socket.data.state !== 'idle'; -}; diff --git a/src/services/util/util.service.ts b/src/services/util/util.service.ts index 19fe426..83a5d8c 100644 --- a/src/services/util/util.service.ts +++ b/src/services/util/util.service.ts @@ -1,16 +1,24 @@ -import { Injectable } from '@nestjs/common'; +import { Inject, Injectable, LoggerService } from '@nestjs/common'; import { WhiteboardIntegrationService } from '../whiteboard-integration/whiteboard.integration.service'; import { UserInfo } from '../whiteboard-integration/user.info'; import { ContentModifiedInputData, ContributionInputData, + FetchInputData, InfoInputData, + SaveInputData, WhoInputData, } from '../whiteboard-integration/inputs'; +import { ExcalidrawContent } from '../../excalidraw-backend/types'; +import { isFetchErrorData } from '../whiteboard-integration/outputs'; +import { excalidrawInitContent } from '../../util'; +import { WINSTON_MODULE_NEST_PROVIDER } from 'nest-winston'; +import { DeepReadonly } from '../../excalidraw-backend/utils'; @Injectable() export class UtilService { constructor( + @Inject(WINSTON_MODULE_NEST_PROVIDER) private logger: LoggerService, private readonly integrationService: WhiteboardIntegrationService, ) {} @@ -46,4 +54,33 @@ export class UtilService { new ContributionInputData(roomId, users), ); } + + public save(roomId: string, content: DeepReadonly) { + return this.integrationService.save( + new SaveInputData(roomId, JSON.stringify(content)), + ); + } + + /** + * Fetches the content of the whiteboard from DB or if not found returns an initial empty content. + * @param roomId Whiteboard ID + */ + public async fetchContentFromDbOrEmpty( + roomId: string, + ): Promise { + const { data } = await this.integrationService.fetch( + new FetchInputData(roomId), + ); + + if (isFetchErrorData(data)) { + return excalidrawInitContent; + } + + try { + return JSON.parse(data.content); + } catch (e: any) { + this.logger.error(e, e?.stack); + return excalidrawInitContent; + } + } } diff --git a/src/services/whiteboard-integration/authorization.privilege.ts b/src/services/whiteboard-integration/authorization.privilege.ts deleted file mode 100644 index 9c109cb..0000000 --- a/src/services/whiteboard-integration/authorization.privilege.ts +++ /dev/null @@ -1,43 +0,0 @@ -export enum AuthorizationPrivilege { - CREATE = 'create', - READ = 'read', - UPDATE = 'update', - DELETE = 'delete', - GRANT = 'grant', // allow the issuing / revoking of credentials of the same type within a given scope - GRANT_GLOBAL_ADMINS = 'grant-global-admins', - AUTHORIZATION_RESET = 'authorization-reset', - ADMIN = 'admin', - PLATFORM_ADMIN = 'platform-admin', // To determine if the user should have access to the platform administration - CREATE_WHITEBOARD = 'create-whiteboard', - CREATE_SUBSPACE = 'create-subspace', - CREATE_POST = 'create-post', - CREATE_MESSAGE = 'create-message', - CREATE_MESSAGE_REPLY = 'create-message-reply', - CREATE_MESSAGE_REACTION = 'create-message-reaction', - CREATE_DISCUSSION = 'create-discussion', - CONTRIBUTE = 'contribute', - CREATE_SPACE = 'create-space', - CREATE_ORGANIZATION = 'create-organization', - FILE_UPLOAD = 'file-upload', - FILE_DELETE = 'file-delete', - UPDATE_WHITEBOARD = 'update-whiteboard', - UPDATE_INNOVATION_FLOW = 'update-innovation-flow', - COMMUNITY_JOIN = 'community-join', - COMMUNITY_APPLY = 'community-apply', - COMMUNITY_INVITE = 'community-invite', - COMMUNITY_INVITE_ACCEPT = 'community-invite-accept', - COMMUNITY_ADD_MEMBER = 'community-add-member', // only for global admins - CREATE_CALLOUT = 'create-callout', - UPDATE_CALLOUT_PUBLISHER = 'update-callout-publisher', - CREATE_RELATION = 'create-relation', - READ_USERS = 'read-users', - READ_USER_PII = 'read-user-pii', - MOVE_POST = 'move-post', - MOVE_CONTRIBUTION = 'move-contribution', - ACCESS_INTERACTIVE_GUIDANCE = 'access-interactive-guidance', - ACCESS_VIRTUAL_CONTRIBUTOR = 'access-virtual-contributor', - ACCESS_DASHBOARD_REFRESH = 'access-dashboard-refresh', - CREATE_WHITEBOARD_RT = 'create-whiteboard-rt', - UPDATE_CONTENT = 'update-content', - SAVE_AS_TEMPLATE = 'save-as-template', -} diff --git a/src/services/whiteboard-integration/event.pattern.enum.ts b/src/services/whiteboard-integration/event.pattern.enum.ts index 40d3797..0cf18f3 100644 --- a/src/services/whiteboard-integration/event.pattern.enum.ts +++ b/src/services/whiteboard-integration/event.pattern.enum.ts @@ -1,4 +1,5 @@ export enum WhiteboardIntegrationEventPattern { CONTRIBUTION = 'contribution', CONTENT_MODIFIED = 'contentModified', + SAVE = 'save', } diff --git a/src/services/whiteboard-integration/inputs/fetch.input.data.ts b/src/services/whiteboard-integration/inputs/fetch.input.data.ts new file mode 100644 index 0000000..35cf6f2 --- /dev/null +++ b/src/services/whiteboard-integration/inputs/fetch.input.data.ts @@ -0,0 +1,13 @@ +import { BaseInputData } from './base.input.data'; + +/** + * Class representing the data input for when fetching the content of a whiteboard. + */ +export class FetchInputData extends BaseInputData { + /** + * @param {string} whiteboardId - The ID of the whiteboard which content is going to be fetched. + */ + constructor(public whiteboardId: string) { + super('fetch-input'); + } +} diff --git a/src/services/whiteboard-integration/inputs/index.ts b/src/services/whiteboard-integration/inputs/index.ts index 9fb1c5b..8778b5f 100644 --- a/src/services/whiteboard-integration/inputs/index.ts +++ b/src/services/whiteboard-integration/inputs/index.ts @@ -3,3 +3,6 @@ export * from './info.input.data'; export * from './contribution.input.data'; export * from './content.modified.input.data'; + +export * from './save.input.data'; +export * from './fetch.input.data'; diff --git a/src/services/whiteboard-integration/inputs/save.input.data.ts b/src/services/whiteboard-integration/inputs/save.input.data.ts new file mode 100644 index 0000000..34a3499 --- /dev/null +++ b/src/services/whiteboard-integration/inputs/save.input.data.ts @@ -0,0 +1,18 @@ +import { BaseInputData } from './base.input.data'; + +/** + * Class representing the data input for when content is modified. + */ +export class SaveInputData extends BaseInputData { + /** + * Creates a new ContentModifiedInputData instance. + * @param {string} whiteboardId - The ID of the whiteboard which is going to be saved. + * @param {string} content - The content of the whiteboard. Must be a valid Excalidraw whiteboard content + */ + constructor( + public whiteboardId: string, + public content: string, + ) { + super('save-input'); + } +} diff --git a/src/services/whiteboard-integration/message.pattern.enum.ts b/src/services/whiteboard-integration/message.pattern.enum.ts index ca3bc92..fd18a52 100644 --- a/src/services/whiteboard-integration/message.pattern.enum.ts +++ b/src/services/whiteboard-integration/message.pattern.enum.ts @@ -2,4 +2,6 @@ export enum WhiteboardIntegrationMessagePattern { WHO = 'who', INFO = 'info', HEALTH_CHECK = 'health-check', + SAVE = 'save', + FETCH = 'fetch', } diff --git a/src/services/whiteboard-integration/outputs/fetch.output.data.ts b/src/services/whiteboard-integration/outputs/fetch.output.data.ts new file mode 100644 index 0000000..73c2e6d --- /dev/null +++ b/src/services/whiteboard-integration/outputs/fetch.output.data.ts @@ -0,0 +1,21 @@ +import { BaseOutputData } from './base.output.data'; + +export class FetchOutputData extends BaseOutputData { + constructor(public data: FetchContentData | FetchErrorData) { + super('fetch-output'); + } +} + +export class FetchContentData { + constructor(public content: string) {} +} + +export class FetchErrorData { + constructor(public error: string) {} +} + +export const isFetchErrorData = ( + data: FetchContentData | FetchErrorData, +): data is FetchErrorData => { + return (data as FetchErrorData).error !== undefined; +}; diff --git a/src/services/whiteboard-integration/outputs/index.ts b/src/services/whiteboard-integration/outputs/index.ts index 3377ce2..ab6c013 100644 --- a/src/services/whiteboard-integration/outputs/index.ts +++ b/src/services/whiteboard-integration/outputs/index.ts @@ -1,2 +1,4 @@ export * from './info.output.data'; export * from './health.check.output.data'; +export * from './save.output.data'; +export * from './fetch.output.data'; diff --git a/src/services/whiteboard-integration/outputs/save.output.data.ts b/src/services/whiteboard-integration/outputs/save.output.data.ts new file mode 100644 index 0000000..62a48a5 --- /dev/null +++ b/src/services/whiteboard-integration/outputs/save.output.data.ts @@ -0,0 +1,21 @@ +import { BaseOutputData } from './base.output.data'; + +export class SaveOutputData extends BaseOutputData { + constructor(public data: SaveContentData | SaveErrorData) { + super('save-output'); + } +} + +export class SaveContentData { + public readonly success = true; +} + +export class SaveErrorData { + constructor(public error: string) {} +} + +export const isSaveErrorData = ( + data: SaveContentData | SaveErrorData, +): data is SaveErrorData => { + return (data as SaveErrorData).error !== undefined; +}; diff --git a/src/services/whiteboard-integration/whiteboard.integration.adapter.service.ts b/src/services/whiteboard-integration/whiteboard.integration.adapter.service.ts index e39f1a8..2b5df73 100644 --- a/src/services/whiteboard-integration/whiteboard.integration.adapter.service.ts +++ b/src/services/whiteboard-integration/whiteboard.integration.adapter.service.ts @@ -9,12 +9,21 @@ import { import { UserInfo } from './user.info'; import { WhiteboardIntegrationMessagePattern } from './message.pattern.enum'; import { - WhoInputData, ContentModifiedInputData, ContributionInputData, + FetchInputData, InfoInputData, + SaveInputData, + WhoInputData, } from './inputs'; -import { HealthCheckOutputData, InfoOutputData } from './outputs'; +import { + FetchErrorData, + FetchOutputData, + HealthCheckOutputData, + InfoOutputData, + SaveErrorData, + SaveOutputData, +} from './outputs'; import { WhiteboardIntegrationEventPattern } from './event.pattern.enum'; import { ConfigService } from '@nestjs/config'; import { ConfigType } from '../../config'; @@ -51,7 +60,7 @@ export class WhiteboardIntegrationAdapterService { ); return; } - + // don't block the constructor this.client .connect() .then(() => { @@ -59,7 +68,9 @@ export class WhiteboardIntegrationAdapterService { 'Client proxy successfully connected to RabbitMQ', ); }) - .catch(this.logger.error); + .catch((error: RMQConnectionError | undefined) => + this.logger.error(error?.err, error?.err.stack), + ); this.timeoutMs = this.configService.get( 'settings.application.queue_response_timeout', @@ -97,7 +108,7 @@ export class WhiteboardIntegrationAdapterService { data, ); } catch (e) { - // ... do nothing + this.logger.error(e, e?.stack); } } @@ -108,9 +119,36 @@ export class WhiteboardIntegrationAdapterService { data, ); } catch (e) { - // ... do nothing + this.logger.error(e, e?.stack); + } + } + + public async save(data: SaveInputData) { + try { + return await this.sendWithResponse( + WhiteboardIntegrationMessagePattern.SAVE, + data, + ); + } catch (e) { + return new SaveOutputData( + new SaveErrorData(e?.message ?? JSON.stringify(e)), + ); + } + } + + public async fetch(data: FetchInputData) { + try { + return await this.sendWithResponse( + WhiteboardIntegrationMessagePattern.FETCH, + data, + ); + } catch (e) { + return new FetchOutputData( + new FetchErrorData(e?.message ?? JSON.stringify(e)), + ); } } + /** * Is there a healthy connection to the queue */ @@ -157,19 +195,21 @@ export class WhiteboardIntegrationAdapterService { timeout({ each: timeoutMs }), ); - return firstValueFrom(result$).catch(({ err }: RMQConnectionError) => { - this.logger.error( - { - message: `Error was received while waiting for response: ${err.message}`, - pattern, - data, - timeout: timeoutMs, - }, - err.stack, - ); + return firstValueFrom(result$).catch( + (error: RMQConnectionError | undefined) => { + this.logger.error( + { + message: `Error was received while waiting for response: ${error?.err?.message}`, + pattern, + data, + timeout: timeoutMs, + }, + error?.err.stack, + ); - throw new Error('Error while processing integration request.'); - }); + throw new Error('Error while processing integration request.'); + }, + ); }; // todo: work on exception handling: logging here vs at consumer /** diff --git a/src/services/whiteboard-integration/whiteboard.integration.service.ts b/src/services/whiteboard-integration/whiteboard.integration.service.ts index 6bd798b..bbe131e 100644 --- a/src/services/whiteboard-integration/whiteboard.integration.service.ts +++ b/src/services/whiteboard-integration/whiteboard.integration.service.ts @@ -3,7 +3,9 @@ import { WhiteboardIntegrationAdapterService } from './whiteboard.integration.ad import { ContentModifiedInputData, ContributionInputData, + FetchInputData, InfoInputData, + SaveInputData, WhoInputData, } from './inputs'; @@ -32,4 +34,12 @@ export class WhiteboardIntegrationService { public contribution(data: ContributionInputData) { return this.integrationAdapter.contribution(data); } + + public save(data: SaveInputData) { + return this.integrationAdapter.save(data); + } + + public fetch(data: FetchInputData) { + return this.integrationAdapter.fetch(data); + } } diff --git a/src/types/index.ts b/src/types/index.ts index ae82c59..f01e9a2 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -1 +1,2 @@ export * from './abort.error'; +export * from './rmq.connection.error'; diff --git a/src/types/rmq.connection.error.ts b/src/types/rmq.connection.error.ts new file mode 100644 index 0000000..23d6bcd --- /dev/null +++ b/src/types/rmq.connection.error.ts @@ -0,0 +1,14 @@ +export type RMQConnectionError = { + err: { + stack: string; + message: string; + }; + url: { + protocol: string; + hostname: string; + username: string; + password: string; + port: number; + heartbeat: number; + }; +}; diff --git a/src/util/array.random.element.ts b/src/util/array.random.element.ts deleted file mode 100644 index 199243e..0000000 --- a/src/util/array.random.element.ts +++ /dev/null @@ -1,4 +0,0 @@ -export const arrayRandomElement = (array: Array): T => { - const randomIndex = Math.floor(Math.random() * array.length); - return array[randomIndex]; -}; diff --git a/src/util/excalidraw.init.data.ts b/src/util/excalidraw.init.data.ts new file mode 100644 index 0000000..f924c5b --- /dev/null +++ b/src/util/excalidraw.init.data.ts @@ -0,0 +1,10 @@ +import { ExcalidrawContent } from '../excalidraw-backend/types'; + +export const excalidrawInitContent: ExcalidrawContent = { + appState: undefined, + source: '', + type: 'excalidraw', + version: 1, + elements: [], + files: {}, +}; diff --git a/src/util/index.ts b/src/util/index.ts index 52588c4..760d723 100644 --- a/src/util/index.ts +++ b/src/util/index.ts @@ -1,2 +1,4 @@ -export * from './array.random.element'; export * from './is.abort.error'; +export * from './excalidraw.init.data'; + +export * from './json-to-arraybuffer/json.to.arraybuffer'; diff --git a/src/util/json-to-arraybuffer/json.to.arraybuffer.spec.ts b/src/util/json-to-arraybuffer/json.to.arraybuffer.spec.ts new file mode 100644 index 0000000..ab3a7ba --- /dev/null +++ b/src/util/json-to-arraybuffer/json.to.arraybuffer.spec.ts @@ -0,0 +1,80 @@ +import { jsonToArrayBuffer } from './json.to.arraybuffer'; + +describe('jsonToArrayBuffer', () => { + it('should convert an empty object to an ArrayBuffer', () => { + const json = {}; + const arrayBuffer = jsonToArrayBuffer(json); + const expectedBuffer = new TextEncoder().encode( + JSON.stringify(json), + ).buffer; + expect(arrayBuffer).toEqual(expectedBuffer); + }); + + it('should convert a simple JSON object to an ArrayBuffer', () => { + const json = { key: 'value' }; + const arrayBuffer = jsonToArrayBuffer(json); + const expectedBuffer = new TextEncoder().encode( + JSON.stringify(json), + ).buffer; + expect(arrayBuffer).toEqual(expectedBuffer); + }); + + it('should convert a nested JSON object to an ArrayBuffer', () => { + const json = { key: { nestedKey: 'nestedValue' } }; + const arrayBuffer = jsonToArrayBuffer(json); + const expectedBuffer = new TextEncoder().encode( + JSON.stringify(json), + ).buffer; + expect(arrayBuffer).toEqual(expectedBuffer); + }); + + it('should convert a JSON object with different data types to an ArrayBuffer', () => { + const json = { + string: 'value', + number: 123, + boolean: true, + nullValue: null, + }; + const arrayBuffer = jsonToArrayBuffer(json); + const expectedBuffer = new TextEncoder().encode( + JSON.stringify(json), + ).buffer; + expect(arrayBuffer).toEqual(expectedBuffer); + }); + + it('should convert a JSON object with an array to an ArrayBuffer', () => { + const json = { array: [1, 2, 3] }; + const arrayBuffer = jsonToArrayBuffer(json); + const expectedBuffer = new TextEncoder().encode( + JSON.stringify(json), + ).buffer; + expect(arrayBuffer).toEqual(expectedBuffer); + }); + + describe('jsonToArrayBuffer decode', () => { + const testCases = [ + { json: {} }, + { json: { key: 'value' } }, + { json: { key: { nestedKey: 'nestedValue' } } }, + { + json: { string: 'value', number: 123, boolean: true, nullValue: null }, + }, + { json: { array: [1, 2, 3] } }, + ]; + + test.each(testCases)( + 'should encode and decode JSON correctly %#', + ({ json }) => { + const arrayBuffer = jsonToArrayBuffer(json); + const decodedJson = arrayBufferToJson(arrayBuffer); + expect(decodedJson).toEqual(json); + }, + ); + }); +}); + +const arrayBufferToJson = (buffer: ArrayBuffer): Record => { + const decoder = new TextDecoder(); + const jsonString = decoder.decode(new Uint8Array(buffer)); + return JSON.parse(jsonString); +}; diff --git a/src/util/json-to-arraybuffer/json.to.arraybuffer.ts b/src/util/json-to-arraybuffer/json.to.arraybuffer.ts new file mode 100644 index 0000000..8b1ff0a --- /dev/null +++ b/src/util/json-to-arraybuffer/json.to.arraybuffer.ts @@ -0,0 +1,8 @@ +export const jsonToArrayBuffer = ( + json: Record, +): ArrayBuffer => { + const jsonString = JSON.stringify(json); + const encoder = new TextEncoder(); + const uint8Array = encoder.encode(jsonString); + return uint8Array.buffer; +};